customize: Add --append-line.

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

Also adds a test.
This commit is contained in:
Richard W.M. Jones
2016-09-30 14:47:48 +01:00
parent a31f657db0
commit 5681db5ed8
6 changed files with 194 additions and 1 deletions

View File

@@ -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

View File

@@ -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 \

71
customize/append_line.ml Normal file
View File

@@ -0,0 +1,71 @@
(* 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 if String.find content "\r" >= 0 then "\r"
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
)

20
customize/append_line.mli Normal file
View File

@@ -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. *)

View File

@@ -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

View File

@@ -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";