Split virt-builder into build and customize steps, so that we can spin
off a separate tool called 'virt-customize'. This commit does not in
fact create such a tool, but it moves all the common code into a
library, in the customize/ subdirectory of the source.
Although this is mostly refactoring, it does change the order in which
virt-builder command line arguments are processed, so they are now
processed in the order they appear, not the inflexible fixed order
used before.
---
.gitignore | 3 +
Makefile.am | 12 +-
builder/Makefile.am | 33 ++-
builder/builder.ml | 332 +----------------------
builder/cmdline.ml | 206 +++-----------
builder/virt-builder.pod | 264 +-----------------
configure.ac | 1 +
customize/.depend | 30 +++
customize/Makefile.am | 181 +++++++++++++
customize/crypt-c.c | 44 +++
customize/crypt.ml | 19 ++
customize/crypt.mli | 22 ++
customize/customize_cmdline.ml | 183 +++++++++++++
customize/customize_cmdline.mli | 75 ++++++
customize/customize_run.ml | 315 ++++++++++++++++++++++
customize/customize_run.mli | 26 ++
customize/firstboot.ml | 171 ++++++++++++
customize/firstboot.mli | 27 ++
customize/hostname.ml | 110 ++++++++
customize/hostname.mli | 21 ++
customize/password.ml | 175 ++++++++++++
customize/password.mli | 42 +++
customize/perl_edit.ml | 78 ++++++
customize/perl_edit.mli | 19 ++
customize/random_seed.ml | 96 +++++++
customize/random_seed.mli | 21 ++
customize/timezone.ml | 39 +++
customize/timezone.mli | 22 ++
customize/urandom.ml | 69 +++++
customize/urandom.mli | 26 ++
generator/Makefile.am | 2 +
generator/customize.ml | 577 ++++++++++++++++++++++++++++++++++++++++
generator/main.ml | 6 +
mllib/Makefile.am | 26 --
mllib/crypt-c.c | 44 ---
mllib/crypt.ml | 19 --
mllib/crypt.mli | 22 --
mllib/firstboot.ml | 171 ------------
mllib/firstboot.mli | 27 --
mllib/hostname.ml | 110 --------
mllib/hostname.mli | 21 --
mllib/password.ml | 175 ------------
mllib/password.mli | 42 ---
mllib/perl_edit.ml | 78 ------
mllib/perl_edit.mli | 19 --
mllib/random_seed.ml | 96 -------
mllib/random_seed.mli | 21 --
mllib/timezone.ml | 39 ---
mllib/timezone.mli | 22 --
mllib/urandom.ml | 69 -----
mllib/urandom.mli | 26 --
po-docs/ja/Makefile.am | 9 +
po-docs/podfiles | 2 +
po-docs/uk/Makefile.am | 9 +
po/POTFILES | 2 +-
po/POTFILES-ml | 8 -
src/guestfs.pod | 4 +
sysprep/Makefile.am | 23 +-
58 files changed, 2512 insertions(+), 1819 deletions(-)
create mode 100644 customize/.depend
create mode 100644 customize/Makefile.am
create mode 100644 customize/crypt-c.c
create mode 100644 customize/crypt.ml
create mode 100644 customize/crypt.mli
create mode 100644 customize/customize_cmdline.ml
create mode 100644 customize/customize_cmdline.mli
create mode 100644 customize/customize_run.ml
create mode 100644 customize/customize_run.mli
create mode 100644 customize/firstboot.ml
create mode 100644 customize/firstboot.mli
create mode 100644 customize/hostname.ml
create mode 100644 customize/hostname.mli
create mode 100644 customize/password.ml
create mode 100644 customize/password.mli
create mode 100644 customize/perl_edit.ml
create mode 100644 customize/perl_edit.mli
create mode 100644 customize/random_seed.ml
create mode 100644 customize/random_seed.mli
create mode 100644 customize/timezone.ml
create mode 100644 customize/timezone.mli
create mode 100644 customize/urandom.ml
create mode 100644 customize/urandom.mli
create mode 100644 generator/customize.ml
delete mode 100644 mllib/crypt-c.c
delete mode 100644 mllib/crypt.ml
delete mode 100644 mllib/crypt.mli
delete mode 100644 mllib/firstboot.ml
delete mode 100644 mllib/firstboot.mli
delete mode 100644 mllib/hostname.ml
delete mode 100644 mllib/hostname.mli
delete mode 100644 mllib/password.ml
delete mode 100644 mllib/password.mli
delete mode 100644 mllib/perl_edit.ml
delete mode 100644 mllib/perl_edit.mli
delete mode 100644 mllib/random_seed.ml
delete mode 100644 mllib/random_seed.mli
delete mode 100644 mllib/timezone.ml
delete mode 100644 mllib/timezone.mli
delete mode 100644 mllib/urandom.ml
delete mode 100644 mllib/urandom.mli
diff --git a/.gitignore b/.gitignore
index 317ddd5..8d62ec2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -90,6 +90,9 @@ Makefile.in
/config.sub
/configure
/csharp/Libguestfs.cs
+/customize/customize-options.pod
+/customize/customize-synopsis.pod
+/customize/virt-customize
/daemon/actions.h
/daemon/errnostring.c
/daemon/errnostring-gperf.c
diff --git a/Makefile.am b/Makefile.am
index aa176db..5b8a82e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -123,10 +123,16 @@ endif
# Unconditional because nothing is built yet.
SUBDIRS += csharp
-# OCaml tools. Note 'mllib' contains random shared code used by
-# all of the OCaml tools.
+# OCaml tools. Note 'mllib' and 'customize' contain shared code used
+# by other OCaml tools, so these must come first.
if HAVE_OCAML
-SUBDIRS += mllib builder builder/website resize sparsify sysprep
+SUBDIRS += \
+ mllib \
+ customize \
+ builder builder/website \
+ resize \
+ sparsify \
+ sysprep
endif
# Perl tools.
diff --git a/builder/Makefile.am b/builder/Makefile.am
index ad791e9..a777942 100644
--- a/builder/Makefile.am
+++ b/builder/Makefile.am
@@ -77,26 +77,28 @@ if HAVE_OCAML
# Note this list must be in dependency order.
deps = \
$(top_builddir)/mllib/libdir.cmx \
+ $(top_builddir)/mllib/config.cmx \
$(top_builddir)/mllib/common_gettext.cmx \
$(top_builddir)/mllib/common_utils.cmx \
- $(top_builddir)/mllib/urandom.cmx \
- $(top_builddir)/mllib/random_seed.cmx \
- $(top_builddir)/mllib/hostname.cmx \
- $(top_builddir)/mllib/timezone.cmx \
- $(top_builddir)/mllib/firstboot.cmx \
- $(top_builddir)/mllib/perl_edit.cmx \
- $(top_builddir)/mllib/crypt-c.o \
- $(top_builddir)/mllib/crypt.cmx \
$(top_builddir)/mllib/fsync-c.o \
$(top_builddir)/mllib/fsync.cmx \
- $(top_builddir)/mllib/password.cmx \
$(top_builddir)/mllib/planner.cmx \
- $(top_builddir)/mllib/config.cmx \
- $(top_builddir)/fish/guestfish-uri.o \
$(top_builddir)/mllib/uri-c.o \
$(top_builddir)/mllib/uRI.cmx \
$(top_builddir)/mllib/mkdtemp-c.o \
$(top_builddir)/mllib/mkdtemp.cmx \
+ $(top_builddir)/customize/urandom.cmx \
+ $(top_builddir)/customize/random_seed.cmx \
+ $(top_builddir)/customize/hostname.cmx \
+ $(top_builddir)/customize/timezone.cmx \
+ $(top_builddir)/customize/firstboot.cmx \
+ $(top_builddir)/customize/perl_edit.cmx \
+ $(top_builddir)/customize/crypt-c.o \
+ $(top_builddir)/customize/crypt.cmx \
+ $(top_builddir)/customize/password.cmx \
+ $(top_builddir)/customize/customize_cmdline.cmx \
+ $(top_builddir)/customize/customize_run.cmx \
+ $(top_builddir)/fish/guestfish-uri.o \
index-scan.o \
index-struct.o \
index-parse.o \
@@ -135,7 +137,8 @@ OCAMLPACKAGES = \
-package str,unix \
-I $(top_builddir)/src/.libs \
-I $(top_builddir)/ocaml \
- -I $(top_builddir)/mllib
+ -I $(top_builddir)/mllib \
+ -I $(top_builddir)/customize
if HAVE_OCAML_PKG_GETTEXT
OCAMLPACKAGES += -package gettext-stub
endif
@@ -182,10 +185,12 @@ noinst_DATA += $(top_builddir)/html/virt-builder.1.html
virt-builder.1 $(top_builddir)/html/virt-builder.1.html: stamp-virt-builder.pod
-stamp-virt-builder.pod: virt-builder.pod
+stamp-virt-builder.pod: virt-builder.pod $(top_srcdir)/customize/customize-synopsis.pod
$(top_srcdir)/customize/customize-options.pod
$(PODWRAPPER) \
--man virt-builder.1 \
--html $(top_builddir)/html/virt-builder.1.html \
+ --insert $(top_srcdir)/customize/customize-synopsis.pod:__CUSTOMIZE_SYNOPSIS__ \
+ --insert $(top_srcdir)/customize/customize-options.pod:__CUSTOMIZE_OPTIONS__ \
--license GPLv2+ \
$<
touch $@
@@ -236,7 +241,7 @@ depend: .depend
.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
rm -f $@ $@-t
- $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(abs_top_builddir)/mllib $^ | \
+ $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(abs_top_builddir)/mllib -I
$(abs_top_builddir)/customize $^ | \
$(SED) 's/ *$$//' | \
$(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
$(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
diff --git a/builder/builder.ml b/builder/builder.ml
index b3ca46a..164b2dd 100644
--- a/builder/builder.ml
+++ b/builder/builder.ml
@@ -38,12 +38,9 @@ let () = Random.self_init ()
let main () =
(* Command line argument parsing - see cmdline.ml. *)
let mode, arg,
- arch, attach, cache, check_signature, curl, debug, delete,
- delete_on_failure, edit, firstboot, run, format, gpg, hostname, install,
- list_format, links, memsize, mkdirs,
- network, output, password_crypto, quiet, root_password, scrub,
- scrub_logfile, selinux_relabel, size, smp, sources, sync, timezone,
- update, upload, writes =
+ arch, attach, cache, check_signature, curl, debug,
+ delete_on_failure, format, gpg, list_format, memsize,
+ network, ops, output, quiet, size, smp, sources, sync =
parse_cmdline () in
(* Timestamped messages in ordinary, non-debug non-quiet mode. *)
@@ -593,7 +590,7 @@ let main () =
(match smp with None -> () | Some smp -> g#set_smp smp);
g#set_network network;
- g#set_selinux selinux_relabel;
+ g#set_selinux ops.flags.selinux_relabel;
(* The output disk is being created, so use cache=unsafe here. *)
g#add_drive_opts ~format:output_format ~cachemode:"unsafe"
output_filename;
@@ -626,313 +623,7 @@ let main () =
eprintf (f_"%s: no guest operating systems or multiboot OS found in this disk
image\nThis is a failure of the source repository. Use -v for more information.\n")
prog;
exit 1 in
- (* Set the random seed. *)
- msg (f_"Setting a random seed");
- if not (Random_seed.set_random_seed g root) then
- eprintf (f_"%s: warning: random seed could not be set for this type of
guest\n%!") prog;
-
- (* Set the hostname. *)
- (match hostname with
- | None -> ()
- | Some hostname ->
- msg (f_"Setting the hostname: %s") hostname;
- if not (Hostname.set_hostname g root hostname) then
- eprintf (f_"%s: warning: hostname could not be set for this type of
guest\n%!") prog
- );
-
- (* Set the timezone. *)
- (match timezone with
- | None -> ()
- | Some timezone ->
- msg (f_"Setting the timezone: %s") timezone;
- if not (Timezone.set_timezone ~prog g root timezone) then
- eprintf (f_"%s: warning: timezone could not be set for this type of
guest\n%!") prog
- );
-
- (* Root password.
- * Note 'None' means that we randomize the root password.
- *)
- let () =
- match g#inspect_get_type root with
- | "linux" ->
- let password_map = Hashtbl.create 1 in
- let pw =
- match root_password with
- | Some pw ->
- msg (f_"Setting root password");
- pw
- | None ->
- msg (f_"Setting random root password [did you mean to use
--root-password?]");
- parse_selector ~prog "random" in
- Hashtbl.replace password_map "root" pw;
- set_linux_passwords ~prog ?password_crypto g root password_map
- | _ ->
- eprintf (f_"%s: warning: root password could not be set for this type of
guest\n%!") prog in
-
- (* Based on the guest type, choose a log file location. *)
- let logfile =
- match g#inspect_get_type root with
- | "windows" | "dos" ->
- if g#is_dir ~followsymlinks:true "/Temp" then
"/Temp/builder.log"
- else "/builder.log"
- | _ ->
- if g#is_dir ~followsymlinks:true "/tmp" then
"/tmp/builder.log"
- else "/builder.log" in
-
- (* Function to cat the log file, for debugging and error messages. *)
- let debug_logfile () =
- try
- (* XXX If stderr is redirected this actually truncates the
- * redirection file, which is pretty annoying to say the
- * least.
- *)
- g#download logfile "/dev/stderr"
- with exn ->
- eprintf (f_"%s: log file %s: %s (ignored)\n")
- prog logfile (Printexc.to_string exn) in
-
- (* Useful wrapper for scripts. *)
- let do_run ~display cmd =
- (* Add a prologue to the scripts:
- * - Pass environment variables through from the host.
- * - Send stdout and stderr to a log file so we capture all output
- * in error messages.
- * Also catch errors and dump the log file completely on error.
- *)
- let env_vars =
- filter_map (
- fun name ->
- try Some (sprintf "export %s=%s" name (quote (Sys.getenv name)))
- with Not_found -> None
- ) [ "http_proxy"; "https_proxy"; "ftp_proxy";
"no_proxy" ] in
- let env_vars = String.concat "\n" env_vars ^ "\n" in
-
- let cmd = sprintf "\
-exec >>%s 2>&1
-%s
-%s
-" (quote logfile) env_vars cmd in
-
- if debug then eprintf "running command:\n%s\n%!" cmd;
- try ignore (g#sh cmd)
- with
- Guestfs.Error msg ->
- debug_logfile ();
- eprintf (f_"%s: %s: command exited with an error\n") prog display;
- exit 1
- in
-
- (*
http://distrowatch.com/dwres.php?resource=package-management *)
- let guest_install_command packages =
- let quoted_args = String.concat " " (List.map quote packages) in
- match g#inspect_get_package_management root with
- | "apt" ->
- (*
http://unix.stackexchange.com/questions/22820 *)
- sprintf "
- export DEBIAN_FRONTEND=noninteractive
- apt_opts='-q -y -o Dpkg::Options::=--force-confnew'
- apt-get $apt_opts update
- apt-get $apt_opts install %s
- " quoted_args
- | "pisi" ->
- sprintf "pisi it %s" quoted_args
- | "pacman" ->
- sprintf "pacman -S %s" quoted_args
- | "urpmi" ->
- sprintf "urpmi %s" quoted_args
- | "yum" ->
- sprintf "yum -y install %s" quoted_args
- | "zypper" ->
- (* XXX Should we use -n option? *)
- sprintf "zypper in %s" quoted_args
- | "unknown" ->
- eprintf (f_"%s: --install is not supported for this guest operating
system\n")
- prog;
- exit 1
- | pm ->
- eprintf (f_"%s: sorry, don't know how to use --install with the
'%s' package manager\n")
- prog pm;
- exit 1
-
- and guest_update_command () =
- match g#inspect_get_package_management root with
- | "apt" ->
- (*
http://unix.stackexchange.com/questions/22820 *)
- sprintf "
- export DEBIAN_FRONTEND=noninteractive
- apt_opts='-q -y -o Dpkg::Options::=--force-confnew'
- apt-get $apt_opts update
- apt-get $apt_opts upgrade
- "
- | "pisi" ->
- sprintf "pisi upgrade"
- | "pacman" ->
- sprintf "pacman -Su"
- | "urpmi" ->
- sprintf "urpmi --auto-select"
- | "yum" ->
- sprintf "yum -y update"
- | "zypper" ->
- sprintf "zypper update"
- | "unknown" ->
- eprintf (f_"%s: --update is not supported for this guest operating
system\n")
- prog;
- exit 1
- | pm ->
- eprintf (f_"%s: sorry, don't know how to use --update with the
'%s' package manager\n")
- prog pm;
- exit 1
- in
-
- (* Update core/template packages. *)
- if update then (
- msg (f_"Updating core packages");
-
- let cmd = guest_update_command () in
- do_run ~display:cmd cmd
- );
-
- (* Install packages. *)
- if install <> [] then (
- msg (f_"Installing packages: %s") (String.concat " " install);
-
- let cmd = guest_install_command install in
- do_run ~display:cmd cmd
- );
-
- (* Make directories. *)
- List.iter (
- fun dir ->
- msg (f_"Making directory: %s") dir;
- g#mkdir_p dir
- ) mkdirs;
-
- (* Write files. *)
- List.iter (
- fun (file, content) ->
- msg (f_"Writing: %s") file;
- g#write file content
- ) writes;
-
- (* Upload files. *)
- List.iter (
- fun (file, dest) ->
- msg (f_"Uploading: %s to %s") file dest;
- let dest =
- if g#is_dir ~followsymlinks:true dest then
- dest ^ "/" ^ Filename.basename file
- else
- dest in
- (* Do the file upload. *)
- g#upload file dest;
-
- (* Copy (some of) the permissions from the local file to the
- * uploaded file.
- *)
- let statbuf = stat file in
- let perms = statbuf.st_perm land 0o7777 (* sticky & set*id *) in
- g#chmod perms dest;
- let uid, gid = statbuf.st_uid, statbuf.st_gid in
- g#chown uid gid dest
- ) upload;
-
- (* Edit files. *)
- List.iter (
- fun (file, expr) ->
- msg (f_"Editing: %s") file;
-
- if not (g#is_file file) then (
- eprintf (f_"%s: error: %s is not a regular file in the guest\n")
- prog file;
- exit 1
- );
-
- Perl_edit.edit_file ~debug g file expr
- ) edit;
-
- (* Delete files. *)
- List.iter (
- fun file ->
- msg (f_"Deleting: %s") file;
- g#rm_rf file
- ) delete;
-
- (* Symbolic links. *)
- List.iter (
- fun (target, links) ->
- List.iter (
- fun link ->
- msg (f_"Linking: %s -> %s") link target;
- g#ln_sf target link
- ) links
- ) links;
-
- (* Scrub files. *)
- List.iter (
- fun file ->
- msg (f_"Scrubbing: %s") file;
- g#scrub_file file
- ) scrub;
-
- (* Firstboot scripts/commands/install. *)
- let () =
- let i = ref 0 in
- List.iter (
- fun op ->
- incr i;
- match op with
- | `Script script ->
- msg (f_"Installing firstboot script: [%d] %s") !i script;
- let cmd = read_whole_file script in
- Firstboot.add_firstboot_script g root !i cmd
- | `Command cmd ->
- msg (f_"Installing firstboot command: [%d] %s") !i cmd;
- Firstboot.add_firstboot_script g root !i cmd
- | `Packages pkgs ->
- msg (f_"Installing firstboot packages: [%d] %s") !i
- (String.concat " " pkgs);
- let cmd = guest_install_command pkgs in
- Firstboot.add_firstboot_script g root !i cmd
- ) firstboot in
-
- (* Run scripts. *)
- List.iter (
- function
- | `Script script ->
- msg (f_"Running: %s") script;
- let cmd = read_whole_file script in
- do_run ~display:script cmd
- | `Command cmd ->
- msg (f_"Running: %s") cmd;
- do_run ~display:cmd cmd
- ) run;
-
- if selinux_relabel then (
- msg (f_"SELinux relabelling");
- let cmd = sprintf "
- if load_policy && fixfiles restore; then
- rm -f /.autorelabel
- else
- touch /.autorelabel
- echo '%s: SELinux relabelling failed, will relabel at boot instead.'
- fi
- " prog in
- do_run ~display:"load_policy && fixfiles restore" cmd
- );
-
- (* Clean up the log file:
- *
- * If debugging, dump out the log file.
- * Then if asked, scrub the log file.
- *)
- if debug then debug_logfile ();
- if scrub_logfile && g#exists logfile then (
- msg (f_"Scrubbing the log file");
-
- (* Try various methods with decreasing complexity. *)
- try g#scrub_file logfile
- with _ -> g#rm_f logfile
- );
+ Customize_run.run ~prog ~debug ~quiet g root ops;
(* Collect some stats about the final output file.
* Notes:
@@ -976,19 +667,6 @@ exec >>%s 2>&1
(* Unmount everything and we're done! *)
msg (f_"Finishing off");
- (* Kill any daemons (eg. started by newly installed packages) using
- * the sysroot.
- * XXX How to make this nicer?
- * XXX fuser returns an error if it doesn't kill any processes, which
- * is not very useful.
- *)
- (try ignore (g#debug "sh" [| "fuser"; "-k";
"/sysroot" |])
- with exn ->
- if debug then
- eprintf (f_"%s: %s (ignored)\n") prog (Printexc.to_string exn)
- );
- g#ping_daemon (); (* tiny delay after kill *)
-
g#umount_all ();
g#shutdown ();
g#close ();
diff --git a/builder/cmdline.ml b/builder/cmdline.ml
index 2657906..2d242c7 100644
--- a/builder/cmdline.ml
+++ b/builder/cmdline.ml
@@ -21,10 +21,10 @@
open Common_gettext.Gettext
open Common_utils
+open Customize_cmdline
+
module G = Guestfs
-open Password
-
open Unix
open Printf
@@ -62,67 +62,14 @@ let parse_cmdline () =
let curl = ref "curl" in
let debug = ref false in
- let delete = ref [] in
- let add_delete s = delete := s :: !delete in
-
let delete_on_failure = ref true in
- let edit = ref [] in
- let add_edit arg =
- let i =
- try String.index arg ':'
- with Not_found ->
- eprintf (f_"%s: invalid --edit format, see the man page.\n") prog;
- exit 1 in
- let len = String.length arg in
- let file = String.sub arg 0 i in
- let expr = String.sub arg (i+1) (len-(i+1)) in
- edit := (file, expr) :: !edit
- in
-
let fingerprints = ref [] in
let add_fingerprint arg = fingerprints := arg :: !fingerprints in
- let firstboot = ref [] in
- let add_firstboot s =
- if not (Sys.file_exists s) then (
- if not (String.contains s ' ') then
- eprintf (f_"%s: %s: %s: file not found\n") prog "--firstboot"
s
- else
- eprintf (f_"%s: %s: %s: file not found [did you mean %s?]\n") prog
"--firstboot" s "--firstboot-command";
- exit 1
- );
- firstboot := `Script s :: !firstboot
- in
- let add_firstboot_cmd s = firstboot := `Command s :: !firstboot in
- let add_firstboot_install pkgs =
- let pkgs = string_nsplit "," pkgs in
- firstboot := `Packages pkgs :: !firstboot
- in
-
let format = ref "" in
let gpg = ref "gpg" in
- let hostname = ref None in
- let set_hostname s = hostname := Some s in
-
- let install = ref [] in
- let add_install pkgs =
- let pkgs = string_nsplit "," pkgs in
- install := pkgs @ !install
- in
-
- let links = ref [] in
- let add_link arg =
- let target, lns =
- match string_nsplit ":" arg with
- | [] | [_] ->
- eprintf (f_"%s: invalid --link format, see the man page.\n") prog;
- exit 1
- | target :: lns -> target, lns in
- links := (target, lns) :: !links
- in
-
let list_format = ref `Short in
let list_set_long () = list_format := `Long in
let list_set_format arg =
@@ -137,44 +84,11 @@ let parse_cmdline () =
let memsize = ref None in
let set_memsize arg = memsize := Some arg in
- let mkdirs = ref [] in
- let add_mkdir arg = mkdirs := arg :: !mkdirs in
-
let network = ref true in
let output = ref "" in
- let password_crypto : password_crypto option ref = ref None in
- let set_password_crypto arg =
- password_crypto := Some (password_crypto_of_string ~prog arg)
- in
-
let quiet = ref false in
- let root_password = ref None in
- let set_root_password arg =
- let pw = parse_selector ~prog arg in
- root_password := Some pw
- in
-
- let run = ref [] in
- let add_run s =
- if not (Sys.file_exists s) then (
- if not (String.contains s ' ') then
- eprintf (f_"%s: %s: %s: file not found\n") prog "--run" s
- else
- eprintf (f_"%s: %s: %s: file not found [did you mean %s?]\n") prog
"--run" s "--run-command";
- exit 1
- );
- run := `Script s :: !run
- in
- let add_run_cmd s = run := `Command s :: !run in
-
- let scrub = ref [] in
- let add_scrub s = scrub := s :: !scrub in
-
- let scrub_logfile = ref false in
- let selinux_relabel = ref false in
-
let size = ref None in
let set_size arg = size := Some (parse_size ~prog arg) in
@@ -186,43 +100,8 @@ let parse_cmdline () =
let sync = ref true in
- let timezone = ref None in
- let set_timezone s = timezone := Some s in
-
- let update = ref false in
-
- let upload = ref [] in
- let add_upload arg =
- let i =
- try String.index arg ':'
- with Not_found ->
- eprintf (f_"%s: invalid --upload format, see the man page.\n") prog;
- exit 1 in
- let len = String.length arg in
- let file = String.sub arg 0 i in
- if not (Sys.file_exists file) then (
- eprintf (f_"%s: --upload: %s: file not found\n") prog file;
- exit 1
- );
- let dest = String.sub arg (i+1) (len-(i+1)) in
- upload := (file, dest) :: !upload
- in
-
- let writes = ref [] in
- let add_write arg =
- let i =
- try String.index arg ':'
- with Not_found ->
- eprintf (f_"%s: invalid --write format, see the man page.\n") prog;
- exit 1 in
- let len = String.length arg in
- let file = String.sub arg 0 i in
- let content = String.sub arg (i+1) (len-(i+1)) in
- writes := (file, content) :: !writes
- in
-
let ditto = " -\"-" in
- let argspec = Arg.align [
+ let argspec = [
"--arch", Arg.Set_string arch, "arch" ^ " " ^
s_"Set the output architecture";
"--attach", Arg.String attach_disk, "iso" ^ " " ^
s_"Attach data disk/ISO during install";
"--attach-format", Arg.String set_attach_format,
@@ -238,65 +117,46 @@ let parse_cmdline () =
" " ^ s_"Disable digital
signatures";
"--no-check-signatures", Arg.Clear check_signature, ditto;
"--curl", Arg.Set_string curl, "curl" ^ " " ^
s_"Set curl binary/command";
- "--delete", Arg.String add_delete, "name" ^ " " ^
s_"Delete a file or dir";
"--delete-cache", Arg.Unit delete_cache_mode,
" " ^ s_"Delete the template
cache";
"--no-delete-on-failure", Arg.Clear delete_on_failure,
" " ^ s_"Don't delete
output file on failure";
- "--edit", Arg.String add_edit, "file:expr" ^ "
" ^ s_"Edit file with Perl expr";
"--fingerprint", Arg.String add_fingerprint,
"AAAA.." ^ " " ^
s_"Fingerprint of valid signing key";
- "--firstboot", Arg.String add_firstboot, "script" ^ " "
^ s_"Run script at first guest boot";
- "--firstboot-command", Arg.String add_firstboot_cmd, "cmd+args" ^
" " ^ s_"Run command at first guest boot";
- "--firstboot-install", Arg.String add_firstboot_install,
- "pkg,pkg" ^ " " ^
s_"Add package(s) to install at firstboot";
"--format", Arg.Set_string format, "raw|qcow2" ^ "
" ^ s_"Output format (default: raw)";
"--get-kernel", Arg.Unit get_kernel_mode,
"image" ^ " " ^
s_"Get kernel from image";
"--gpg", Arg.Set_string gpg, "gpg" ^ " " ^
s_"Set GPG binary/command";
- "--hostname", Arg.String set_hostname, "hostname" ^ "
" ^ s_"Set the hostname";
- "--install", Arg.String add_install, "pkg,pkg" ^ " "
^ s_"Add package(s) to install";
- "--link", Arg.String add_link, "target:link.." ^ "
" ^ s_"Create symbolic links";
"-l", Arg.Unit list_mode, " " ^ s_"List
available templates";
"--list", Arg.Unit list_mode, ditto;
"--long", Arg.Unit list_set_long, " " ^ s_"Shortcut
for --list-format short";
"--list-format", Arg.String list_set_format,
"short|long|json" ^ " " ^
s_"Set the format for --list (default: short)";
- "--no-logfile", Arg.Set scrub_logfile, " " ^ s_"Scrub build
log file";
"--long-options", Arg.Unit display_long_options, " " ^
s_"List long options";
"-m",
Arg.Int set_memsize, "mb" ^ " " ^
s_"Set memory size";
"--memsize",
Arg.Int set_memsize, "mb" ^ ditto;
- "--mkdir", Arg.String add_mkdir, "dir" ^ " " ^
s_"Create directory";
"--network", Arg.Set network, " " ^ s_"Enable
appliance network (default)";
"--no-network", Arg.Clear network, " " ^ s_"Disable
appliance network";
"--notes", Arg.Unit notes_mode, " " ^ s_"Display
installation notes";
"-o", Arg.Set_string output, "file" ^ " " ^
s_"Set output filename";
"--output", Arg.Set_string output, "file" ^ ditto;
- "--password-crypto", Arg.String set_password_crypto,
- "md5|sha256|sha512" ^ " "
^ s_"Set password crypto";
"--print-cache", Arg.Unit print_cache_mode,
" " ^ s_"Print info about
template cache";
"--quiet", Arg.Set quiet, " " ^ s_"No progress
messages";
- "--root-password", Arg.String set_root_password,
- "..." ^ " " ^ s_"Set
root password";
- "--run", Arg.String add_run, "script" ^ " "
^ s_"Run script in disk image";
- "--run-command", Arg.String add_run_cmd, "cmd+args" ^ "
" ^ s_"Run command in disk image";
- "--scrub", Arg.String add_scrub, "name" ^ " " ^
s_"Scrub a file";
- "--selinux-relabel", Arg.Set selinux_relabel,
- " " ^ s_"Relabel files with
correct SELinux labels";
"--size", Arg.String set_size, "size" ^ " " ^
s_"Set output disk size";
"--smp",
Arg.Int set_smp, "vcpus" ^ " " ^
s_"Set number of vCPUs";
"--source", Arg.String add_source, "URL" ^ " " ^
s_"Set source URL";
"--no-sync", Arg.Clear sync, " " ^ s_"Do not
fsync output file on exit";
- "--timezone",Arg.String set_timezone, "timezone" ^ "
" ^ s_"Set the default timezone";
- "--update", Arg.Set update, " " ^ s_"Update core
packages";
- "--upload", Arg.String add_upload, "file:dest" ^ "
" ^ s_"Upload file to dest";
"-v", Arg.Set debug, " " ^ s_"Enable
debugging messages";
"--verbose", Arg.Set debug, ditto;
"-V", Arg.Unit display_version, " " ^ s_"Display
version and exit";
"--version", Arg.Unit display_version, ditto;
- "--write", Arg.String add_write, "file:content" ^ "
" ^ s_"Write file";
] in
+ let customize_argspec, get_customize_ops =
+ Customize_cmdline.argspec ~prog () in
+ let argspec = argspec @ customize_argspec in
+ let argspec = List.sort compare argspec in
+ let argspec = Arg.align argspec in
long_options := argspec;
let args = ref [] in
@@ -328,36 +188,20 @@ read the man page virt-builder(1).
let check_signature = !check_signature in
let curl = !curl in
let debug = !debug in
- let delete = List.rev !delete in
let delete_on_failure = !delete_on_failure in
- let edit = List.rev !edit in
let fingerprints = List.rev !fingerprints in
- let firstboot = List.rev !firstboot in
- let run = List.rev !run in
let format = match !format with "" -> None | s -> Some s in
let gpg = !gpg in
- let hostname = !hostname in
- let install = List.rev !install in
let list_format = !list_format in
- let links = List.rev !links in
let memsize = !memsize in
- let mkdirs = List.rev !mkdirs in
let network = !network in
+ let ops = get_customize_ops () in
let output = match !output with "" -> None | s -> Some s in
- let password_crypto = !password_crypto in
let quiet = !quiet in
- let root_password = !root_password in
- let scrub = List.rev !scrub in
- let scrub_logfile = !scrub_logfile in
- let selinux_relabel = !selinux_relabel in
let size = !size in
let smp = !smp in
let sources = List.rev !sources in
let sync = !sync in
- let timezone = !timezone in
- let update = !update in
- let upload = List.rev !upload in
- let writes = List.rev !writes in
(* Check options. *)
let arg =
@@ -442,7 +286,15 @@ read the man page virt-builder(1).
| arch ->
let target_arch = Architecture.filter_arch arch in
if Architecture.arch_is_compatible Architecture.current_arch target_arch <>
true then (
- if install <> [] || run <> [] || update then (
+ let requires_execute_on_guest = List.exists (
+ function
+ | `Command _ | `InstallPackages _ | `Script _ | `Update -> true
+ | `Delete _ | `Edit _ | `FirstbootCommand _ | `FirstbootPackages _
+ | `FirstbootScript _ | `Hostname _ | `Link _ | `Mkdir _
+ | `RootPassword _ | `Scrub _ | `Timezone _ | `Upload _
+ | `Write _ -> false
+ ) ops.ops in
+ if requires_execute_on_guest then (
eprintf (f_"%s: sorry, cannot run commands on a guest with a different
architecture\n")
prog;
exit 1
@@ -450,10 +302,20 @@ read the man page virt-builder(1).
);
target_arch in
+ (* If user didn't elect any root password, that means we set a random
+ * root password.
+ *)
+ let ops =
+ let has_set_root_password = List.exists (
+ function `RootPassword _ -> true | _ -> false
+ ) ops.ops in
+ if has_set_root_password then ops
+ else (
+ let pw = Password.parse_selector ~prog "random" in
+ { ops with ops = ops.ops @ [ `RootPassword pw ] }
+ ) in
+
mode, arg,
- arch, attach, cache, check_signature, curl, debug, delete,
- delete_on_failure, edit, firstboot, run, format, gpg, hostname, install,
- list_format, links, memsize, mkdirs,
- network, output, password_crypto, quiet, root_password, scrub,
- scrub_logfile, selinux_relabel, size, smp, sources, sync, timezone,
- update, upload, writes
+ arch, attach, cache, check_signature, curl, debug,
+ delete_on_failure, format, gpg, list_format, memsize,
+ network, ops, output, quiet, size, smp, sources, sync
diff --git a/builder/virt-builder.pod b/builder/virt-builder.pod
index 7cf345c..2429f66 100644
--- a/builder/virt-builder.pod
+++ b/builder/virt-builder.pod
@@ -13,23 +13,8 @@ virt-builder - Build virtual machine images quickly
virt-builder os-version
[-o|--output DISKIMAGE] [--size SIZE] [--format raw|qcow2]
- [--arch ARCHITECTURE]
- [--attach ISOFILE]
- [--root-password SELECTOR]
- [--hostname HOSTNAME]
- [--timezone TIMEZONE]
- [--update]
- [--install PKG,[PKG...]]
- [--mkdir DIR]
- [--write FILE:CONTENT]
- [--upload FILE:DEST]
- [--link TARGET:LINK[:LINK]]
- [--edit FILE:EXPR]
- [--delete FILE] [--scrub FILE]
- [--selinux-relabel]
- [--run SCRIPT] [--run-command 'CMD ARGS ...']
- [--firstboot SCRIPT] [--firstboot-command 'CMD ARGS ...']
- [--firstboot-install PKG,[PKG...]]
+ [--arch ARCHITECTURE] [--attach ISOFILE]
+__CUSTOMIZE_SYNOPSIS__
virt-builder -l|--list [--long] [--list-format short|long|json]
@@ -261,15 +246,6 @@ curl parameters, for example to disable https certificate checks:
virt-builder --curl "curl --insecure" [...]
-=item B<--delete> FILE
-
-=item B<--delete> DIR
-
-Delete a file from the guest. Or delete a directory (and all its
-contents, recursively).
-
-See also: I<--upload>, I<--scrub>.
-
=item B<--delete-cache>
Delete the template cache. See L</CACHING>.
@@ -283,17 +259,6 @@ debug images.
The default is to delete the output file if virt-builder fails (or,
for example, some script that it runs fails).
-=item B<--edit> FILE:EXPR
-
-Edit C<FILE> using the Perl expression C<EXPR>.
-
-Be careful to properly quote the expression to prevent it from
-being altered by the shell.
-
-Note that this option is only available when Perl 5 is installed.
-
-See L<virt-edit(1)/NON-INTERACTIVE EDITING>.
-
=item B<--fingerprint> 'AAAA BBBB ...'
Check that the index and templates are signed by the key with the
@@ -305,33 +270,6 @@ URLs, then you can have either no fingerprint, one fingerprint or
multiple fingerprints. If you have multiple, then each must
correspond 1-1 with a source URL.
-=item B<--firstboot> SCRIPT
-
-=item B<--firstboot-command> 'CMD ARGS ...'
-
-Install C<SCRIPT> inside the guest, so that when the guest first boots
-up, the script runs (as root, late in the boot process).
-
-The script is automatically chmod +x after installation in the guest.
-
-The alternative version I<--firstboot-command> is the same, but it
-conveniently wraps the command up in a single line script for you.
-
-You can have multiple I<--firstboot> and I<--firstboot-command>
-options. They run in the same order that they appear on the command
-line.
-
-See also I<--run>.
-
-=item B<--firstboot-install> PKG[,PKG,...]
-
-Install the named packages (a comma-separated list). These are
-installed when the guest first boots using the guest's package manager
-(eg. apt, yum, etc.) and the guest's network connection.
-
-For an overview on the different ways to install packages, see
-L</INSTALLING PACKAGES>.
-
=item B<--format> qcow2
=item B<--format> raw
@@ -365,29 +303,6 @@ alternate home directory:
virt-builder --gpg "gpg --homedir /tmp" [...]
-=item B<--hostname> HOSTNAME
-
-Set the hostname of the guest to C<HOSTNAME>. You can use a
-dotted hostname.domainname (FQDN) if you want.
-
-=item B<--install> PKG[,PKG,...]
-
-Install the named packages (a comma-separated list). These are
-installed during the image build using the guest's package manager
-(eg. apt, yum, etc.) and the host's network connection.
-
-For an overview on the different ways to install packages, see
-L</INSTALLING PACKAGES>.
-
-See also I<--update>.
-
-=item B<--link TARGET:LINK>
-
-=item B<--link TARGET:LINK[:LINK...]>
-
-Create symbolic link(s) in the guest, starting at C<LINK> and
-pointing at C<TARGET>.
-
=item B<-l>
=item B<--list>
@@ -429,14 +344,6 @@ I<--long> is a shorthand for the C<long> format.
See also: I<--source>, I<--notes>, L</SOURCES OF TEMPLATES>.
-=item B<--no-logfile>
-
-Scrub C<builder.log> (log file from build commands) from the image
-after building is complete. If you don't want to reveal precisely how
-the image was built, use this option.
-
-See also: L</LOG FILE>.
-
=item B<-m> MB
=item B<--memsize> MB
@@ -449,13 +356,6 @@ The default can be found with this command:
guestfish get-memsize
-=item B<--mkdir> DIR
-
-Create a directory in the guest.
-
-This uses S<C<mkdir -p>> so any intermediate directories are created,
-and it also works if the directory already exists.
-
=item B<--network>
=item B<--no-network>
@@ -539,20 +439,6 @@ volume.
When used with I<--get-kernel>, this option specifies the output
directory.
-=item B<--password-crypto> password-crypto
-
-Set the password encryption to C<md5>, C<sha256> or C<sha512>.
-
-C<sha256> and C<sha512> require glibc E<ge> 2.7 (check crypt(3) inside
-the guest).
-
-C<md5> will work with relatively old Linux guests (eg. RHEL 3), but
-is not secure against modern attacks.
-
-The default is C<sha512> unless libguestfs detects an old guest that
-didn't have support for SHA-512, in which case it will use C<md5>.
-You can override libguestfs by specifying this option.
-
=item B<--print-cache>
Print information about the template cache. See L</CACHING>.
@@ -561,62 +447,6 @@ Print information about the template cache. See L</CACHING>.
Don't print ordinary progress messages.
-=item B<--root-password> SELECTOR
-
-Set the root password.
-
-See L</USERS AND PASSWORDS> below for the format of the C<SELECTOR>
-field, and also how to set up user accounts.
-
-Note if you I<don't> set I<--root-password> then the guest is given
-a I<random> root password.
-
-=item B<--run> SCRIPT
-
-=item B<--run-command> 'CMD ARGS ...'
-
-Run the shell script (or any program) called C<SCRIPT> on the disk
-image. The script runs virtualized inside a small appliance, chrooted
-into the guest filesystem.
-
-The script is automatically chmod +x.
-
-If libguestfs supports it then a limited network connection is
-available but it only allows outgoing network connections. You can
-also attach data disks (eg. ISO files) as another way to provide data
-(eg. software packages) to the script without needing a network
-connection (I<--attach>). You can also upload data files (I<--upload>).
-
-The alternative version I<--run-command> is the same, but it
-conveniently wraps the command up in a single line script for you.
-
-You can have multiple I<--run> and I<--run-command> options. They run
-in the same order that they appear on the command line.
-
-See also: I<--firstboot>, I<--attach>, I<--upload>.
-
-=item B<--scrub> FILE
-
-Scrub a file from the guest. This is like I<--delete> except that:
-
-=over 4
-
-=item *
-
-It scrubs the data so a guest could not recover it.
-
-=item *
-
-It cannot delete directories, only regular files.
-
-=back
-
-=item B<--selinux-relabel>
-
-Relabel files in the guest so that they have the correct SELinux label.
-
-You should only use this option for guests which support SELinux.
-
=item B<--size> SIZE
Select the size of the output disk, where the size can be specified
@@ -649,34 +479,6 @@ Note that you should not point I<--source> to sources that you
don't
trust (unless the source is signed by someone you do trust). See also
the I<--no-network> option.
-=item B<--timezone> TIMEZONE
-
-Set the default timezone of the guest to C<TIMEZONE>. Use a location
-string like C<Europe/London>
-
-=item B<--update>
-
-Do the equivalent of C<yum update>, C<apt-get upgrade>, or whatever
-command is required to update the packages already installed in the
-template to their latest versions.
-
-See also I<--install>.
-
-=item B<--upload> FILE:DEST
-
-Upload local file C<FILE> to destination C<DEST> in the disk image.
-File owner and permissions from the original are preserved, so you
-should set them to what you want them to be in the disk image.
-
-C<DEST> could be the final filename. This can be used to rename
-the file on upload.
-
-If C<DEST> is a directory name (which must already exist in the guest)
-then the file is uploaded into that directory, and it keeps the same
-name as on the local filesystem.
-
-See also: I<--mkdir>, I<--delete>, I<--scrub>.
-
=item B<-v>
=item B<--verbose>
@@ -692,12 +494,12 @@ your bug report.
Display version number and exit.
-=item B<--write> FILE:CONTENT
-
-Write C<CONTENT> to C<FILE>.
-
=back
+=head2 Customization options
+
+__CUSTOMIZE_OPTIONS__
+
=head1 REFERENCE
=head2 INSTALLING PACKAGES
@@ -996,58 +798,8 @@ A new random seed is generated for the guest.
=item *
-The hostname and timezone are set (I<--hostname>, I<--timezone>).
-
-=item *
-
-The root password is changed (I<--root-password>).
-
-=item *
-
-Core packages are updated (I<--update>).
-
-=item *
-
-Packages are installed (I<--install>).
-
-=item *
-
-Directories are created (I<--mkdir>).
-
-=item *
-
-Files are written (I<--write>).
-
-=item *
-
-Files are uploaded (I<--upload>).
-
-=item *
-
-Files are edited (I<--edit>).
-
-=item *
-
-Files are deleted (I<--delete>, I<--scrub>).
-
-=item *
-
-Symbolic links are created (I<--link>).
-
-=item *
-
-Firstboot scripts are installed (I<--firstboot>,
-I<--firstboot-command>, I<--firstboot-install>).
-
-Note that although firstboot scripts are installed at this step, they
-do not run until the guest is booted first time. Firstboot scripts
-will run in the order they appear on the command line.
-
-=item *
-
-Scripts are run (I<--run>, I<--run-command>).
-
-Scripts run in the order they appear on the command line.
+Guest customization is performed, in the order specified on the
+command line.
=item *
diff --git a/configure.ac b/configure.ac
index ac339a3..4640d03 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1613,6 +1613,7 @@ AC_CONFIG_FILES([Makefile
builder/website/Makefile
cat/Makefile
csharp/Makefile
+ customize/Makefile
daemon/Makefile
df/Makefile
diff/Makefile
diff --git a/customize/.depend b/customize/.depend
new file mode 100644
index 0000000..7a9594b
--- /dev/null
+++ b/customize/.depend
@@ -0,0 +1,30 @@
+./crypt.cmi :
+./crypt.cmo : ./crypt.cmi
+./crypt.cmx : ./crypt.cmi
+./customize_cmdline.cmi : password.cmi
+./customize_cmdline.cmo : password.cmi /home/rjones/d/libguestfs/mllib/common_utils.cmo
/home/rjones/d/libguestfs/mllib/common_gettext.cmo ./customize_cmdline.cmi
+./customize_cmdline.cmx : password.cmx /home/rjones/d/libguestfs/mllib/common_utils.cmx
/home/rjones/d/libguestfs/mllib/common_gettext.cmx ./customize_cmdline.cmi
+./customize_run.cmi : ../ocaml/guestfs.cmi customize_cmdline.cmi
+./customize_run.cmo : timezone.cmi random_seed.cmi perl_edit.cmi password.cmi
hostname.cmi ../ocaml/guestfs.cmi firstboot.cmi customize_cmdline.cmi
/home/rjones/d/libguestfs/mllib/common_utils.cmo
/home/rjones/d/libguestfs/mllib/common_gettext.cmo ./customize_run.cmi
+./customize_run.cmx : timezone.cmx random_seed.cmx perl_edit.cmx password.cmx
hostname.cmx ../ocaml/guestfs.cmx firstboot.cmx customize_cmdline.cmx
/home/rjones/d/libguestfs/mllib/common_utils.cmx
/home/rjones/d/libguestfs/mllib/common_gettext.cmx ./customize_run.cmi
+./firstboot.cmi : ../ocaml/guestfs.cmi
+./firstboot.cmo : ../ocaml/guestfs.cmi /home/rjones/d/libguestfs/mllib/common_utils.cmo
/home/rjones/d/libguestfs/mllib/common_gettext.cmo ./firstboot.cmi
+./firstboot.cmx : ../ocaml/guestfs.cmx /home/rjones/d/libguestfs/mllib/common_utils.cmx
/home/rjones/d/libguestfs/mllib/common_gettext.cmx ./firstboot.cmi
+./hostname.cmi : ../ocaml/guestfs.cmi
+./hostname.cmo : ../ocaml/guestfs.cmi /home/rjones/d/libguestfs/mllib/common_utils.cmo
./hostname.cmi
+./hostname.cmx : ../ocaml/guestfs.cmx /home/rjones/d/libguestfs/mllib/common_utils.cmx
./hostname.cmi
+./password.cmi : ../ocaml/guestfs.cmi
+./password.cmo : urandom.cmi crypt.cmi /home/rjones/d/libguestfs/mllib/common_utils.cmo
/home/rjones/d/libguestfs/mllib/common_gettext.cmo ./password.cmi
+./password.cmx : urandom.cmx crypt.cmx /home/rjones/d/libguestfs/mllib/common_utils.cmx
/home/rjones/d/libguestfs/mllib/common_gettext.cmx ./password.cmi
+./perl_edit.cmi : ../ocaml/guestfs.cmi
+./perl_edit.cmo : ../ocaml/guestfs.cmi /home/rjones/d/libguestfs/mllib/common_utils.cmo
/home/rjones/d/libguestfs/mllib/common_gettext.cmo ./perl_edit.cmi
+./perl_edit.cmx : ../ocaml/guestfs.cmx /home/rjones/d/libguestfs/mllib/common_utils.cmx
/home/rjones/d/libguestfs/mllib/common_gettext.cmx ./perl_edit.cmi
+./random_seed.cmi : ../ocaml/guestfs.cmi
+./random_seed.cmo : urandom.cmi ../ocaml/guestfs.cmi ./random_seed.cmi
+./random_seed.cmx : urandom.cmx ../ocaml/guestfs.cmx ./random_seed.cmi
+./timezone.cmi : ../ocaml/guestfs.cmi
+./timezone.cmo : ../ocaml/guestfs.cmi /home/rjones/d/libguestfs/mllib/common_utils.cmo
./timezone.cmi
+./timezone.cmx : ../ocaml/guestfs.cmx /home/rjones/d/libguestfs/mllib/common_utils.cmx
./timezone.cmi
+./urandom.cmi :
+./urandom.cmo : ./urandom.cmi
+./urandom.cmx : ./urandom.cmi
diff --git a/customize/Makefile.am b/customize/Makefile.am
new file mode 100644
index 0000000..abe5660
--- /dev/null
+++ b/customize/Makefile.am
@@ -0,0 +1,181 @@
+# virt-customize
+# Copyright (C) 2014 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.
+
+include $(top_srcdir)/subdir-rules.mk
+
+EXTRA_DIST = \
+ $(SOURCES)
+
+CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o
+
+generator_built = \
+ customize_cmdline.mli \
+ customize_cmdline.ml \
+ customize-options.pod \
+ customize-synopsis.pod
+
+# Alphabetical order.
+SOURCES = \
+ crypt.ml \
+ crypt.mli \
+ crypt-c.c \
+ customize_cmdline.ml \
+ customize_cmdline.mli \
+ customize_run.ml \
+ customize_run.mli \
+ firstboot.ml \
+ firstboot.mli \
+ hostname.ml \
+ hostname.mli \
+ password.ml \
+ password.mli \
+ perl_edit.ml \
+ perl_edit.mli \
+ random_seed.ml \
+ random_seed.mli \
+ timezone.ml \
+ timezone.mli \
+ urandom.ml \
+ urandom.mli
+
+if HAVE_OCAML
+
+deps = \
+ $(top_builddir)/mllib/common_gettext.cmx \
+ $(top_builddir)/mllib/common_utils.cmx \
+ crypt-c.o
+
+if HAVE_OCAMLOPT
+OBJECTS = $(deps)
+else
+OBJECTS = $(patsubst %.cmx,%.cmo,$(deps))
+endif
+
+# This list must be in dependency order.
+ocaml_modules = \
+ crypt \
+ firstboot \
+ hostname \
+ urandom \
+ password \
+ perl_edit \
+ random_seed \
+ timezone \
+ customize_cmdline \
+ customize_run
+
+if HAVE_OCAMLOPT
+OBJECTS += $(patsubst %,%.cmx,$(ocaml_modules))
+else
+OBJECTS += $(patsubst %,%.cmo,$(ocaml_modules))
+endif
+
+# XXX virt-customize isn't a complete tool yet, so currently this is
+# just a dummy target binary.
+noinst_SCRIPTS = virt-customize
+
+# -I $(top_builddir)/src/.libs is a hack which forces corresponding -L
+# option to be passed to gcc, so we don't try linking against an
+# installed copy of libguestfs.
+OCAMLPACKAGES = \
+ -package str,unix \
+ -I $(top_builddir)/src/.libs \
+ -I $(top_builddir)/ocaml \
+ -I $(top_builddir)/mllib
+if HAVE_OCAML_PKG_GETTEXT
+OCAMLPACKAGES += -package gettext-stub
+endif
+
+OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
+OCAMLOPTFLAGS = $(OCAMLCFLAGS)
+
+OCAMLCLIBS = \
+ $(LIBXML2_LIBS) -lncurses -lcrypt \
+ -L../src/.libs -lutils \
+ -L../gnulib/lib/.libs -lgnu
+
+virt-customize: $(OBJECTS)
+if HAVE_OCAMLOPT
+ $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \
+ mlguestfs.cmxa -linkpkg $^ \
+ -cclib '$(OCAMLCLIBS)' \
+ $(OCAML_GCOV_LDFLAGS) \
+ -o $@
+else
+ $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) \
+ mlguestfs.cma -linkpkg $^ \
+ -cclib '$(OCAMLCLIBS)' \
+ -custom \
+ $(OCAML_GCOV_LDFLAGS) \
+ -o $@
+endif
+
+.mli.cmi:
+ $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
+.ml.cmo:
+ $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
+.ml.cmx:
+ $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -c $< -o $@
+
+# This OCaml module has to be generated by make (configure will put
+# unexpanded prefix macro in).
+
+libdir.ml: Makefile
+ echo 'let libdir = "$(libdir)"' > $@-t
+ mv $@-t $@
+
+# automake will decide we don't need C support in this file. Really
+# we do, so we have to provide it ourselves.
+
+DEFAULT_INCLUDES = \
+ -I. \
+ -I$(top_builddir) \
+ -I$(shell $(OCAMLC) -where) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/fish
+
+.c.o:
+ $(CC) $(CFLAGS) $(PROF_CFLAGS) $(DEFAULT_INCLUDES) -c $< -o $@
+
+# Tests.
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+TESTS =
+
+check-valgrind:
+ $(MAKE) VG="$(top_builddir)/run @VG@" check
+
+# Dependencies.
+depend: .depend
+
+.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
+ rm -f $@ $@-t
+ $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(abs_top_builddir)/mllib $^ | \
+ $(SED) 's/ *$$//' | \
+ $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
+ $(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
+ sort > $@-t
+ mv $@-t $@
+
+-include .depend
+
+endif
+
+DISTCLEANFILES = .depend
+
+.PHONY: depend docs
diff --git a/customize/crypt-c.c b/customize/crypt-c.c
new file mode 100644
index 0000000..29a91e4
--- /dev/null
+++ b/customize/crypt-c.c
@@ -0,0 +1,44 @@
+/* virt-sysprep - interface to crypt(3)
+ * Copyright (C) 2013 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.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <caml/alloc.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+
+value
+virt_sysprep_crypt (value keyv, value saltv)
+{
+ CAMLparam2 (keyv, saltv);
+ CAMLlocal1 (rv);
+ char *r;
+
+ /* Note that crypt returns a pointer to a statically allocated
+ * buffer in glibc. For this and other reasons, this function
+ * is not thread safe.
+ */
+ r = crypt (String_val (keyv), String_val (saltv));
+ rv = caml_copy_string (r);
+
+ CAMLreturn (rv);
+}
diff --git a/customize/crypt.ml b/customize/crypt.ml
new file mode 100644
index 0000000..2c48c0d
--- /dev/null
+++ b/customize/crypt.ml
@@ -0,0 +1,19 @@
+(* virt-sysprep
+ * Copyright (C) 2013 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.
+ *)
+
+external crypt : string -> string -> string = "virt_sysprep_crypt"
diff --git a/customize/crypt.mli b/customize/crypt.mli
new file mode 100644
index 0000000..ef4066f
--- /dev/null
+++ b/customize/crypt.mli
@@ -0,0 +1,22 @@
+(* virt-sysprep
+ * Copyright (C) 2013 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.
+ *)
+
+(** Wrapper around glibc crypt(3) function. *)
+
+val crypt : string -> string -> string
+(** [crypt key salt] returns the password ([key]) encrypted. *)
diff --git a/customize/customize_cmdline.ml b/customize/customize_cmdline.ml
new file mode 100644
index 0000000..6ff2b4d
--- /dev/null
+++ b/customize/customize_cmdline.ml
@@ -0,0 +1,183 @@
+(* libguestfs generated file
+ * WARNING: THIS FILE IS GENERATED FROM:
+ * generator/ *.ml
+ * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.
+ *
+ * Copyright (C) 2009-2014 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.
+ *)
+
+(* Command line argument parsing, both for the virt-customize binary
+ * and for the other tools that share the same code.
+ *)
+
+open Printf
+
+open Common_utils
+open Common_gettext.Gettext
+
+type ops = {
+ ops : op list;
+ flags : flags;
+}
+and op = [
+ | `Delete of string
+ (* --delete PATH *)
+ | `Edit of string * string
+ (* --edit FILE:EXPR *)
+ | `FirstbootScript of string
+ (* --firstboot SCRIPT *)
+ | `FirstbootCommand of string
+ (* --firstboot-command 'CMD ARGS' *)
+ | `FirstbootPackages of string list
+ (* --firstboot-install PKG,PKG.. *)
+ | `Hostname of string
+ (* --hostname HOSTNAME *)
+ | `InstallPackages of string list
+ (* --install PKG,PKG.. *)
+ | `Link of string * string list
+ (* --link TARGET:LINK[:LINK..] *)
+ | `Mkdir of string
+ (* --mkdir DIR *)
+ | `RootPassword of Password.password_selector
+ (* --root-password SELECTOR *)
+ | `Script of string
+ (* --run SCRIPT *)
+ | `Command of string
+ (* --run-command 'CMD ARGS' *)
+ | `Scrub of string
+ (* --scrub FILE *)
+ | `Timezone of string
+ (* --timezone TIMEZONE *)
+ | `Update
+ (* --update *)
+ | `Upload of string * string
+ (* --upload FILE:DEST *)
+ | `Write of string * string
+ (* --write FILE:CONTENT *)
+]
+and flags = {
+ scrub_logfile : bool;
+ (* --no-logfile *)
+ password_crypto : Password.password_crypto option;
+ (* --password-crypto md5|sha256|sha512 *)
+ selinux_relabel : bool;
+ (* --selinux-relabel *)
+}
+
+let rec argspec ~prog () =
+ let ops = ref [] in
+ let scrub_logfile = ref false in
+ let password_crypto = ref None in
+ let selinux_relabel = ref false in
+
+ let rec get_ops () = {
+ ops = List.rev !ops;
+ flags = get_flags ();
+ }
+ and get_flags () = {
+ scrub_logfile = !scrub_logfile;
+ password_crypto = !password_crypto;
+ selinux_relabel = !selinux_relabel;
+ }
+ in
+
+ let split_string_pair option_name arg =
+ let i =
+ try String.index arg ':'
+ with Not_found ->
+ eprintf (f_"%s: invalid format for '--%s' parameter, see the man
page.\n")
+ prog option_name;
+ exit 1 in
+ let len = String.length arg in
+ String.sub arg 0 i, String.sub arg (i+1) (len-(i+1))
+ in
+ let split_string_list arg =
+ string_nsplit "," arg
+ in
+ let split_links_list option_name arg =
+ match string_nsplit ":" arg with
+ | [] | [_] ->
+ eprintf (f_"%s: invalid format for '--%s' parameter, see the man
page.\n")
+ prog option_name;
+ exit 1
+ | target :: lns -> target, lns
+ in
+
+ let argspec = [
+ "--delete",
+ Arg.String (fun s -> ops := `Delete s :: !ops),
+ s_"PATH" ^ " " ^ s_"Delete a file or directory";
+ "--edit",
+ Arg.String (fun s -> let p = split_string_pair "edit" s in ops := `Edit
p :: !ops),
+ s_"FILE:EXPR" ^ " " ^ s_"Edit file using Perl
expression";
+ "--firstboot",
+ Arg.String (fun s -> ops := `FirstbootScript s :: !ops),
+ s_"SCRIPT" ^ " " ^ s_"Run script at first guest boot";
+ "--firstboot-command",
+ Arg.String (fun s -> ops := `FirstbootCommand s :: !ops),
+ s_"'CMD ARGS'" ^ " " ^ s_"Run command at first guest
boot";
+ "--firstboot-install",
+ Arg.String (fun s -> let ss = split_string_list s in ops := `FirstbootPackages ss
:: !ops),
+ s_"PKG,PKG.." ^ " " ^ s_"Add package(s) to install at first
boot";
+ "--hostname",
+ Arg.String (fun s -> ops := `Hostname s :: !ops),
+ s_"HOSTNAME" ^ " " ^ s_"Set the hostname";
+ "--install",
+ Arg.String (fun s -> let ss = split_string_list s in ops := `InstallPackages ss ::
!ops),
+ s_"PKG,PKG.." ^ " " ^ s_"Add package(s) to install";
+ "--link",
+ Arg.String (fun s -> let ss = split_links_list "link" s in ops := `Link
ss :: !ops),
+ s_"TARGET:LINK[:LINK..]" ^ " " ^ s_"Create symbolic
links";
+ "--mkdir",
+ Arg.String (fun s -> ops := `Mkdir s :: !ops),
+ s_"DIR" ^ " " ^ s_"Create a directory";
+ "--root-password",
+ Arg.String (fun s -> let sel = Password.parse_selector ~prog s in ops :=
`RootPassword sel :: !ops),
+ s_"SELECTOR" ^ " " ^ s_"Set root password";
+ "--run",
+ Arg.String (fun s -> ops := `Script s :: !ops),
+ s_"SCRIPT" ^ " " ^ s_"Run script in disk image";
+ "--run-command",
+ Arg.String (fun s -> ops := `Command s :: !ops),
+ s_"'CMD ARGS'" ^ " " ^ s_"Run command in disk
image";
+ "--scrub",
+ Arg.String (fun s -> ops := `Scrub s :: !ops),
+ s_"FILE" ^ " " ^ s_"Scrub a file";
+ "--timezone",
+ Arg.String (fun s -> ops := `Timezone s :: !ops),
+ s_"TIMEZONE" ^ " " ^ s_"Set the default timezone";
+ "--update",
+ Arg.Unit (fun () -> ops := `Update :: !ops),
+ " " ^ s_"Update core packages";
+ "--upload",
+ Arg.String (fun s -> let p = split_string_pair "upload" s in ops :=
`Upload p :: !ops),
+ s_"FILE:DEST" ^ " " ^ s_"Upload local file to
destination";
+ "--write",
+ Arg.String (fun s -> let p = split_string_pair "write" s in ops :=
`Write p :: !ops),
+ s_"FILE:CONTENT" ^ " " ^ s_"Write file";
+ "--no-logfile",
+ Arg.Set scrub_logfile,
+ " " ^ s_"Scrub build log file";
+ "--password-crypto",
+ Arg.String (fun s -> password_crypto := Some (Password.password_crypto_of_string
~prog s)),
+ "md5|sha256|sha512" ^ " " ^ s_"Set password crypto";
+ "--selinux-relabel",
+ Arg.Set selinux_relabel,
+ " " ^ s_"Relabel files with correct SELinux labels";
+] in
+
+ argspec, get_ops
diff --git a/customize/customize_cmdline.mli b/customize/customize_cmdline.mli
new file mode 100644
index 0000000..c2b8d8b
--- /dev/null
+++ b/customize/customize_cmdline.mli
@@ -0,0 +1,75 @@
+(* libguestfs generated file
+ * WARNING: THIS FILE IS GENERATED FROM:
+ * generator/ *.ml
+ * ANY CHANGES YOU MAKE TO THIS FILE WILL BE LOST.
+ *
+ * Copyright (C) 2009-2014 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.
+ *)
+
+(** Command line argument parsing, both for the virt-customize binary
+ and for the other tools that share the same code. *)
+
+type ops = {
+ ops : op list;
+ flags : flags;
+}
+and op = [
+ | `Delete of string
+ (* --delete PATH *)
+ | `Edit of string * string
+ (* --edit FILE:EXPR *)
+ | `FirstbootScript of string
+ (* --firstboot SCRIPT *)
+ | `FirstbootCommand of string
+ (* --firstboot-command 'CMD ARGS' *)
+ | `FirstbootPackages of string list
+ (* --firstboot-install PKG,PKG.. *)
+ | `Hostname of string
+ (* --hostname HOSTNAME *)
+ | `InstallPackages of string list
+ (* --install PKG,PKG.. *)
+ | `Link of string * string list
+ (* --link TARGET:LINK[:LINK..] *)
+ | `Mkdir of string
+ (* --mkdir DIR *)
+ | `RootPassword of Password.password_selector
+ (* --root-password SELECTOR *)
+ | `Script of string
+ (* --run SCRIPT *)
+ | `Command of string
+ (* --run-command 'CMD ARGS' *)
+ | `Scrub of string
+ (* --scrub FILE *)
+ | `Timezone of string
+ (* --timezone TIMEZONE *)
+ | `Update
+ (* --update *)
+ | `Upload of string * string
+ (* --upload FILE:DEST *)
+ | `Write of string * string
+ (* --write FILE:CONTENT *)
+]
+and flags = {
+ scrub_logfile : bool;
+ (* --no-logfile *)
+ password_crypto : Password.password_crypto option;
+ (* --password-crypto md5|sha256|sha512 *)
+ selinux_relabel : bool;
+ (* --selinux-relabel *)
+}
+
+val argspec : prog:string -> unit -> (Arg.key * Arg.spec * Arg.doc) list * (unit
-> ops)
diff --git a/customize/customize_run.ml b/customize/customize_run.ml
new file mode 100644
index 0000000..3756070
--- /dev/null
+++ b/customize/customize_run.ml
@@ -0,0 +1,315 @@
+(* virt-customize
+ * Copyright (C) 2014 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 Unix
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Customize_cmdline
+open Password
+
+let quote = Filename.quote
+
+let run ~prog ~debug ~quiet (g : Guestfs.guestfs) root (ops : ops) =
+ (* Timestamped messages in ordinary, non-debug non-quiet mode. *)
+ let msg fs = make_message_function ~quiet fs in
+
+ (* Based on the guest type, choose a log file location. *)
+ let logfile =
+ match g#inspect_get_type root with
+ | "windows" | "dos" ->
+ if g#is_dir ~followsymlinks:true "/Temp" then
"/Temp/builder.log"
+ else "/builder.log"
+ | _ ->
+ if g#is_dir ~followsymlinks:true "/tmp" then
"/tmp/builder.log"
+ else "/builder.log" in
+
+ (* Function to cat the log file, for debugging and error messages. *)
+ let debug_logfile () =
+ try
+ (* XXX If stderr is redirected this actually truncates the
+ * redirection file, which is pretty annoying to say the
+ * least.
+ *)
+ g#download logfile "/dev/stderr"
+ with exn ->
+ eprintf (f_"%s: log file %s: %s (ignored)\n")
+ prog logfile (Printexc.to_string exn) in
+
+ (* Useful wrapper for scripts. *)
+ let do_run ~display cmd =
+ (* Add a prologue to the scripts:
+ * - Pass environment variables through from the host.
+ * - Send stdout and stderr to a log file so we capture all output
+ * in error messages.
+ * Also catch errors and dump the log file completely on error.
+ *)
+ let env_vars =
+ filter_map (
+ fun name ->
+ try Some (sprintf "export %s=%s" name (quote (Sys.getenv name)))
+ with Not_found -> None
+ ) [ "http_proxy"; "https_proxy"; "ftp_proxy";
"no_proxy" ] in
+ let env_vars = String.concat "\n" env_vars ^ "\n" in
+
+ let cmd = sprintf "\
+exec >>%s 2>&1
+%s
+%s
+" (quote logfile) env_vars cmd in
+
+ if debug then eprintf "running command:\n%s\n%!" cmd;
+ try ignore (g#sh cmd)
+ with
+ Guestfs.Error msg ->
+ debug_logfile ();
+ eprintf (f_"%s: %s: command exited with an error\n") prog display;
+ exit 1
+ in
+
+ (*
http://distrowatch.com/dwres.php?resource=package-management *)
+ let guest_install_command packages =
+ let quoted_args = String.concat " " (List.map quote packages) in
+ match g#inspect_get_package_management root with
+ | "apt" ->
+ (*
http://unix.stackexchange.com/questions/22820 *)
+ sprintf "
+ export DEBIAN_FRONTEND=noninteractive
+ apt_opts='-q -y -o Dpkg::Options::=--force-confnew'
+ apt-get $apt_opts update
+ apt-get $apt_opts install %s
+ " quoted_args
+ | "pisi" ->
+ sprintf "pisi it %s" quoted_args
+ | "pacman" ->
+ sprintf "pacman -S %s" quoted_args
+ | "urpmi" ->
+ sprintf "urpmi %s" quoted_args
+ | "yum" ->
+ sprintf "yum -y install %s" quoted_args
+ | "zypper" ->
+ (* XXX Should we use -n option? *)
+ sprintf "zypper in %s" quoted_args
+ | "unknown" ->
+ eprintf (f_"%s: --install is not supported for this guest operating
system\n")
+ prog;
+ exit 1
+ | pm ->
+ eprintf (f_"%s: sorry, don't know how to use --install with the
'%s' package manager\n")
+ prog pm;
+ exit 1
+
+ and guest_update_command () =
+ match g#inspect_get_package_management root with
+ | "apt" ->
+ (*
http://unix.stackexchange.com/questions/22820 *)
+ sprintf "
+ export DEBIAN_FRONTEND=noninteractive
+ apt_opts='-q -y -o Dpkg::Options::=--force-confnew'
+ apt-get $apt_opts update
+ apt-get $apt_opts upgrade
+ "
+ | "pisi" ->
+ sprintf "pisi upgrade"
+ | "pacman" ->
+ sprintf "pacman -Su"
+ | "urpmi" ->
+ sprintf "urpmi --auto-select"
+ | "yum" ->
+ sprintf "yum -y update"
+ | "zypper" ->
+ sprintf "zypper update"
+ | "unknown" ->
+ eprintf (f_"%s: --update is not supported for this guest operating
system\n")
+ prog;
+ exit 1
+ | pm ->
+ eprintf (f_"%s: sorry, don't know how to use --update with the
'%s' package manager\n")
+ prog pm;
+ exit 1
+ in
+
+ (* Set the random seed. *)
+ msg (f_"Setting a random seed");
+ if not (Random_seed.set_random_seed g root) then
+ eprintf (f_"%s: warning: random seed could not be set for this type of
guest\n%!") prog;
+
+ (* Used for numbering firstboot commands. *)
+ let i = ref 0 in
+
+ (* Perform the remaining customizations in command-line order. *)
+ List.iter (
+ function
+ | `Command cmd ->
+ msg (f_"Running: %s") cmd;
+ do_run ~display:cmd cmd
+
+ | `Delete path ->
+ msg (f_"Deleting: %s") path;
+ g#rm_rf path
+
+ | `Edit (path, expr) ->
+ msg (f_"Editing: %s") path;
+
+ if not (g#is_file path) then (
+ eprintf (f_"%s: error: %s is not a regular file in the guest\n")
+ prog path;
+ exit 1
+ );
+
+ Perl_edit.edit_file ~debug g path expr
+
+ | `FirstbootCommand cmd ->
+ incr i;
+ msg (f_"Installing firstboot command: [%d] %s") !i cmd;
+ Firstboot.add_firstboot_script g root !i cmd
+
+ | `FirstbootPackages pkgs ->
+ incr i;
+ msg (f_"Installing firstboot packages: [%d] %s") !i
+ (String.concat " " pkgs);
+ let cmd = guest_install_command pkgs in
+ Firstboot.add_firstboot_script g root !i cmd
+
+ | `FirstbootScript script ->
+ incr i;
+ msg (f_"Installing firstboot script: [%d] %s") !i script;
+ let cmd = read_whole_file script in
+ Firstboot.add_firstboot_script g root !i cmd
+
+ | `Hostname hostname ->
+ msg (f_"Setting the hostname: %s") hostname;
+ if not (Hostname.set_hostname g root hostname) then
+ eprintf (f_"%s: warning: hostname could not be set for this type of
guest\n%!")
+ prog
+
+ | `InstallPackages pkgs ->
+ msg (f_"Installing packages: %s") (String.concat " " pkgs);
+ let cmd = guest_install_command pkgs in
+ do_run ~display:cmd cmd
+
+ | `Link (target, links) ->
+ List.iter (
+ fun link ->
+ msg (f_"Linking: %s -> %s") link target;
+ g#ln_sf target link
+ ) links
+
+ | `Mkdir dir ->
+ msg (f_"Making directory: %s") dir;
+ g#mkdir_p dir
+
+ | `RootPassword pw ->
+ (match g#inspect_get_type root with
+ | "linux" ->
+ msg (f_"Setting root password");
+ let password_map = Hashtbl.create 1 in
+ Hashtbl.replace password_map "root" pw;
+ let password_crypto = ops.flags.password_crypto in
+ set_linux_passwords ~prog ?password_crypto g root password_map
+
+ | _ ->
+ eprintf (f_"%s: warning: root password could not be set for this type of
guest\n%!")
+ prog
+ )
+
+ | `Script script ->
+ msg (f_"Running: %s") script;
+ let cmd = read_whole_file script in
+ do_run ~display:script cmd
+
+ | `Scrub path ->
+ msg (f_"Scrubbing: %s") path;
+ g#scrub_file path
+
+ | `Timezone tz ->
+ msg (f_"Setting the timezone: %s") tz;
+ if not (Timezone.set_timezone ~prog g root tz) then
+ eprintf (f_"%s: warning: timezone could not be set for this type of
guest\n%!")
+ prog
+
+ | `Update ->
+ msg (f_"Updating core packages");
+ let cmd = guest_update_command () in
+ do_run ~display:cmd cmd
+
+ | `Upload (path, dest) ->
+ msg (f_"Uploading: %s to %s") path dest;
+ let dest =
+ if g#is_dir ~followsymlinks:true dest then
+ dest ^ "/" ^ Filename.basename path
+ else
+ dest in
+ (* Do the file upload. *)
+ g#upload path dest;
+
+ (* Copy (some of) the permissions from the local file to the
+ * uploaded file.
+ *)
+ let statbuf = stat path in
+ let perms = statbuf.st_perm land 0o7777 (* sticky & set*id *) in
+ g#chmod perms dest;
+ let uid, gid = statbuf.st_uid, statbuf.st_gid in
+ g#chown uid gid dest
+
+ | `Write (path, content) ->
+ msg (f_"Writing: %s") path;
+ g#write path content
+ ) ops.ops;
+
+ if ops.flags.selinux_relabel then (
+ msg (f_"SELinux relabelling");
+ let cmd = sprintf "
+ if load_policy && fixfiles restore; then
+ rm -f /.autorelabel
+ else
+ touch /.autorelabel
+ echo '%s: SELinux relabelling failed, will relabel at boot instead.'
+ fi
+ " prog in
+ do_run ~display:"load_policy && fixfiles restore" cmd
+ );
+
+ (* Clean up the log file:
+ *
+ * If debugging, dump out the log file.
+ * Then if asked, scrub the log file.
+ *)
+ if debug then debug_logfile ();
+ if ops.flags.scrub_logfile && g#exists logfile then (
+ msg (f_"Scrubbing the log file");
+
+ (* Try various methods with decreasing complexity. *)
+ try g#scrub_file logfile
+ with _ -> g#rm_f logfile
+ );
+
+ (* Kill any daemons (eg. started by newly installed packages) using
+ * the sysroot.
+ * XXX How to make this nicer?
+ * XXX fuser returns an error if it doesn't kill any processes, which
+ * is not very useful.
+ *)
+ (try ignore (g#debug "sh" [| "fuser"; "-k";
"/sysroot" |])
+ with exn ->
+ if debug then
+ eprintf (f_"%s: %s (ignored)\n") prog (Printexc.to_string exn)
+ );
+ g#ping_daemon () (* tiny delay after kill *)
diff --git a/customize/customize_run.mli b/customize/customize_run.mli
new file mode 100644
index 0000000..0fa7683
--- /dev/null
+++ b/customize/customize_run.mli
@@ -0,0 +1,26 @@
+(* virt-customize
+ * Copyright (C) 2014 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.
+ *)
+
+(* After command line arguments have been parsed, call this function
+ * to perform the operations on a guest handle.
+ *
+ * Note that inspection must have been done on the handle, and
+ * filesystems must be mounted up.
+ *)
+
+val run : prog:string -> debug:bool -> quiet:bool -> Guestfs.guestfs ->
string -> Customize_cmdline.ops -> unit
diff --git a/customize/firstboot.ml b/customize/firstboot.ml
new file mode 100644
index 0000000..9e4c7b6
--- /dev/null
+++ b/customize/firstboot.ml
@@ -0,0 +1,171 @@
+(* virt-sysprep
+ * Copyright (C) 2012 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 Printf
+
+open Common_utils
+open Common_gettext.Gettext
+
+(* For Linux guests. *)
+let firstboot_dir = "/usr/lib/virt-sysprep"
+
+let firstboot_sh = sprintf "\
+#!/bin/sh -
+
+### BEGIN INIT INFO
+# Provides: virt-sysprep
+# Required-Start: $null
+# Should-Start: $all
+# Required-Stop: $null
+# Should-Stop: $all
+# Default-Start: 2 3 5
+# Default-Stop: 0 1 6
+# Short-Description: Start scripts to run once at next boot
+# Description: Start scripts to run once at next boot
+# These scripts run the first time the guest boots,
+# and then are deleted. Output or errors from the scripts
+# are written to ~root/virt-sysprep-firstboot.log.
+### END INIT INFO
+
+d=%s/scripts
+logfile=~root/virt-sysprep-firstboot.log
+
+echo \"$0\" \"$@\" 2>&1 | tee $logfile
+echo \"Scripts dir: $d\" 2>&1 | tee $logfile
+
+if test \"$1\" = \"start\"
+then
+ for f in $d/* ; do
+ if test -x \"$f\"
+ then
+ echo '=== Running' $f '===' 2>&1 | tee $logfile
+ $f 2>&1 | tee $logfile
+ rm -f $f
+ fi
+ done
+fi
+" firstboot_dir
+
+let firstboot_service = sprintf "\
+[Unit]
+Description=virt-sysprep firstboot service
+After=network.target
+Before=prefdm.service
+
+[Service]
+Type=oneshot
+ExecStart=%s/firstboot.sh start
+RemainAfterExit=yes
+StandardOutput=journal+console
+StandardError=inherit
+
+[Install]
+WantedBy=default.target
+" firstboot_dir
+
+let failed fs =
+ ksprintf (fun msg -> failwith (s_"firstboot: failed: " ^ msg)) fs
+
+let rec install_service (g : Guestfs.guestfs) distro =
+ g#mkdir_p firstboot_dir;
+ g#mkdir_p (sprintf "%s/scripts" firstboot_dir);
+ g#write (sprintf "%s/firstboot.sh" firstboot_dir) firstboot_sh;
+ g#chmod 0o755 (sprintf "%s/firstboot.sh" firstboot_dir);
+
+ (* Note we install both systemd and sysvinit services. This is
+ * because init systems can be switched at runtime, and it's easy to
+ * tell if systemd is installed (eg. Ubuntu uses upstart but installs
+ * systemd configuration directories). There is no danger of a
+ * firstboot script running twice because they disable themselves
+ * after running.
+ *)
+ if g#is_dir "/etc/systemd/system" then
+ install_systemd_service g;
+ if g#is_dir "/etc/rc.d" || g#is_dir "/etc/init.d" then
+ install_sysvinit_service g distro
+
+(* Install the systemd firstboot service, if not installed already. *)
+and install_systemd_service g =
+ g#write (sprintf "%s/firstboot.service" firstboot_dir) firstboot_service;
+ g#mkdir_p "/etc/systemd/system/default.target.wants";
+ g#ln_sf (sprintf "%s/firstboot.service" firstboot_dir)
+ "/etc/systemd/system/default.target.wants"
+
+and install_sysvinit_service g = function
+ |
"fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based"
->
+ install_sysvinit_redhat g
+ | "opensuse"|"sles"|"suse-based" ->
+ install_sysvinit_suse g
+ | "debian"|"ubuntu" ->
+ install_sysvinit_debian g
+ | distro ->
+ failed "guest type %s is not supported" distro
+
+and install_sysvinit_redhat g =
+ g#mkdir_p "/etc/rc.d/rc2.d";
+ g#mkdir_p "/etc/rc.d/rc3.d";
+ g#mkdir_p "/etc/rc.d/rc5.d";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/rc.d/rc2.d/S99virt-sysprep-firstboot";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/rc.d/rc3.d/S99virt-sysprep-firstboot";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/rc.d/rc5.d/S99virt-sysprep-firstboot"
+
+(* Make firstboot.sh look like a runlevel script to avoid insserv warnings. *)
+and install_sysvinit_suse g =
+ g#mkdir_p "/etc/init.d/rc2.d";
+ g#mkdir_p "/etc/init.d/rc3.d";
+ g#mkdir_p "/etc/init.d/rc5.d";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/init.d/virt-sysprep-firstboot";
+ g#ln_sf "../virt-sysprep-firstboot"
+ "/etc/init.d/rc2.d/S99virt-sysprep-firstboot";
+ g#ln_sf "../virt-sysprep-firstboot"
+ "/etc/init.d/rc3.d/S99virt-sysprep-firstboot";
+ g#ln_sf "../virt-sysprep-firstboot"
+ "/etc/init.d/rc5.d/S99virt-sysprep-firstboot"
+
+and install_sysvinit_debian g =
+ g#mkdir_p "/etc/init.d";
+ g#mkdir_p "/etc/rc2.d";
+ g#mkdir_p "/etc/rc3.d";
+ g#mkdir_p "/etc/rc5.d";
+ g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
+ "/etc/init.d/virt-sysprep-firstboot";
+ g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
+ "/etc/rc2.d/S99virt-sysprep-firstboot";
+ g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
+ "/etc/rc3.d/S99virt-sysprep-firstboot";
+ g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
+ "/etc/rc5.d/S99virt-sysprep-firstboot"
+
+let add_firstboot_script (g : Guestfs.guestfs) root i content =
+ let typ = g#inspect_get_type root in
+ let distro = g#inspect_get_distro root in
+ match typ, distro with
+ | "linux", _ ->
+ install_service g distro;
+ let t = Int64.of_float (Unix.time ()) in
+ let r = string_random8 () in
+ let filename = sprintf "%s/scripts/%04d-%Ld-%s" firstboot_dir i t r in
+ g#write filename content;
+ g#chmod 0o755 filename
+
+ | _ ->
+ failed "guest type %s/%s is not supported" typ distro
diff --git a/customize/firstboot.mli b/customize/firstboot.mli
new file mode 100644
index 0000000..4fb8812
--- /dev/null
+++ b/customize/firstboot.mli
@@ -0,0 +1,27 @@
+(* virt-sysprep
+ * Copyright (C) 2012 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 add_firstboot_script : Guestfs.guestfs -> string -> int -> string ->
unit
+ (** [add_firstboot_script g root idx content] adds a firstboot
+ script called [shortname] containing [content].
+
+ NB. [content] is the contents of the script, {b not} a filename.
+
+ The scripts run in index ([idx]) order.
+
+ You should make sure the filesystem is relabelled after calling this. *)
diff --git a/customize/hostname.ml b/customize/hostname.ml
new file mode 100644
index 0000000..70ca934
--- /dev/null
+++ b/customize/hostname.ml
@@ -0,0 +1,110 @@
+(* virt-sysprep
+ * Copyright (C) 2012-2014 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 Printf
+
+let rec set_hostname (g : Guestfs.guestfs) root hostname =
+ let typ = g#inspect_get_type root in
+ let distro = g#inspect_get_distro root in
+ let major_version = g#inspect_get_major_version root in
+
+ match typ, distro, major_version with
+ (* Fedora 18 (hence RHEL 7+) changed to using /etc/hostname
+ * (RHBZ#881953, RHBZ#858696). We may also need to modify
+ * /etc/machine-info (RHBZ#890027).
+ *)
+ | "linux", "fedora", v when v >= 18 ->
+ update_etc_hostname g hostname;
+ update_etc_machine_info g hostname;
+ true
+
+ | "linux",
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
v
+ when v >= 7 ->
+ update_etc_hostname g hostname;
+ update_etc_machine_info g hostname;
+ true
+
+ | "linux", ("debian"|"ubuntu"), _ ->
+ let old_hostname = read_etc_hostname g in
+ update_etc_hostname g hostname;
+ (match old_hostname with
+ | Some old_hostname -> replace_host_in_etc_hosts g old_hostname hostname
+ | None -> ()
+ );
+ true
+
+ | "linux",
("fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based"),
_ ->
+ replace_line_in_file g "/etc/sysconfig/network" "HOSTNAME"
hostname;
+ true
+
+ | "linux", ("opensuse"|"sles"|"suse-based"), _
->
+ g#write "/etc/HOSTNAME" hostname;
+ true
+
+ | _ ->
+ false
+
+(* Replace <key>=... entry in file. The code assumes it's a small,
+ * plain text file.
+ *)
+and replace_line_in_file g filename key value =
+ let content =
+ if g#is_file filename then (
+ let lines = Array.to_list (g#read_lines filename) in
+ let lines = List.filter (
+ fun line -> not (string_prefix line (key ^ "="))
+ ) lines in
+ let lines = lines @ [sprintf "%s=%s" key value] in
+ String.concat "\n" lines ^ "\n"
+ ) else (
+ sprintf "%s=%s\n" key value
+ ) in
+ g#write filename content
+
+and update_etc_hostname g hostname =
+ g#write "/etc/hostname" (hostname ^ "\n")
+
+and update_etc_machine_info g hostname =
+ replace_line_in_file g "/etc/machine-info" "PRETTY_HOSTNAME"
hostname
+
+and read_etc_hostname g =
+ let filename = "/etc/hostname" in
+ if g#is_file filename then (
+ let lines = Array.to_list (g#read_lines filename) in
+ match lines with
+ | hd :: _ -> Some hd
+ | [] -> None
+ ) else
+ None
+
+and replace_host_in_etc_hosts g oldhost newhost =
+ if g#is_file "/etc/hosts" then (
+ let expr = "/files/etc/hosts/*[label() != '#comment']/*[label() !=
'ipaddr']" in
+ g#aug_init "/" 0;
+ let matches = Array.to_list (g#aug_match expr) in
+ List.iter (
+ fun m ->
+ let value = g#aug_get m in
+ if value = oldhost then (
+ g#aug_set m newhost
+ )
+ ) matches;
+ g#aug_save ()
+ )
diff --git a/customize/hostname.mli b/customize/hostname.mli
new file mode 100644
index 0000000..15487f6
--- /dev/null
+++ b/customize/hostname.mli
@@ -0,0 +1,21 @@
+(* virt-sysprep
+ * Copyright (C) 2012-2014 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 set_hostname : Guestfs.guestfs -> string -> string -> bool
+(** Set the hostname in a guest. Returns true if it was able to
+ do set it, false if not. *)
diff --git a/customize/password.ml b/customize/password.ml
new file mode 100644
index 0000000..6527138
--- /dev/null
+++ b/customize/password.ml
@@ -0,0 +1,175 @@
+(* virt-sysprep
+ * Copyright (C) 2012-2014 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_gettext.Gettext
+open Common_utils
+open Printf
+
+type password_crypto = [`MD5 | `SHA256 | `SHA512 ]
+
+type password_selector = {
+ pw_password : password;
+ pw_locked : bool;
+}
+and password =
+| Password of string
+| Random_password
+| Disabled_password
+
+type password_map = (string, password_selector) Hashtbl.t
+
+let make_random_password =
+ (* Get random characters from the set [A-Za-z0-9] with some
+ * homoglyphs removed.
+ *)
+ let chars = "ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789"
in
+ fun () -> Urandom.urandom_uniform 16 chars
+
+let password_crypto_of_string ~prog = function
+ | "md5" -> `MD5
+ | "sha256" -> `SHA256
+ | "sha512" -> `SHA512
+ | arg ->
+ eprintf (f_"%s: password-crypto: unknown algorithm %s, use \"md5\",
\"sha256\" or \"sha512\".\n")
+ prog arg;
+ exit 1
+
+let rec parse_selector ~prog arg =
+ parse_selector_list ~prog arg (string_nsplit ":" arg)
+
+and parse_selector_list ~prog orig_arg = function
+ | [ "lock"|"locked" ] ->
+ { pw_locked = true; pw_password = Disabled_password }
+ | ("lock"|"locked") :: rest ->
+ let pw = parse_selector_list ~prog orig_arg rest in
+ { pw with pw_locked = true }
+ | [ "file"; filename ] ->
+ { pw_password = Password (read_password_from_file filename);
+ pw_locked = false }
+ | "password" :: password ->
+ { pw_password = Password (String.concat ":" password); pw_locked = false }
+ | [ "random" ] ->
+ { pw_password = Random_password; pw_locked = false }
+ | [ "disable"|"disabled" ] ->
+ { pw_password = Disabled_password; pw_locked = false }
+ | _ ->
+ eprintf (f_"%s: invalid password selector '%s'; see the man
page.\n")
+ prog orig_arg;
+ exit 1
+
+and read_password_from_file filename =
+ let chan = open_in filename in
+ let password = input_line chan in
+ close_in chan;
+ password
+
+(* Permissible characters in a salt. *)
+let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"
+
+let rec set_linux_passwords ~prog ?password_crypto g root passwords =
+ let crypto =
+ match password_crypto with
+ | None -> default_crypto g root
+ | Some c -> c in
+
+ (* XXX Would like to use Augeas here, but Augeas doesn't support
+ * /etc/shadow (as of 1.1.0).
+ *)
+
+ let shadow = Array.to_list (g#read_lines "/etc/shadow") in
+ let shadow =
+ List.map (
+ fun line ->
+ try
+ (* Each line is: "user:[!!]password:..."
+ * !! at the front of the password field means the account is locked.
+ * 'i' points to the first colon, 'j' to the second colon.
+ *)
+ let i = String.index line ':' in
+ let user = String.sub line 0 i in
+ let selector = Hashtbl.find passwords user in
+ let j = String.index_from line (i+1) ':' in
+ let rest = String.sub line j (String.length line - j) in
+ let pwfield =
+ match selector with
+ | { pw_locked = locked;
+ pw_password = Password password } ->
+ if locked then "!!" else "" ^ encrypt password crypto
+ | { pw_locked = locked;
+ pw_password = Random_password } ->
+ let password = make_random_password () in
+ printf (f_"Setting random password of %s to %s\n%!")
+ user password;
+ if locked then "!!" else "" ^ encrypt password crypto
+ | { pw_locked = true; pw_password = Disabled_password } ->
"!!*"
+ | { pw_locked = false; pw_password = Disabled_password } -> "*"
in
+ user ^ ":" ^ pwfield ^ rest
+ with Not_found -> line
+ ) shadow in
+
+ g#write "/etc/shadow" (String.concat "\n" shadow ^
"\n");
+ (* In virt-sysprep /.autorelabel will label it correctly. *)
+ g#chmod 0 "/etc/shadow"
+
+(* Encrypt each password. Use glibc (on the host). See:
+ *
https://rwmj.wordpress.com/2013/07/09/setting-the-root-or-other-passwords...
+ *)
+and encrypt password crypto =
+ (* Get random characters from the set [A-Za-z0-9./] *)
+ let salt = Urandom.urandom_uniform 16 chars in
+ let salt =
+ (match crypto with
+ | `MD5 -> "$1$"
+ | `SHA256 -> "$5$"
+ | `SHA512 -> "$6$") ^ salt ^ "$" in
+ let r = Crypt.crypt password salt in
+ (*printf "password: encrypt %s with salt %s -> %s\n" password salt r;*)
+ r
+
+(* glibc 2.7 was released in Oct 2007. Approximately, all guests that
+ * precede this date only support md5, whereas all guests after this
+ * date can support sha512.
+ *)
+and default_crypto g root =
+ let distro = g#inspect_get_distro root in
+ let major = g#inspect_get_major_version root in
+ match distro, major with
+ |
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
v when v >= 6 ->
+ `SHA512
+ |
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
_ ->
+ `MD5 (* RHEL 5 does not appear to support SHA512, according to crypt(3) *)
+
+ | "fedora", v when v >= 9 -> `SHA512
+ | "fedora", _ -> `MD5
+
+ | "debian", v when v >= 5 -> `SHA512
+ | "debian", _ -> `MD5
+
+ (* Very likely earlier versions of Ubuntu than 10.04 had new crypt,
+ * but Ubuntu 10.04 is the earliest version I have checked.
+ *)
+ | "ubuntu", v when v >= 10 -> `SHA512
+ | "ubuntu", _ -> `MD5
+
+ | _, _ ->
+ eprintf (f_"\
+virt-sysprep: password: warning: using insecure md5 password encryption for
+guest of type %s version %d.
+If this is incorrect, use --password-crypto option and file a bug.\n%!")
+ distro major;
+ `MD5
diff --git a/customize/password.mli b/customize/password.mli
new file mode 100644
index 0000000..c662b1b
--- /dev/null
+++ b/customize/password.mli
@@ -0,0 +1,42 @@
+(* virt-sysprep
+ * Copyright (C) 2012-2014 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.
+ *)
+
+type password_crypto = [ `MD5 | `SHA256 | `SHA512 ]
+
+val password_crypto_of_string : prog:string -> string -> password_crypto
+(** Parse --password-crypto parameter on command line. *)
+
+type password_selector = {
+ pw_password : password; (** The password. *)
+ pw_locked : bool; (** If the account should be locked. *)
+}
+and password =
+| Password of string (** Password (literal string). *)
+| Random_password (** Choose a random password. *)
+| Disabled_password (** [*] in the password field. *)
+
+val parse_selector : prog:string -> string -> password_selector
+(** Parse the selector field in --password/--root-password. Note this
+ doesn't parse the username part. Exits if the format is not valid. *)
+
+type password_map = (string, password_selector) Hashtbl.t
+(** A map of username -> selector. *)
+
+val set_linux_passwords : prog:string -> ?password_crypto:password_crypto ->
Guestfs.guestfs -> string -> password_map -> unit
+(** Adjust the passwords of a Linux guest according to the
+ password map. *)
diff --git a/customize/perl_edit.ml b/customize/perl_edit.ml
new file mode 100644
index 0000000..28e5dea
--- /dev/null
+++ b/customize/perl_edit.ml
@@ -0,0 +1,78 @@
+(* virt-builder
+ * Copyright (C) 2013 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_gettext.Gettext
+open Common_utils
+
+open Printf
+
+(* Implement the --edit option.
+ *
+ * Code copied from virt-edit.
+ *)
+let rec edit_file ~debug (g : Guestfs.guestfs) file expr =
+ let file_old = file ^ "~" in
+ g#rename file file_old;
+
+ (* Download the file to a temporary. *)
+ let tmpfile = Filename.temp_file "vbedit" "" in
+ unlink_on_exit tmpfile;
+ g#download file_old tmpfile;
+
+ do_perl_edit ~debug g tmpfile expr;
+
+ (* Upload the file. Unlike virt-edit we can afford to fail here
+ * so we don't need the temporary upload file.
+ *)
+ g#upload tmpfile file;
+
+ (* However like virt-edit we do need to copy attributes. *)
+ g#copy_attributes ~all:true file_old file;
+ g#rm file_old
+
+and do_perl_edit ~debug g file expr =
+ (* Pass the expression to Perl via the environment. This sidesteps
+ * any quoting problems with the already complex Perl command line.
+ *)
+ Unix.putenv "virt_edit_expr" expr;
+
+ (* Call out to a canned Perl script. *)
+ let cmd = sprintf "\
+ perl -e '
+ $lineno = 0;
+ $expr = $ENV{virt_edit_expr};
+ while (<STDIN>) {
+ $lineno++;
+ eval $expr;
+ die if $@;
+ print STDOUT $_ or die \"print: $!\";
+ }
+ close STDOUT or die \"close: $!\";
+ ' < %s > %s.out" file file in
+
+ if debug then
+ eprintf "%s\n%!" cmd;
+
+ let r = Sys.command cmd in
+ if r <> 0 then (
+ eprintf (f_"virt-builder: error: could not evaluate Perl expression
'%s'\n")
+ expr;
+ exit 1
+ );
+
+ Unix.rename (file ^ ".out") file
diff --git a/customize/perl_edit.mli b/customize/perl_edit.mli
new file mode 100644
index 0000000..fd30dcc
--- /dev/null
+++ b/customize/perl_edit.mli
@@ -0,0 +1,19 @@
+(* virt-builder
+ * Copyright (C) 2013 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 edit_file : debug:bool -> Guestfs.guestfs -> string -> string -> unit
diff --git a/customize/random_seed.ml b/customize/random_seed.ml
new file mode 100644
index 0000000..84236cd
--- /dev/null
+++ b/customize/random_seed.ml
@@ -0,0 +1,96 @@
+(* virt-sysprep
+ * Copyright (C) 2012-2014 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.
+ *)
+
+(* It's important that we write a random seed if we possibly can.
+ * Unfortunately some installers (hello, Debian) don't include the file
+ * in the basic guest, so we have to work out where to create it.
+ *)
+let rec set_random_seed (g : Guestfs.guestfs) root =
+ let typ = g#inspect_get_type root in
+ let created = ref false in
+
+ if typ = "linux" then (
+ let files = [
+ "/var/lib/random-seed"; (* Fedora *)
+ "/var/lib/urandom/random-seed"; (* Debian *)
+ "/var/lib/misc/random-seed"; (* SuSE *)
+ ] in
+ List.iter (
+ fun file ->
+ if g#is_file file then (
+ make_random_seed_file g file;
+ created := true
+ )
+ ) files;
+ );
+
+ if not !created then (
+ (* Backup plan: Try to create a new file. *)
+
+ let distro = g#inspect_get_distro root in
+ let file =
+ match typ, distro with
+ | "linux",
("fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based")
->
+ Some "/var/lib/random-seed"
+ | "linux", ("debian"|"ubuntu") ->
+ Some "/var/lib/urandom/random-seed"
+ | "linux", ("opensuse"|"sles"|"suse-based")
->
+ Some "/var/lib/misc/random-seed"
+ | _ ->
+ None in
+ match file with
+ | Some file ->
+ make_random_seed_file g file;
+ created := true
+ | None -> ()
+ );
+
+ !created
+
+and make_random_seed_file g file =
+ let file_exists = g#is_file file in
+ let n =
+ if file_exists then (
+ let n = Int64.to_int (g#filesize file) in
+
+ (* This file is usually 512 bytes in size. However during
+ * guest creation of some guests it can be just 8 bytes long.
+ * Cap the file size to [512, 8192] bytes.
+ *)
+ min (max n 512) 8192
+ )
+ else
+ (* Default to 512 bytes of randomness. *)
+ 512 in
+
+ (* Get n bytes of randomness from the host. *)
+ let entropy = Urandom.urandom_bytes n in
+
+ if file_exists then (
+ (* Truncate the original file and append, in order to
+ * preserve original permissions.
+ *)
+ g#truncate file;
+ g#write_append file entropy
+ )
+ else (
+ (* Create a new file, set the permissions restrictively. *)
+ g#write file entropy;
+ g#chown 0 0 file;
+ g#chmod 0o600 file
+ )
diff --git a/customize/random_seed.mli b/customize/random_seed.mli
new file mode 100644
index 0000000..b5261f2
--- /dev/null
+++ b/customize/random_seed.mli
@@ -0,0 +1,21 @@
+(* virt-sysprep
+ * Copyright (C) 2012-2014 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 set_random_seed : Guestfs.guestfs -> string -> bool
+(** Set the random seed in the guest. Returns true if it was able to
+ do set it, false if not. *)
diff --git a/customize/timezone.ml b/customize/timezone.ml
new file mode 100644
index 0000000..8b302d9
--- /dev/null
+++ b/customize/timezone.ml
@@ -0,0 +1,39 @@
+(* Set timezone in virt-sysprep and virt-builder.
+ * Copyright (C) 2014 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 Printf
+
+let set_timezone ~prog (g : Guestfs.guestfs) root timezone =
+ let typ = g#inspect_get_type root in
+
+ match typ with
+ (* Every known Linux has /etc/localtime be either a copy of or a
+ * symlink to a timezone file in /usr/share/zoneinfo.
+ * Even systemd didn't fuck this up.
+ *)
+ | "linux" ->
+ let target = sprintf "/usr/share/zoneinfo/%s" timezone in
+ if not (g#exists target) then
+ error ~prog "timezone '%s' does not exist, use a location like
'Europe/London'" timezone;
+ g#ln_sf target "/etc/localtime";
+ true
+
+ | _ ->
+ false
diff --git a/customize/timezone.mli b/customize/timezone.mli
new file mode 100644
index 0000000..ad0d4b2
--- /dev/null
+++ b/customize/timezone.mli
@@ -0,0 +1,22 @@
+(* Set timezone in virt-sysprep and virt-builder.
+ * Copyright (C) 2014 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 set_timezone : prog:string -> Guestfs.guestfs -> string -> string ->
bool
+(** [set_timezone ~prog g root "Europe/London"] sets the default timezone
+ of the guest. Returns [true] if it was able to set the
+ timezone or [false] if not. *)
diff --git a/customize/urandom.ml b/customize/urandom.ml
new file mode 100644
index 0000000..9b613e8
--- /dev/null
+++ b/customize/urandom.ml
@@ -0,0 +1,69 @@
+(* Read /dev/urandom.
+ * Copyright (C) 2013 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.
+ *)
+
+(* Read and return N bytes (only) from /dev/urandom.
+ *
+ * As pointed out by Edwin Török, previous versions of this had a big
+ * problem. They used the OCaml buffered I/O library which would read
+ * a lot more data than requested. This version uses unbuffered I/O
+ * from the Unix module.
+ *)
+
+open Unix
+
+let open_urandom_fd () = openfile "/dev/urandom" [O_RDONLY] 0
+
+let read_byte fd =
+ let s = String.make 1 ' ' in
+ fun () ->
+ if read fd s 0 1 = 0 then (
+ close fd;
+ raise End_of_file
+ );
+ Char.code s.[0]
+
+let urandom_bytes n =
+ assert (n > 0);
+ let ret = String.make n ' ' in
+ let fd = open_urandom_fd () in
+ for i = 0 to n-1 do
+ ret.[i] <- Char.chr (read_byte fd ())
+ done;
+ close fd;
+ ret
+
+(* Return a random number uniformly distributed in [0, upper_bound)
+ * avoiding modulo bias.
+ *)
+let rec uniform_random read upper_bound =
+ let c = read () in
+ if c >= 256 mod upper_bound then c mod upper_bound
+ else uniform_random read upper_bound
+
+let urandom_uniform n chars =
+ assert (n > 0);
+ let nr_chars = String.length chars in
+ assert (nr_chars > 0);
+
+ let ret = String.make n ' ' in
+ let fd = open_urandom_fd () in
+ for i = 0 to n-1 do
+ ret.[i] <- chars.[uniform_random (read_byte fd) nr_chars]
+ done;
+ close fd;
+ ret
diff --git a/customize/urandom.mli b/customize/urandom.mli
new file mode 100644
index 0000000..ffc77dd
--- /dev/null
+++ b/customize/urandom.mli
@@ -0,0 +1,26 @@
+(* Read /dev/urandom.
+ * Copyright (C) 2013 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.
+ *)
+
+(** Read and return N bytes (only) from /dev/urandom. *)
+
+val urandom_bytes : int -> string
+(** Read N bytes from /dev/urandom and return it as a binary string. *)
+
+val urandom_uniform : int -> string -> string
+(** [urandom_uniform n chars] returns [n] bytes, uniformly
+ distributed from the sets of characters [chars]. *)
diff --git a/generator/Makefile.am b/generator/Makefile.am
index c129747..e66644c 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -27,6 +27,7 @@ sources = \
c.ml \
checks.ml \
csharp.ml \
+ customize.ml \
daemon.ml \
docstrings.ml \
erlang.ml \
@@ -89,6 +90,7 @@ objects = \
golang.cmo \
bindtests.cmo \
errnostring.cmo \
+ customize.cmo \
main.cmo
EXTRA_DIST = $(sources) files-generated.txt
diff --git a/generator/customize.ml b/generator/customize.ml
new file mode 100644
index 0000000..84ffeaa
--- /dev/null
+++ b/generator/customize.ml
@@ -0,0 +1,577 @@
+(* libguestfs
+ * Copyright (C) 2014 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
+ *)
+
+(* Please read generator/README first. *)
+
+open Printf
+
+open Docstrings
+open Pr
+
+(* Command-line arguments used by virt-customize, virt-builder and
+ * virt-sysprep.
+ *)
+
+type op = {
+ op_name : string; (* argument name, without "--" *)
+ op_type : op_type; (* argument value type *)
+ op_discrim : string; (* argument discriminator in OCaml code *)
+ op_shortdesc : string; (* single-line description *)
+ op_pod_longdesc : string; (* multi-line description *)
+}
+and op_type =
+| Unit (* no argument *)
+| String of string (* string *)
+| StringPair of string (* string:string *)
+| StringList of string (* string,string,... *)
+| TargetLinks of string (* target:link[:link...] *)
+| PasswordSelector of string (* password selector *)
+
+let ops = [
+ { op_name = "delete";
+ op_type = String "PATH";
+ op_discrim = "`Delete";
+ op_shortdesc = "Delete a file or directory";
+ op_pod_longdesc = "\
+Delete a file from the guest. Or delete a directory (and all its
+contents, recursively).
+
+See also: I<--upload>, I<--scrub>.";
+ };
+ { op_name = "edit";
+ op_type = StringPair "FILE:EXPR";
+ op_discrim = "`Edit";
+ op_shortdesc = "Edit file using Perl expression";
+ op_pod_longdesc = "\
+Edit C<FILE> using the Perl expression C<EXPR>.
+
+Be careful to properly quote the expression to prevent it from
+being altered by the shell.
+
+Note that this option is only available when Perl 5 is installed.
+
+See L<virt-edit(1)/NON-INTERACTIVE EDITING>.";
+ };
+ { op_name = "firstboot";
+ op_type = String "SCRIPT";
+ op_discrim = "`FirstbootScript";
+ op_shortdesc = "Run script at first guest boot";
+ op_pod_longdesc = "\
+Install C<SCRIPT> inside the guest, so that when the guest first boots
+up, the script runs (as root, late in the boot process).
+
+The script is automatically chmod +x after installation in the guest.
+
+The alternative version I<--firstboot-command> is the same, but it
+conveniently wraps the command up in a single line script for you.
+
+You can have multiple I<--firstboot> options. They run in the same
+order that they appear on the command line.
+
+See also I<--run>.";
+ };
+ { op_name = "firstboot-command";
+ op_type = String "'CMD ARGS'";
+ op_discrim = "`FirstbootCommand";
+ op_shortdesc = "Run command at first guest boot";
+ op_pod_longdesc = "\
+Run command (and arguments) inside the guest when the guest first
+boots up (as root, late in the boot process).
+
+You can have multiple I<--firstboot> options. They run in the same
+order that they appear on the command line.
+
+See also I<--run>.";
+ };
+ { op_name = "firstboot-install";
+ op_type = StringList "PKG,PKG..";
+ op_discrim = "`FirstbootPackages";
+ op_shortdesc = "Add package(s) to install at first boot";
+ op_pod_longdesc = "\
+Install the named packages (a comma-separated list). These are
+installed when the guest first boots using the guest's package manager
+(eg. apt, yum, etc.) and the guest's network connection.
+
+For an overview on the different ways to install packages, see
+L<virt-builder(1)/INSTALLING PACKAGES>.";
+ };
+ { op_name = "hostname";
+ op_type = String "HOSTNAME";
+ op_discrim = "`Hostname";
+ op_shortdesc = "Set the hostname";
+ op_pod_longdesc = "\
+Set the hostname of the guest to C<HOSTNAME>. You can use a
+dotted hostname.domainname (FQDN) if you want.";
+ };
+ { op_name = "install";
+ op_type = StringList "PKG,PKG..";
+ op_discrim = "`InstallPackages";
+ op_shortdesc = "Add package(s) to install";
+ op_pod_longdesc = "\
+Install the named packages (a comma-separated list). These are
+installed during the image build using the guest's package manager
+(eg. apt, yum, etc.) and the host's network connection.
+
+For an overview on the different ways to install packages, see
+L<virt-builder(1)/INSTALLING PACKAGES>.
+
+See also I<--update>.";
+ };
+ { op_name = "link";
+ op_type = TargetLinks "TARGET:LINK[:LINK..]";
+ op_discrim = "`Link";
+ op_shortdesc = "Create symbolic links";
+ op_pod_longdesc = "\
+Create symbolic link(s) in the guest, starting at C<LINK> and
+pointing at C<TARGET>.";
+ };
+ { op_name = "mkdir";
+ op_type = String "DIR";
+ op_discrim = "`Mkdir";
+ op_shortdesc = "Create a directory";
+ op_pod_longdesc = "\
+Create a directory in the guest.
+
+This uses S<C<mkdir -p>> so any intermediate directories are created,
+and it also works if the directory already exists.";
+ };
+ { op_name = "root-password";
+ op_type = PasswordSelector "SELECTOR";
+ op_discrim = "`RootPassword";
+ op_shortdesc = "Set root password";
+ op_pod_longdesc = "\
+Set the root password.
+
+See L<virt-builder(1)/USERS AND PASSWORDS> for the format of
+the C<SELECTOR> field, and also how to set up user accounts.
+
+Note: In virt-builder, if you I<don't> set I<--root-password>
+then the guest is given a I<random> root password.";
+ };
+ { op_name = "run";
+ op_type = String "SCRIPT";
+ op_discrim = "`Script";
+ op_shortdesc = "Run script in disk image";
+ op_pod_longdesc = "\
+Run the shell script (or any program) called C<SCRIPT> on the disk
+image. The script runs virtualized inside a small appliance, chrooted
+into the guest filesystem.
+
+The script is automatically chmod +x.
+
+If libguestfs supports it then a limited network connection is
+available but it only allows outgoing network connections. You can
+also attach data disks (eg. ISO files) as another way to provide data
+(eg. software packages) to the script without needing a network
+connection (I<--attach>). You can also upload data files (I<--upload>).
+
+You can have multiple I<--run> options. They run
+in the same order that they appear on the command line.
+
+See also: I<--firstboot>, I<--attach>, I<--upload>.";
+ };
+ { op_name = "run-command";
+ op_type = String "'CMD ARGS'";
+ op_discrim = "`Command";
+ op_shortdesc = "Run command in disk image";
+ op_pod_longdesc = "\
+Run the command and arguments on the disk image. The command runs
+virtualized inside a small appliance, chrooted into the guest filesystem.
+
+If libguestfs supports it then a limited network connection is
+available but it only allows outgoing network connections. You can
+also attach data disks (eg. ISO files) as another way to provide data
+(eg. software packages) to the script without needing a network
+connection (I<--attach>). You can also upload data files (I<--upload>).
+
+You can have multiple I<--run-command> options. They run
+in the same order that they appear on the command line.
+
+See also: I<--firstboot>, I<--attach>, I<--upload>.";
+ };
+ { op_name = "scrub";
+ op_type = String "FILE";
+ op_discrim = "`Scrub";
+ op_shortdesc = "Scrub a file" ;
+ op_pod_longdesc = "\
+Scrub a file from the guest. This is like I<--delete> except that:
+
+=over 4
+
+=item *
+
+It scrubs the data so a guest could not recover it.
+
+=item *
+
+It cannot delete directories, only regular files.
+
+=back";
+ };
+ { op_name = "timezone";
+ op_type = String "TIMEZONE";
+ op_discrim = "`Timezone";
+ op_shortdesc = "Set the default timezone";
+ op_pod_longdesc = "\
+Set the default timezone of the guest to C<TIMEZONE>. Use a location
+string like C<Europe/London>";
+ };
+ { op_name = "update";
+ op_type = Unit;
+ op_discrim = "`Update";
+ op_shortdesc = "Update core packages";
+ op_pod_longdesc = "\
+Do the equivalent of C<yum update>, C<apt-get upgrade>, or whatever
+command is required to update the packages already installed in the
+template to their latest versions.
+
+See also I<--install>.";
+ };
+ { op_name = "upload";
+ op_type = StringPair "FILE:DEST";
+ op_discrim = "`Upload";
+ op_shortdesc = "Upload local file to destination";
+ op_pod_longdesc = "\
+Upload local file C<FILE> to destination C<DEST> in the disk image.
+File owner and permissions from the original are preserved, so you
+should set them to what you want them to be in the disk image.
+
+C<DEST> could be the final filename. This can be used to rename
+the file on upload.
+
+If C<DEST> is a directory name (which must already exist in the guest)
+then the file is uploaded into that directory, and it keeps the same
+name as on the local filesystem.
+
+See also: I<--mkdir>, I<--delete>, I<--scrub>.";
+ };
+ { op_name = "write";
+ op_type = StringPair "FILE:CONTENT";
+ op_discrim = "`Write";
+ op_shortdesc = "Write file";
+ op_pod_longdesc = "\
+Write C<CONTENT> to C<FILE>.";
+ };
+]
+
+(* Flags. *)
+type flag = {
+ flag_name : string; (* argument name, without "--" *)
+ flag_type : flag_type; (* argument value type *)
+ flag_ml_var : string; (* variable name in OCaml code *)
+ flag_shortdesc : string; (* single-line description *)
+ flag_pod_longdesc : string; (* multi-line description *)
+}
+and flag_type =
+| FlagBool of bool (* boolean is the default value *)
+| FlagPasswordCrypto of string
+
+let flags = [
+ { flag_name = "no-logfile";
+ flag_type = FlagBool false;
+ flag_ml_var = "scrub_logfile";
+ flag_shortdesc = "Scrub build log file";
+ flag_pod_longdesc = "\
+Scrub C<builder.log> (log file from build commands) from the image
+after building is complete. If you don't want to reveal precisely how
+the image was built, use this option.
+
+See also: L</LOG FILE>.";
+ };
+ { flag_name = "password-crypto";
+ flag_type = FlagPasswordCrypto "md5|sha256|sha512";
+ flag_ml_var = "password_crypto";
+ flag_shortdesc = "Set password crypto";
+ flag_pod_longdesc = "\
+Set the password encryption to C<md5>, C<sha256> or C<sha512>.
+
+C<sha256> and C<sha512> require glibc E<ge> 2.7 (check crypt(3) inside
+the guest).
+
+C<md5> will work with relatively old Linux guests (eg. RHEL 3), but
+is not secure against modern attacks.
+
+The default is C<sha512> unless libguestfs detects an old guest that
+didn't have support for SHA-512, in which case it will use C<md5>.
+You can override libguestfs by specifying this option.";
+ };
+ { flag_name = "selinux-relabel";
+ flag_type = FlagBool false (* XXX - the default in virt-builder *);
+ flag_ml_var = "selinux_relabel";
+ flag_shortdesc = "Relabel files with correct SELinux labels";
+ flag_pod_longdesc = "\
+Relabel files in the guest so that they have the correct SELinux label.
+
+You should only use this option for guests which support SELinux.";
+ };
+]
+
+let rec generate_customize_cmdline_mli () =
+ generate_header OCamlStyle GPLv2plus;
+
+ pr "\
+(** Command line argument parsing, both for the virt-customize binary
+ and for the other tools that share the same code. *)
+
+";
+ generate_ops_struct_decl ();
+ pr "\n";
+
+ pr "val argspec : prog:string -> unit -> (Arg.key * Arg.spec * Arg.doc) list
* (unit -> ops)\n"
+
+and generate_customize_cmdline_ml () =
+ generate_header OCamlStyle GPLv2plus;
+
+ pr "\
+(* Command line argument parsing, both for the virt-customize binary
+ * and for the other tools that share the same code.
+ *)
+
+open Printf
+
+open Common_utils
+open Common_gettext.Gettext
+
+";
+ generate_ops_struct_decl ();
+ pr "\n";
+
+ pr "\
+let rec argspec ~prog () =
+ let ops = ref [] in
+";
+ List.iter (
+ function
+ | { flag_type = FlagBool default; flag_ml_var = var } ->
+ pr " let %s = ref %b in\n" var default
+ | { flag_type = FlagPasswordCrypto _; flag_ml_var = var } ->
+ pr " let %s = ref None in\n" var
+ ) flags;
+ pr "\
+
+ let rec get_ops () = {
+ ops = List.rev !ops;
+ flags = get_flags ();
+ }
+ and get_flags () = {
+";
+ List.iter (fun { flag_ml_var = var } -> pr " %s = !%s;\n" var var)
flags;
+ pr " }
+ in
+
+ let split_string_pair option_name arg =
+ let i =
+ try String.index arg ':'
+ with Not_found ->
+ eprintf (f_\"%%s: invalid format for '--%%s' parameter, see the man
page.\\n\")
+ prog option_name;
+ exit 1 in
+ let len = String.length arg in
+ String.sub arg 0 i, String.sub arg (i+1) (len-(i+1))
+ in
+ let split_string_list arg =
+ string_nsplit \",\" arg
+ in
+ let split_links_list option_name arg =
+ match string_nsplit \":\" arg with
+ | [] | [_] ->
+ eprintf (f_\"%%s: invalid format for '--%%s' parameter, see the man
page.\\n\")
+ prog option_name;
+ exit 1
+ | target :: lns -> target, lns
+ in
+
+ let argspec = [
+";
+
+ List.iter (
+ function
+ | { op_type = Unit; op_name = name; op_discrim = discrim;
+ op_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ pr " Arg.Unit (fun () -> ops := %s :: !ops),\n" discrim;
+ pr " \" \" ^ s_\"%s\";\n" shortdesc
+ | { op_type = String v; op_name = name; op_discrim = discrim;
+ op_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ pr " Arg.String (fun s -> ops := %s s :: !ops),\n" discrim;
+ pr " s_\"%s\" ^ \" \" ^ s_\"%s\";\n" v
shortdesc
+ | { op_type = StringPair v; op_name = name; op_discrim = discrim;
+ op_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ pr " Arg.String (fun s -> let p = split_string_pair \"%s\" s
in ops := %s p :: !ops),\n" name discrim;
+ pr " s_\"%s\" ^ \" \" ^ s_\"%s\";\n" v
shortdesc
+ | { op_type = StringList v; op_name = name; op_discrim = discrim;
+ op_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ pr " Arg.String (fun s -> let ss = split_string_list s in ops := %s ss
:: !ops),\n" discrim;
+ pr " s_\"%s\" ^ \" \" ^ s_\"%s\";\n" v
shortdesc
+ | { op_type = TargetLinks v; op_name = name; op_discrim = discrim;
+ op_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ pr " Arg.String (fun s -> let ss = split_links_list \"%s\" s
in ops := %s ss :: !ops),\n" name discrim;
+ pr " s_\"%s\" ^ \" \" ^ s_\"%s\";\n" v
shortdesc
+ | { op_type = PasswordSelector v; op_name = name; op_discrim = discrim;
+ op_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ pr " Arg.String (fun s -> let sel = Password.parse_selector ~prog s in
ops := %s sel :: !ops),\n" discrim;
+ pr " s_\"%s\" ^ \" \" ^ s_\"%s\";\n" v
shortdesc
+ ) ops;
+
+ List.iter (
+ function
+ | { flag_type = FlagBool default; flag_ml_var = var; flag_name = name;
+ flag_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ if default (* is true *) then
+ pr " Arg.Clear %s,\n" var
+ else
+ pr " Arg.Set %s,\n" var;
+ pr " \" \" ^ s_\"%s\";\n" shortdesc
+ | { flag_type = FlagPasswordCrypto v; flag_ml_var = var;
+ flag_name = name; flag_shortdesc = shortdesc } ->
+ pr " \"--%s\",\n" name;
+ pr " Arg.String (fun s -> %s := Some (Password.password_crypto_of_string
~prog s)),\n" var;
+ pr " \"%s\" ^ \" \" ^ s_\"%s\";\n" v
shortdesc
+ ) flags;
+
+ pr "\
+ ] in
+
+ argspec, get_ops
+"
+
+and generate_ops_struct_decl () =
+ pr "\
+type ops = {
+ ops : op list;
+ flags : flags;
+}
+";
+
+ (* Operations. *)
+ pr "and op = [\n";
+ List.iter (
+ function
+ | { op_type = Unit; op_discrim = discrim; op_name = name } ->
+ pr " | %s\n (* --%s *)\n" discrim name
+ | { op_type = String v; op_discrim = discrim; op_name = name } ->
+ pr " | %s of string\n (* --%s %s *)\n" discrim name v
+ | { op_type = StringPair v; op_discrim = discrim;
+ op_name = name } ->
+ pr " | %s of string * string\n (* --%s %s *)\n" discrim name v
+ | { op_type = StringList v; op_discrim = discrim;
+ op_name = name } ->
+ pr " | %s of string list\n (* --%s %s *)\n" discrim name v
+ | { op_type = TargetLinks v; op_discrim = discrim;
+ op_name = name } ->
+ pr " | %s of string * string list\n (* --%s %s *)\n" discrim name
v
+ | { op_type = PasswordSelector v; op_discrim = discrim;
+ op_name = name } ->
+ pr " | %s of Password.password_selector\n (* --%s %s *)\n"
+ discrim name v
+ ) ops;
+ pr "]\n";
+
+ (* Flags. *)
+ pr "and flags = {\n";
+ List.iter (
+ function
+ | { flag_type = FlagBool _; flag_ml_var = var; flag_name = name } ->
+ pr " %s : bool;\n (* --%s *)\n" var name
+ | { flag_type = FlagPasswordCrypto v; flag_ml_var = var;
+ flag_name = name } ->
+ pr " %s : Password.password_crypto option;\n (* --%s %s *)\n"
+ var name v
+ ) flags;
+ pr "}\n"
+
+let generate_customize_synopsis_pod () =
+ (* generate_header PODStyle GPLv2plus; - NOT POSSIBLE *)
+
+ let options =
+ List.map (
+ function
+ | { op_type = Unit; op_name = n } ->
+ n, sprintf "[--%s]" n
+ | { op_type = String v | StringPair v | StringList v | TargetLinks v
+ | PasswordSelector v;
+ op_name = n } ->
+ n, sprintf "[--%s %s]" n v
+ ) ops @
+ List.map (
+ function
+ | { flag_type = FlagBool _; flag_name = n } ->
+ n, sprintf "[--%s]" n
+ | { flag_type = FlagPasswordCrypto v; flag_name = n } ->
+ n, sprintf "[--%s %s]" n v
+ ) flags in
+
+ (* Print the option names in the synopsis, line-wrapped. *)
+ let col = ref 4 in
+ pr " ";
+
+ List.iter (
+ fun (_, str) ->
+ let len = String.length str + 1 in
+ col := !col + len;
+ if !col >= 72 then (
+ col := 4 + len;
+ pr "\n "
+ );
+ pr " %s" str
+ ) options;
+ if !col > 4 then
+ pr "\n"
+
+let generate_customize_options_pod () =
+ generate_header PODStyle GPLv2plus;
+
+ pr "=over 4\n\n";
+
+ let pod =
+ List.map (
+ function
+ | { op_type = Unit; op_name = n; op_pod_longdesc = ld } ->
+ n, sprintf "B<--%s>" n, ld
+ | { op_type = String v | StringPair v | StringList v | TargetLinks v
+ | PasswordSelector v;
+ op_name = n; op_pod_longdesc = ld } ->
+ n, sprintf "B<--%s> %s" n v, ld
+ ) ops @
+ List.map (
+ function
+ | { flag_type = FlagBool _; flag_name = n; flag_pod_longdesc = ld } ->
+ n, sprintf "B<--%s>" n, ld
+ | { flag_type = FlagPasswordCrypto v;
+ flag_name = n; flag_pod_longdesc = ld } ->
+ n, sprintf "B<--%s> %s" n v, ld
+ ) flags in
+ let pod = List.sort compare pod in
+
+ List.iter (
+ fun (_, item, longdesc) ->
+ pr "\
+=item %s
+
+%s
+
+" item longdesc
+ ) pod;
+
+ pr "=back\n\n"
diff --git a/generator/main.ml b/generator/main.ml
index d1fa4d2..63ddb9a 100644
--- a/generator/main.ml
+++ b/generator/main.ml
@@ -46,6 +46,7 @@ open Gobject
open Golang
open Bindtests
open Errnostring
+open Customize
let perror msg = function
| Unix_error (err, _, _) ->
@@ -208,6 +209,11 @@ Run it from the top source directory using the command
generate_gobject_session_header;
output_to "gobject/src/session.c" generate_gobject_session_source;
+ output_to "customize/customize_cmdline.mli" generate_customize_cmdline_mli;
+ output_to "customize/customize_cmdline.ml" generate_customize_cmdline_ml;
+ output_to "customize/customize-synopsis.pod"
generate_customize_synopsis_pod;
+ output_to "customize/customize-options.pod" generate_customize_options_pod;
+
(* Generate the list of files generated -- last. *)
printf "generated %d lines of code\n" (get_lines_generated ());
let files = List.sort compare (get_files_generated ()) in
diff --git a/mllib/Makefile.am b/mllib/Makefile.am
index e275213..fe215f8 100644
--- a/mllib/Makefile.am
+++ b/mllib/Makefile.am
@@ -28,37 +28,20 @@ SOURCES = \
common_utils.ml \
common_utils_tests.ml \
config.ml \
- crypt-c.c \
- crypt.ml \
- crypt.mli \
- firstboot.mli \
- firstboot.ml \
fsync-c.c \
fsync.mli \
fsync.ml \
mkdtemp.mli \
mkdtemp.ml \
mkdtemp-c.c \
- hostname.mli \
- hostname.ml \
- password.mli \
- password.ml \
- perl_edit.mli \
- perl_edit.ml \
planner.mli \
planner.ml \
progress-c.c \
progress.mli \
progress.ml \
- random_seed.mli \
- random_seed.ml \
- timezone.mli \
- timezone.ml \
tty-c.c \
tTY.mli \
tTY.ml \
- urandom.mli \
- urandom.ml \
uri-c.c \
uRI.mli \
uRI.ml
@@ -73,18 +56,10 @@ ocaml_modules = config \
libdir \
common_gettext \
common_utils \
- urandom \
- random_seed \
- hostname \
- timezone \
- firstboot \
- perl_edit \
tTY \
fsync \
progress \
uRI \
- crypt \
- password \
mkdtemp \
planner
@@ -95,7 +70,6 @@ OBJECTS = \
fsync-c.o \
progress-c.o \
uri-c.o \
- crypt-c.o \
mkdtemp-c.o
if HAVE_OCAMLOPT
diff --git a/mllib/crypt-c.c b/mllib/crypt-c.c
deleted file mode 100644
index 29a91e4..0000000
--- a/mllib/crypt-c.c
+++ /dev/null
@@ -1,44 +0,0 @@
-/* virt-sysprep - interface to crypt(3)
- * Copyright (C) 2013 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.
- */
-
-#include <config.h>
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <caml/alloc.h>
-#include <caml/memory.h>
-#include <caml/mlvalues.h>
-
-value
-virt_sysprep_crypt (value keyv, value saltv)
-{
- CAMLparam2 (keyv, saltv);
- CAMLlocal1 (rv);
- char *r;
-
- /* Note that crypt returns a pointer to a statically allocated
- * buffer in glibc. For this and other reasons, this function
- * is not thread safe.
- */
- r = crypt (String_val (keyv), String_val (saltv));
- rv = caml_copy_string (r);
-
- CAMLreturn (rv);
-}
diff --git a/mllib/crypt.ml b/mllib/crypt.ml
deleted file mode 100644
index 2c48c0d..0000000
--- a/mllib/crypt.ml
+++ /dev/null
@@ -1,19 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2013 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.
- *)
-
-external crypt : string -> string -> string = "virt_sysprep_crypt"
diff --git a/mllib/crypt.mli b/mllib/crypt.mli
deleted file mode 100644
index ef4066f..0000000
--- a/mllib/crypt.mli
+++ /dev/null
@@ -1,22 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2013 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.
- *)
-
-(** Wrapper around glibc crypt(3) function. *)
-
-val crypt : string -> string -> string
-(** [crypt key salt] returns the password ([key]) encrypted. *)
diff --git a/mllib/firstboot.ml b/mllib/firstboot.ml
deleted file mode 100644
index 9e4c7b6..0000000
--- a/mllib/firstboot.ml
+++ /dev/null
@@ -1,171 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012 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 Printf
-
-open Common_utils
-open Common_gettext.Gettext
-
-(* For Linux guests. *)
-let firstboot_dir = "/usr/lib/virt-sysprep"
-
-let firstboot_sh = sprintf "\
-#!/bin/sh -
-
-### BEGIN INIT INFO
-# Provides: virt-sysprep
-# Required-Start: $null
-# Should-Start: $all
-# Required-Stop: $null
-# Should-Stop: $all
-# Default-Start: 2 3 5
-# Default-Stop: 0 1 6
-# Short-Description: Start scripts to run once at next boot
-# Description: Start scripts to run once at next boot
-# These scripts run the first time the guest boots,
-# and then are deleted. Output or errors from the scripts
-# are written to ~root/virt-sysprep-firstboot.log.
-### END INIT INFO
-
-d=%s/scripts
-logfile=~root/virt-sysprep-firstboot.log
-
-echo \"$0\" \"$@\" 2>&1 | tee $logfile
-echo \"Scripts dir: $d\" 2>&1 | tee $logfile
-
-if test \"$1\" = \"start\"
-then
- for f in $d/* ; do
- if test -x \"$f\"
- then
- echo '=== Running' $f '===' 2>&1 | tee $logfile
- $f 2>&1 | tee $logfile
- rm -f $f
- fi
- done
-fi
-" firstboot_dir
-
-let firstboot_service = sprintf "\
-[Unit]
-Description=virt-sysprep firstboot service
-After=network.target
-Before=prefdm.service
-
-[Service]
-Type=oneshot
-ExecStart=%s/firstboot.sh start
-RemainAfterExit=yes
-StandardOutput=journal+console
-StandardError=inherit
-
-[Install]
-WantedBy=default.target
-" firstboot_dir
-
-let failed fs =
- ksprintf (fun msg -> failwith (s_"firstboot: failed: " ^ msg)) fs
-
-let rec install_service (g : Guestfs.guestfs) distro =
- g#mkdir_p firstboot_dir;
- g#mkdir_p (sprintf "%s/scripts" firstboot_dir);
- g#write (sprintf "%s/firstboot.sh" firstboot_dir) firstboot_sh;
- g#chmod 0o755 (sprintf "%s/firstboot.sh" firstboot_dir);
-
- (* Note we install both systemd and sysvinit services. This is
- * because init systems can be switched at runtime, and it's easy to
- * tell if systemd is installed (eg. Ubuntu uses upstart but installs
- * systemd configuration directories). There is no danger of a
- * firstboot script running twice because they disable themselves
- * after running.
- *)
- if g#is_dir "/etc/systemd/system" then
- install_systemd_service g;
- if g#is_dir "/etc/rc.d" || g#is_dir "/etc/init.d" then
- install_sysvinit_service g distro
-
-(* Install the systemd firstboot service, if not installed already. *)
-and install_systemd_service g =
- g#write (sprintf "%s/firstboot.service" firstboot_dir) firstboot_service;
- g#mkdir_p "/etc/systemd/system/default.target.wants";
- g#ln_sf (sprintf "%s/firstboot.service" firstboot_dir)
- "/etc/systemd/system/default.target.wants"
-
-and install_sysvinit_service g = function
- |
"fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based"
->
- install_sysvinit_redhat g
- | "opensuse"|"sles"|"suse-based" ->
- install_sysvinit_suse g
- | "debian"|"ubuntu" ->
- install_sysvinit_debian g
- | distro ->
- failed "guest type %s is not supported" distro
-
-and install_sysvinit_redhat g =
- g#mkdir_p "/etc/rc.d/rc2.d";
- g#mkdir_p "/etc/rc.d/rc3.d";
- g#mkdir_p "/etc/rc.d/rc5.d";
- g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
- "/etc/rc.d/rc2.d/S99virt-sysprep-firstboot";
- g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
- "/etc/rc.d/rc3.d/S99virt-sysprep-firstboot";
- g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
- "/etc/rc.d/rc5.d/S99virt-sysprep-firstboot"
-
-(* Make firstboot.sh look like a runlevel script to avoid insserv warnings. *)
-and install_sysvinit_suse g =
- g#mkdir_p "/etc/init.d/rc2.d";
- g#mkdir_p "/etc/init.d/rc3.d";
- g#mkdir_p "/etc/init.d/rc5.d";
- g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
- "/etc/init.d/virt-sysprep-firstboot";
- g#ln_sf "../virt-sysprep-firstboot"
- "/etc/init.d/rc2.d/S99virt-sysprep-firstboot";
- g#ln_sf "../virt-sysprep-firstboot"
- "/etc/init.d/rc3.d/S99virt-sysprep-firstboot";
- g#ln_sf "../virt-sysprep-firstboot"
- "/etc/init.d/rc5.d/S99virt-sysprep-firstboot"
-
-and install_sysvinit_debian g =
- g#mkdir_p "/etc/init.d";
- g#mkdir_p "/etc/rc2.d";
- g#mkdir_p "/etc/rc3.d";
- g#mkdir_p "/etc/rc5.d";
- g#ln_sf (sprintf "%s/firstboot.sh" firstboot_dir)
- "/etc/init.d/virt-sysprep-firstboot";
- g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
- "/etc/rc2.d/S99virt-sysprep-firstboot";
- g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
- "/etc/rc3.d/S99virt-sysprep-firstboot";
- g#ln_sf "/etc/init.d/virt-sysprep-firstboot"
- "/etc/rc5.d/S99virt-sysprep-firstboot"
-
-let add_firstboot_script (g : Guestfs.guestfs) root i content =
- let typ = g#inspect_get_type root in
- let distro = g#inspect_get_distro root in
- match typ, distro with
- | "linux", _ ->
- install_service g distro;
- let t = Int64.of_float (Unix.time ()) in
- let r = string_random8 () in
- let filename = sprintf "%s/scripts/%04d-%Ld-%s" firstboot_dir i t r in
- g#write filename content;
- g#chmod 0o755 filename
-
- | _ ->
- failed "guest type %s/%s is not supported" typ distro
diff --git a/mllib/firstboot.mli b/mllib/firstboot.mli
deleted file mode 100644
index 4fb8812..0000000
--- a/mllib/firstboot.mli
+++ /dev/null
@@ -1,27 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012 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 add_firstboot_script : Guestfs.guestfs -> string -> int -> string ->
unit
- (** [add_firstboot_script g root idx content] adds a firstboot
- script called [shortname] containing [content].
-
- NB. [content] is the contents of the script, {b not} a filename.
-
- The scripts run in index ([idx]) order.
-
- You should make sure the filesystem is relabelled after calling this. *)
diff --git a/mllib/hostname.ml b/mllib/hostname.ml
deleted file mode 100644
index 70ca934..0000000
--- a/mllib/hostname.ml
+++ /dev/null
@@ -1,110 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012-2014 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 Printf
-
-let rec set_hostname (g : Guestfs.guestfs) root hostname =
- let typ = g#inspect_get_type root in
- let distro = g#inspect_get_distro root in
- let major_version = g#inspect_get_major_version root in
-
- match typ, distro, major_version with
- (* Fedora 18 (hence RHEL 7+) changed to using /etc/hostname
- * (RHBZ#881953, RHBZ#858696). We may also need to modify
- * /etc/machine-info (RHBZ#890027).
- *)
- | "linux", "fedora", v when v >= 18 ->
- update_etc_hostname g hostname;
- update_etc_machine_info g hostname;
- true
-
- | "linux",
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
v
- when v >= 7 ->
- update_etc_hostname g hostname;
- update_etc_machine_info g hostname;
- true
-
- | "linux", ("debian"|"ubuntu"), _ ->
- let old_hostname = read_etc_hostname g in
- update_etc_hostname g hostname;
- (match old_hostname with
- | Some old_hostname -> replace_host_in_etc_hosts g old_hostname hostname
- | None -> ()
- );
- true
-
- | "linux",
("fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based"),
_ ->
- replace_line_in_file g "/etc/sysconfig/network" "HOSTNAME"
hostname;
- true
-
- | "linux", ("opensuse"|"sles"|"suse-based"), _
->
- g#write "/etc/HOSTNAME" hostname;
- true
-
- | _ ->
- false
-
-(* Replace <key>=... entry in file. The code assumes it's a small,
- * plain text file.
- *)
-and replace_line_in_file g filename key value =
- let content =
- if g#is_file filename then (
- let lines = Array.to_list (g#read_lines filename) in
- let lines = List.filter (
- fun line -> not (string_prefix line (key ^ "="))
- ) lines in
- let lines = lines @ [sprintf "%s=%s" key value] in
- String.concat "\n" lines ^ "\n"
- ) else (
- sprintf "%s=%s\n" key value
- ) in
- g#write filename content
-
-and update_etc_hostname g hostname =
- g#write "/etc/hostname" (hostname ^ "\n")
-
-and update_etc_machine_info g hostname =
- replace_line_in_file g "/etc/machine-info" "PRETTY_HOSTNAME"
hostname
-
-and read_etc_hostname g =
- let filename = "/etc/hostname" in
- if g#is_file filename then (
- let lines = Array.to_list (g#read_lines filename) in
- match lines with
- | hd :: _ -> Some hd
- | [] -> None
- ) else
- None
-
-and replace_host_in_etc_hosts g oldhost newhost =
- if g#is_file "/etc/hosts" then (
- let expr = "/files/etc/hosts/*[label() != '#comment']/*[label() !=
'ipaddr']" in
- g#aug_init "/" 0;
- let matches = Array.to_list (g#aug_match expr) in
- List.iter (
- fun m ->
- let value = g#aug_get m in
- if value = oldhost then (
- g#aug_set m newhost
- )
- ) matches;
- g#aug_save ()
- )
diff --git a/mllib/hostname.mli b/mllib/hostname.mli
deleted file mode 100644
index 15487f6..0000000
--- a/mllib/hostname.mli
+++ /dev/null
@@ -1,21 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012-2014 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 set_hostname : Guestfs.guestfs -> string -> string -> bool
-(** Set the hostname in a guest. Returns true if it was able to
- do set it, false if not. *)
diff --git a/mllib/password.ml b/mllib/password.ml
deleted file mode 100644
index 6527138..0000000
--- a/mllib/password.ml
+++ /dev/null
@@ -1,175 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012-2014 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_gettext.Gettext
-open Common_utils
-open Printf
-
-type password_crypto = [`MD5 | `SHA256 | `SHA512 ]
-
-type password_selector = {
- pw_password : password;
- pw_locked : bool;
-}
-and password =
-| Password of string
-| Random_password
-| Disabled_password
-
-type password_map = (string, password_selector) Hashtbl.t
-
-let make_random_password =
- (* Get random characters from the set [A-Za-z0-9] with some
- * homoglyphs removed.
- *)
- let chars = "ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789"
in
- fun () -> Urandom.urandom_uniform 16 chars
-
-let password_crypto_of_string ~prog = function
- | "md5" -> `MD5
- | "sha256" -> `SHA256
- | "sha512" -> `SHA512
- | arg ->
- eprintf (f_"%s: password-crypto: unknown algorithm %s, use \"md5\",
\"sha256\" or \"sha512\".\n")
- prog arg;
- exit 1
-
-let rec parse_selector ~prog arg =
- parse_selector_list ~prog arg (string_nsplit ":" arg)
-
-and parse_selector_list ~prog orig_arg = function
- | [ "lock"|"locked" ] ->
- { pw_locked = true; pw_password = Disabled_password }
- | ("lock"|"locked") :: rest ->
- let pw = parse_selector_list ~prog orig_arg rest in
- { pw with pw_locked = true }
- | [ "file"; filename ] ->
- { pw_password = Password (read_password_from_file filename);
- pw_locked = false }
- | "password" :: password ->
- { pw_password = Password (String.concat ":" password); pw_locked = false }
- | [ "random" ] ->
- { pw_password = Random_password; pw_locked = false }
- | [ "disable"|"disabled" ] ->
- { pw_password = Disabled_password; pw_locked = false }
- | _ ->
- eprintf (f_"%s: invalid password selector '%s'; see the man
page.\n")
- prog orig_arg;
- exit 1
-
-and read_password_from_file filename =
- let chan = open_in filename in
- let password = input_line chan in
- close_in chan;
- password
-
-(* Permissible characters in a salt. *)
-let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./"
-
-let rec set_linux_passwords ~prog ?password_crypto g root passwords =
- let crypto =
- match password_crypto with
- | None -> default_crypto g root
- | Some c -> c in
-
- (* XXX Would like to use Augeas here, but Augeas doesn't support
- * /etc/shadow (as of 1.1.0).
- *)
-
- let shadow = Array.to_list (g#read_lines "/etc/shadow") in
- let shadow =
- List.map (
- fun line ->
- try
- (* Each line is: "user:[!!]password:..."
- * !! at the front of the password field means the account is locked.
- * 'i' points to the first colon, 'j' to the second colon.
- *)
- let i = String.index line ':' in
- let user = String.sub line 0 i in
- let selector = Hashtbl.find passwords user in
- let j = String.index_from line (i+1) ':' in
- let rest = String.sub line j (String.length line - j) in
- let pwfield =
- match selector with
- | { pw_locked = locked;
- pw_password = Password password } ->
- if locked then "!!" else "" ^ encrypt password crypto
- | { pw_locked = locked;
- pw_password = Random_password } ->
- let password = make_random_password () in
- printf (f_"Setting random password of %s to %s\n%!")
- user password;
- if locked then "!!" else "" ^ encrypt password crypto
- | { pw_locked = true; pw_password = Disabled_password } ->
"!!*"
- | { pw_locked = false; pw_password = Disabled_password } -> "*"
in
- user ^ ":" ^ pwfield ^ rest
- with Not_found -> line
- ) shadow in
-
- g#write "/etc/shadow" (String.concat "\n" shadow ^
"\n");
- (* In virt-sysprep /.autorelabel will label it correctly. *)
- g#chmod 0 "/etc/shadow"
-
-(* Encrypt each password. Use glibc (on the host). See:
- *
https://rwmj.wordpress.com/2013/07/09/setting-the-root-or-other-passwords...
- *)
-and encrypt password crypto =
- (* Get random characters from the set [A-Za-z0-9./] *)
- let salt = Urandom.urandom_uniform 16 chars in
- let salt =
- (match crypto with
- | `MD5 -> "$1$"
- | `SHA256 -> "$5$"
- | `SHA512 -> "$6$") ^ salt ^ "$" in
- let r = Crypt.crypt password salt in
- (*printf "password: encrypt %s with salt %s -> %s\n" password salt r;*)
- r
-
-(* glibc 2.7 was released in Oct 2007. Approximately, all guests that
- * precede this date only support md5, whereas all guests after this
- * date can support sha512.
- *)
-and default_crypto g root =
- let distro = g#inspect_get_distro root in
- let major = g#inspect_get_major_version root in
- match distro, major with
- |
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
v when v >= 6 ->
- `SHA512
- |
("rhel"|"centos"|"scientificlinux"|"redhat-based"),
_ ->
- `MD5 (* RHEL 5 does not appear to support SHA512, according to crypt(3) *)
-
- | "fedora", v when v >= 9 -> `SHA512
- | "fedora", _ -> `MD5
-
- | "debian", v when v >= 5 -> `SHA512
- | "debian", _ -> `MD5
-
- (* Very likely earlier versions of Ubuntu than 10.04 had new crypt,
- * but Ubuntu 10.04 is the earliest version I have checked.
- *)
- | "ubuntu", v when v >= 10 -> `SHA512
- | "ubuntu", _ -> `MD5
-
- | _, _ ->
- eprintf (f_"\
-virt-sysprep: password: warning: using insecure md5 password encryption for
-guest of type %s version %d.
-If this is incorrect, use --password-crypto option and file a bug.\n%!")
- distro major;
- `MD5
diff --git a/mllib/password.mli b/mllib/password.mli
deleted file mode 100644
index c662b1b..0000000
--- a/mllib/password.mli
+++ /dev/null
@@ -1,42 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012-2014 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.
- *)
-
-type password_crypto = [ `MD5 | `SHA256 | `SHA512 ]
-
-val password_crypto_of_string : prog:string -> string -> password_crypto
-(** Parse --password-crypto parameter on command line. *)
-
-type password_selector = {
- pw_password : password; (** The password. *)
- pw_locked : bool; (** If the account should be locked. *)
-}
-and password =
-| Password of string (** Password (literal string). *)
-| Random_password (** Choose a random password. *)
-| Disabled_password (** [*] in the password field. *)
-
-val parse_selector : prog:string -> string -> password_selector
-(** Parse the selector field in --password/--root-password. Note this
- doesn't parse the username part. Exits if the format is not valid. *)
-
-type password_map = (string, password_selector) Hashtbl.t
-(** A map of username -> selector. *)
-
-val set_linux_passwords : prog:string -> ?password_crypto:password_crypto ->
Guestfs.guestfs -> string -> password_map -> unit
-(** Adjust the passwords of a Linux guest according to the
- password map. *)
diff --git a/mllib/perl_edit.ml b/mllib/perl_edit.ml
deleted file mode 100644
index 28e5dea..0000000
--- a/mllib/perl_edit.ml
+++ /dev/null
@@ -1,78 +0,0 @@
-(* virt-builder
- * Copyright (C) 2013 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_gettext.Gettext
-open Common_utils
-
-open Printf
-
-(* Implement the --edit option.
- *
- * Code copied from virt-edit.
- *)
-let rec edit_file ~debug (g : Guestfs.guestfs) file expr =
- let file_old = file ^ "~" in
- g#rename file file_old;
-
- (* Download the file to a temporary. *)
- let tmpfile = Filename.temp_file "vbedit" "" in
- unlink_on_exit tmpfile;
- g#download file_old tmpfile;
-
- do_perl_edit ~debug g tmpfile expr;
-
- (* Upload the file. Unlike virt-edit we can afford to fail here
- * so we don't need the temporary upload file.
- *)
- g#upload tmpfile file;
-
- (* However like virt-edit we do need to copy attributes. *)
- g#copy_attributes ~all:true file_old file;
- g#rm file_old
-
-and do_perl_edit ~debug g file expr =
- (* Pass the expression to Perl via the environment. This sidesteps
- * any quoting problems with the already complex Perl command line.
- *)
- Unix.putenv "virt_edit_expr" expr;
-
- (* Call out to a canned Perl script. *)
- let cmd = sprintf "\
- perl -e '
- $lineno = 0;
- $expr = $ENV{virt_edit_expr};
- while (<STDIN>) {
- $lineno++;
- eval $expr;
- die if $@;
- print STDOUT $_ or die \"print: $!\";
- }
- close STDOUT or die \"close: $!\";
- ' < %s > %s.out" file file in
-
- if debug then
- eprintf "%s\n%!" cmd;
-
- let r = Sys.command cmd in
- if r <> 0 then (
- eprintf (f_"virt-builder: error: could not evaluate Perl expression
'%s'\n")
- expr;
- exit 1
- );
-
- Unix.rename (file ^ ".out") file
diff --git a/mllib/perl_edit.mli b/mllib/perl_edit.mli
deleted file mode 100644
index fd30dcc..0000000
--- a/mllib/perl_edit.mli
+++ /dev/null
@@ -1,19 +0,0 @@
-(* virt-builder
- * Copyright (C) 2013 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 edit_file : debug:bool -> Guestfs.guestfs -> string -> string -> unit
diff --git a/mllib/random_seed.ml b/mllib/random_seed.ml
deleted file mode 100644
index 84236cd..0000000
--- a/mllib/random_seed.ml
+++ /dev/null
@@ -1,96 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012-2014 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.
- *)
-
-(* It's important that we write a random seed if we possibly can.
- * Unfortunately some installers (hello, Debian) don't include the file
- * in the basic guest, so we have to work out where to create it.
- *)
-let rec set_random_seed (g : Guestfs.guestfs) root =
- let typ = g#inspect_get_type root in
- let created = ref false in
-
- if typ = "linux" then (
- let files = [
- "/var/lib/random-seed"; (* Fedora *)
- "/var/lib/urandom/random-seed"; (* Debian *)
- "/var/lib/misc/random-seed"; (* SuSE *)
- ] in
- List.iter (
- fun file ->
- if g#is_file file then (
- make_random_seed_file g file;
- created := true
- )
- ) files;
- );
-
- if not !created then (
- (* Backup plan: Try to create a new file. *)
-
- let distro = g#inspect_get_distro root in
- let file =
- match typ, distro with
- | "linux",
("fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based")
->
- Some "/var/lib/random-seed"
- | "linux", ("debian"|"ubuntu") ->
- Some "/var/lib/urandom/random-seed"
- | "linux", ("opensuse"|"sles"|"suse-based")
->
- Some "/var/lib/misc/random-seed"
- | _ ->
- None in
- match file with
- | Some file ->
- make_random_seed_file g file;
- created := true
- | None -> ()
- );
-
- !created
-
-and make_random_seed_file g file =
- let file_exists = g#is_file file in
- let n =
- if file_exists then (
- let n = Int64.to_int (g#filesize file) in
-
- (* This file is usually 512 bytes in size. However during
- * guest creation of some guests it can be just 8 bytes long.
- * Cap the file size to [512, 8192] bytes.
- *)
- min (max n 512) 8192
- )
- else
- (* Default to 512 bytes of randomness. *)
- 512 in
-
- (* Get n bytes of randomness from the host. *)
- let entropy = Urandom.urandom_bytes n in
-
- if file_exists then (
- (* Truncate the original file and append, in order to
- * preserve original permissions.
- *)
- g#truncate file;
- g#write_append file entropy
- )
- else (
- (* Create a new file, set the permissions restrictively. *)
- g#write file entropy;
- g#chown 0 0 file;
- g#chmod 0o600 file
- )
diff --git a/mllib/random_seed.mli b/mllib/random_seed.mli
deleted file mode 100644
index b5261f2..0000000
--- a/mllib/random_seed.mli
+++ /dev/null
@@ -1,21 +0,0 @@
-(* virt-sysprep
- * Copyright (C) 2012-2014 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 set_random_seed : Guestfs.guestfs -> string -> bool
-(** Set the random seed in the guest. Returns true if it was able to
- do set it, false if not. *)
diff --git a/mllib/timezone.ml b/mllib/timezone.ml
deleted file mode 100644
index 8b302d9..0000000
--- a/mllib/timezone.ml
+++ /dev/null
@@ -1,39 +0,0 @@
-(* Set timezone in virt-sysprep and virt-builder.
- * Copyright (C) 2014 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 Printf
-
-let set_timezone ~prog (g : Guestfs.guestfs) root timezone =
- let typ = g#inspect_get_type root in
-
- match typ with
- (* Every known Linux has /etc/localtime be either a copy of or a
- * symlink to a timezone file in /usr/share/zoneinfo.
- * Even systemd didn't fuck this up.
- *)
- | "linux" ->
- let target = sprintf "/usr/share/zoneinfo/%s" timezone in
- if not (g#exists target) then
- error ~prog "timezone '%s' does not exist, use a location like
'Europe/London'" timezone;
- g#ln_sf target "/etc/localtime";
- true
-
- | _ ->
- false
diff --git a/mllib/timezone.mli b/mllib/timezone.mli
deleted file mode 100644
index ad0d4b2..0000000
--- a/mllib/timezone.mli
+++ /dev/null
@@ -1,22 +0,0 @@
-(* Set timezone in virt-sysprep and virt-builder.
- * Copyright (C) 2014 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 set_timezone : prog:string -> Guestfs.guestfs -> string -> string ->
bool
-(** [set_timezone ~prog g root "Europe/London"] sets the default timezone
- of the guest. Returns [true] if it was able to set the
- timezone or [false] if not. *)
diff --git a/mllib/urandom.ml b/mllib/urandom.ml
deleted file mode 100644
index 9b613e8..0000000
--- a/mllib/urandom.ml
+++ /dev/null
@@ -1,69 +0,0 @@
-(* Read /dev/urandom.
- * Copyright (C) 2013 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.
- *)
-
-(* Read and return N bytes (only) from /dev/urandom.
- *
- * As pointed out by Edwin Török, previous versions of this had a big
- * problem. They used the OCaml buffered I/O library which would read
- * a lot more data than requested. This version uses unbuffered I/O
- * from the Unix module.
- *)
-
-open Unix
-
-let open_urandom_fd () = openfile "/dev/urandom" [O_RDONLY] 0
-
-let read_byte fd =
- let s = String.make 1 ' ' in
- fun () ->
- if read fd s 0 1 = 0 then (
- close fd;
- raise End_of_file
- );
- Char.code s.[0]
-
-let urandom_bytes n =
- assert (n > 0);
- let ret = String.make n ' ' in
- let fd = open_urandom_fd () in
- for i = 0 to n-1 do
- ret.[i] <- Char.chr (read_byte fd ())
- done;
- close fd;
- ret
-
-(* Return a random number uniformly distributed in [0, upper_bound)
- * avoiding modulo bias.
- *)
-let rec uniform_random read upper_bound =
- let c = read () in
- if c >= 256 mod upper_bound then c mod upper_bound
- else uniform_random read upper_bound
-
-let urandom_uniform n chars =
- assert (n > 0);
- let nr_chars = String.length chars in
- assert (nr_chars > 0);
-
- let ret = String.make n ' ' in
- let fd = open_urandom_fd () in
- for i = 0 to n-1 do
- ret.[i] <- chars.[uniform_random (read_byte fd) nr_chars]
- done;
- close fd;
- ret
diff --git a/mllib/urandom.mli b/mllib/urandom.mli
deleted file mode 100644
index ffc77dd..0000000
--- a/mllib/urandom.mli
+++ /dev/null
@@ -1,26 +0,0 @@
-(* Read /dev/urandom.
- * Copyright (C) 2013 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.
- *)
-
-(** Read and return N bytes (only) from /dev/urandom. *)
-
-val urandom_bytes : int -> string
-(** Read N bytes from /dev/urandom and return it as a binary string. *)
-
-val urandom_uniform : int -> string -> string
-(** [urandom_uniform n chars] returns [n] bytes, uniformly
- distributed from the sets of characters [chars]. *)
diff --git a/po-docs/ja/Makefile.am b/po-docs/ja/Makefile.am
index e954f04..f17be96 100644
--- a/po-docs/ja/Makefile.am
+++ b/po-docs/ja/Makefile.am
@@ -107,6 +107,15 @@ guestfish.1: guestfish.pod guestfish-actions.pod
guestfish-commands.pod guestfis
--insert $(srcdir)/guestfish-prepopts.pod:__PREPOPTS__ \
$<
+virt-builder.1: virt-builder.pod customize-synopsis.pod customize-options.pod
+ $(PODWRAPPER) \
+ --no-strict-checks \
+ --man $@ \
+ --license GPLv2+ \
+ --insert $(srcdir)/customize-synopsis.pod:__CUSTOMIZE_SYNOPSIS__ \
+ --insert $(srcdir)/customize-options.pod:__CUSTOMIZE_OPTIONS__ \
+ $<
+
virt-sysprep.1: virt-sysprep.pod sysprep-extra-options.pod sysprep-operations.pod
$(PODWRAPPER) \
--no-strict-checks \
diff --git a/po-docs/podfiles b/po-docs/podfiles
index d863554..802f18e 100644
--- a/po-docs/podfiles
+++ b/po-docs/podfiles
@@ -5,6 +5,8 @@
../cat/virt-cat.pod
../cat/virt-filesystems.pod
../cat/virt-ls.pod
+../customize/customize-options.pod
+../customize/customize-synopsis.pod
../daemon/guestfsd.pod
../df/virt-df.pod
../diff/virt-diff.pod
diff --git a/po-docs/uk/Makefile.am b/po-docs/uk/Makefile.am
index e954f04..f17be96 100644
--- a/po-docs/uk/Makefile.am
+++ b/po-docs/uk/Makefile.am
@@ -107,6 +107,15 @@ guestfish.1: guestfish.pod guestfish-actions.pod
guestfish-commands.pod guestfis
--insert $(srcdir)/guestfish-prepopts.pod:__PREPOPTS__ \
$<
+virt-builder.1: virt-builder.pod customize-synopsis.pod customize-options.pod
+ $(PODWRAPPER) \
+ --no-strict-checks \
+ --man $@ \
+ --license GPLv2+ \
+ --insert $(srcdir)/customize-synopsis.pod:__CUSTOMIZE_SYNOPSIS__ \
+ --insert $(srcdir)/customize-options.pod:__CUSTOMIZE_OPTIONS__ \
+ $<
+
virt-sysprep.1: virt-sysprep.pod sysprep-extra-options.pod sysprep-operations.pod
$(PODWRAPPER) \
--no-strict-checks \
diff --git a/po/POTFILES b/po/POTFILES
index ecdbae4..37dbbaa 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -11,6 +11,7 @@ cat/cat.c
cat/filesystems.c
cat/ls.c
cat/visit.c
+customize/crypt-c.c
daemon/9p.c
daemon/acl.c
daemon/augeas.c
@@ -239,7 +240,6 @@ inspector/inspector.c
java/com_redhat_et_libguestfs_GuestFS.c
lua/lua-guestfs.c
make-fs/make-fs.c
-mllib/crypt-c.c
mllib/fsync-c.c
mllib/mkdtemp-c.c
mllib/progress-c.c
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index ed96697..3870f3d 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -17,21 +17,13 @@ mllib/common_gettext.ml
mllib/common_utils.ml
mllib/common_utils_tests.ml
mllib/config.ml
-mllib/crypt.ml
-mllib/firstboot.ml
mllib/fsync.ml
-mllib/hostname.ml
mllib/libdir.ml
mllib/mkdtemp.ml
-mllib/password.ml
-mllib/perl_edit.ml
mllib/planner.ml
mllib/progress.ml
-mllib/random_seed.ml
mllib/tTY.ml
-mllib/timezone.ml
mllib/uRI.ml
-mllib/urandom.ml
resize/resize.ml
sparsify/cmdline.ml
sparsify/copying.ml
diff --git a/src/guestfs.pod b/src/guestfs.pod
index b3c32eb..e6e91f4 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -4272,6 +4272,10 @@ and documentation.
Outside contributions, experimental parts.
+=item C<customize>
+
+virt-customize mini-library.
+
=item C<daemon>
The daemon that runs inside the libguestfs appliance and carries out
diff --git a/sysprep/Makefile.am b/sysprep/Makefile.am
index d20ad08..1bff338 100644
--- a/sysprep/Makefile.am
+++ b/sysprep/Makefile.am
@@ -88,20 +88,20 @@ if HAVE_OCAML
deps = \
$(top_builddir)/mllib/common_gettext.cmx \
$(top_builddir)/mllib/common_utils.cmx \
- $(top_builddir)/fish/guestfish-uri.o \
$(top_builddir)/mllib/uri-c.o \
$(top_builddir)/mllib/uRI.cmx \
- $(top_builddir)/mllib/crypt-c.o \
- $(top_builddir)/mllib/crypt.cmx \
- $(top_builddir)/mllib/urandom.cmx \
- $(top_builddir)/mllib/password.cmx \
- $(top_builddir)/mllib/random_seed.cmx \
- $(top_builddir)/mllib/hostname.cmx \
- $(top_builddir)/mllib/timezone.cmx \
- $(top_builddir)/mllib/firstboot.cmx \
$(top_builddir)/mllib/config.cmx \
$(top_builddir)/mllib/mkdtemp-c.o \
$(top_builddir)/mllib/mkdtemp.cmx \
+ $(top_builddir)/customize/crypt-c.o \
+ $(top_builddir)/customize/crypt.cmx \
+ $(top_builddir)/customize/urandom.cmx \
+ $(top_builddir)/customize/password.cmx \
+ $(top_builddir)/customize/random_seed.cmx \
+ $(top_builddir)/customize/hostname.cmx \
+ $(top_builddir)/customize/timezone.cmx \
+ $(top_builddir)/customize/firstboot.cmx \
+ $(top_builddir)/fish/guestfish-uri.o \
sysprep_operation.cmx \
$(patsubst %,sysprep_operation_%.cmx,$(operations)) \
main.cmx
@@ -121,7 +121,8 @@ OCAMLPACKAGES = \
-package str,unix \
-I $(top_builddir)/src/.libs \
-I $(top_builddir)/ocaml \
- -I $(top_builddir)/mllib
+ -I $(top_builddir)/mllib \
+ -I $(top_builddir)/customize
if HAVE_OCAML_PKG_GETTEXT
OCAMLPACKAGES += -package gettext-stub
endif
@@ -225,7 +226,7 @@ depend: .depend
.depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
rm -f $@ $@-t
- $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(abs_top_builddir)/mllib $^ | \
+ $(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(abs_top_builddir)/mllib -I
$(abs_top_builddir)/customize $^ | \
$(SED) 's/ *$$//' | \
$(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
$(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
--
1.8.5.3