[Libguestfs] [PATCH] customize: Add --append-line.

Richard W.M. Jones rjones at redhat.com
Fri Sep 30 14:04:18 UTC 2016


This appends a single line to a file, with some cleverness
involving guessing the right line endings to use.

Also adds a test.
---
 builder/test-virt-builder.sh | 57 +++++++++++++++++++++++++++++++++++-
 customize/Makefile.am        |  2 ++
 customize/append_line.ml     | 70 ++++++++++++++++++++++++++++++++++++++++++++
 customize/append_line.mli    | 20 +++++++++++++
 customize/customize_run.ml   | 11 +++++++
 generator/customize.ml       | 34 +++++++++++++++++++++
 6 files changed, 193 insertions(+), 1 deletion(-)
 create mode 100644 customize/append_line.ml
 create mode 100644 customize/append_line.mli

diff --git a/builder/test-virt-builder.sh b/builder/test-virt-builder.sh
index 2a9227b..80dcd98 100755
--- a/builder/test-virt-builder.sh
+++ b/builder/test-virt-builder.sh
@@ -70,6 +70,19 @@ $VG virt-builder phony-fedora \
     --delete /Makefile \
     --link /etc/foo/bar/baz/foo:/foo \
     --link /etc/foo/bar/baz/foo:/foo1:/foo2:/foo3 \
+    --append-line '/etc/append1:hello' \
+    --append-line '/etc/append2:line1' \
+    --append-line '/etc/append2:line2' \
+    --write '/etc/append3:line1' \
+    --append-line '/etc/append3:line2' \
+    --write '/etc/append4:line1
+' \
+    --append-line '/etc/append4:line2' \
+    --touch /etc/append5 \
+    --append-line '/etc/append5:line1' \
+    --write '/etc/append6:
+' \
+    --append-line '/etc/append6:line2' \
     --firstboot Makefile --firstboot-command 'echo "hello"' \
     --firstboot-install "minicom,inkscape"
 
@@ -97,6 +110,24 @@ echo -----
 # Password
 is-file /etc/shadow
 cat /etc/shadow | sed -r '/^root:/!d;s,^(root:\\\$6\\\$).*,\\1,g'
+
+echo -----
+# Line appending
+# Note that the guestfish 'cat' command appends a newline
+echo append1:
+cat /etc/append1
+echo append2:
+cat /etc/append2
+echo append3:
+cat /etc/append3
+echo append4:
+cat /etc/append4
+echo append5:
+cat /etc/append5
+echo append6:
+cat /etc/append6
+
+echo -----
 EOF
 
 if [ "$(cat test-virt-builder.out)" != "true
@@ -113,7 +144,31 @@ true
 /usr/share/zoneinfo/Europe/London
 -----
 true
-root:\$6\$" ]; then
+root:\$6\$
+-----
+append1:
+hello
+
+append2:
+line1
+line2
+
+append3:
+line1
+line2
+
+append4:
+line1
+line2
+
+append5:
+line1
+
+append6:
+
+line2
+
+-----" ]; then
     echo "$0: unexpected output:"
     cat test-virt-builder.out
     exit 1
diff --git a/customize/Makefile.am b/customize/Makefile.am
index ae37b51..ce5c662 100644
--- a/customize/Makefile.am
+++ b/customize/Makefile.am
@@ -34,6 +34,7 @@ generator_built = \
 	customize-synopsis.pod
 
 SOURCES_MLI = \
+	append_line.mli \
 	crypt.mli \
 	customize_cmdline.mli \
 	customize_run.mli \
@@ -51,6 +52,7 @@ SOURCES_MLI = \
 # This list must be in dependency order.
 SOURCES_ML = \
 	customize_utils.ml \
+	append_line.ml \
 	crypt.ml \
 	firstboot.ml \
 	hostname.ml \
diff --git a/customize/append_line.ml b/customize/append_line.ml
new file mode 100644
index 0000000..0095ff6
--- /dev/null
+++ b/customize/append_line.ml
@@ -0,0 +1,70 @@
+(* virt-customize
+ * Copyright (C) 2016 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Common_utils
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+let append_line (g : G.guestfs) root path line =
+  (* The default line ending for this guest type.  This is only
+   * used when we don't know anything more about the file.
+   *)
+  let default_newline () =
+    match g#inspect_get_type root with
+    | "windows" -> "\r\n"
+    | _ -> "\n"
+  in
+
+  if not (g#exists path) then (
+    g#write path (line ^ default_newline ())
+  )
+  else (
+    (* Stat the file.  We want to know it's a regular file, and
+     * also its size.
+     *)
+    let { G.st_mode = mode; st_size = size } = g#statns path in
+    if Int64.logand mode 0o170000_L <> 0o100000_L then
+      error (f_"append_line: %s is not a file") path;
+
+    (* Guess the line ending from the first part of the file, else
+     * use the default for this guest type.
+     *)
+    let newline =
+      let content = g#pread path 8192 0L in
+      if String.find content "\r\n" >= 0 then "\r\n"
+      else if String.find content "\n" >= 0 then "\n"
+      else default_newline () in
+
+    let line = line ^ newline in
+
+    (* Do we need to append a newline to the existing file? *)
+    let last_chars =
+      let len = String.length newline in
+      if size <= 0L then newline (* empty file ends in virtual newline *)
+      else if size >= Int64.of_int len then
+        g#pread path len (size -^ Int64.of_int len)
+      else
+        g#pread path len 0L in
+    let line =
+      if last_chars = newline then line
+      else newline ^ line in
+
+    (* Finally, append. *)
+    g#write_append path line
+  )
diff --git a/customize/append_line.mli b/customize/append_line.mli
new file mode 100644
index 0000000..11c2da5
--- /dev/null
+++ b/customize/append_line.mli
@@ -0,0 +1,20 @@
+(* virt-customize
+ * Copyright (C) 2016 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+val append_line : Guestfs.guestfs -> string -> string -> string -> unit
+(** append_line [g root file line] appends a single line to a text file. *)
diff --git a/customize/customize_run.ml b/customize/customize_run.ml
index 3e759a2..9ec7b5a 100644
--- a/customize/customize_run.ml
+++ b/customize/customize_run.ml
@@ -25,6 +25,7 @@ open Common_utils
 open Customize_utils
 open Customize_cmdline
 open Password
+open Append_line
 
 let run (g : Guestfs.guestfs) root (ops : ops) =
   (* Is the host_cpu compatible with the guest arch?  ie. Can we
@@ -204,6 +205,16 @@ exec >>%s 2>&1
   (* Perform the remaining customizations in command-line order. *)
   List.iter (
     function
+    | `AppendLine (path, line) ->
+       (* It's an error if it's not a single line.  This is
+        * to prevent incorrect line endings being added to a file.
+        *)
+       if String.contains line '\n' then
+         error (f_"--append-line: line must not contain newline characters.  Use the --append-line option multiple times to add several lines.");
+
+       message (f_"Appending line to %s") path;
+       append_line g root path line
+
     | `Chmod (mode, path) ->
       message (f_"Changing permissions of %s to %s") path mode;
       (* If the mode string is octal, add the OCaml prefix for octal values
diff --git a/generator/customize.ml b/generator/customize.ml
index 259cd26..d3a1946 100644
--- a/generator/customize.ml
+++ b/generator/customize.ml
@@ -49,6 +49,40 @@ and op_type =
 | SMPoolSelector of string              (* pool selector *)
 
 let ops = [
+  { op_name = "append-line";
+    op_type = StringPair "FILE:LINE";
+    op_discrim = "`AppendLine";
+    op_shortdesc = "Append line(s) to the file";
+    op_pod_longdesc = "\
+Append a single line of text to the C<FILE>.  If the file does not already
+end with a newline, then one is added before the appended
+line.  Also a newline is added to the end of the C<LINE> string
+automatically.
+
+For example (assuming ordinary shell quoting) this command:
+
+ --append-line '/etc/hosts:10.0.0.1 foo'
+
+will add either C<10.0.0.1 foo⏎> or C<⏎10.0.0.1 foo⏎> to
+the file, the latter only if the existing file does not
+already end with a newline.
+
+C<⏎> represents a newline character, which is guessed by
+looking at the existing content of the file, so this command
+does the right thing for files using Unix or Windows line endings.
+It also works for empty or non-existent files.
+
+To insert several lines, use the same option several times:
+
+ --append-line '/etc/hosts:10.0.0.1 foo' \
+ --append-line '/etc/hosts:10.0.0.2 bar'
+
+To insert a blank line before the appended line, do:
+
+ --append-line '/etc/hosts:'
+ --append-line '/etc/hosts:10.0.0.1 foo'";
+  };
+
   { op_name = "chmod";
     op_type = StringPair "PERMISSIONS:FILE";
     op_discrim = "`Chmod";
-- 
2.9.3




More information about the Libguestfs mailing list