From f82e17b4a6c7fe35c16d45ad9c555d0ad90af2be Mon Sep 17 00:00:00 2001
From: Pino Toscano <ptoscano@redhat.com>
Date: Wed, 14 May 2014 10:24:41 -0400
Subject: [PATCH 027/118] Backport virt-sysprep from libguestfs 1.24

Copy the virt-sysprep code from libguestfs 1.24.x, bringing all the
features of it to libguestfs 1.20.x. This is done as single commit
because cherry-picking individual commits is simply not worth it, and
would require to cherry-pick a big number of commits, possibly bringing
in new features implemented elsewhere, etc.

Move existing files (where possible) or import them from 1.24.x,
adapting the current ocaml-based tools to the updated functions in
mllib.
---
 .gitignore                                         |   8 +-
 Makefile.am                                        |   2 +-
 configure.ac                                       |  17 +-
 mllib/Makefile.am                                  | 164 +++++++++
 mllib/common_utils.ml                              | 410 +++++++++++++++++++++
 mllib/common_utils_tests.ml                        |  80 ++++
 mllib/config.ml.in                                 |  20 +
 mllib/crypt-c.c                                    |  44 +++
 mllib/crypt.ml                                     |  19 +
 mllib/crypt.mli                                    |  22 ++
 mllib/firstboot.ml                                 | 169 +++++++++
 mllib/firstboot.mli                                |  27 ++
 mllib/hostname.ml                                  |  80 ++++
 mllib/hostname.mli                                 |  21 ++
 mllib/password.ml                                  | 142 +++++++
 mllib/password.mli                                 |  35 ++
 mllib/progress-c.c                                 | 106 ++++++
 mllib/progress.ml                                  |  58 +++
 mllib/progress.mli                                 |  19 +
 mllib/random_seed.ml                               |  96 +++++
 mllib/random_seed.mli                              |  21 ++
 mllib/tTY.ml                                       |  19 +
 mllib/tTY.mli                                      |  23 ++
 mllib/tty-c.c                                      |  38 ++
 mllib/urandom.ml                                   |  69 ++++
 mllib/urandom.mli                                  |  26 ++
 po/POTFILES                                        |   5 +-
 po/POTFILES-ml                                     |   9 +-
 resize/Makefile.am                                 |  39 +-
 resize/common_utils.ml                             | 248 -------------
 resize/common_utils_tests.ml                       |  78 ----
 resize/progress-c.c                                | 106 ------
 resize/progress.ml                                 |  58 ---
 resize/progress.mli                                |  19 -
 resize/resize.ml                                   |   3 +-
 resize/tTY.ml                                      |  19 -
 resize/tTY.mli                                     |  23 --
 resize/tty-c.c                                     |  38 --
 sparsify/Makefile.am                               |  20 +-
 sparsify/sparsify.ml                               |   1 +
 sysprep/Makefile.am                                |  55 ++-
 sysprep/firstboot.ml                               | 161 --------
 sysprep/firstboot.mli                              |  27 --
 sysprep/main.ml                                    |  31 +-
 sysprep/sysprep_operation.ml                       |  86 +++--
 sysprep/sysprep_operation.mli                      |  25 +-
 sysprep/sysprep_operation_abrt_data.ml             |  17 +-
 sysprep/sysprep_operation_bash_history.ml          |  21 +-
 sysprep/sysprep_operation_blkid_tab.ml             |  16 +-
 sysprep/sysprep_operation_ca_certificates.ml       |  16 +-
 sysprep/sysprep_operation_crash_data.ml            |  17 +-
 sysprep/sysprep_operation_cron_spool.ml            |  18 +-
 sysprep/sysprep_operation_dhcp_client_state.ml     |  16 +-
 sysprep/sysprep_operation_dhcp_server_state.ml     |  16 +-
 sysprep/sysprep_operation_dovecot_data.ml          |  16 +-
 sysprep/sysprep_operation_firewall_rules.ml        |  57 +++
 sysprep/sysprep_operation_firstboot.ml             |  61 ++-
 sysprep/sysprep_operation_flag_reconfiguration.ml  |  17 +-
 sysprep/sysprep_operation_hostname.ml              |  89 ++---
 sysprep/sysprep_operation_kerberos_data.ml         |  16 +-
 sysprep/sysprep_operation_logfiles.ml              |  29 +-
 sysprep/sysprep_operation_lvm_uuids.ml             |  17 +-
 sysprep/sysprep_operation_machine_id.ml            |  17 +-
 sysprep/sysprep_operation_mail_spool.ml            |  16 +-
 sysprep/sysprep_operation_net_hostname.ml          |  17 +-
 sysprep/sysprep_operation_net_hwaddr.ml            |  17 +-
 sysprep/sysprep_operation_pacct_log.ml             |  17 +-
 sysprep/sysprep_operation_package_manager_cache.ml |  25 +-
 sysprep/sysprep_operation_pam_data.ml              |  16 +-
 sysprep/sysprep_operation_password.ml              | 162 ++++++++
 sysprep/sysprep_operation_puppet_data_log.ml       |  16 +-
 sysprep/sysprep_operation_random_seed.ml           |  44 +--
 sysprep/sysprep_operation_rhn_systemid.ml          |  16 +-
 sysprep/sysprep_operation_rpm_db.ml                |  17 +-
 sysprep/sysprep_operation_samba_db_log.ml          |  16 +-
 sysprep/sysprep_operation_script.ml                |  39 +-
 sysprep/sysprep_operation_smolt_uuid.ml            |  16 +-
 sysprep/sysprep_operation_ssh_hostkeys.ml          |  17 +-
 sysprep/sysprep_operation_ssh_userdir.ml           |  21 +-
 sysprep/sysprep_operation_sssd_db_log.ml           |  16 +-
 sysprep/sysprep_operation_tmp_files.ml             |  52 +++
 sysprep/sysprep_operation_udev_persistent_net.ml   |  17 +-
 sysprep/sysprep_operation_user_account.ml          |  21 +-
 sysprep/sysprep_operation_utmp.ml                  |  17 +-
 sysprep/sysprep_operation_yum_uuid.ml              |  17 +-
 tests/guests/guest-aux/make-debian-img.sh          |   1 +
 tests/guests/guest-aux/make-ubuntu-img.sh          |   1 +
 87 files changed, 2536 insertions(+), 1320 deletions(-)
 create mode 100644 mllib/Makefile.am
 create mode 100644 mllib/common_utils.ml
 create mode 100644 mllib/common_utils_tests.ml
 create mode 100644 mllib/config.ml.in
 create mode 100644 mllib/crypt-c.c
 create mode 100644 mllib/crypt.ml
 create mode 100644 mllib/crypt.mli
 create mode 100644 mllib/firstboot.ml
 create mode 100644 mllib/firstboot.mli
 create mode 100644 mllib/hostname.ml
 create mode 100644 mllib/hostname.mli
 create mode 100644 mllib/password.ml
 create mode 100644 mllib/password.mli
 create mode 100644 mllib/progress-c.c
 create mode 100644 mllib/progress.ml
 create mode 100644 mllib/progress.mli
 create mode 100644 mllib/random_seed.ml
 create mode 100644 mllib/random_seed.mli
 create mode 100644 mllib/tTY.ml
 create mode 100644 mllib/tTY.mli
 create mode 100644 mllib/tty-c.c
 create mode 100644 mllib/urandom.ml
 create mode 100644 mllib/urandom.mli
 delete mode 100644 resize/common_utils.ml
 delete mode 100644 resize/common_utils_tests.ml
 delete mode 100644 resize/progress-c.c
 delete mode 100644 resize/progress.ml
 delete mode 100644 resize/progress.mli
 delete mode 100644 resize/tTY.ml
 delete mode 100644 resize/tTY.mli
 delete mode 100644 resize/tty-c.c
 delete mode 100644 sysprep/firstboot.ml
 delete mode 100644 sysprep/firstboot.mli
 create mode 100644 sysprep/sysprep_operation_firewall_rules.ml
 create mode 100644 sysprep/sysprep_operation_password.ml
 create mode 100644 sysprep/sysprep_operation_tmp_files.ml

diff --git a/.gitignore b/.gitignore
index c005329..f379b5e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -239,6 +239,12 @@ Makefile.in
 /m4/ltversion.m4
 /maint.mk
 /missing
+/mllib/.depend
+/mllib/common_gettext.ml
+/mllib/common_utils_tests
+/mllib/config.ml
+/mllib/dummy
+/mllib/libdir.ml
 /ocaml/bindtests.bc
 /ocaml/bindtests.opt
 /ocaml/bindtests.ml
@@ -338,8 +344,6 @@ Makefile.in
 /rescue/virt-rescue
 /rescue/virt-rescue.1
 /resize/.depend
-/resize/common_gettext.ml
-/resize/common_utils_tests
 /resize/stamp-virt-resize.pod
 /resize/virt-resize
 /resize/virt-resize.1
diff --git a/Makefile.am b/Makefile.am
index b3fe403..fab3c13 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -105,7 +105,7 @@ SUBDIRS += csharp
 
 # OCaml tools.
 if HAVE_OCAML
-SUBDIRS += resize sparsify sysprep
+SUBDIRS += mllib resize sparsify sysprep
 endif
 
 # Perl tools.
diff --git a/configure.ac b/configure.ac
index 57bef84..2f89a61 100644
--- a/configure.ac
+++ b/configure.ac
@@ -969,10 +969,13 @@ AS_IF([test "x$OCAMLC" != "xno"],[
 
     dnl Write a common gettext module used by each OCaml tool.
     dnl If OCaml gettext is not available then we write dummy functions.
-    AC_MSG_NOTICE([creating resize/common_gettext.ml])
-    rm -f resize/common_gettext.ml
+    AC_MSG_NOTICE([creating mllib/common_gettext.ml])
+    rm -f mllib/common_gettext.ml
+    # If we're building in a different directory, then 'mllib/' might
+    # not exist yet, so create it:
+    mkdir -p mllib
 
-    cat <<EOF > resize/common_gettext.ml
+    cat <<EOF > mllib/common_gettext.ml
 (* This file is generated automatically by ./configure. *)
 
 (** Gettext functions for OCaml virt tools.
@@ -1010,7 +1013,7 @@ EOF
 
     if test "x$OCAML_PKG_gettext" != "xno"; then
         # ocaml-gettext available: real module.
-        cat <<EOF >>resize/common_gettext.ml
+        cat <<EOF >>mllib/common_gettext.ml
 module Gettext = Gettext.Program (
   struct
     let textdomain = "$PACKAGE_NAME"
@@ -1022,7 +1025,7 @@ module Gettext = Gettext.Program (
 EOF
     else
         # No gettext: module containing dummy gettext functions.
-        cat <<EOF >>resize/common_gettext.ml
+        cat <<EOF >>mllib/common_gettext.ml
 module Gettext = struct
   external s_ : string -> string = "%identity"
   external f_ : ('a, 'b, 'c, 'd, 'e, 'f) format6
@@ -1038,7 +1041,7 @@ end
 EOF
     fi
 
-    chmod -w resize/common_gettext.ml
+    chmod -w mllib/common_gettext.ml
 ])
 AM_CONDITIONAL([HAVE_OCAML_PKG_GETTEXT],
     [test "x$OCAMLC" != "xno" && test "x$OCAMLFIND" != "xno" && test "x$OCAML_PKG_gettext" != "xno"])
@@ -1551,6 +1554,8 @@ AC_CONFIG_FILES([Makefile
                  java/examples/Makefile
                  lua/Makefile
                  lua/examples/Makefile
+                 mllib/Makefile
+                 mllib/config.ml
                  ocaml/META
                  ocaml/Makefile
                  ocaml/examples/Makefile
diff --git a/mllib/Makefile.am b/mllib/Makefile.am
new file mode 100644
index 0000000..14b9a4f
--- /dev/null
+++ b/mllib/Makefile.am
@@ -0,0 +1,164 @@
+# libguestfs OCaml tools common code
+# Copyright (C) 2011-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
+
+VPATH += $(top_srcdir)/fish
+
+# Alphabetical order.
+SOURCES = \
+	common_gettext.ml \
+	common_utils.ml \
+	common_utils_tests.ml \
+	config.ml \
+	crypt-c.c \
+	crypt.ml \
+	crypt.mli \
+	firstboot.mli \
+	firstboot.ml \
+	hostname.mli \
+	hostname.ml \
+	password.mli \
+	password.ml \
+	progress-c.c \
+	progress.mli \
+	progress.ml \
+	random_seed.mli \
+	random_seed.ml \
+	tty-c.c \
+	tTY.mli \
+	tTY.ml \
+	urandom.mli \
+	urandom.ml
+
+if HAVE_OCAML
+
+# Notes:
+# - We're not actually building a functioning program here, we're just
+#   linking everything together to check all the modules build OK.
+# - This list must be in dependency order.
+OBJECTS = \
+	$(top_builddir)/fish/guestfish-progress.o \
+	tty-c.o \
+	progress-c.o \
+	crypt-c.o \
+	config.cmx \
+	libdir.cmx \
+	common_gettext.cmx \
+	common_utils.cmx \
+	urandom.cmx \
+	random_seed.cmx \
+	hostname.cmx \
+	firstboot.cmx \
+	tTY.cmx \
+	progress.cmx \
+	crypt.cmx \
+	password.cmx
+
+noinst_SCRIPTS = dummy
+
+# -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 ../ocaml
+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
+
+dummy: $(OBJECTS)
+	$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \
+	  mlguestfs.cmxa -linkpkg $^ \
+	  -cclib '$(OCAMLCLIBS)' \
+	  $(OCAML_GCOV_LDFLAGS) \
+	  -o $@
+
+.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 \
+	$(LIBXML2_CFLAGS) \
+	-I$(srcdir)/../gnulib/lib -I../gnulib/lib
+
+.c.o:
+	$(CC) $(CFLAGS) $(PROF_CFLAGS) $(DEFAULT_INCLUDES) -c $< -o $@
+
+# Tests.
+
+check_SCRIPTS = common_utils_tests
+
+common_utils_tests: common_gettext.cmx common_utils.cmx common_utils_tests.cmx
+	$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \
+	  mlguestfs.cmxa -linkpkg $^ -cclib -lncurses -o $@
+
+TESTS_ENVIRONMENT = $(top_builddir)/run --test
+
+TESTS = common_utils_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) $^ | \
+	  $(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/mllib/common_utils.ml b/mllib/common_utils.ml
new file mode 100644
index 0000000..1bda757
--- /dev/null
+++ b/mllib/common_utils.ml
@@ -0,0 +1,410 @@
+(* Common utilities for OCaml tools in libguestfs.
+ * Copyright (C) 2010-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 Printf
+
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+let (//) = Filename.concat
+
+let ( +^ ) = Int64.add
+let ( -^ ) = Int64.sub
+let ( *^ ) = Int64.mul
+let ( /^ ) = Int64.div
+let ( &^ ) = Int64.logand
+let ( ~^ ) = Int64.lognot
+
+(* Return 'i' rounded up to the next multiple of 'a'. *)
+let roundup64 i a = let a = a -^ 1L in (i +^ a) &^ (~^ a)
+
+let int_of_le32 str =
+  assert (String.length str = 4);
+  let c0 = Char.code (String.unsafe_get str 0) in
+  let c1 = Char.code (String.unsafe_get str 1) in
+  let c2 = Char.code (String.unsafe_get str 2) in
+  let c3 = Char.code (String.unsafe_get str 3) in
+  Int64.of_int c0 +^
+    (Int64.shift_left (Int64.of_int c1) 8) +^
+    (Int64.shift_left (Int64.of_int c2) 16) +^
+    (Int64.shift_left (Int64.of_int c3) 24)
+
+let le32_of_int i =
+  let c0 = i &^ 0xffL in
+  let c1 = Int64.shift_right (i &^ 0xff00L) 8 in
+  let c2 = Int64.shift_right (i &^ 0xff0000L) 16 in
+  let c3 = Int64.shift_right (i &^ 0xff000000L) 24 in
+  let s = String.create 4 in
+  String.unsafe_set s 0 (Char.unsafe_chr (Int64.to_int c0));
+  String.unsafe_set s 1 (Char.unsafe_chr (Int64.to_int c1));
+  String.unsafe_set s 2 (Char.unsafe_chr (Int64.to_int c2));
+  String.unsafe_set s 3 (Char.unsafe_chr (Int64.to_int c3));
+  s
+
+let output_spaces chan n = for i = 0 to n-1 do output_char chan ' ' done
+
+let wrap ?(chan = stdout) ?(hanging = 0) str =
+  let rec _wrap col str =
+    let n = String.length str in
+    let i = try String.index str ' ' with Not_found -> n in
+    let col =
+      if col+i >= 72 then (
+        output_char chan '\n';
+        output_spaces chan hanging;
+        i+hanging+1
+      ) else col+i+1 in
+    output_string chan (String.sub str 0 i);
+    if i < n then (
+      output_char chan ' ';
+      _wrap col (String.sub str (i+1) (n-(i+1)))
+    )
+  in
+  _wrap 0 str
+
+let string_prefix str prefix =
+  let n = String.length prefix in
+  String.length str >= n && String.sub str 0 n = prefix
+
+let rec string_find s sub =
+  let len = String.length s in
+  let sublen = String.length sub in
+  let rec loop i =
+    if i <= len-sublen then (
+      let rec loop2 j =
+        if j < sublen then (
+          if s.[i+j] = sub.[j] then loop2 (j+1)
+          else -1
+        ) else
+          i (* found *)
+      in
+      let r = loop2 0 in
+      if r = -1 then loop (i+1) else r
+    ) else
+      -1 (* not found *)
+  in
+  loop 0
+
+let rec replace_str s s1 s2 =
+  let len = String.length s in
+  let sublen = String.length s1 in
+  let i = string_find s s1 in
+  if i = -1 then s
+  else (
+    let s' = String.sub s 0 i in
+    let s'' = String.sub s (i+sublen) (len-i-sublen) in
+    s' ^ s2 ^ replace_str s'' s1 s2
+  )
+
+(* Split a string into multiple strings at each separator. *)
+let rec string_nsplit sep str =
+  let len = String.length str in
+  let seplen = String.length sep in
+  let i = string_find str sep in
+  if i = -1 then [str]
+  else (
+    let s' = String.sub str 0 i in
+    let s'' = String.sub str (i+seplen) (len-i-seplen) in
+    s' :: string_nsplit sep s''
+  )
+
+(* Split a string at the first occurrence of the separator, returning
+ * the part before and the part after.  If separator is not found,
+ * return the whole string and an empty string.
+ *)
+let string_split sep str =
+  let len = String.length sep in
+  let seplen = String.length str in
+  let i = string_find str sep in
+
+  if i = -1 then str, ""
+  else (
+    String.sub str 0 i, String.sub str (i + len) (seplen - i - len)
+  )
+
+let string_random8 =
+  let chars = "abcdefghijklmnopqrstuvwxyz0123456789" in
+  fun () ->
+    String.concat "" (
+      List.map (
+        fun _ ->
+          let c = Random.int 36 in
+          let c = chars.[c] in
+          String.make 1 c
+      ) [1;2;3;4;5;6;7;8]
+    )
+
+(* Drop elements from a list while a predicate is true. *)
+let rec dropwhile f = function
+  | [] -> []
+  | x :: xs when f x -> dropwhile f xs
+  | xs -> xs
+
+(* Take elements from a list while a predicate is true. *)
+let rec takewhile f = function
+  | x :: xs when f x -> x :: takewhile f xs
+  | _ -> []
+
+let rec filter_map f = function
+  | [] -> []
+  | x :: xs ->
+      match f x with
+      | Some y -> y :: filter_map f xs
+      | None -> filter_map f xs
+
+let iteri f xs =
+  let rec loop i = function
+    | [] -> ()
+    | x :: xs -> f i x; loop (i+1) xs
+  in
+  loop 0 xs
+
+(* Timestamped progress messages, used for ordinary messages when not
+ * --quiet.
+ *)
+let start_t = Unix.time ()
+let make_message_function ~quiet fs =
+  let p str =
+    if not quiet then (
+      let t = sprintf "%.1f" (Unix.time () -. start_t) in
+      printf "[%6s] %s\n%!" t str
+    )
+  in
+  ksprintf p fs
+
+let error ~prog fs =
+  let display str =
+    wrap ~chan:stderr (sprintf (f_"%s: error: %s") prog str);
+    prerr_newline ();
+    prerr_newline ();
+    wrap ~chan:stderr
+      (sprintf (f_"%s: If reporting bugs, run %s with debugging enabled (-v) and include the complete output.")
+         prog prog);
+    prerr_newline ();
+    exit 1
+  in
+  ksprintf display fs
+
+let feature_available (g : Guestfs.guestfs) names =
+  try g#available names; true
+  with G.Error _ -> false
+
+let read_whole_file path =
+  let buf = Buffer.create 16384 in
+  let chan = open_in path in
+  let maxlen = 16384 in
+  let s = String.create maxlen in
+  let rec loop () =
+    let r = input chan s 0 maxlen in
+    if r > 0 then (
+      Buffer.add_substring buf s 0 r;
+      loop ()
+    )
+  in
+  loop ();
+  close_in chan;
+  Buffer.contents buf
+
+(* Parse a size field, eg. "10G". *)
+let parse_size =
+  let const_re = Str.regexp "^\\([.0-9]+\\)\\([bKMG]\\)$" in
+  fun ~prog field ->
+    let matches rex = Str.string_match rex field 0 in
+    let sub i = Str.matched_group i field in
+    let size_scaled f = function
+      | "b" -> Int64.of_float f
+      | "K" -> Int64.of_float (f *. 1024.)
+      | "M" -> Int64.of_float (f *. 1024. *. 1024.)
+      | "G" -> Int64.of_float (f *. 1024. *. 1024. *. 1024.)
+      | _ -> assert false
+    in
+
+    if matches const_re then (
+      size_scaled (float_of_string (sub 1)) (sub 2)
+    )
+    else
+      error ~prog "%s: cannot parse size field" field
+
+(* Parse a size field, eg. "10G", "+20%" etc.  Used particularly by
+ * virt-resize --resize and --resize-force options.
+ *)
+let parse_resize =
+  let const_re = Str.regexp "^\\([.0-9]+\\)\\([bKMG]\\)$"
+  and plus_const_re = Str.regexp "^\\+\\([.0-9]+\\)\\([bKMG]\\)$"
+  and minus_const_re = Str.regexp "^-\\([.0-9]+\\)\\([bKMG]\\)$"
+  and percent_re = Str.regexp "^\\([.0-9]+\\)%$"
+  and plus_percent_re = Str.regexp "^\\+\\([.0-9]+\\)%$"
+  and minus_percent_re = Str.regexp "^-\\([.0-9]+\\)%$"
+  in
+  fun ~prog oldsize field ->
+    let matches rex = Str.string_match rex field 0 in
+    let sub i = Str.matched_group i field in
+    let size_scaled f = function
+      | "b" -> Int64.of_float f
+      | "K" -> Int64.of_float (f *. 1024.)
+      | "M" -> Int64.of_float (f *. 1024. *. 1024.)
+      | "G" -> Int64.of_float (f *. 1024. *. 1024. *. 1024.)
+      | _ -> assert false
+    in
+
+    if matches const_re then (
+      size_scaled (float_of_string (sub 1)) (sub 2)
+    )
+    else if matches plus_const_re then (
+      let incr = size_scaled (float_of_string (sub 1)) (sub 2) in
+      oldsize +^ incr
+    )
+    else if matches minus_const_re then (
+      let incr = size_scaled (float_of_string (sub 1)) (sub 2) in
+      oldsize -^ incr
+    )
+    else if matches percent_re then (
+      let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
+      oldsize *^ percent /^ 1000L
+    )
+    else if matches plus_percent_re then (
+      let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
+      oldsize +^ oldsize *^ percent /^ 1000L
+    )
+    else if matches minus_percent_re then (
+      let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
+      oldsize -^ oldsize *^ percent /^ 1000L
+    )
+    else
+      error ~prog "%s: cannot parse resize field" field
+
+let human_size i =
+  let sign, i = if i < 0L then "-", Int64.neg i else "", i in
+
+  if i < 1024L then
+    sprintf "%s%Ld" sign i
+  else (
+    let f = Int64.to_float i /. 1024. in
+    let i = i /^ 1024L in
+    if i < 1024L then
+      sprintf "%s%.1fK" sign f
+    else (
+      let f = Int64.to_float i /. 1024. in
+      let i = i /^ 1024L in
+      if i < 1024L then
+        sprintf "%s%.1fM" sign f
+      else (
+        let f = Int64.to_float i /. 1024. in
+        (*let i = i /^ 1024L in*)
+        sprintf "%s%.1fG" sign f
+      )
+    )
+  )
+
+(* Skip any leading '-' characters when comparing command line args. *)
+let skip_dashes str =
+  let n = String.length str in
+  let rec loop i =
+    if i >= n then invalid_arg "skip_dashes"
+    else if String.unsafe_get str i = '-' then loop (i+1)
+    else i
+  in
+  let i = loop 0 in
+  if i = 0 then str
+  else String.sub str i (n-i)
+
+let compare_command_line_args a b =
+  compare (String.lowercase (skip_dashes a)) (String.lowercase (skip_dashes b))
+
+(* Implements `--long-options'. *)
+let long_options = ref ([] : (Arg.key * Arg.spec * Arg.doc) list)
+let display_long_options () =
+  List.iter (
+    fun (arg, _, _) ->
+      if string_prefix arg "--" && arg <> "--long-options" then
+        printf "%s\n" arg
+  ) !long_options;
+  exit 0
+
+(* Run an external command, slurp up the output as a list of lines. *)
+let external_command ~prog cmd =
+  let chan = Unix.open_process_in cmd in
+  let lines = ref [] in
+  (try while true do lines := input_line chan :: !lines done
+   with End_of_file -> ());
+  let lines = List.rev !lines in
+  let stat = Unix.close_process_in chan in
+  (match stat with
+  | Unix.WEXITED 0 -> ()
+  | Unix.WEXITED i ->
+    error ~prog (f_"external command '%s' exited with error %d") cmd i
+  | Unix.WSIGNALED i ->
+    error ~prog (f_"external command '%s' killed by signal %d") cmd i
+  | Unix.WSTOPPED i ->
+    error ~prog (f_"external command '%s' stopped by signal %d") cmd i
+  );
+  lines
+
+(* Run uuidgen to return a random UUID. *)
+let uuidgen ~prog () =
+  let lines = external_command ~prog "uuidgen -r" in
+  assert (List.length lines >= 1);
+  let uuid = List.hd lines in
+  let len = String.length uuid in
+  let uuid, len =
+    if len > 0 && uuid.[len-1] = '\n' then
+      String.sub uuid 0 (len-1), len-1
+    else
+      uuid, len in
+  if len < 10 then assert false; (* sanity check on uuidgen *)
+  uuid
+
+(* Unlink a temporary file on exit. *)
+let unlink_on_exit =
+  let files = ref [] in
+  let registered_handlers = ref false in
+
+  let rec unlink_files () =
+    List.iter (
+      fun file -> try Unix.unlink file with _ -> ()
+    ) !files
+  and register_handlers () =
+    (* Unlink on exit. *)
+    at_exit unlink_files
+  in
+
+  fun file ->
+    files := file :: !files;
+    if not !registered_handlers then (
+      register_handlers ();
+      registered_handlers := true
+    )
+
+(* Using the libguestfs API, recursively remove only files from the
+ * given directory.  Useful for cleaning /var/cache etc in sysprep
+ * without removing the actual directory structure.  Also if 'dir' is
+ * not a directory or doesn't exist, ignore it.
+ *
+ * XXX Could be faster with a specific API for doing this.
+ *)
+let rm_rf_only_files (g : Guestfs.guestfs) dir =
+  if g#is_dir dir then (
+    let files = Array.map (Filename.concat dir) (g#find dir) in
+    let files = Array.to_list files in
+    let files = List.filter g#is_file files in
+    List.iter g#rm files
+  )
+
+let is_char_device file =
+  try (Unix.stat file).Unix.st_kind = Unix.S_CHR
+  with Unix.Unix_error _ -> false
diff --git a/mllib/common_utils_tests.ml b/mllib/common_utils_tests.ml
new file mode 100644
index 0000000..e06b3a4
--- /dev/null
+++ b/mllib/common_utils_tests.ml
@@ -0,0 +1,80 @@
+(* virt-resize
+ * Copyright (C) 2011 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.
+ *)
+
+(* This file tests the Common_utils module. *)
+
+open Common_utils
+
+let prog = "common_utils_tests"
+
+(* Test Common_utils.int_of_le32 and Common_utils.le32_of_int. *)
+let () =
+  assert (int_of_le32 "\x80\x60\x40\x20" = 0x20406080L);
+  assert (le32_of_int 0x20406080L = "\x80\x60\x40\x20")
+
+(* Test Common_utils.parse_size. *)
+let () =
+  (* For absolute sizes, oldsize is ignored. *)
+  assert (parse_resize ~prog 100_L "100b" = 100_L);
+  assert (parse_resize ~prog 1000_L "100b" = 100_L);
+  assert (parse_resize ~prog 10000_L "100b" = 100_L);
+  assert (parse_resize ~prog 100_L "100K" = 102400_L);
+  (* Fractions are always rounded down. *)
+  assert (parse_resize ~prog 100_L "1.1K" = 1126_L);
+  assert (parse_resize ~prog 100_L "100.1M" = 104962457_L);
+  assert (parse_resize ~prog 100_L "123.4G" = 132499741081_L);
+
+  (* oldsize +/- a constant. *)
+  assert (parse_resize ~prog 100_L "+1b" = 101_L);
+  assert (parse_resize ~prog 100_L "-2b" = 98_L);
+  assert (parse_resize ~prog 100_L "+1K" = 1124_L);
+  assert (parse_resize ~prog 1024_L "-1K" = 0_L);
+  assert (parse_resize ~prog 1126_L "-1.1K" = 0_L);
+  assert (parse_resize ~prog 1024_L "+1.1M" = 1154457_L);
+  assert (parse_resize ~prog 132499741081_L "-123.3G" = 107374182_L);
+
+  (* oldsize +/- a percentage. *)
+  assert (parse_resize ~prog 100_L "+1%" = 101_L);
+  assert (parse_resize ~prog 100_L "-1%" = 99_L);
+  assert (parse_resize ~prog 100000_L "+1%" = 101000_L);
+  assert (parse_resize ~prog 100000_L "-1%" = 99000_L);
+  assert (parse_resize ~prog 100000_L "+50%" = 150000_L);
+  assert (parse_resize ~prog 100000_L "-50%" = 50000_L);
+  assert (parse_resize ~prog 100000_L "+100%" = 200000_L);
+  assert (parse_resize ~prog 100000_L "-100%" = 0_L);
+  assert (parse_resize ~prog 100000_L "+200%" = 300000_L);
+  assert (parse_resize ~prog 100000_L "+300%" = 400000_L);
+
+  (* Implementation rounds numbers so that only a single digit after
+   * the decimal point is significant.
+   *)
+  assert (parse_resize ~prog 100000_L "+1.1%" = 101100_L);
+  assert (parse_resize ~prog 100000_L "+1.12%" = 101100_L)
+
+(* Test Common_utils.human_size. *)
+let () =
+  assert (human_size 100_L = "100");
+  assert (human_size (-100_L) = "-100");
+  assert (human_size 1024_L = "1.0K");
+  assert (human_size (-1024_L) = "-1.0K");
+  assert (human_size 1126_L = "1.1K");
+  assert (human_size (-1126_L) = "-1.1K");
+  assert (human_size 1363149_L = "1.3M");
+  assert (human_size (-1363149_L) = "-1.3M");
+  assert (human_size 3650722201_L = "3.4G");
+  assert (human_size (-3650722201_L) = "-3.4G")
diff --git a/mllib/config.ml.in b/mllib/config.ml.in
new file mode 100644
index 0000000..d18bf7b
--- /dev/null
+++ b/mllib/config.ml.in
@@ -0,0 +1,20 @@
+(* configuration for mllib.
+ * 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.
+ *)
+
+let package_name = "@PACKAGE_NAME@"
+let package_version = "@PACKAGE_VERSION@"
diff --git a/mllib/crypt-c.c b/mllib/crypt-c.c
new file mode 100644
index 0000000..29a91e4
--- /dev/null
+++ b/mllib/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/mllib/crypt.ml b/mllib/crypt.ml
new file mode 100644
index 0000000..2c48c0d
--- /dev/null
+++ b/mllib/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/mllib/crypt.mli b/mllib/crypt.mli
new file mode 100644
index 0000000..ef4066f
--- /dev/null
+++ b/mllib/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/mllib/firstboot.ml b/mllib/firstboot.ml
new file mode 100644
index 0000000..0207cc1
--- /dev/null
+++ b/mllib/firstboot.ml
@@ -0,0 +1,169 @@
+(* 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\" \"$@\" >>$logfile
+echo \"Scripts dir: $d\" >>$logfile
+
+if test \"$1\" = \"start\"
+then
+  for f in $d/* ; do
+    if test -x \"$f\"
+    then
+      echo '=== Running' $f '===' >>$logfile
+      $f >>$logfile 2>&1
+      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
+
+[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
new file mode 100644
index 0000000..4fb8812
--- /dev/null
+++ b/mllib/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/mllib/hostname.ml b/mllib/hostname.ml
new file mode 100644
index 0000000..6702f29
--- /dev/null
+++ b/mllib/hostname.ml
@@ -0,0 +1,80 @@
+(* 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"), _ ->
+    update_etc_hostname g hostname;
+    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
+
+and update_etc_machine_info g hostname =
+  replace_line_in_file g "/etc/machine-info" "PRETTY_HOSTNAME" hostname
diff --git a/mllib/hostname.mli b/mllib/hostname.mli
new file mode 100644
index 0000000..15487f6
--- /dev/null
+++ b/mllib/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/mllib/password.ml b/mllib/password.ml
new file mode 100644
index 0000000..8cadc9d
--- /dev/null
+++ b/mllib/password.ml
@@ -0,0 +1,142 @@
+(* 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 Printf
+
+type password_crypto = [`MD5 | `SHA256 | `SHA512 ]
+
+type password_selector = Set_password of string
+
+type password_map = (string, password_selector) Hashtbl.t
+
+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 =
+  let i =
+    try String.index arg ':'
+    with Not_found ->
+      eprintf (f_"%s: invalid password format; see the man page.\n") prog;
+      exit 1 in
+  let key, value =
+    let len = String.length arg in
+    String.sub arg 0 i, String.sub arg (i+1) (len-(i+1)) in
+
+  match key with
+  | "file" -> Set_password (read_password_from_file value)
+  | "password" -> Set_password value
+  | _ ->
+    eprintf (f_"%s: password format, \"%s:...\" is not recognized; see the man page.\n") prog key;
+    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:..."
+           * '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
+          match selector with
+          | Set_password password ->
+            user ^ ":" ^ encrypt password crypto ^ 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-in-a-linux-guest/
+ *)
+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
new file mode 100644
index 0000000..4c72ed7
--- /dev/null
+++ b/mllib/password.mli
@@ -0,0 +1,35 @@
+(* 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 = Set_password of string
+
+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/progress-c.c b/mllib/progress-c.c
new file mode 100644
index 0000000..6d777b6
--- /dev/null
+++ b/mllib/progress-c.c
@@ -0,0 +1,106 @@
+/* virt-resize - interface to progress bar mini library
+ * Copyright (C) 2011 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 <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <locale.h>
+
+#include <caml/alloc.h>
+#include <caml/custom.h>
+#include <caml/fail.h>
+#include <caml/memory.h>
+#include <caml/mlvalues.h>
+
+#include "progress.h"
+
+#define Bar_val(v) (*((struct progress_bar **)Data_custom_val(v)))
+
+static void
+progress_bar_finalize (value barv)
+{
+  struct progress_bar *bar = Bar_val (barv);
+
+  if (bar)
+    progress_bar_free (bar);
+}
+
+static struct custom_operations progress_bar_custom_operations = {
+  (char *) "progress_bar_custom_operations",
+  progress_bar_finalize,
+  custom_compare_default,
+  custom_hash_default,
+  custom_serialize_default,
+  custom_deserialize_default
+};
+
+value
+virt_resize_progress_bar_init (value machine_readablev)
+{
+  CAMLparam1 (machine_readablev);
+  CAMLlocal1 (barv);
+  struct progress_bar *bar;
+  int machine_readable = Bool_val (machine_readablev);
+  unsigned flags = 0;
+
+  /* XXX Have to do this to get nl_langinfo to work properly.  However
+   * we should really only call this from main.
+   */
+  setlocale (LC_ALL, "");
+
+  if (machine_readable)
+    flags |= PROGRESS_BAR_MACHINE_READABLE;
+  bar = progress_bar_init (flags);
+  if (bar == NULL)
+    caml_raise_out_of_memory ();
+
+  barv = caml_alloc_custom (&progress_bar_custom_operations,
+                            sizeof (struct progress_bar *), 0, 1);
+  Bar_val (barv) = bar;
+
+  CAMLreturn (barv);
+}
+
+/* NB: "noalloc" function. */
+value
+virt_resize_progress_bar_reset (value barv)
+{
+  struct progress_bar *bar = Bar_val (barv);
+
+  progress_bar_reset (bar);
+
+  return Val_unit;
+}
+
+/* NB: "noalloc" function. */
+value
+virt_resize_progress_bar_set (value barv,
+                              value positionv, value totalv)
+{
+  struct progress_bar *bar = Bar_val (barv);
+  uint64_t position = Int64_val (positionv);
+  uint64_t total = Int64_val (totalv);
+
+  progress_bar_set (bar, position, total);
+
+  return Val_unit;
+}
diff --git a/mllib/progress.ml b/mllib/progress.ml
new file mode 100644
index 0000000..194120e
--- /dev/null
+++ b/mllib/progress.ml
@@ -0,0 +1,58 @@
+(* virt-resize
+ * Copyright (C) 2010-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 Unix
+open Printf
+
+module G = Guestfs
+
+type progress_bar
+external progress_bar_init : machine_readable:bool -> progress_bar
+  = "virt_resize_progress_bar_init"
+external progress_bar_reset : progress_bar -> unit
+  = "virt_resize_progress_bar_reset" "noalloc"
+external progress_bar_set : progress_bar -> int64 -> int64 -> unit
+  = "virt_resize_progress_bar_set" "noalloc"
+
+let set_up_progress_bar ?(machine_readable = false) (g : Guestfs.guestfs) =
+  (* Only display progress bars if the machine_readable flag is set or
+   * the output is a tty.
+   *)
+  if machine_readable || TTY.isatty_stdout () then (
+    (* Initialize the C mini library. *)
+    let bar = progress_bar_init ~machine_readable in
+
+    (* Reset the progress bar before every libguestfs function. *)
+    let enter_callback g event evh buf array =
+      if event = G.EVENT_ENTER then
+        progress_bar_reset bar
+    in
+
+    (* A progress event: move the progress bar. *)
+    let progress_callback g event evh buf array =
+      if event = G.EVENT_PROGRESS && Array.length array >= 4 then (
+        let position = array.(2)
+        and total = array.(3) in
+
+        progress_bar_set bar position total
+      )
+    in
+
+    ignore (g#set_event_callback enter_callback [G.EVENT_ENTER]);
+    ignore (g#set_event_callback progress_callback [G.EVENT_PROGRESS])
+  )
diff --git a/mllib/progress.mli b/mllib/progress.mli
new file mode 100644
index 0000000..a9b3d37
--- /dev/null
+++ b/mllib/progress.mli
@@ -0,0 +1,19 @@
+(* virt-resize
+ * Copyright (C) 2010-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 set_up_progress_bar : ?machine_readable:bool -> Guestfs.guestfs -> unit
diff --git a/mllib/random_seed.ml b/mllib/random_seed.ml
new file mode 100644
index 0000000..84236cd
--- /dev/null
+++ b/mllib/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/mllib/random_seed.mli b/mllib/random_seed.mli
new file mode 100644
index 0000000..b5261f2
--- /dev/null
+++ b/mllib/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/mllib/tTY.ml b/mllib/tTY.ml
new file mode 100644
index 0000000..80f0e1e
--- /dev/null
+++ b/mllib/tTY.ml
@@ -0,0 +1,19 @@
+(* virt-resize
+ * 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 isatty_stdout : unit -> bool = "virt_resize_isatty_stdout" "noalloc"
diff --git a/mllib/tTY.mli b/mllib/tTY.mli
new file mode 100644
index 0000000..69441f1
--- /dev/null
+++ b/mllib/tTY.mli
@@ -0,0 +1,23 @@
+(* virt-resize
+ * 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.
+ *)
+
+(** TTY utilities. *)
+
+val isatty_stdout : unit -> bool
+(** RHEL 5-era ocaml didn't have Unix.isatty.  This is not needed in
+    recent OCaml. *)
diff --git a/mllib/tty-c.c b/mllib/tty-c.c
new file mode 100644
index 0000000..64fa884
--- /dev/null
+++ b/mllib/tty-c.c
@@ -0,0 +1,38 @@
+/* virt-resize - interface to isatty
+ * 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/memory.h>
+#include <caml/mlvalues.h>
+
+/* RHEL 5-era ocaml didn't have Unix.isatty.
+ *
+ * Note this function is marked as "noalloc" so it must not call any
+ * OCaml allocation functions:
+ * http://camltastic.blogspot.co.uk/2008/08/tip-calling-c-functions-directly-with.html
+ */
+value
+virt_resize_isatty_stdout (value unitv)
+{
+  return isatty (1) ? Val_true : Val_false;
+}
diff --git a/mllib/urandom.ml b/mllib/urandom.ml
new file mode 100644
index 0000000..9b613e8
--- /dev/null
+++ b/mllib/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/mllib/urandom.mli b/mllib/urandom.mli
new file mode 100644
index 0000000..ffc77dd
--- /dev/null
+++ b/mllib/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/po/POTFILES b/po/POTFILES
index 7b28d38..f539a0f 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -208,6 +208,9 @@ gobject/src/tristate.c
 inspector/virt-inspector.c
 java/com_redhat_et_libguestfs_GuestFS.c
 lua/lua-guestfs.c
+mllib/crypt-c.c
+mllib/progress-c.c
+mllib/tty-c.c
 ocaml/guestfs-c-actions.c
 ocaml/guestfs-c.c
 perl/Guestfs.c
@@ -219,8 +222,6 @@ python/guestfs-py-byhand.c
 python/guestfs-py.c
 rescue/test-virt-rescue.pl
 rescue/virt-rescue.c
-resize/progress-c.c
-resize/tty-c.c
 ruby/ext/guestfs/_guestfs.c
 src/actions-0.c
 src/actions-1.c
diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index d23b29d..c375dd6 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -1,11 +1,5 @@
-resize/common_gettext.ml
-resize/common_utils.ml
-resize/common_utils_tests.ml
-resize/progress.ml
 resize/resize.ml
-resize/tTY.ml
 sparsify/sparsify.ml
-sysprep/firstboot.ml
 sysprep/main.ml
 sysprep/sysprep_operation.ml
 sysprep/sysprep_operation_abrt_data.ml
@@ -17,6 +11,7 @@ sysprep/sysprep_operation_cron_spool.ml
 sysprep/sysprep_operation_dhcp_client_state.ml
 sysprep/sysprep_operation_dhcp_server_state.ml
 sysprep/sysprep_operation_dovecot_data.ml
+sysprep/sysprep_operation_firewall_rules.ml
 sysprep/sysprep_operation_firstboot.ml
 sysprep/sysprep_operation_flag_reconfiguration.ml
 sysprep/sysprep_operation_hostname.ml
@@ -30,6 +25,7 @@ sysprep/sysprep_operation_net_hwaddr.ml
 sysprep/sysprep_operation_pacct_log.ml
 sysprep/sysprep_operation_package_manager_cache.ml
 sysprep/sysprep_operation_pam_data.ml
+sysprep/sysprep_operation_password.ml
 sysprep/sysprep_operation_puppet_data_log.ml
 sysprep/sysprep_operation_random_seed.ml
 sysprep/sysprep_operation_rhn_systemid.ml
@@ -40,6 +36,7 @@ sysprep/sysprep_operation_smolt_uuid.ml
 sysprep/sysprep_operation_ssh_hostkeys.ml
 sysprep/sysprep_operation_ssh_userdir.ml
 sysprep/sysprep_operation_sssd_db_log.ml
+sysprep/sysprep_operation_tmp_files.ml
 sysprep/sysprep_operation_udev_persistent_net.ml
 sysprep/sysprep_operation_user_account.ml
 sysprep/sysprep_operation_utmp.ml
diff --git a/resize/Makefile.am b/resize/Makefile.am
index 2431e34..d03a11c 100644
--- a/resize/Makefile.am
+++ b/resize/Makefile.am
@@ -26,28 +26,19 @@ CLEANFILES = *~ *.cmi *.cmo *.cmx *.cmxa *.o virt-resize test.img
 
 # Alphabetical order.
 SOURCES = \
-	common_gettext.ml \
-	common_utils.ml \
-	common_utils_tests.ml \
-	progress-c.c \
-	progress.mli \
-	progress.ml \
-	resize.ml \
-	tty-c.c \
-	tTY.mli \
-	tTY.ml
+	resize.ml
 
 if HAVE_OCAML
 
 # Note this list must be in dependency order.
 OBJECTS = \
+	$(top_builddir)/mllib/tty-c.o \
+	$(top_builddir)/mllib/tTY.cmx \
 	$(top_builddir)/fish/guestfish-progress.o \
-	tty-c.o \
-	progress-c.o \
-	common_gettext.cmx \
-	common_utils.cmx \
-	tTY.cmx \
-	progress.cmx \
+	$(top_builddir)/mllib/progress-c.o \
+	$(top_builddir)/mllib/progress.cmx \
+	$(top_builddir)/mllib/common_gettext.cmx \
+	$(top_builddir)/mllib/common_utils.cmx \
 	resize.cmx
 
 bin_SCRIPTS = virt-resize
@@ -55,7 +46,11 @@ bin_SCRIPTS = virt-resize
 # -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 ../ocaml
+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
@@ -105,15 +100,9 @@ CLEANFILES += stamp-virt-resize.pod
 
 # Tests.
 
-check_SCRIPTS = common_utils_tests
-
-common_utils_tests: common_gettext.cmx common_utils.cmx common_utils_tests.cmx
-	$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \
-	  mlguestfs.cmxa -linkpkg $^ -cclib -lncurses -o $@
-
 TESTS_ENVIRONMENT = $(top_builddir)/run --test
 
-TESTS = common_utils_tests
+TESTS =
 if ENABLE_APPLIANCE
 TESTS += test-virt-resize.sh
 endif
@@ -126,7 +115,7 @@ depend: .depend
 
 .depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
 	rm -f $@ $@-t
-	$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) $^ | \
+	$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(top_srcdir)/mllib $^ | \
 	  $(SED) 's/ *$$//' | \
 	  $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
 	  $(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
diff --git a/resize/common_utils.ml b/resize/common_utils.ml
deleted file mode 100644
index 3e50d19..0000000
--- a/resize/common_utils.ml
+++ /dev/null
@@ -1,248 +0,0 @@
-(* Common utilities for OCaml tools in libguestfs.
- * Copyright (C) 2010-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 Printf
-
-open Common_gettext.Gettext
-
-module G = Guestfs
-
-let (//) = Filename.concat
-
-let ( +^ ) = Int64.add
-let ( -^ ) = Int64.sub
-let ( *^ ) = Int64.mul
-let ( /^ ) = Int64.div
-let ( &^ ) = Int64.logand
-let ( ~^ ) = Int64.lognot
-
-let int_of_le32 str =
-  assert (String.length str = 4);
-  let c0 = Char.code (String.unsafe_get str 0) in
-  let c1 = Char.code (String.unsafe_get str 1) in
-  let c2 = Char.code (String.unsafe_get str 2) in
-  let c3 = Char.code (String.unsafe_get str 3) in
-  Int64.of_int c0 +^
-    (Int64.shift_left (Int64.of_int c1) 8) +^
-    (Int64.shift_left (Int64.of_int c2) 16) +^
-    (Int64.shift_left (Int64.of_int c3) 24)
-
-let le32_of_int i =
-  let c0 = i &^ 0xffL in
-  let c1 = Int64.shift_right (i &^ 0xff00L) 8 in
-  let c2 = Int64.shift_right (i &^ 0xff0000L) 16 in
-  let c3 = Int64.shift_right (i &^ 0xff000000L) 24 in
-  let s = String.create 4 in
-  String.unsafe_set s 0 (Char.unsafe_chr (Int64.to_int c0));
-  String.unsafe_set s 1 (Char.unsafe_chr (Int64.to_int c1));
-  String.unsafe_set s 2 (Char.unsafe_chr (Int64.to_int c2));
-  String.unsafe_set s 3 (Char.unsafe_chr (Int64.to_int c3));
-  s
-
-let output_spaces chan n = for i = 0 to n-1 do output_char chan ' ' done
-
-let wrap ?(chan = stdout) ?(hanging = 0) str =
-  let rec _wrap col str =
-    let n = String.length str in
-    let i = try String.index str ' ' with Not_found -> n in
-    let col =
-      if col+i >= 72 then (
-        output_char chan '\n';
-        output_spaces chan hanging;
-        i+hanging+1
-      ) else col+i+1 in
-    output_string chan (String.sub str 0 i);
-    if i < n then (
-      output_char chan ' ';
-      _wrap col (String.sub str (i+1) (n-(i+1)))
-    )
-  in
-  _wrap 0 str
-
-let string_prefix str prefix =
-  let n = String.length prefix in
-  String.length str >= n && String.sub str 0 n = prefix
-
-let rec string_find s sub =
-  let len = String.length s in
-  let sublen = String.length sub in
-  let rec loop i =
-    if i <= len-sublen then (
-      let rec loop2 j =
-        if j < sublen then (
-          if s.[i+j] = sub.[j] then loop2 (j+1)
-          else -1
-        ) else
-          i (* found *)
-      in
-      let r = loop2 0 in
-      if r = -1 then loop (i+1) else r
-    ) else
-      -1 (* not found *)
-  in
-  loop 0
-
-let rec replace_str s s1 s2 =
-  let len = String.length s in
-  let sublen = String.length s1 in
-  let i = string_find s s1 in
-  if i = -1 then s
-  else (
-    let s' = String.sub s 0 i in
-    let s'' = String.sub s (i+sublen) (len-i-sublen) in
-    s' ^ s2 ^ replace_str s'' s1 s2
-  )
-
-let rec string_split sep str =
-  let len = String.length str in
-  let seplen = String.length sep in
-  let i = string_find str sep in
-  if i = -1 then [str]
-  else (
-    let s' = String.sub str 0 i in
-    let s'' = String.sub str (i+seplen) (len-i-seplen) in
-    s' :: string_split sep s''
-  )
-
-let string_random8 =
-  let chars = "abcdefghijklmnopqrstuvwxyz0123456789" in
-  fun () ->
-    String.concat "" (
-      List.map (
-        fun _ ->
-          let c = Random.int 36 in
-          let c = chars.[c] in
-          String.make 1 c
-      ) [1;2;3;4;5;6;7;8]
-    )
-
-let error fs =
-  let display str =
-    wrap ~chan:stderr (s_"virt-resize: error: " ^ str);
-    prerr_newline ();
-    prerr_newline ();
-    wrap ~chan:stderr
-      (s_"If reporting bugs, run virt-resize with the '-d' option and include the complete output.");
-    prerr_newline ();
-    exit 1
-  in
-  ksprintf display fs
-
-let feature_available (g : Guestfs.guestfs) names =
-  try g#available names; true
-  with G.Error _ -> false
-
-let read_whole_file path =
-  let buf = Buffer.create 16384 in
-  let chan = open_in path in
-  let maxlen = 16384 in
-  let s = String.create maxlen in
-  let rec loop () =
-    let r = input chan s 0 maxlen in
-    if r > 0 then (
-      Buffer.add_substring buf s 0 r;
-      loop ()
-    )
-  in
-  loop ();
-  close_in chan;
-  Buffer.contents buf
-
-(* Parse the size field from --resize and --resize-force options. *)
-let parse_size =
-  let const_re = Str.regexp "^\\([.0-9]+\\)\\([bKMG]\\)$"
-  and plus_const_re = Str.regexp "^\\+\\([.0-9]+\\)\\([bKMG]\\)$"
-  and minus_const_re = Str.regexp "^-\\([.0-9]+\\)\\([bKMG]\\)$"
-  and percent_re = Str.regexp "^\\([.0-9]+\\)%$"
-  and plus_percent_re = Str.regexp "^\\+\\([.0-9]+\\)%$"
-  and minus_percent_re = Str.regexp "^-\\([.0-9]+\\)%$"
-  in
-  fun oldsize field ->
-    let matches rex = Str.string_match rex field 0 in
-    let sub i = Str.matched_group i field in
-    let size_scaled f = function
-      | "b" -> Int64.of_float f
-      | "K" -> Int64.of_float (f *. 1024.)
-      | "M" -> Int64.of_float (f *. 1024. *. 1024.)
-      | "G" -> Int64.of_float (f *. 1024. *. 1024. *. 1024.)
-      | _ -> assert false
-    in
-
-    if matches const_re then (
-      size_scaled (float_of_string (sub 1)) (sub 2)
-    )
-    else if matches plus_const_re then (
-      let incr = size_scaled (float_of_string (sub 1)) (sub 2) in
-      oldsize +^ incr
-    )
-    else if matches minus_const_re then (
-      let incr = size_scaled (float_of_string (sub 1)) (sub 2) in
-      oldsize -^ incr
-    )
-    else if matches percent_re then (
-      let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
-      oldsize *^ percent /^ 1000L
-    )
-    else if matches plus_percent_re then (
-      let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
-      oldsize +^ oldsize *^ percent /^ 1000L
-    )
-    else if matches minus_percent_re then (
-      let percent = Int64.of_float (10. *. float_of_string (sub 1)) in
-      oldsize -^ oldsize *^ percent /^ 1000L
-    )
-    else
-      error "virt-resize: %s: cannot parse size field" field
-
-let human_size i =
-  let sign, i = if i < 0L then "-", Int64.neg i else "", i in
-
-  if i < 1024L then
-    sprintf "%s%Ld" sign i
-  else (
-    let f = Int64.to_float i /. 1024. in
-    let i = i /^ 1024L in
-    if i < 1024L then
-      sprintf "%s%.1fK" sign f
-    else (
-      let f = Int64.to_float i /. 1024. in
-      let i = i /^ 1024L in
-      if i < 1024L then
-        sprintf "%s%.1fM" sign f
-      else (
-        let f = Int64.to_float i /. 1024. in
-        (*let i = i /^ 1024L in*)
-        sprintf "%s%.1fG" sign f
-      )
-    )
-  )
-
-(* Skip any leading '-' characters when comparing command line args. *)
-let skip_dashes str =
-  let n = String.length str in
-  let rec loop i =
-    if i >= n then invalid_arg "skip_dashes"
-    else if String.unsafe_get str i = '-' then loop (i+1)
-    else i
-  in
-  let i = loop 0 in
-  if i = 0 then str
-  else String.sub str i (n-i)
-
-let compare_command_line_args a b =
-  compare (String.lowercase (skip_dashes a)) (String.lowercase (skip_dashes b))
diff --git a/resize/common_utils_tests.ml b/resize/common_utils_tests.ml
deleted file mode 100644
index 6f96db7..0000000
--- a/resize/common_utils_tests.ml
+++ /dev/null
@@ -1,78 +0,0 @@
-(* virt-resize
- * Copyright (C) 2011 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.
- *)
-
-(* This file tests the Common_utils module. *)
-
-open Common_utils
-
-(* Test Common_utils.int_of_le32 and Common_utils.le32_of_int. *)
-let () =
-  assert (int_of_le32 "\x80\x60\x40\x20" = 0x20406080L);
-  assert (le32_of_int 0x20406080L = "\x80\x60\x40\x20")
-
-(* Test Common_utils.parse_size. *)
-let () =
-  (* For absolute sizes, oldsize is ignored. *)
-  assert (parse_size 100_L "100b" = 100_L);
-  assert (parse_size 1000_L "100b" = 100_L);
-  assert (parse_size 10000_L "100b" = 100_L);
-  assert (parse_size 100_L "100K" = 102400_L);
-  (* Fractions are always rounded down. *)
-  assert (parse_size 100_L "1.1K" = 1126_L);
-  assert (parse_size 100_L "100.1M" = 104962457_L);
-  assert (parse_size 100_L "123.4G" = 132499741081_L);
-
-  (* oldsize +/- a constant. *)
-  assert (parse_size 100_L "+1b" = 101_L);
-  assert (parse_size 100_L "-2b" = 98_L);
-  assert (parse_size 100_L "+1K" = 1124_L);
-  assert (parse_size 1024_L "-1K" = 0_L);
-  assert (parse_size 1126_L "-1.1K" = 0_L);
-  assert (parse_size 1024_L "+1.1M" = 1154457_L);
-  assert (parse_size 132499741081_L "-123.3G" = 107374182_L);
-
-  (* oldsize +/- a percentage. *)
-  assert (parse_size 100_L "+1%" = 101_L);
-  assert (parse_size 100_L "-1%" = 99_L);
-  assert (parse_size 100000_L "+1%" = 101000_L);
-  assert (parse_size 100000_L "-1%" = 99000_L);
-  assert (parse_size 100000_L "+50%" = 150000_L);
-  assert (parse_size 100000_L "-50%" = 50000_L);
-  assert (parse_size 100000_L "+100%" = 200000_L);
-  assert (parse_size 100000_L "-100%" = 0_L);
-  assert (parse_size 100000_L "+200%" = 300000_L);
-  assert (parse_size 100000_L "+300%" = 400000_L);
-
-  (* Implementation rounds numbers so that only a single digit after
-   * the decimal point is significant.
-   *)
-  assert (parse_size 100000_L "+1.1%" = 101100_L);
-  assert (parse_size 100000_L "+1.12%" = 101100_L)
-
-(* Test Common_utils.human_size. *)
-let () =
-  assert (human_size 100_L = "100");
-  assert (human_size (-100_L) = "-100");
-  assert (human_size 1024_L = "1.0K");
-  assert (human_size (-1024_L) = "-1.0K");
-  assert (human_size 1126_L = "1.1K");
-  assert (human_size (-1126_L) = "-1.1K");
-  assert (human_size 1363149_L = "1.3M");
-  assert (human_size (-1363149_L) = "-1.3M");
-  assert (human_size 3650722201_L = "3.4G");
-  assert (human_size (-3650722201_L) = "-3.4G")
diff --git a/resize/progress-c.c b/resize/progress-c.c
deleted file mode 100644
index 6d777b6..0000000
--- a/resize/progress-c.c
+++ /dev/null
@@ -1,106 +0,0 @@
-/* virt-resize - interface to progress bar mini library
- * Copyright (C) 2011 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 <stdint.h>
-#include <string.h>
-#include <unistd.h>
-#include <locale.h>
-
-#include <caml/alloc.h>
-#include <caml/custom.h>
-#include <caml/fail.h>
-#include <caml/memory.h>
-#include <caml/mlvalues.h>
-
-#include "progress.h"
-
-#define Bar_val(v) (*((struct progress_bar **)Data_custom_val(v)))
-
-static void
-progress_bar_finalize (value barv)
-{
-  struct progress_bar *bar = Bar_val (barv);
-
-  if (bar)
-    progress_bar_free (bar);
-}
-
-static struct custom_operations progress_bar_custom_operations = {
-  (char *) "progress_bar_custom_operations",
-  progress_bar_finalize,
-  custom_compare_default,
-  custom_hash_default,
-  custom_serialize_default,
-  custom_deserialize_default
-};
-
-value
-virt_resize_progress_bar_init (value machine_readablev)
-{
-  CAMLparam1 (machine_readablev);
-  CAMLlocal1 (barv);
-  struct progress_bar *bar;
-  int machine_readable = Bool_val (machine_readablev);
-  unsigned flags = 0;
-
-  /* XXX Have to do this to get nl_langinfo to work properly.  However
-   * we should really only call this from main.
-   */
-  setlocale (LC_ALL, "");
-
-  if (machine_readable)
-    flags |= PROGRESS_BAR_MACHINE_READABLE;
-  bar = progress_bar_init (flags);
-  if (bar == NULL)
-    caml_raise_out_of_memory ();
-
-  barv = caml_alloc_custom (&progress_bar_custom_operations,
-                            sizeof (struct progress_bar *), 0, 1);
-  Bar_val (barv) = bar;
-
-  CAMLreturn (barv);
-}
-
-/* NB: "noalloc" function. */
-value
-virt_resize_progress_bar_reset (value barv)
-{
-  struct progress_bar *bar = Bar_val (barv);
-
-  progress_bar_reset (bar);
-
-  return Val_unit;
-}
-
-/* NB: "noalloc" function. */
-value
-virt_resize_progress_bar_set (value barv,
-                              value positionv, value totalv)
-{
-  struct progress_bar *bar = Bar_val (barv);
-  uint64_t position = Int64_val (positionv);
-  uint64_t total = Int64_val (totalv);
-
-  progress_bar_set (bar, position, total);
-
-  return Val_unit;
-}
diff --git a/resize/progress.ml b/resize/progress.ml
deleted file mode 100644
index 194120e..0000000
--- a/resize/progress.ml
+++ /dev/null
@@ -1,58 +0,0 @@
-(* virt-resize
- * Copyright (C) 2010-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 Unix
-open Printf
-
-module G = Guestfs
-
-type progress_bar
-external progress_bar_init : machine_readable:bool -> progress_bar
-  = "virt_resize_progress_bar_init"
-external progress_bar_reset : progress_bar -> unit
-  = "virt_resize_progress_bar_reset" "noalloc"
-external progress_bar_set : progress_bar -> int64 -> int64 -> unit
-  = "virt_resize_progress_bar_set" "noalloc"
-
-let set_up_progress_bar ?(machine_readable = false) (g : Guestfs.guestfs) =
-  (* Only display progress bars if the machine_readable flag is set or
-   * the output is a tty.
-   *)
-  if machine_readable || TTY.isatty_stdout () then (
-    (* Initialize the C mini library. *)
-    let bar = progress_bar_init ~machine_readable in
-
-    (* Reset the progress bar before every libguestfs function. *)
-    let enter_callback g event evh buf array =
-      if event = G.EVENT_ENTER then
-        progress_bar_reset bar
-    in
-
-    (* A progress event: move the progress bar. *)
-    let progress_callback g event evh buf array =
-      if event = G.EVENT_PROGRESS && Array.length array >= 4 then (
-        let position = array.(2)
-        and total = array.(3) in
-
-        progress_bar_set bar position total
-      )
-    in
-
-    ignore (g#set_event_callback enter_callback [G.EVENT_ENTER]);
-    ignore (g#set_event_callback progress_callback [G.EVENT_PROGRESS])
-  )
diff --git a/resize/progress.mli b/resize/progress.mli
deleted file mode 100644
index a9b3d37..0000000
--- a/resize/progress.mli
+++ /dev/null
@@ -1,19 +0,0 @@
-(* virt-resize
- * Copyright (C) 2010-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 set_up_progress_bar : ?machine_readable:bool -> Guestfs.guestfs -> unit
diff --git a/resize/resize.ml b/resize/resize.ml
index 580ff8f..f9053c1 100644
--- a/resize/resize.ml
+++ b/resize/resize.ml
@@ -29,6 +29,7 @@ let min_extra_partition = 10L *^ 1024L *^ 1024L
 
 (* Command line argument parsing. *)
 let prog = Filename.basename Sys.executable_name
+let error fs = error ~prog fs
 
 type align_first_t = [ `Never | `Always | `Auto ]
 
@@ -628,7 +629,7 @@ let () =
 
     (* Parse the size field. *)
     let oldsize = p.p_part.G.part_size in
-    let newsize = parse_size oldsize sizefield in
+    let newsize = parse_resize ~prog oldsize sizefield in
 
     if newsize <= 0L then
       error (f_"%s: new partition size is zero or negative") dev;
diff --git a/resize/tTY.ml b/resize/tTY.ml
deleted file mode 100644
index 80f0e1e..0000000
--- a/resize/tTY.ml
+++ /dev/null
@@ -1,19 +0,0 @@
-(* virt-resize
- * 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 isatty_stdout : unit -> bool = "virt_resize_isatty_stdout" "noalloc"
diff --git a/resize/tTY.mli b/resize/tTY.mli
deleted file mode 100644
index 69441f1..0000000
--- a/resize/tTY.mli
+++ /dev/null
@@ -1,23 +0,0 @@
-(* virt-resize
- * 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.
- *)
-
-(** TTY utilities. *)
-
-val isatty_stdout : unit -> bool
-(** RHEL 5-era ocaml didn't have Unix.isatty.  This is not needed in
-    recent OCaml. *)
diff --git a/resize/tty-c.c b/resize/tty-c.c
deleted file mode 100644
index 64fa884..0000000
--- a/resize/tty-c.c
+++ /dev/null
@@ -1,38 +0,0 @@
-/* virt-resize - interface to isatty
- * 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/memory.h>
-#include <caml/mlvalues.h>
-
-/* RHEL 5-era ocaml didn't have Unix.isatty.
- *
- * Note this function is marked as "noalloc" so it must not call any
- * OCaml allocation functions:
- * http://camltastic.blogspot.co.uk/2008/08/tip-calling-c-functions-directly-with.html
- */
-value
-virt_resize_isatty_stdout (value unitv)
-{
-  return isatty (1) ? Val_true : Val_false;
-}
diff --git a/sparsify/Makefile.am b/sparsify/Makefile.am
index 3bc1d0e..2df0dbf 100644
--- a/sparsify/Makefile.am
+++ b/sparsify/Makefile.am
@@ -33,12 +33,12 @@ if HAVE_OCAML
 # Note this list must be in dependency order.
 OBJECTS = \
 	$(top_builddir)/fish/guestfish-progress.o \
-	$(top_builddir)/resize/tty-c.o \
-	$(top_builddir)/resize/progress-c.o \
-	$(top_builddir)/resize/common_gettext.cmx \
-	$(top_builddir)/resize/common_utils.cmx \
-	$(top_builddir)/resize/tTY.cmx \
-	$(top_builddir)/resize/progress.cmx \
+	$(top_builddir)/mllib/tty-c.o \
+	$(top_builddir)/mllib/progress-c.o \
+	$(top_builddir)/mllib/common_gettext.cmx \
+	$(top_builddir)/mllib/common_utils.cmx \
+	$(top_builddir)/mllib/tTY.cmx \
+	$(top_builddir)/mllib/progress.cmx \
 	sparsify.cmx
 
 bin_SCRIPTS = virt-sparsify
@@ -46,7 +46,11 @@ bin_SCRIPTS = virt-sparsify
 # -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)/resize
+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
@@ -110,7 +114,7 @@ depend: .depend
 
 .depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
 	rm -f $@ $@-t
-	$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(top_srcdir)/resize $^ | \
+	$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(top_srcdir)/mllib $^ | \
 	  $(SED) 's/ *$$//' | \
 	  $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
 	  $(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
diff --git a/sparsify/sparsify.ml b/sparsify/sparsify.ml
index 6ea34ee..a32ef83 100644
--- a/sparsify/sparsify.ml
+++ b/sparsify/sparsify.ml
@@ -30,6 +30,7 @@ let () = Random.self_init ()
 
 (* Command line argument parsing. *)
 let prog = Filename.basename Sys.executable_name
+let error fs = error ~prog fs
 
 let indisk, outdisk, compress, convert, debug_gc,
   format, ignores, machine_readable,
diff --git a/sysprep/Makefile.am b/sysprep/Makefile.am
index 61e93be..8bea9fc 100644
--- a/sysprep/Makefile.am
+++ b/sysprep/Makefile.am
@@ -1,5 +1,5 @@
 # libguestfs virt-sysprep tool
-# Copyright (C) 2012 Red Hat Inc.
+# 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
@@ -40,6 +40,7 @@ operations = \
 	dhcp_server_state \
 	dovecot_data \
 	flag_reconfiguration \
+	firewall_rules \
 	firstboot \
 	hostname \
 	kerberos_data \
@@ -52,6 +53,7 @@ operations = \
 	pacct_log \
 	package_manager_cache \
 	pam_data \
+	password \
 	puppet_data_log \
 	random_seed \
 	rhn_systemid \
@@ -62,14 +64,13 @@ operations = \
 	ssh_hostkeys \
 	ssh_userdir \
 	sssd_db_log \
+	tmp_files \
 	udev_persistent_net \
 	user_account \
 	utmp yum_uuid
 
 # Alphabetical order.
 SOURCES = \
-	firstboot.ml \
-	firstboot.mli \
 	main.ml \
 	sysprep_operation.ml \
 	sysprep_operation.mli \
@@ -79,9 +80,16 @@ if HAVE_OCAML
 
 # Note this list must be in dependency order.
 OBJECTS = \
-	$(top_builddir)/resize/common_gettext.cmx \
-	$(top_builddir)/resize/common_utils.cmx \
-	firstboot.cmx \
+	$(top_builddir)/mllib/common_gettext.cmx \
+	$(top_builddir)/mllib/common_utils.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/firstboot.cmx \
+	$(top_builddir)/mllib/config.cmx \
 	sysprep_operation.cmx \
 	$(patsubst %,sysprep_operation_%.cmx,$(operations)) \
 	main.cmx
@@ -91,7 +99,11 @@ bin_SCRIPTS = virt-sysprep
 # -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)/resize
+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
@@ -99,10 +111,15 @@ endif
 OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
 OCAMLOPTFLAGS = $(OCAMLCFLAGS)
 
+OCAMLCLIBS  = \
+	$(LIBXML2_LIBS) -lncurses -lcrypt \
+	-L../src/.libs -lutils \
+	-L../gnulib/lib/.libs -lgnu
+
 virt-sysprep: $(OBJECTS)
 	$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \
 	  mlguestfs.cmxa -linkpkg $^ \
-	  -cclib -lncurses \
+	  -cclib '$(OCAMLCLIBS)' \
 	  $(OCAML_GCOV_LDFLAGS) \
 	  -o $@
 
@@ -113,6 +130,19 @@ virt-sysprep: $(OBJECTS)
 .ml.cmx:
 	$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) -c $< -o $@
 
+# 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 $@
+
 # Manual pages and HTML files for the website.
 man_MANS = virt-sysprep.1
 noinst_DATA = $(top_builddir)/html/virt-sysprep.1.html
@@ -144,6 +174,7 @@ sysprep-operations.pod: virt-sysprep
 TESTS_ENVIRONMENT = \
 	abs_builddir=$(abs_builddir) \
 	abs_srcdir=$(abs_srcdir) \
+	PATH=$(abs_top_builddir)/fuse:$(PATH) \
 	$(top_builddir)/run --test
 
 if ENABLE_APPLIANCE
@@ -165,7 +196,7 @@ depend: .depend
 
 .depend: $(wildcard $(abs_srcdir)/*.mli) $(wildcard $(abs_srcdir)/*.ml)
 	rm -f $@ $@-t
-	$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(top_srcdir)/resize $^ | \
+	$(OCAMLFIND) ocamldep -I ../ocaml -I $(abs_srcdir) -I $(top_srcdir)/mllib $^ | \
 	  $(SED) 's/ *$$//' | \
 	  $(SED) -e :a -e '/ *\\$$/N; s/ *\\\n */ /; ta' | \
 	  $(SED) -e 's,$(abs_srcdir)/,$(builddir)/,g' | \
@@ -176,8 +207,6 @@ depend: .depend
 
 endif
 
-.PHONY: depend docs
+DISTCLEANFILES = .depend
 
-# Parallel builds don't obey dependencies for some reason we
-# don't understand.
-.NOTPARALLEL:
+.PHONY: depend docs
diff --git a/sysprep/firstboot.ml b/sysprep/firstboot.ml
deleted file mode 100644
index 439a56e..0000000
--- a/sysprep/firstboot.ml
+++ /dev/null
@@ -1,161 +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 Sysprep_operation
-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
-
-if test \"$1\" = \"start\"
-then
-  for f in $d/* ; do
-    if test -x \"$f\"
-    then
-      echo '=== Running' $f '===' >>$logfile
-      $f >>$logfile 2>&1
-      rm -f $f
-    fi
-  done
-fi
-" firstboot_dir
-
-let firstboot_service = sprintf "\
-[Unit]
-Description=virt-sysprep firstboot service
-After=syslog.target network.target
-Before=prefdm.service
-
-[Service]
-Type=oneshot
-ExecStart=%s/firstboot.sh
-RemainAfterExit=yes
-
-[Install]
-WantedBy=default.target
-" firstboot_dir
-
-let failed fs =
-  ksprintf (fun msg -> failwith (s_"firstboot: failed: " ^ msg)) fs
-
-let rec install_service g 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);
-
-  (* systemd, else assume sysvinit *)
-  if g#is_dir "/etc/systemd" then
-    install_systemd_service g
-  else
-    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" ->
-    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 root id 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/%Ld-%s-%s" firstboot_dir t r id in
-    g#write filename content;
-    g#chmod 0o755 filename
-
-  | _ ->
-    failed "guest type %s/%s is not supported" typ distro
diff --git a/sysprep/firstboot.mli b/sysprep/firstboot.mli
deleted file mode 100644
index 910dd75..0000000
--- a/sysprep/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 -> string -> string -> unit
-  (** [add_firstboot_script g root id content] adds a firstboot
-      script called [shortname] containing [content].
-
-      NB. [content] is the contents of the script, {b not} a filename.
-
-      [id] should be a short name containing only 7 bit ASCII [-a-z0-9].
-
-      You should make sure the filesystem is relabelled after calling this. *)
diff --git a/sysprep/main.ml b/sysprep/main.ml
index 212538f..6e7a3c0 100644
--- a/sysprep/main.ml
+++ b/sysprep/main.ml
@@ -1,5 +1,5 @@
 (* virt-sysprep
- * Copyright (C) 2012 Red Hat Inc.
+ * 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
@@ -31,7 +31,7 @@ let () = Sysprep_operation.bake ()
 (* Command line argument parsing. *)
 let prog = Filename.basename Sys.executable_name
 
-let debug_gc, operations, g, selinux_relabel, quiet =
+let debug_gc, operations, g, selinux_relabel, quiet, mount_opts =
   let debug_gc = ref false in
   let domain = ref None in
   let dryrun = ref false in
@@ -43,12 +43,10 @@ let debug_gc, operations, g, selinux_relabel, quiet =
   let selinux_relabel = ref `Auto in
   let trace = ref false in
   let verbose = ref false in
+  let mount_opts = ref "" in
 
   let display_version () =
-    let g = new G.guestfs () in
-    let version = g#version () in
-    printf "virt-sysprep %Ld.%Ld.%Ld%s\n"
-      version.G.major version.G.minor version.G.release version.G.extra;
+    printf "virt-sysprep %s\n" Config.package_version;
     exit 0
   and add_file file =
     let format = match !format with "auto" -> None | fmt -> Some fmt in
@@ -74,7 +72,7 @@ let debug_gc, operations, g, selinux_relabel, quiet =
       eprintf (f_"%s: you cannot pass an empty argument to --enable\n") prog;
       exit 1
     );
-    let ops = string_split "," ops in
+    let ops = string_nsplit "," ops in
     let opset = List.fold_left (
       fun opset op_name ->
         try Sysprep_operation.add_to_set op_name opset
@@ -109,6 +107,8 @@ let debug_gc, operations, g, selinux_relabel, quiet =
     "--enable",  Arg.String set_enable,     s_"operations" ^ " " ^ s_"Enable specific operations";
     "--format",  Arg.Set_string format,     s_"format" ^ " " ^ s_"Set format (default: auto)";
     "--list-operations", Arg.Unit list_operations, " " ^ s_"List supported operations";
+    "--long-options", Arg.Unit display_long_options, " " ^ s_"List long options";
+    "--mount-options", Arg.Set_string mount_opts, s_"opts" ^ " " ^ s_"Set mount options (eg /:noatime;/var:rw,noatime)";
     "-q",        Arg.Set quiet,             " " ^ s_"Don't print log messages";
     "--quiet",   Arg.Set quiet,             " " ^ s_"Don't print log messages";
     "--selinux-relabel", Arg.Unit force_selinux_relabel, " " ^ s_"Force SELinux relabel";
@@ -123,6 +123,7 @@ let debug_gc, operations, g, selinux_relabel, quiet =
   let args =
     List.sort (fun (a,_,_) (b,_,_) -> compare_command_line_args a b) args in
   let argspec = Arg.align args in
+  long_options := argspec;
   let anon_fun _ = raise (Arg.Bad (s_"extra parameter on the command line")) in
   let usage_msg =
     sprintf (f_"\
@@ -174,6 +175,15 @@ read the man page virt-sysprep(1).
   let trace = !trace in
   let verbose = !verbose in
 
+  (* Parse the mount options string into a function that maps the
+   * mountpoint to the mount options.
+   *)
+  let mount_opts = !mount_opts in
+  let mount_opts =
+    List.map (string_split ":") (string_nsplit ";" mount_opts) in
+  let mount_opts mp =
+    try List.assoc mp mount_opts with Not_found -> "" in
+
   if not quiet then
     printf (f_"Examining the guest ...\n%!");
 
@@ -184,7 +194,7 @@ read the man page virt-sysprep(1).
   add g dryrun;
   g#launch ();
 
-  debug_gc, operations, g, selinux_relabel, quiet
+  debug_gc, operations, g, selinux_relabel, quiet, mount_opts
 
 let do_sysprep () =
   (* Inspection. *)
@@ -203,7 +213,10 @@ let do_sysprep () =
         let mps = List.sort cmp mps in
         List.iter (
           fun (mp, dev) ->
-            try g#mount dev mp
+            (* Get mount options for this mountpoint. *)
+            let opts = mount_opts mp in
+
+            try g#mount_options opts dev mp;
             with Guestfs.Error msg -> eprintf (f_"%s (ignored)\n") msg
         ) mps;
 
diff --git a/sysprep/sysprep_operation.ml b/sysprep/sysprep_operation.ml
index 2adddba..1fb4f17 100644
--- a/sysprep/sysprep_operation.ml
+++ b/sysprep/sysprep_operation.ml
@@ -22,6 +22,8 @@ open Printf
 
 open Common_gettext.Gettext
 
+let prog = "virt-sysprep"
+
 type flag = [ `Created_files ]
 
 type callback = Guestfs.guestfs -> string -> flag list
@@ -31,10 +33,27 @@ type operation = {
   enabled_by_default : bool;
   heading : string;
   pod_description : string option;
-  extra_args : ((Arg.key * Arg.spec * Arg.doc) * string) list;
+  pod_notes : string option;
+  extra_args : extra_arg list;
   perform_on_filesystems : callback option;
   perform_on_devices : callback option;
 }
+and extra_arg = {
+  extra_argspec : Arg.key * Arg.spec * Arg.doc;
+  extra_pod_argval : string option;
+  extra_pod_description : string;
+}
+
+let defaults = {
+  name = "";
+  enabled_by_default = false;
+  heading = "";
+  pod_description = None;
+  pod_notes = None;
+  extra_args = [];
+  perform_on_filesystems = None;
+  perform_on_devices = None;
+}
 
 let all_operations = ref []
 let enabled_by_default_operations = ref []
@@ -114,6 +133,20 @@ and check op =
         op.name;
       exit 1
     )
+  );
+  (match op.pod_notes with
+  | None -> ()
+  | Some notes ->
+    let n = String.length notes in
+    if n = 0 then (
+      eprintf (f_"virt-sysprep: operation %s has no POD notes\n") op.name;
+      exit 1
+    );
+    if notes.[n-1] = '\n' then (
+      eprintf (f_"virt-sysprep: POD notes for %s must not end with newline\n")
+        op.name;
+      exit 1
+    )
   )
 
 let extra_args () =
@@ -121,7 +154,7 @@ let extra_args () =
 
   List.flatten (
     List.map (fun { extra_args = extra_args } ->
-      List.map fst extra_args
+      List.map (fun { extra_argspec = argspec } -> argspec) extra_args
     ) !all_operations
   )
 
@@ -138,6 +171,14 @@ let dump_pod () =
       (match op.pod_description with
       | None -> ()
       | Some description -> printf "%s\n\n" description
+      );
+      (match op.pod_notes with
+      | None -> ()
+      | Some notes ->
+        printf "=head3 ";
+        printf (f_"Notes on %s") op.name;
+        printf "\n\n";
+        printf "%s\n\n" notes
       )
   ) !all_operations
 
@@ -150,25 +191,28 @@ let dump_pod_options () =
   ) !all_operations in
   let args = List.flatten args in
   let args = List.map (
-    fun (op_name, ((arg_name, spec, _), pod)) ->
-      match spec with
-      | Arg.Unit _
-      | Arg.Bool _
-      | Arg.Set _
-      | Arg.Clear _ ->
-        let heading = sprintf "B<%s>" arg_name in
-        arg_name, (op_name, heading, pod)
-      | Arg.String _
-      | Arg.Set_string _
-      | Arg.Int _
-      | Arg.Set_int _
-      | Arg.Float _
-      | Arg.Set_float _ ->
-        let heading = sprintf "B<%s> %s" arg_name (skip_dashes arg_name) in
-        arg_name, (op_name, heading, pod)
-      | Arg.Tuple _
-      | Arg.Symbol _
-      | Arg.Rest _ -> assert false (* XXX not implemented *)
+    function
+    | (op_name,
+       { extra_argspec = (arg_name,
+                          (Arg.Unit _ | Arg.Bool _ | Arg.Set _ | Arg.Clear _),
+                          _);
+         extra_pod_argval = None;
+         extra_pod_description = pod }) ->
+      let heading = sprintf "B<%s>" arg_name in
+      arg_name, (op_name, heading, pod)
+
+    | (op_name,
+       { extra_argspec = (arg_name,
+                          (Arg.String _ | Arg.Set_string _ | Arg.Int _ |
+                           Arg.Set_int _ | Arg.Float _ | Arg.Set_float _),
+                          _);
+         extra_pod_argval = Some arg_val;
+         extra_pod_description = pod }) ->
+      let heading = sprintf "B<%s> %s" arg_name arg_val in
+      arg_name, (op_name, heading, pod)
+
+    | _ ->
+      failwith "sysprep_operation.ml: argument type not implemented"
   ) args in
 
   let args =
diff --git a/sysprep/sysprep_operation.mli b/sysprep/sysprep_operation.mli
index 87a0b9a..16eee64 100644
--- a/sysprep/sysprep_operation.mli
+++ b/sysprep/sysprep_operation.mli
@@ -18,6 +18,8 @@
 
 (** Structure used to describe sysprep operations. *)
 
+val prog : string
+
 type flag = [ `Created_files ]
 
 type callback = Guestfs.guestfs -> string -> flag list
@@ -38,12 +40,15 @@ type operation = {
   pod_description : string option;
   (** POD-format long description, used for the man page. *)
 
-  extra_args : ((Arg.key * Arg.spec * Arg.doc) * string) list;
+  pod_notes : string option;
+  (** POD-format notes, used for the man page to describe any
+      problems, shortcomings or bugs with this operation. *)
+
+  extra_args : extra_arg list;
   (** Extra command-line arguments, if any.  eg. The [hostname]
       operation has an extra [--hostname] parameter.
 
-      Each element of the list is the argspec (see {!Arg.spec} etc.)
-      and the corresponding full POD documentation.
+      For a description of each list element, see {!extra_arg} below.
 
       You can decide the types of the arguments, whether they are
       mandatory etc. *)
@@ -75,6 +80,20 @@ type operation = {
       operation to work directly on block devices, LVs etc. *)
 }
 
+and extra_arg = {
+  extra_argspec : Arg.key * Arg.spec * Arg.doc;
+  (** The argspec.  See OCaml [Arg] module. *)
+
+  extra_pod_argval : string option;
+  (** The argument value, used only in the virt-sysprep man page. *)
+
+  extra_pod_description : string;
+  (** The long description, used only in the virt-sysprep man page. *)
+}
+
+val defaults : operation
+(** This is so operations can write [let op = { defaults with ... }]. *)
+
 val register_operation : operation -> unit
 (** Register an operation. *)
 
diff --git a/sysprep/sysprep_operation_abrt_data.ml b/sysprep/sysprep_operation_abrt_data.ml
index c830bd4..d923fec 100644
--- a/sysprep/sysprep_operation_abrt_data.ml
+++ b/sysprep/sysprep_operation_abrt_data.ml
@@ -32,16 +32,15 @@ let abrt_data_perform g root =
   )
   else []
 
-let abrt_data_op = {
-  name = "abrt-data";
-  enabled_by_default = true;
-  heading = s_"Remove the crash data generated by ABRT";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "abrt-data";
+    enabled_by_default = true;
+    heading = s_"Remove the crash data generated by ABRT";
+    pod_description = Some (s_"\
 Remove the automatically generated ABRT crash data in
 C</var/spool/abrt/>.");
-  extra_args = [];
-  perform_on_filesystems = Some abrt_data_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some abrt_data_perform;
 }
 
-let () = register_operation abrt_data_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_bash_history.ml b/sysprep/sysprep_operation_bash_history.ml
index 6e1bd3f..f9efa47 100644
--- a/sysprep/sysprep_operation_bash_history.ml
+++ b/sysprep/sysprep_operation_bash_history.ml
@@ -33,16 +33,19 @@ let bash_history_perform g root =
   )
   else []
 
-let bash_history_op = {
-  name = "bash-history";
-  enabled_by_default = true;
-  heading = s_"Remove the bash history in the guest";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "bash-history";
+    enabled_by_default = true;
+    heading = s_"Remove the bash history in the guest";
+    pod_description = Some (s_"\
 Remove the bash history of user \"root\" and any other users
 who have a C<.bash_history> file in their home directory.");
-  extra_args = [];
-  perform_on_filesystems = Some bash_history_perform;
-  perform_on_devices = None;
+    pod_notes = Some (s_"\
+Currently this only looks in C</root> and C</home/*> for
+home directories, so users with home directories in other
+locations won't have the bash history removed.");
+    perform_on_filesystems = Some bash_history_perform;
 }
 
-let () = register_operation bash_history_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_blkid_tab.ml b/sysprep/sysprep_operation_blkid_tab.ml
index c75c3f3..fe9d10f 100644
--- a/sysprep/sysprep_operation_blkid_tab.ml
+++ b/sysprep/sysprep_operation_blkid_tab.ml
@@ -43,14 +43,12 @@ let blkid_tab_perform g root =
   )
   else []
 
-let blkid_tab_op = {
-  name = "blkid-tab";
-  enabled_by_default = true;
-  heading = s_"Remove blkid tab in the guest";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some blkid_tab_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "blkid-tab";
+    enabled_by_default = true;
+    heading = s_"Remove blkid tab in the guest";
+    perform_on_filesystems = Some blkid_tab_perform;
 }
 
-let () = register_operation blkid_tab_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_ca_certificates.ml b/sysprep/sysprep_operation_ca_certificates.ml
index 9b0d396..86a9c54 100644
--- a/sysprep/sysprep_operation_ca_certificates.ml
+++ b/sysprep/sysprep_operation_ca_certificates.ml
@@ -47,14 +47,12 @@ let ca_certificates_perform g root =
   )
   else []
 
-let ca_certificates_op = {
-  name = "ca-certificates";
-  enabled_by_default = false;
-  heading = s_"Remove CA certificates in the guest";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some ca_certificates_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "ca-certificates";
+    enabled_by_default = false;
+    heading = s_"Remove CA certificates in the guest";
+    perform_on_filesystems = Some ca_certificates_perform;
 }
 
-let () = register_operation ca_certificates_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_crash_data.ml b/sysprep/sysprep_operation_crash_data.ml
index 1746cac..edeb5e2 100644
--- a/sysprep/sysprep_operation_crash_data.ml
+++ b/sysprep/sysprep_operation_crash_data.ml
@@ -33,15 +33,14 @@ let crash_data_perform g root =
   );
   []
 
-let crash_data_op = {
-  name = "crash-data";
-  enabled_by_default = true;
-  heading = s_"Remove the crash data generated by kexec-tools";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "crash-data";
+    enabled_by_default = true;
+    heading = s_"Remove the crash data generated by kexec-tools";
+    pod_description = Some (s_"\
 Remove the automatically generated kdump kernel crash data.");
-  extra_args = [];
-  perform_on_filesystems = Some crash_data_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some crash_data_perform;
 }
 
-let () = register_operation crash_data_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_cron_spool.ml b/sysprep/sysprep_operation_cron_spool.ml
index 90eea03..1a036dd 100644
--- a/sysprep/sysprep_operation_cron_spool.ml
+++ b/sysprep/sysprep_operation_cron_spool.ml
@@ -21,7 +21,7 @@ open Common_gettext.Gettext
 
 module G = Guestfs
 
-let cron_spool_perform g root =
+let cron_spool_perform (g : Guestfs.guestfs) root =
   Array.iter g#rm_rf (g#glob_expand "/var/spool/cron/*");
   Array.iter g#rm (g#glob_expand "/var/spool/atjobs/*");
   Array.iter g#rm (g#glob_expand "/var/spool/atjobs/.SEQ");
@@ -33,14 +33,12 @@ let cron_spool_perform g root =
   Array.iter g#rm (g#glob_expand "/var/spool/at/spool/*");
   []
 
-let cron_spool_op = {
-  name = "cron-spool";
-  enabled_by_default = true;
-  heading = s_"Remove user at-jobs and cron-jobs";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some cron_spool_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "cron-spool";
+    enabled_by_default = true;
+    heading = s_"Remove user at-jobs and cron-jobs";
+    perform_on_filesystems = Some cron_spool_perform;
 }
 
-let () = register_operation cron_spool_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_dhcp_client_state.ml b/sysprep/sysprep_operation_dhcp_client_state.ml
index fcda3d5..69c506f 100644
--- a/sysprep/sysprep_operation_dhcp_client_state.ml
+++ b/sysprep/sysprep_operation_dhcp_client_state.ml
@@ -30,14 +30,12 @@ let dhcp_client_state_perform g root =
   );
   []
 
-let dhcp_client_state_op = {
-  name = "dhcp-client-state";
-  enabled_by_default = true;
-  heading = s_"Remove DHCP client leases";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some dhcp_client_state_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "dhcp-client-state";
+    enabled_by_default = true;
+    heading = s_"Remove DHCP client leases";
+    perform_on_filesystems = Some dhcp_client_state_perform;
 }
 
-let () = register_operation dhcp_client_state_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_dhcp_server_state.ml b/sysprep/sysprep_operation_dhcp_server_state.ml
index a713cbd..9ef2abf 100644
--- a/sysprep/sysprep_operation_dhcp_server_state.ml
+++ b/sysprep/sysprep_operation_dhcp_server_state.ml
@@ -25,14 +25,12 @@ let dhcp_server_state_perform g root =
   Array.iter g#rm_rf (g#glob_expand "/var/lib/dhcpd/*");
   []
 
-let dhcp_server_state_op = {
-  name = "dhcp-server-state";
-  enabled_by_default = true;
-  heading = s_"Remove DHCP server leases";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some dhcp_server_state_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "dhcp-server-state";
+    enabled_by_default = true;
+    heading = s_"Remove DHCP server leases";
+    perform_on_filesystems = Some dhcp_server_state_perform;
 }
 
-let () = register_operation dhcp_server_state_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_dovecot_data.ml b/sysprep/sysprep_operation_dovecot_data.ml
index 8a1fe49..c2004e8 100644
--- a/sysprep/sysprep_operation_dovecot_data.ml
+++ b/sysprep/sysprep_operation_dovecot_data.ml
@@ -32,14 +32,12 @@ let dovecot_data_perform g root =
   )
   else []
 
-let dovecot_data_op = {
-  name = "dovecot-data";
-  enabled_by_default = true;
-  heading = s_"Remove Dovecot (mail server) data";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some dovecot_data_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "dovecot-data";
+    enabled_by_default = true;
+    heading = s_"Remove Dovecot (mail server) data";
+    perform_on_filesystems = Some dovecot_data_perform;
 }
 
-let () = register_operation dovecot_data_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_firewall_rules.ml b/sysprep/sysprep_operation_firewall_rules.ml
new file mode 100644
index 0000000..cdb816e
--- /dev/null
+++ b/sysprep/sysprep_operation_firewall_rules.ml
@@ -0,0 +1,57 @@
+(* virt-sysprep
+ * Copyright (C) 2013 Fujitsu Limited.
+ *
+ * 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 Sysprep_operation
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+let firewall_rules_perform g root =
+  let typ = g#inspect_get_type root in
+  if typ <> "windows" then (
+    let paths = [ "/etc/sysconfig/iptables";
+                  "/etc/firewalld/services/*";
+                  "/etc/firewalld/zones/*"; ] in
+    List.iter (
+      fun path ->
+        let files = g#glob_expand path in
+        Array.iter (
+          fun file ->
+            try g#rm file with G.Error _ -> ()
+        ) files;
+    ) paths;
+
+    []
+  )
+  else []
+
+let op = {
+  defaults with
+    name = "firewall-rules";
+    enabled_by_default = false;
+    heading = s_"Remove the firewall rules";
+    pod_description = Some (s_"\
+This removes custom firewall rules by removing C</etc/sysconfig/iptables>
+or custom firewalld configuration in C</etc/firewalld/*/*>.
+
+Note this is I<not> enabled by default since it may expose guests to
+exploits.  Use with care.");
+    perform_on_filesystems = Some firewall_rules_perform;
+}
+
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_firstboot.ml b/sysprep/sysprep_operation_firstboot.ml
index 1e70c65..6be3e92 100644
--- a/sysprep/sysprep_operation_firstboot.ml
+++ b/sysprep/sysprep_operation_firstboot.ml
@@ -26,38 +26,30 @@ module G = Guestfs
 
 let files = ref []
 
-let make_id_from_filename filename =
-  let ret = String.copy filename in
-  for i = 0 to String.length ret - 1 do
-    let c = String.unsafe_get ret i in
-    if not ((c >= 'a' && c <= 'z') ||
-               (c >= 'A' && c <= 'Z') ||
-               (c >= '0' && c <= '9')) then
-      String.unsafe_set ret i '-'
-  done;
-  ret
-
 let firstboot_perform g root =
   (* Read the files and add them using the {!Firstboot} module. *)
+  let files = List.rev !files in
+  let i = ref 0 in
   List.iter (
     fun filename ->
+      incr i;
+      let i = !i in
       let content = read_whole_file filename in
-      let basename = Filename.basename filename in
-      let id = make_id_from_filename basename in
-      Firstboot.add_firstboot_script g root id content
-  ) !files;
-  [ `Created_files ]
+      Firstboot.add_firstboot_script g root i content
+  ) files;
+  if files <> [] then [ `Created_files ] else []
 
-let firstboot_op = {
-  name = "firstboot";
+let op = {
+  defaults with
+    name = "firstboot";
 
-  (* enabled_by_default because we only do anything if the
-   * --firstboot parameter is used.
-   *)
-  enabled_by_default = true;
+    (* enabled_by_default because we only do anything if the
+     * --firstboot parameter is used.
+     *)
+    enabled_by_default = true;
 
-  heading = s_"Add scripts to run once at next boot";
-  pod_description = Some (s_"\
+    heading = s_"Add scripts to run once at next boot";
+    pod_description = Some (s_"\
 Supply one of more shell scripts (using the I<--firstboot> option).
 
 These are run the first time the guest boots, and then are
@@ -66,21 +58,22 @@ configuration that must run in the context of the guest
 operating system, for example C<yum update>.
 
 Output or errors from the scripts are written to
-C<~root/virt-sysprep-firstboot.log> (in the guest).
+C<~root/virt-sysprep-firstboot.log> (in the guest).");
 
+    pod_notes = Some (s_"\
 Currently this is only implemented for Linux guests using
-either System V init, or systemd.");
+either SysVinit-style scripts, Upstart or systemd.");
 
-  extra_args = [
-    ("--firstboot", Arg.String (fun s -> files := s :: !files),
-     s_"script" ^ " " ^ s_"run script once next time guest boots"),
-    s_"\
+    extra_args = [
+      { extra_argspec = "--firstboot", Arg.String (fun s -> files := s :: !files), s_"script" ^ " " ^ s_"run script once next time guest boots";
+        extra_pod_argval = Some "SCRIPT";
+        extra_pod_description = s_"\
 Run script(s) once next time the guest boots.  You can supply
 the I<--firstboot> option as many times as needed."
-  ];
+      }
+    ];
 
-  perform_on_filesystems = Some firstboot_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some firstboot_perform;
 }
 
-let () = register_operation firstboot_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_flag_reconfiguration.ml b/sysprep/sysprep_operation_flag_reconfiguration.ml
index 761d5eb..25abfef 100644
--- a/sysprep/sysprep_operation_flag_reconfiguration.ml
+++ b/sysprep/sysprep_operation_flag_reconfiguration.ml
@@ -29,16 +29,15 @@ let flag_reconfiguration g root =
   )
   else []
 
-let flag_reconfiguration_op = {
-  name = "flag-reconfiguration";
-  enabled_by_default = false;
-  heading = s_"Flag the system for reconfiguration";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "flag-reconfiguration";
+    enabled_by_default = false;
+    heading = s_"Flag the system for reconfiguration";
+    pod_description = Some (s_"\
 Note that this may require user intervention when the
 guest is booted.");
-  extra_args = [];
-  perform_on_filesystems = Some flag_reconfiguration;
-  perform_on_devices = None;
+    perform_on_filesystems = Some flag_reconfiguration;
 }
 
-let () = register_operation flag_reconfiguration_op;
+let () = register_operation op;
diff --git a/sysprep/sysprep_operation_hostname.ml b/sysprep/sysprep_operation_hostname.ml
index d38335e..3066623 100644
--- a/sysprep/sysprep_operation_hostname.ml
+++ b/sysprep/sysprep_operation_hostname.ml
@@ -26,81 +26,34 @@ module G = Guestfs
 
 let hostname = ref "localhost.localdomain"
 
-let hostname_perform g root =
-  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
+let hostname_perform (g : Guestfs.guestfs) root =
+  if Hostname.set_hostname g root !hostname then [ `Created_files ] else []
 
-  (* Replace <key>=... entry in file.  The code assumes it's a small,
-   * plain text file.
-   *)
-  let replace_line_in_file 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
-  in
+let op = {
+  defaults with
+    name = "hostname";
+    enabled_by_default = true;
+    heading = s_"Change the hostname of the guest";
 
-  let update_etc_hostname () =
-    g#write "/etc/hostname" !hostname
-  in
-
-  let update_etc_machine_info () =
-    replace_line_in_file "/etc/machine-info" "PRETTY_HOSTNAME" !hostname
-  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 ();
-    update_etc_machine_info ();
-    [ `Created_files ]
-  | "linux", ("rhel"|"centos"|"scientificlinux"|"redhat-based"), v when v >= 7 ->
-    update_etc_hostname ();
-    update_etc_machine_info ();
-    [ `Created_files ]
-  | "linux", ("debian"|"ubuntu"), _ ->
-    update_etc_hostname ();
-    [ `Created_files ]
-
-  | "linux", ("fedora"|"rhel"|"centos"|"scientificlinux"|"redhat-based"), _ ->
-    replace_line_in_file "/etc/sysconfig/network" "HOSTNAME" !hostname;
-    [ `Created_files ]
-
-  | "linux", ("opensuse"|"sles"|"suse-based"), _ ->
-    g#write "/etc/HOSTNAME" !hostname;
-    [ `Created_files ]
-
-  | _ -> []
-
-let hostname_op = {
-  name = "hostname";
-  enabled_by_default = true;
-  heading = s_"Change the hostname of the guest";
-  pod_description = Some (s_"\
+    pod_description = Some (s_"\
 This operation changes the hostname of the guest to the value
 given in the I<--hostname> parameter.
 
 If the I<--hostname> parameter is not given, then the hostname is changed
 to C<localhost.localdomain>.");
-  extra_args = [
-    ("--hostname", Arg.Set_string hostname, s_"hostname" ^ " " ^ s_"New hostname"),
-    s_"\
+
+    pod_notes = Some (s_"\
+Currently this can only set the hostname on Linux guests.");
+
+    extra_args = [
+      { extra_argspec = "--hostname", Arg.Set_string hostname, s_"hostname" ^ " " ^ s_"New hostname";
+        extra_pod_argval = Some "HOSTNAME";
+        extra_pod_description = s_"\
 Change the hostname.  If not given, defaults to C<localhost.localdomain>."
-  ];
-  perform_on_filesystems = Some hostname_perform;
-  perform_on_devices = None;
+      }
+    ];
+
+    perform_on_filesystems = Some hostname_perform;
 }
 
-let () = register_operation hostname_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_kerberos_data.ml b/sysprep/sysprep_operation_kerberos_data.ml
index 1e120aa..0652719 100644
--- a/sysprep/sysprep_operation_kerberos_data.ml
+++ b/sysprep/sysprep_operation_kerberos_data.ml
@@ -40,14 +40,12 @@ let kerberos_data_perform g root =
   )
   else []
 
-let kerberos_data_op = {
-  name = "kerberos-data";
-  enabled_by_default = false;
-  heading = s_"Remove Kerberos data in the guest";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some kerberos_data_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "kerberos-data";
+    enabled_by_default = false;
+    heading = s_"Remove Kerberos data in the guest";
+    perform_on_filesystems = Some kerberos_data_perform;
 }
 
-let () = register_operation kerberos_data_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_logfiles.ml b/sysprep/sysprep_operation_logfiles.ml
index 88b0977..8994af9 100644
--- a/sysprep/sysprep_operation_logfiles.ml
+++ b/sysprep/sysprep_operation_logfiles.ml
@@ -40,7 +40,6 @@ let globs = List.sort compare [
   "/var/log/wtmp*";
   "/var/log/apache2/*_log";
   "/var/log/apache2/*_log-*";
-  "/var/log/audit/audit.log";
   "/var/log/ntp";
 
   (* logfiles configured by /etc/logrotate.d/* *)
@@ -80,6 +79,15 @@ let globs = List.sort compare [
 
   (* man pages cache *)
   "/var/cache/man/*";
+
+  (* log file of sysstat *)
+  "/var/log/sa/*";
+
+  (* log file of gdm *)
+  "/var/log/gdm/*";
+
+  (* log file of ntp *)
+  "/var/log/ntpstats/*";
 ]
 let globs_as_pod = String.concat "\n" (List.map ((^) " ") globs)
 
@@ -90,18 +98,17 @@ let logfiles_perform g root =
   );
   []
 
-let logfiles_op = {
-  name = "logfiles";
-  enabled_by_default = true;
-  heading = s_"Remove many log files from the guest";
-  pod_description = Some (
-    sprintf (f_"\
+let op = {
+  defaults with
+    name = "logfiles";
+    enabled_by_default = true;
+    heading = s_"Remove many log files from the guest";
+    pod_description = Some (
+      sprintf (f_"\
 On Linux the following files are removed:
 
 %s") globs_as_pod);
-  extra_args = [];
-  perform_on_filesystems = Some logfiles_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some logfiles_perform;
 }
 
-let () = register_operation logfiles_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_lvm_uuids.ml b/sysprep/sysprep_operation_lvm_uuids.ml
index b116d66..7790d0b 100644
--- a/sysprep/sysprep_operation_lvm_uuids.ml
+++ b/sysprep/sysprep_operation_lvm_uuids.ml
@@ -38,16 +38,15 @@ let rec lvm_uuids_perform g root =
   );
   []
 
-let lvm_uuids_op = {
-  name = "lvm-uuids";
-  enabled_by_default = true;
-  heading = s_"Change LVM2 PV and VG UUIDs";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "lvm-uuids";
+    enabled_by_default = true;
+    heading = s_"Change LVM2 PV and VG UUIDs";
+    pod_description = Some (s_"\
 On Linux guests that have LVM2 physical volumes (PVs) or volume groups (VGs),
 new random UUIDs are generated and assigned to those PVs and VGs.");
-  extra_args = [];
-  perform_on_filesystems = None;
-  perform_on_devices = Some lvm_uuids_perform;
+    perform_on_devices = Some lvm_uuids_perform;
 }
 
-let () = register_operation lvm_uuids_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_machine_id.ml b/sysprep/sysprep_operation_machine_id.ml
index c826da4..5eadea4 100644
--- a/sysprep/sysprep_operation_machine_id.ml
+++ b/sysprep/sysprep_operation_machine_id.ml
@@ -31,18 +31,17 @@ let machine_id_perform g root =
   )
   else []
 
-let machine_id_op = {
-  name = "machine-id";
-  enabled_by_default = true;
-  heading = s_"Remove the local machine ID";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "machine-id";
+    enabled_by_default = true;
+    heading = s_"Remove the local machine ID";
+    pod_description = Some (s_"\
 The machine ID is usually generated from a random source during system
 installation and stays constant for all subsequent boots.  Optionally,
 for stateless systems it is generated during runtime at boot if it is
 found to be empty.");
-  extra_args = [];
-  perform_on_filesystems = Some machine_id_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some machine_id_perform;
 }
 
-let () = register_operation machine_id_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_mail_spool.ml b/sysprep/sysprep_operation_mail_spool.ml
index 1b0cbe3..fa2b4e7 100644
--- a/sysprep/sysprep_operation_mail_spool.ml
+++ b/sysprep/sysprep_operation_mail_spool.ml
@@ -30,14 +30,12 @@ let mail_spool_perform g root =
   ];
   []
 
-let mail_spool_op = {
-  name = "mail-spool";
-  enabled_by_default = true;
-  heading = s_"Remove email from the local mail spool directory";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some mail_spool_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "mail-spool";
+    enabled_by_default = true;
+    heading = s_"Remove email from the local mail spool directory";
+    perform_on_filesystems = Some mail_spool_perform;
 }
 
-let () = register_operation mail_spool_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_net_hostname.ml b/sysprep/sysprep_operation_net_hostname.ml
index 37fb78b..a540357 100644
--- a/sysprep/sysprep_operation_net_hostname.ml
+++ b/sysprep/sysprep_operation_net_hostname.ml
@@ -43,16 +43,15 @@ let net_hostname_perform g root =
 
   | _ -> []
 
-let net_hostname_op = {
-  name = "net-hostname";
-  enabled_by_default = true;
-  heading = s_"Remove HOSTNAME in network interface configuration";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "net-hostname";
+    enabled_by_default = true;
+    heading = s_"Remove HOSTNAME in network interface configuration";
+    pod_description = Some (s_"\
 For Fedora and Red Hat Enterprise Linux,
 this is removed from C<ifcfg-*> files.");
-  extra_args = [];
-  perform_on_filesystems = Some net_hostname_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some net_hostname_perform;
 }
 
-let () = register_operation net_hostname_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_net_hwaddr.ml b/sysprep/sysprep_operation_net_hwaddr.ml
index fe7fac0..6409767 100644
--- a/sysprep/sysprep_operation_net_hwaddr.ml
+++ b/sysprep/sysprep_operation_net_hwaddr.ml
@@ -43,16 +43,15 @@ let net_hwaddr_perform g root =
 
   | _ -> []
 
-let net_hwaddr_op = {
-  name = "net-hwaddr";
-  enabled_by_default = true;
-  heading = s_"Remove HWADDR (hard-coded MAC address) configuration";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "net-hwaddr";
+    enabled_by_default = true;
+    heading = s_"Remove HWADDR (hard-coded MAC address) configuration";
+    pod_description = Some (s_"\
 For Fedora and Red Hat Enterprise Linux,
 this is removed from C<ifcfg-*> files.");
-  extra_args = [];
-  perform_on_filesystems = Some net_hwaddr_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some net_hwaddr_perform;
 }
 
-let () = register_operation net_hwaddr_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_pacct_log.ml b/sysprep/sysprep_operation_pacct_log.ml
index f2d47c7..10dc25f 100644
--- a/sysprep/sysprep_operation_pacct_log.ml
+++ b/sysprep/sysprep_operation_pacct_log.ml
@@ -45,16 +45,15 @@ let pacct_log_perform g root =
 
   | _ -> []
 
-let pacct_log_op = {
-  name = "pacct-log";
-  enabled_by_default = true;
-  heading = s_"Remove the process accounting log files";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "pacct-log";
+    enabled_by_default = true;
+    heading = s_"Remove the process accounting log files";
+    pod_description = Some (s_"\
 The system wide process accounting will store to the pacct
 log files if the process accounting is on.");
-  extra_args = [];
-  perform_on_filesystems = Some pacct_log_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some pacct_log_perform;
 }
 
-let () = register_operation pacct_log_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_package_manager_cache.ml b/sysprep/sysprep_operation_package_manager_cache.ml
index 69c9245..586490e 100644
--- a/sysprep/sysprep_operation_package_manager_cache.ml
+++ b/sysprep/sysprep_operation_package_manager_cache.ml
@@ -18,6 +18,7 @@
 
 open Sysprep_operation
 open Common_gettext.Gettext
+open Common_utils
 
 module G = Guestfs
 
@@ -26,24 +27,22 @@ let package_manager_cache_perform g root =
   let cache_dirs =
     match packager with
     | "zypper" ->
-      Some (g#glob_expand "/var/cache/zypp*/*")
+      Some (Array.to_list (g#glob_expand "/var/cache/zypp*"))
     | "yum" ->
-      Some (g#glob_expand "/var/cache/yum/*")
+      Some [ "/var/cache/yum/" ]
     | "apt" ->
-      Some (g#glob_expand "/var/cache/apt/archives/*")
+      Some [ "/var/cache/apt/archives/" ]
     | _ -> None in
   match cache_dirs with
-  | Some dirs -> Array.iter g#rm_rf dirs; []
+  | Some dirs -> List.iter (rm_rf_only_files g) dirs; []
   | _ -> []
 
-let package_manager_cache_op = {
-  name = "package-manager-cache";
-  enabled_by_default = true;
-  heading = s_"Remove package manager cache";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some package_manager_cache_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "package-manager-cache";
+    enabled_by_default = true;
+    heading = s_"Remove package manager cache";
+    perform_on_filesystems = Some package_manager_cache_perform;
 }
 
-let () = register_operation package_manager_cache_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_pam_data.ml b/sysprep/sysprep_operation_pam_data.ml
index 931890d..82c88f9 100644
--- a/sysprep/sysprep_operation_pam_data.ml
+++ b/sysprep/sysprep_operation_pam_data.ml
@@ -40,14 +40,12 @@ let pam_data_perform g root =
   )
   else []
 
-let pam_data_op = {
-  name = "pam-data";
-  enabled_by_default = true;
-  heading = s_"Remove the PAM data in the guest";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some pam_data_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "pam-data";
+    enabled_by_default = true;
+    heading = s_"Remove the PAM data in the guest";
+    perform_on_filesystems = Some pam_data_perform;
 }
 
-let () = register_operation pam_data_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_password.ml b/sysprep/sysprep_operation_password.ml
new file mode 100644
index 0000000..a5c894f
--- /dev/null
+++ b/sysprep/sysprep_operation_password.ml
@@ -0,0 +1,162 @@
+(* 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.
+ *)
+
+open Printf
+
+open Sysprep_operation
+open Common_gettext.Gettext
+open Password
+
+module G = Guestfs
+
+let passwords = Hashtbl.create 13
+
+let rec set_root_password arg =
+  set_password "root" arg
+
+and set_user_password arg =
+  let i =
+    try String.index arg ':'
+    with Not_found ->
+      eprintf (f_"virt-sysprep: password: invalid --password format; see the man page.\n");
+      exit 1 in
+  let len = String.length arg in
+  set_password (String.sub arg 0 i) (String.sub arg (i+1) (len-(i+1)))
+
+and set_password user arg =
+  let pw = parse_selector ~prog arg in
+
+  if Hashtbl.mem passwords user then (
+    eprintf (f_"virt-sysprep: password: multiple --root-password/--password options set the password for user '%s' twice.\n") user;
+    exit 1
+  );
+
+  Hashtbl.replace passwords user pw
+
+let password_crypto : password_crypto option ref = ref None
+
+let set_password_crypto arg =
+  password_crypto := Some (password_crypto_of_string ~prog arg)
+
+let password_perform g root =
+  if Hashtbl.length passwords > 0 then (
+    let typ = g#inspect_get_type root in
+    match typ with
+    | "linux" ->
+      let password_crypto = !password_crypto in
+      set_linux_passwords ~prog ?password_crypto g root passwords;
+      [ `Created_files ]
+    | _ ->
+      eprintf (f_"virt-sysprep: cannot set passwords for %s guests.\n") typ;
+      exit 1
+  )
+  else []
+
+let op = {
+  defaults with
+    name = "password";
+
+    (* enabled_by_default because we only do anything if the
+     * --password or --root-password parameter is used.
+     *)
+    enabled_by_default = true;
+
+    heading = s_"Set root or user password";
+    pod_description = Some (s_"\
+Set root or another user's password.
+
+Use the I<--root-password> option to specify a replacement root
+password for the guest.  You can only use this option once.
+
+Use the I<--password> option to specify replacement user password(s).
+You can use this option as many times as you want.
+
+Use I<--password-crypto> to change the password encryption used.
+
+See L</OPTIONS> above for details of these options.
+
+This operation is enabled by default, but it only does something
+if there is at least one I<--root-password> or I<--password>
+argument given.");
+
+    pod_notes = Some (s_"\
+Currently this only works for glibc-based Linux guests that
+use shadow passwords.");
+
+    extra_args = [
+      { extra_argspec = "--root-password", Arg.String set_root_password, s_"..." ^ " " ^ s_"set root password (see man page)";
+        extra_pod_argval = Some "SELECTOR";
+        extra_pod_description = s_"\
+Set the root password.  See I<--password> above for the format
+of C<SELECTOR>."
+      };
+
+      { extra_argspec = "--password", Arg.String set_user_password, s_"..." ^ " " ^ s_"set user password (see man page)";
+        extra_pod_argval = Some "USERNAME:SELECTOR";
+        extra_pod_description = s_"\
+Set a user password.  The user must exist already (this option
+does I<not> create users).
+
+The I<--password> option takes C<USERNAME:SELECTOR>.  The
+I<--root-password> option takes just the C<SELECTOR>.  The
+format of the C<SELECTOR> is described below:
+
+=over 4
+
+=item B<--password USERNAME:file:FILENAME>
+
+=item B<--root-password file:FILENAME>
+
+Read the password from C<FILENAME>.  The whole
+first line of this file is the replacement password.
+Any other lines are ignored.  You should create the file
+with mode 0600 to ensure no one else can read it.
+
+=item B<--password USERNAME:password:PASSWORD>
+
+=item B<--root-password password:PASSWORD>
+
+Set the password to the literal string C<PASSWORD>.
+
+B<Note: this is not secure> since any user on the same machine
+can see the cleartext password using L<ps(1)>.
+
+=back"
+      };
+
+      { extra_argspec = "--password-crypto", Arg.String set_password_crypto, s_"md5|sha256|sha512" ^ " " ^ s_"set password crypto";
+        extra_pod_argval = Some "md5|sha256|sha512";
+        extra_pod_description = s_"\
+Set the password encryption to C<md5>, C<sha256> or C<sha512>.
+
+C<sha256> and C<sha512> require glibc E<ge> 2.7
+(check L<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."
+      }
+    ];
+
+    perform_on_filesystems = Some password_perform;
+}
+
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_puppet_data_log.ml b/sysprep/sysprep_operation_puppet_data_log.ml
index 7c4fd89..8094b83 100644
--- a/sysprep/sysprep_operation_puppet_data_log.ml
+++ b/sysprep/sysprep_operation_puppet_data_log.ml
@@ -40,14 +40,12 @@ let puppet_data_log_perform g root =
   )
   else []
 
-let puppet_data_log_op = {
-  name = "puppet-data-log";
-  enabled_by_default = true;
-  heading = s_"Remove the data and log files of puppet";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some puppet_data_log_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "puppet-data-log";
+    enabled_by_default = true;
+    heading = s_"Remove the data and log files of puppet";
+    perform_on_filesystems = Some puppet_data_log_perform;
 }
 
-let () = register_operation puppet_data_log_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_random_seed.ml b/sysprep/sysprep_operation_random_seed.ml
index 6beb16f..0e6a2a2 100644
--- a/sysprep/sysprep_operation_random_seed.ml
+++ b/sysprep/sysprep_operation_random_seed.ml
@@ -19,44 +19,24 @@
 open Sysprep_operation
 open Common_gettext.Gettext
 
-module G = Guestfs
+open Random_seed
 
-let random_seed_perform g root =
-  let typ = g#inspect_get_type root 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 (
-          (* Get 8 bytes of randomness from the host. *)
-          let chan = open_in "/dev/urandom" in
-          let buf = String.create 8 in
-          really_input chan buf 0 8;
-          close_in chan;
+module G = Guestfs
 
-          g#write file buf
-        )
-    ) files;
-    [ `Created_files ]
-  )
-  else []
+let random_seed_perform (g : Guestfs.guestfs) root =
+  if set_random_seed g root then [ `Created_files ] else []
 
-let random_seed_op = {
-  name = "random-seed";
-  enabled_by_default = true;
-  heading = s_"Generate random seed for guest";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "random-seed";
+    enabled_by_default = true;
+    heading = s_"Generate random seed for guest";
+    pod_description = Some (s_"\
 Write some random bytes from the host into the random seed file of the
 guest.
 
 See L</RANDOM SEED> below.");
-  extra_args = [];
-  perform_on_filesystems = Some random_seed_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some random_seed_perform;
 }
 
-let () = register_operation random_seed_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_rhn_systemid.ml b/sysprep/sysprep_operation_rhn_systemid.ml
index 6ccf5af..002aa5a 100644
--- a/sysprep/sysprep_operation_rhn_systemid.ml
+++ b/sysprep/sysprep_operation_rhn_systemid.ml
@@ -32,14 +32,12 @@ let rhn_systemid_perform g root =
     []
   | _ -> []
 
-let rhn_systemid_op = {
-  name = "rhn-systemid";
-  enabled_by_default = true;
-  heading = s_"Remove the RHN system ID";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some rhn_systemid_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "rhn-systemid";
+    enabled_by_default = true;
+    heading = s_"Remove the RHN system ID";
+    perform_on_filesystems = Some rhn_systemid_perform;
 }
 
-let () = register_operation rhn_systemid_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_rpm_db.ml b/sysprep/sysprep_operation_rpm_db.ml
index b347c53..551c266 100644
--- a/sysprep/sysprep_operation_rpm_db.ml
+++ b/sysprep/sysprep_operation_rpm_db.ml
@@ -34,16 +34,15 @@ let rpm_db_perform g root =
   )
   else []
 
-let rpm_db_op = {
-  name = "rpm-db";
-  enabled_by_default = true;
-  heading = s_"Remove host-specific RPM database files";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "rpm-db";
+    enabled_by_default = true;
+    heading = s_"Remove host-specific RPM database files";
+    pod_description = Some (s_"\
 Remove host-specific RPM database files and locks.  RPM will
 recreate these files automatically if needed.");
-  extra_args = [];
-  perform_on_filesystems = Some rpm_db_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some rpm_db_perform;
 }
 
-let () = register_operation rpm_db_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_samba_db_log.ml b/sysprep/sysprep_operation_samba_db_log.ml
index 2f6370a..8ed86ca 100644
--- a/sysprep/sysprep_operation_samba_db_log.ml
+++ b/sysprep/sysprep_operation_samba_db_log.ml
@@ -41,14 +41,12 @@ let samba_db_log_perform g root =
   )
   else []
 
-let samba_db_log_op = {
-  name = "samba-db-log";
-  enabled_by_default = true;
-  heading = s_"Remove the database and log files of Samba";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some samba_db_log_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "samba-db-log";
+    enabled_by_default = true;
+    heading = s_"Remove the database and log files of Samba";
+    perform_on_filesystems = Some samba_db_log_perform;
 }
 
-let () = register_operation samba_db_log_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_script.ml b/sysprep/sysprep_operation_script.ml
index 629d624..a3e8838 100644
--- a/sysprep/sysprep_operation_script.ml
+++ b/sysprep/sysprep_operation_script.ml
@@ -112,11 +112,12 @@ trap cleanup INT TERM QUIT EXIT ERR\n"
   );
   pid
 
-let script_op = {
-  name = "script";
-  enabled_by_default = true;
-  heading = s_"Run arbitrary scripts against the guest";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "script";
+    enabled_by_default = true;
+    heading = s_"Run arbitrary scripts against the guest";
+    pod_description = Some (s_"\
 The C<script> module lets you run arbitrary shell scripts or programs
 against the guest.
 
@@ -139,28 +140,32 @@ can choose a specific one by using the I<--scriptdir> parameter.
 B<Note:> This is different from I<--firstboot> scripts (which run
 in the context of the guest when it is booting first time).
 I<--script> scripts run on the host, not in the guest.");
-  extra_args = [
-    ("--scriptdir", Arg.String set_scriptdir, s_"dir" ^ " " ^ s_"Mount point on host"),
-    s_"\
+    extra_args = [
+      { extra_argspec = "--scriptdir", Arg.String set_scriptdir, s_"dir" ^ " " ^ s_"Mount point on host";
+        extra_pod_argval = Some "SCRIPTDIR";
+        extra_pod_description = s_"\
 The mount point (an empty directory on the host) used when
 the C<script> operation is enabled and one or more scripts
 are specified using I<--script> parameter(s).
 
-B<Note:> C<scriptdir> B<must> be an absolute path.
+B<Note:> C<SCRIPTDIR> B<must> be an absolute path.
 
 If I<--scriptdir> is not specified then a temporary mountpoint
-will be created.";
-    ("--script", Arg.String add_script, s_"script" ^ " " ^ s_"Script or program to run on guest"),
-    s_"\
-Run the named C<script> (a shell script or program) against the
+will be created."
+      };
+
+      { extra_argspec = "--script", Arg.String add_script, s_"script" ^ " " ^ s_"Script or program to run on guest";
+        extra_pod_argval = Some "SCRIPT";
+        extra_pod_description = s_"\
+Run the named C<SCRIPT> (a shell script or program) against the
 guest.  The script can be any program on the host.  The script's
 current directory will be the guest's root directory.
 
 B<Note:> If the script is not on the $PATH, then you must give
 the full absolute path to the script.";
-  ];
-  perform_on_filesystems = Some script_perform;
-  perform_on_devices = None;
+      }
+    ];
+    perform_on_filesystems = Some script_perform;
 }
 
-let () = register_operation script_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_smolt_uuid.ml b/sysprep/sysprep_operation_smolt_uuid.ml
index 295a260..bb560c4 100644
--- a/sysprep/sysprep_operation_smolt_uuid.ml
+++ b/sysprep/sysprep_operation_smolt_uuid.ml
@@ -35,14 +35,12 @@ let smolt_uuid_perform g root =
   )
   else []
 
-let smolt_uuid_op = {
-  name = "smolt-uuid";
-  enabled_by_default = true;
-  heading = s_"Remove the Smolt hardware UUID";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some smolt_uuid_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "smolt-uuid";
+    enabled_by_default = true;
+    heading = s_"Remove the Smolt hardware UUID";
+    perform_on_filesystems = Some smolt_uuid_perform;
 }
 
-let () = register_operation smolt_uuid_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_ssh_hostkeys.ml b/sysprep/sysprep_operation_ssh_hostkeys.ml
index a382c19..6ed7deb 100644
--- a/sysprep/sysprep_operation_ssh_hostkeys.ml
+++ b/sysprep/sysprep_operation_ssh_hostkeys.ml
@@ -30,11 +30,12 @@ let ssh_hostkeys_perform g root =
   )
   else []
 
-let ssh_hostkeys_op = {
-  name = "ssh-hostkeys";
-  enabled_by_default = true;
-  heading = s_"Remove the SSH host keys in the guest";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "ssh-hostkeys";
+    enabled_by_default = true;
+    heading = s_"Remove the SSH host keys in the guest";
+    pod_description = Some (s_"\
 The SSH host keys are regenerated (differently) next time the guest is
 booted.
 
@@ -45,9 +46,7 @@ you a stark warning about the host key changing:
  @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
  @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!");
-  extra_args = [];
-  perform_on_filesystems = Some ssh_hostkeys_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some ssh_hostkeys_perform;
 }
 
-let () = register_operation ssh_hostkeys_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_ssh_userdir.ml b/sysprep/sysprep_operation_ssh_userdir.ml
index 280e365..59cce9d 100644
--- a/sysprep/sysprep_operation_ssh_userdir.ml
+++ b/sysprep/sysprep_operation_ssh_userdir.ml
@@ -33,16 +33,19 @@ let ssh_userdir_perform g root =
   )
   else []
 
-let ssh_userdir_op = {
-  name = "ssh-userdir";
-  enabled_by_default = true;
-  heading = s_"Remove \".ssh\" directories in the guest";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "ssh-userdir";
+    enabled_by_default = true;
+    heading = s_"Remove \".ssh\" directories in the guest";
+    pod_description = Some (s_"\
 Remove the C<.ssh> directory of user \"root\" and any other
 users who have a C<.ssh> directory in their home directory.");
-  extra_args = [];
-  perform_on_filesystems = Some ssh_userdir_perform;
-  perform_on_devices = None;
+    pod_notes = Some (s_"\
+Currently this only looks in C</root> and C</home/*> for
+home directories, so users with home directories in other
+locations won't have the ssh files removed.");
+    perform_on_filesystems = Some ssh_userdir_perform;
 }
 
-let () = register_operation ssh_userdir_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_sssd_db_log.ml b/sysprep/sysprep_operation_sssd_db_log.ml
index 0e3c518..70c0c44 100644
--- a/sysprep/sysprep_operation_sssd_db_log.ml
+++ b/sysprep/sysprep_operation_sssd_db_log.ml
@@ -39,14 +39,12 @@ let sssd_db_log_perform g root =
   )
   else []
 
-let sssd_db_log_op = {
-  name = "sssd-db-log";
-  enabled_by_default = true;
-  heading = s_"Remove the database and log files of sssd";
-  pod_description = None;
-  extra_args = [];
-  perform_on_filesystems = Some sssd_db_log_perform;
-  perform_on_devices = None;
+let op = {
+  defaults with
+    name = "sssd-db-log";
+    enabled_by_default = true;
+    heading = s_"Remove the database and log files of sssd";
+    perform_on_filesystems = Some sssd_db_log_perform;
 }
 
-let () = register_operation sssd_db_log_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_tmp_files.ml b/sysprep/sysprep_operation_tmp_files.ml
new file mode 100644
index 0000000..a42ddbd
--- /dev/null
+++ b/sysprep/sysprep_operation_tmp_files.ml
@@ -0,0 +1,52 @@
+(* virt-sysprep
+ * Copyright (C) 2013 Fujitsu Limited.
+ *
+ * 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 Sysprep_operation
+open Common_gettext.Gettext
+
+module G = Guestfs
+
+let tmp_files_perform g root =
+  let typ = g#inspect_get_type root in
+  if typ <> "windows" then (
+    let paths = [ "/tmp/*";
+                  "/var/tmp/*"; ] in
+    List.iter (
+      fun path ->
+        let files = g#glob_expand path in
+        Array.iter (
+          fun file ->
+            g#rm_rf file;
+        ) files;
+    ) paths;
+
+    []
+  )
+  else []
+
+let op = {
+  defaults with
+    name = "tmp-files";
+    enabled_by_default = true;
+    heading = s_"Remove temporary files";
+    pod_description = Some (s_"\
+This removes temporary files under C</tmp> and C</var/tmp>.");
+    perform_on_filesystems = Some tmp_files_perform;
+}
+
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_udev_persistent_net.ml b/sysprep/sysprep_operation_udev_persistent_net.ml
index 652b9ab..235ef98 100644
--- a/sysprep/sysprep_operation_udev_persistent_net.ml
+++ b/sysprep/sysprep_operation_udev_persistent_net.ml
@@ -30,11 +30,12 @@ let udev_persistent_net_perform g root =
   )
   else []
 
-let udev_persistent_net_op = {
-  name = "udev-persistent-net";
-  enabled_by_default = true;
-  heading = s_"Remove udev persistent net rules";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "udev-persistent-net";
+    enabled_by_default = true;
+    heading = s_"Remove udev persistent net rules";
+    pod_description = Some (s_"\
 Remove udev persistent net rules which map the guest's existing MAC
 address to a fixed ethernet device (eg. eth0).
 
@@ -42,9 +43,7 @@ After a guest is cloned, the MAC address usually changes.  Since the
 old MAC address occupies the old name (eg. eth0), this means the fresh
 MAC address is assigned to a new name (eg. eth1) and this is usually
 undesirable.  Erasing the udev persistent net rules avoids this.");
-  extra_args = [];
-  perform_on_filesystems = Some udev_persistent_net_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some udev_persistent_net_perform;
 }
 
-let () = register_operation udev_persistent_net_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_user_account.ml b/sysprep/sysprep_operation_user_account.ml
index c97b9f4..fc39bc8 100644
--- a/sysprep/sysprep_operation_user_account.ml
+++ b/sysprep/sysprep_operation_user_account.ml
@@ -58,16 +58,19 @@ let user_account_perform g root =
   )
   else []
 
-let user_account_op = {
-  name = "user-account";
-  enabled_by_default = false;
-  heading = s_"Remove the user accounts in the guest";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "user-account";
+    enabled_by_default = false;
+    heading = s_"Remove the user accounts in the guest";
+    pod_description = Some (s_"\
 Remove all the user accounts and their home directories.
 The \"root\" account is not removed.");
-  extra_args = [];
-  perform_on_filesystems = Some user_account_perform;
-  perform_on_devices = None;
+    pod_notes = Some (s_"\
+Currently this does not remove the user accounts from
+C</etc/shadow>.  This is because there is no lens for
+the shadow password file in Augeas.");
+    perform_on_filesystems = Some user_account_perform;
 }
 
-let () = register_operation user_account_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_utmp.ml b/sysprep/sysprep_operation_utmp.ml
index 3e1cab5..be73e16 100644
--- a/sysprep/sysprep_operation_utmp.ml
+++ b/sysprep/sysprep_operation_utmp.ml
@@ -29,17 +29,16 @@ let utmp_perform g root =
   );
   []
 
-let utmp_op = {
-  name = "utmp";
-  enabled_by_default = true;
-  heading = s_"Remove the utmp file";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "utmp";
+    enabled_by_default = true;
+    heading = s_"Remove the utmp file";
+    pod_description = Some (s_"\
 This file records who is currently logged in on a machine.  In modern
 Linux distros it is stored in a ramdisk and hence not part of the
 virtual machine's disk, but it was stored on disk in older distros.");
-  extra_args = [];
-  perform_on_filesystems = Some utmp_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some utmp_perform;
 }
 
-let () = register_operation utmp_op
+let () = register_operation op
diff --git a/sysprep/sysprep_operation_yum_uuid.ml b/sysprep/sysprep_operation_yum_uuid.ml
index 8a864b1..8ffe664 100644
--- a/sysprep/sysprep_operation_yum_uuid.ml
+++ b/sysprep/sysprep_operation_yum_uuid.ml
@@ -29,16 +29,15 @@ let yum_uuid_perform g root =
   )
   else []
 
-let yum_uuid_op = {
-  name = "yum-uuid";
-  enabled_by_default = true;
-  heading = s_"Remove the yum UUID";
-  pod_description = Some (s_"\
+let op = {
+  defaults with
+    name = "yum-uuid";
+    enabled_by_default = true;
+    heading = s_"Remove the yum UUID";
+    pod_description = Some (s_"\
 Yum creates a fresh UUID the next time it runs when it notices that the
 original UUID has been erased.");
-  extra_args = [];
-  perform_on_filesystems = Some yum_uuid_perform;
-  perform_on_devices = None;
+    perform_on_filesystems = Some yum_uuid_perform;
 }
 
-let () = register_operation yum_uuid_op
+let () = register_operation op
diff --git a/tests/guests/guest-aux/make-debian-img.sh b/tests/guests/guest-aux/make-debian-img.sh
index 76d81d3..b5b0961 100755
--- a/tests/guests/guest-aux/make-debian-img.sh
+++ b/tests/guests/guest-aux/make-debian-img.sh
@@ -75,6 +75,7 @@ mount /dev/debian/home /home
 mkdir /bin
 mkdir /etc
 mkdir-p /var/lib/dpkg
+mkdir /var/lib/urandom
 
 upload fstab.tmp.$$ /etc/fstab
 write /etc/debian_version "5.0.1"
diff --git a/tests/guests/guest-aux/make-ubuntu-img.sh b/tests/guests/guest-aux/make-ubuntu-img.sh
index eec80f9..445141f 100755
--- a/tests/guests/guest-aux/make-ubuntu-img.sh
+++ b/tests/guests/guest-aux/make-ubuntu-img.sh
@@ -66,6 +66,7 @@ mkdir /etc
 mkdir /home
 mkdir /usr
 mkdir-p /var/lib/dpkg
+mkdir /var/lib/urandom
 
 upload fstab.tmp.$$ /etc/fstab
 write /etc/debian_version "5.0.1"
-- 
2.7.4

