[Libguestfs] [PATCH] Rewrite virt-sysprep.

Richard W.M. Jones rjones at redhat.com
Sat Mar 31 18:25:20 UTC 2012


From: "Richard W.M. Jones" <rjones at redhat.com>

---
 .gitignore                            |    9 +-
 Makefile.am                           |    7 +-
 clone/Makefile.am                     |   51 ----
 clone/test-virt-sysprep.sh            |   49 ----
 clone/virt-sysprep.in                 |  408 --------------------------
 clone/virt-sysprep.pod                |  521 ---------------------------------
 configure.ac                          |    4 +-
 contrib/make-check-on-installed.pl    |    2 +-
 src/guestfs.pod                       |    9 +-
 sysprep/Makefile.am                   |  126 ++++++++
 sysprep/main.ml                       |  232 +++++++++++++++
 sysprep/sysprep_operation.ml          |  164 +++++++++++
 sysprep/sysprep_operation.mli         |   96 ++++++
 sysprep/sysprep_operation_hostname.ml |   69 +++++
 sysprep/sysprep_operation_utmp.ml     |   43 +++
 sysprep/test-virt-sysprep.sh          |   26 ++
 sysprep/utils.ml                      |   69 +++++
 sysprep/virt-sysprep.pod              |  419 ++++++++++++++++++++++++++
 tests/extra/Makefile.am               |    4 +-
 19 files changed, 1258 insertions(+), 1050 deletions(-)
 delete mode 100644 clone/Makefile.am
 delete mode 100755 clone/test-virt-sysprep.sh
 delete mode 100644 clone/virt-sysprep.in
 delete mode 100755 clone/virt-sysprep.pod
 create mode 100644 sysprep/Makefile.am
 create mode 100644 sysprep/main.ml
 create mode 100644 sysprep/sysprep_operation.ml
 create mode 100644 sysprep/sysprep_operation.mli
 create mode 100644 sysprep/sysprep_operation_hostname.ml
 create mode 100644 sysprep/sysprep_operation_utmp.ml
 create mode 100755 sysprep/test-virt-sysprep.sh
 create mode 100644 sysprep/utils.ml
 create mode 100755 sysprep/virt-sysprep.pod

diff --git a/.gitignore b/.gitignore
index 14d5c75..337ff75 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,9 +26,6 @@ cat/virt-ls
 cat/virt-ls.1
 ChangeLog
 *.class
-clone/stamp-virt-sysprep.pod
-clone/virt-sysprep
-clone/virt-sysprep.1
 *.cma
 *.cmi
 *.cmo
@@ -345,6 +342,12 @@ src/libguestfs.syms
 src/.libs/libguestfs.so
 src/stamp-guestfs.pod
 stamp-h1
+sysprep/.depend
+sysprep/stamp-virt-sysprep.pod
+sysprep/sysprep-extra-options.pod
+sysprep/sysprep-operations.pod
+sysprep/virt-sysprep
+sysprep/virt-sysprep.1
 *.swp
 test1.img
 test2.img
diff --git a/Makefile.am b/Makefile.am
index de2d4ca..5f104fa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -91,7 +91,7 @@ SUBDIRS += csharp
 
 # virt-resize (new version) and virt-sparsify are written in OCaml.
 if HAVE_OCAML
-SUBDIRS += resize sparsify
+SUBDIRS += resize sparsify sysprep
 endif
 
 # Perl tools.
@@ -104,11 +104,6 @@ if HAVE_FUSE
 SUBDIRS += fuse
 endif
 
-# virt-tools in shell.  This uses guestmount and virt-inspector.
-if HAVE_FUSE
-SUBDIRS += clone
-endif
-
 # po-docs must come after tools, inspector.
 if HAVE_PO4A
 SUBDIRS += po-docs
diff --git a/clone/Makefile.am b/clone/Makefile.am
deleted file mode 100644
index 4d586c6..0000000
--- a/clone/Makefile.am
+++ /dev/null
@@ -1,51 +0,0 @@
-# libguestfs cloning tools
-# 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 $(top_srcdir)/subdir-rules.mk
-
-EXTRA_DIST = \
-	test-virt-sysprep.sh \
-	virt-sysprep.pod
-
-CLEANFILES = stamp-virt-sysprep.pod
-
-bin_SCRIPTS = virt-sysprep
-
-# Manual pages and HTML files for the website.
-man_MANS = virt-sysprep.1
-noinst_DATA = $(top_builddir)/html/virt-sysprep.1.html
-
-virt-sysprep.1 $(top_builddir)/html/virt-sysprep.1.html: stamp-virt-sysprep.pod
-
-stamp-virt-sysprep.pod: virt-sysprep.pod
-	$(top_builddir)/podwrapper.sh \
-	  --man virt-sysprep.1 \
-	  --html $(top_builddir)/html/virt-sysprep.1.html \
-	  $<
-	touch $@
-
-# Tests.
-
-random_val := $(shell awk 'BEGIN{srand(); print 1+int(255*rand())}' < /dev/null)
-
-TESTS_ENVIRONMENT = \
-	MALLOC_PERTURB_=$(random_val) \
-	$(top_builddir)/run
-
-if ENABLE_APPLIANCE
-TESTS = test-virt-sysprep.sh
-endif ENABLE_APPLIANCE
diff --git a/clone/test-virt-sysprep.sh b/clone/test-virt-sysprep.sh
deleted file mode 100755
index 3217389..0000000
--- a/clone/test-virt-sysprep.sh
+++ /dev/null
@@ -1,49 +0,0 @@
-#!/bin/bash -
-# libguestfs virt-sysprep test script
-# 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.
-
-export LANG=C
-set -e
-
-if [ ! -w /dev/fuse ]; then
-    echo "SKIPPING virt-sysprep test, because there is no /dev/fuse."
-    exit 0
-fi
-
-rm -f test.img guestfish
-
-qemu-img create -f qcow2 -o backing_file=../tests/guests/fedora.img test.img
-
-# Provide alternate 'virt-inspector' and 'guestmount' binaries
-# that run the just-built programs.
-
-cat <<'EOF' > virt-inspector
-#!/bin/sh -
-../run ../inspector/virt-inspector "$@"
-EOF
-chmod +x virt-inspector
-cat <<'EOF' > guestmount
-#!/bin/sh -
-../run ../fuse/guestmount "$@"
-EOF
-chmod +x guestmount
-
-PATH=.:$PATH
-
-./virt-sysprep -a test.img
-
-rm -f test.img virt-inspector guestmount
diff --git a/clone/virt-sysprep.in b/clone/virt-sysprep.in
deleted file mode 100644
index d505532..0000000
--- a/clone/virt-sysprep.in
+++ /dev/null
@@ -1,408 +0,0 @@
-#!/bin/bash -
-# @configure_input@
-# libguestfs virt-sysprep tool
-# 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.
-
-unset CDPATH
-program="virt-sysprep"
-version="@PACKAGE_VERSION@"
-
-# Uncomment this to see every shell command that is executed.
-#set -x
-
-TEMP=`getopt \
-        -o a:c:d:vVx \
-        --long help,add:,connect:,domain:,enable:,format::,hostname:,list-operations,selinux-relabel,no-selinux-relabel,verbose,version \
-        -n $program -- "$@"`
-if [ $? != 0 ]; then
-    echo "$program: problem parsing the command line arguments"
-    exit 1
-fi
-eval set -- "$TEMP"
-
-# This array accumulates the arguments we pass through to guestmount.
-declare -a params
-i=0
-
-verbose=
-add_params=0
-enable=
-hostname_param=localhost.localdomain
-selinux_relabel=auto
-
-usage ()
-{
-    echo "Usage:"
-    echo "  $program [--options] -d domname"
-    echo "  $program [--options] -a disk.img [-a disk.img ...]"
-    echo
-    echo "Read $program(1) man page for more information."
-    echo
-    echo "NOTE: $program modifies the guest or disk image *in place*."
-    exit $1
-}
-
-while true; do
-    case "$1" in
-        -a|--add)
-            params[i++]="-a"
-            params[i++]="$2"
-            ((add_params++))
-            shift 2;;
-        -c|--connect)
-            params[i++]="-c"
-            params[i++]="$2"
-            shift 2;;
-        -d|--domain)
-            params[i++]="-d"
-            params[i++]="$2"
-            ((add_params++))
-            shift 2;;
-        --enable)
-            if [ -n "$enable" ]; then
-                echo "error: --enable option can only be given once"
-                exit 1
-            fi
-            enable="$2"
-            shift 2;;
-        --format)
-            if [ -n "$2" ]; then
-                params[i++]="--format=$2"
-            else
-                params[i++]="--format"
-            fi
-            shift 2;;
-        --help)
-            usage 0;;
-        --hostname)
-            hostname_param="$2"
-            shift 2;;
-        --list-operations)
-            enable=list
-            shift;;
-        --selinux-relabel)
-            selinux_relabel=yes
-            shift;;
-        --no-selinux-relabel)
-            selinux_relabel=no
-            shift;;
-        -v|--verbose)
-            params[i++]="-v"
-            verbose=yes
-            shift;;
-        -V|--version)
-            echo "$program $version"
-            exit 0;;
-        -x)
-            # Can't pass the -x option directly to guestmount because
-            # that stops guestmount from forking, which means we can't
-            # coordinate with guestmount when it has finished
-            # initializing.  So instead set just the underlying option
-            # in libguestfs by exporting LIBGUESTFS_TRACE.
-            # Unfortunately (a) this omits FUSE calls, but don't worry
-            # about that for now, and more importantly (b) trace
-            # messages disappear into never-never land after the fork.
-            export LIBGUESTFS_TRACE=1
-            shift;;
-        --)
-            shift
-            break;;
-        *)
-            echo "Internal error!"
-            exit 1;;
-    esac
-done
-
-# Different sysprep operations that can be enabled.  Default is to
-# enable all of these, although some of them are only done on certain
-# guest types (see details below).
-if [ -z "$enable" ]; then
-    cron_spool=yes
-    dhcp_client_state=yes
-    dhcp_server_state=yes
-    hostname=yes
-    logfiles=yes
-    mail_spool=yes
-    net_hwaddr=yes
-    random_seed=yes
-    rhn_systemid=yes
-    smolt_uuid=yes
-    ssh_hostkeys=yes
-    udev_persistent_net=yes
-    utmp=yes
-    yum_uuid=yes
-elif [ "$enable" = "list" ]; then
-    echo "cron-spool"
-    echo "dhcp-client-state"
-    echo "dhcp-server-state"
-    echo "hostname"
-    echo "logfiles"
-    echo "mail-spool"
-    echo "net-hwaddr"
-    echo "random-seed"
-    echo "rhn-systemid"
-    echo "smolt-uuid"
-    echo "ssh-hostkeys"
-    echo "udev-persistent-net"
-    echo "utmp"
-    echo "yum-uuid"
-    exit 0
-else
-    for opt in $(echo "$enable" | sed 's/,/ /g'); do
-        case "$opt" in
-            cron-spool)            cron_spool=yes ;;
-            dhcp-client-state)     dhcp_client_state=yes ;;
-            dhcp-server-state)     dhcp_server_state=yes ;;
-            hostname)              hostname=yes ;;
-            logfiles)              logfiles=yes ;;
-            mail-spool)            mail_spool=yes ;;
-            net-hwaddr)            net_hwaddr=yes ;;
-            random-seed)           random_seed=yes ;;
-            rhn-systemid)          rhn_systemid=yes ;;
-            smolt-uuid)            smolt_uuid=yes ;;
-            ssh-hostkeys)          ssh_hostkeys=yes ;;
-            udev-persistent-net)   udev_persistent_net=yes ;;
-            utmp)                  utmp=yes ;;
-            yum-uuid)              yum_uuid=yes ;;
-            *)
-                echo "error: unknown --enable feature: $opt"
-                exit 1
-        esac
-    done
-fi
-
-# Make sure there were no extra parameters on the command line.
-if [ $# -gt 0 ]; then
-    echo "error: $program: extra parameters on the command line"
-    echo
-    usage 1
-fi
-
-# Did the user specify at least one -a or -d option?
-if [ $add_params -eq 0 ]; then
-    echo "error: $program: you need at least one -a or -d option"
-    echo
-    usage 1
-fi
-
-# end of command line parsing
-#----------------------------------------------------------------------
-
-set -e
-
-if [ "$verbose" = "yes" ]; then
-    echo params: "${params[@]}"
-fi
-
-# Create a temporary directory for general purpose use during operations.
-tmpdir="$(mktemp -d)"
-
-cleanup ()
-{
-    if [ -d $tmpdir/mnt ]; then
-        fusermount -u $tmpdir/mnt >/dev/null 2>&1 ||:
-    fi
-    rm -rf $tmpdir ||:
-}
-trap cleanup EXIT ERR
-
-# Run virt-inspector and grab inspection information about this guest.
-virt-inspector "${params[@]}" > $tmpdir/xml
-virt-inspector --xpath \
-    "string(/operatingsystems/operatingsystem[position()=1]/name)" \
-    < $tmpdir/xml > $tmpdir/type
-virt-inspector --xpath \
-    "string(/operatingsystems/operatingsystem[position()=1]/distro)" \
-    < $tmpdir/xml > $tmpdir/distro ||:
-virt-inspector --xpath \
-    "string(/operatingsystems/operatingsystem[position()=1]/package_format)" \
-    < $tmpdir/xml > $tmpdir/package_format ||:
-virt-inspector --xpath \
-    "string(/operatingsystems/operatingsystem[position()=1]/package_management)" \
-    < $tmpdir/xml > $tmpdir/package_management ||:
-
-type="$(cat $tmpdir/type)"
-distro="$(cat $tmpdir/distro)"
-package_format="$(cat $tmpdir/package_format)"
-package_management="$(cat $tmpdir/package_management)"
-
-# Mount the disk.
-mkdir $tmpdir/mnt
-guestmount --rw -i "${params[@]}" $tmpdir/mnt
-
-mnt="$tmpdir/mnt"
-
-#----------------------------------------------------------------------
-# The sysprep operations.
-
-if [ "$cron_spool" = "yes" ]; then
-    rm -rf $mnt/var/spool/cron/*
-fi
-
-if [ "$dhcp_client_state" = "yes" ]; then
-    case "$type" in
-        linux)
-            rm -rf $mnt/var/lib/dhclient/*
-            # RHEL 3:
-            rm -rf $mnt/var/lib/dhcp/*
-            ;;
-    esac
-fi
-
-if [ "$dhcp_server_state" = "yes" ]; then
-    case "$type" in
-        linux)
-            rm -rf $mnt/var/lib/dhcpd/*
-            ;;
-    esac
-fi
-
-if [ "$hostname" = "yes" ]; then
-    case "$type/$distro" in
-        linux/fedora|linux/rhel)
-            echo "HOSTNAME=$hostname_param" > $mnt/etc/sysconfig/network.new
-            sed '/^HOSTNAME=/d' < $mnt/etc/sysconfig/network >> $mnt/etc/sysconfig/network.new
-            mv -f $mnt/etc/sysconfig/network.new $mnt/etc/sysconfig/network
-            created_files=yes
-            ;;
-        linux/debian|linux/ubuntu)
-            echo "$hostname_param" > $mnt/etc/hostname
-            created_files=yes
-            ;;
-    esac
-fi
-
-if [ "$logfiles" = "yes" ]; then
-    case "$type" in
-        linux)
-            rm -rf $mnt/var/log/*.log*
-            rm -rf $mnt/var/log/audit/*
-            rm -rf $mnt/var/log/btmp*
-            rm -rf $mnt/var/log/cron*
-            rm -rf $mnt/var/log/dmesg*
-            rm -rf $mnt/var/log/lastlog*
-            rm -rf $mnt/var/log/maillog*
-            rm -rf $mnt/var/log/mail/*
-            rm -rf $mnt/var/log/messages*
-            rm -rf $mnt/var/log/secure*
-            rm -rf $mnt/var/log/spooler*
-            rm -rf $mnt/var/log/tallylog*
-            rm -rf $mnt/var/log/wtmp*
-            ;;
-    esac
-fi
-
-if [ "$mail_spool" = "yes" ]; then
-    rm -rf $mnt/var/spool/mail/*
-    rm -rf $mnt/var/mail/*
-fi
-
-if [ "$net_hwaddr" = "yes" ]; then
-    case "$type/$distro" in
-        linux/fedora|linux/rhel)
-            if [ -d $mnt/etc/sysconfig/network-scripts ]; then
-                rm_hwaddr ()
-                {
-                    sed '/^HWADDR=/d' < "$1" > "$1.new"
-                    mv -f "$1.new" "$1"
-                }
-                export -f rm_hwaddr
-                find $mnt/etc/sysconfig/network-scripts \
-                    -name 'ifcfg-*' -type f \
-                    -exec bash -c 'rm_hwaddr "$0"' {} \;
-                created_files=yes
-            fi
-            ;;
-    esac
-fi
-
-if [ "$random_seed" = "yes" -a "$type" = "linux" ]; then
-    f=
-    if [ -f $mnt/var/lib/random-seed ]; then
-        # Fedora
-        f=$mnt/var/lib/random-seed
-    elif [ -f $mnt/var/lib/urandom/random-seed ]; then
-        # Debian
-        f=$mnt/var/lib/urandom/random-seed
-    fi
-    if [ -n "$f" ]; then
-        dd if=/dev/urandom of="$f" bs=8 count=1 conv=nocreat,notrunc 2>/dev/null
-    fi
-fi
-
-if [ "$rhn_systemid" = "yes" -a "$type/$distro" = "linux/rhel" ]; then
-    rm -f $mnt/etc/sysconfig/rhn/systemid
-fi
-
-if [ "$smolt_uuid" = "yes" -a "$type" = "linux" ]; then
-    rm -f $mnt/etc/sysconfig/hw-uuid
-    rm -f $mnt/etc/smolt/uuid
-    rm -f $mnt/etc/smolt/hw-uuid
-fi
-
-if [ "$ssh_hostkeys" = "yes" -a "$type" != "windows" ]; then
-    rm -rf $mnt/etc/ssh/*_host_*
-fi
-
-if [ "$udev_persistent_net" = "yes" -a "$type" = "linux" ]; then
-    rm -f $mnt/etc/udev/rules.d/70-persistent-net.rules
-fi
-
-if [ "$utmp" = "yes" -a "$type" != "windows" ]; then
-    rm -f $mnt/var/run/utmp
-fi
-
-if [ "$yum_uuid" = "yes" -a "$package_management" = "yum" ]; then
-    rm -f $mnt/var/lib/yum/uuid
-fi
-
-#----------------------------------------------------------------------
-# Clean up and close down.
-
-# If we created any new files and the guest uses SELinux, then we have
-# to relabel the filesystem on boot.  Could do with a better way to
-# test "guest uses SELinux" (XXX).
-case "$selinux_relabel/$created_files" in
-    yes/*)
-        touch $mnt/.autorelabel;;
-    auto/yes)
-        case "$type/$distro" in
-            linux/fedora|linux/rhel|linux/centos|linux/scientificlinux|linux/redhat-based)
-                touch $mnt/.autorelabel
-                ;;
-        esac
-        ;;
-esac
-
-sync
-
-# Unfortunately various unwanted processes jump into mountpoints.
-# tracker-miner-fs is the latest, previously it was something called
-# gvfs-gdu-volume-monitor.  Therefore if the mountpoint is busy, try
-# to unmount it a few times.  XXX
-count=10
-while ! fusermount -u $tmpdir/mnt && [ $count -gt 0 ]; do
-    sleep 1
-    ((count--))
-done
-if [ $count -eq 0 ]; then exit 1; fi
-
-rm -rf $tmpdir
-
-trap - EXIT ERR
-
-exit 0
diff --git a/clone/virt-sysprep.pod b/clone/virt-sysprep.pod
deleted file mode 100755
index 5cab3eb..0000000
--- a/clone/virt-sysprep.pod
+++ /dev/null
@@ -1,521 +0,0 @@
-=encoding utf8
-
-=head1 NAME
-
-virt-sysprep - Reset or unconfigure a virtual machine so clones can be made
-
-=head1 SYNOPSIS
-
- virt-sysprep [--options] -d domname
-
- virt-sysprep [--options] -a disk.img [-a disk.img ...]
-
-=head1 DESCRIPTION
-
-Virt-sysprep "resets" or "unconfigures" a virtual machine so that
-clones can be made from it.  Steps in this process include removing
-SSH host keys, removing persistent network MAC configuration, and
-removing user accounts.  Each step can be enabled or disabled as
-required.
-
-Virt-sysprep is a simple shell script, allowing easy inspection or
-customization by the system administrator.
-
-Virt-sysprep modifies the guest or disk image I<in place>.  The guest
-must be shut down.  If you want to preserve the existing contents of
-the guest, you I<must copy or clone the disk first>.
-See L</COPYING AND CLONING> below.
-
-You do I<not> need to run virt-sysprep as root.  In fact we'd
-generally recommend that you don't.  The time you might want to run it
-as root is when you need root in order to access the disk image, but
-even in this case it would be better to change the permissions on the
-disk image to be writable as the non-root user running virt-sysprep.
-
-"Sysprep" stands for "system preparation" tool.  The name comes from
-the Microsoft program C<sysprep.exe> which is used to unconfigure
-Windows machines in preparation for cloning them.  Having said that,
-virt-sysprep does I<not> currently work on Microsoft Windows guests.
-We plan to support Windows sysprepping in a future version, and we
-already have code to do it.
-
-=head1 OPTIONS
-
-=over 4
-
-=item B<--help>
-
-Display brief help.
-
-=item B<-a> file
-
-=item B<--add> file
-
-Add I<file> which should be a disk image from a virtual machine.
-
-The format of the disk image is auto-detected.  To override this and
-force a particular format use the I<--format=..> option.
-
-=item B<-c> URI
-
-=item B<--connect> URI
-
-If using libvirt, connect to the given I<URI>.  If omitted, then we
-connect to the default libvirt hypervisor.
-
-If you specify guest block devices directly (I<-a>), then libvirt is
-not used at all.
-
-=item B<-d> guest
-
-=item B<--domain> guest
-
-Add all the disks from the named libvirt guest.  Domain UUIDs can be
-used instead of names.
-
-=item B<--enable=...>
-
-Choose which sysprep operations to perform.  Give a comma-separated
-list of operations, for example:
-
- --enable=ssh-hostkeys,udev-persistent-net
-
-would enable ONLY C<ssh-hostkeys> and C<udev-persistent-net> operations.
-
-If the I<--enable> option is not given, then we default to trying all
-possible sysprep operations.  But some sysprep operations are skipped
-for some guest types.
-
-Use I<--list-operations> to list operations supported by a particular
-version of virt-sysprep.
-
-See L</OPERATIONS> below for a list and an explanation of each
-operation.
-
-=item B<--format=raw|qcow2|..>
-
-=item B<--format>
-
-The default for the I<-a> option is to auto-detect the format of the
-disk image.  Using this forces the disk format for I<-a> options which
-follow on the command line.  Using I<--format> with no argument
-switches back to auto-detection for subsequent I<-a> options.
-
-For example:
-
- virt-sysprep --format=raw -a disk.img
-
-forces raw format (no auto-detection) for C<disk.img>.
-
- virt-sysprep --format=raw -a disk.img --format -a another.img
-
-forces raw format (no auto-detection) for C<disk.img> and reverts to
-auto-detection for C<another.img>.
-
-If you have untrusted raw-format guest disk images, you should use
-this option to specify the disk format.  This avoids a possible
-security problem with malicious guests (CVE-2010-3851).
-
-=item B<--hostname> newhostname
-
-Change the hostname.  See the L</hostname> operation below.
-If not given, defaults to C<localhost.localdomain>.
-
-=item B<--list-operations>
-
-List the operations supported by the virt-sysprep program.
-
-=item B<--selinux-relabel>
-
-=item B<--no-selinux-relabel>
-
-I<--selinux-relabel> forces SELinux relabelling next time the guest
-boots.  I<--no-selinux-relabel> disables relabelling.
-
-The default is to try to detect if SELinux relabelling is required.
-See L</SELINUX RELABELLING> below for more details.
-
-=item B<-v>
-
-=item B<--verbose>
-
-Enable verbose messages for debugging.
-
-=item B<-V>
-
-=item B<--version>
-
-Display version number and exit.
-
-=item B<-x>
-
-Enable tracing of libguestfs API calls.
-
-=back
-
-=head1 OPERATIONS
-
-If the I<--enable> option is I<not> given, then
-I<all sysprep operations are enabled>, although some are skipped
-depending on the type of guest.
-
-Operations can be individually enabled using the I<--enable> option.
-Use a comma-separated list, for example:
-
- virt-sysprep --enable=ssh-hostkeys,udev-persistent-net [etc..]
-
-To list the operations supported by the current version of
-virt-sysprep, use I<--list-operations>.
-
-Future versions of virt-sysprep may add more operations.  If you are
-using virt-sysprep and want predictable behaviour, specify only the
-operations that you want to have enabled.
-
-=head2 cron-spool
-
-Remove user at-jobs and cron-jobs.
-
-=head2 dhcp-client-state
-
-Remove DHCP client leases.
-
-=head2 dhcp-server-state
-
-Remove DHCP server leases.
-
-=head2 hostname
-
-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>.
-
-=head2 logfiles
-
-Remove many log files.
-
-=head2 mail-spool
-
-Remove email from the local mail spool directory.
-
-=head2 net-hwaddr
-
-Remove HWADDR (hard-coded MAC address) configuration.  For Fedora and
-Red Hat Enterprise Linux, this is removed from C<ifcfg-*> files.
-
-=head2 random-seed
-
-Write some random bytes from the host into the random seed file of
-the guest.
-
-See L</RANDOM SEED> below.
-
-=head2 rhn-systemid
-
-Remove the RHN system ID.
-
-=head2 smolt-uuid
-
-Remove the Smolt hardware UUID.
-
-=head2 ssh-hostkeys
-
-Remove the SSH host keys in the guest.
-
-The SSH host keys are regenerated (differently) next time the guest is
-booted.
-
-If, after cloning, the guest gets the same IP address, ssh will give
-you a stark warning about the host key changing:
-
- @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
- @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
- @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
- IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
-
-=head2 udev-persistent-net
-
-Remove udev persistent net rules which map the guest's existing MAC
-address to a fixed ethernet device (eg. eth0).
-
-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.
-
-=head2 utmp
-
-Remove the utmp file.
-
-This 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.
-
-=head2 yum-uuid
-
-Remove the yum UUID.
-
-Yum creates a fresh UUID the next time it runs when it notices that
-the original UUID has been erased.
-
-=head1 COPYING AND CLONING
-
-Virt-sysprep can be used as part of a process of cloning guests, or to
-prepare a template from which guests can be cloned.  There are many
-different ways to achieve this using the virt tools, and this section
-is just an introduction.
-
-A virtual machine (when switched off) consists of two parts:
-
-=over 4
-
-=item I<configuration>
-
-The configuration or description of the guest.  eg. The libvirt
-XML (see C<virsh dumpxml>), the running configuration of the guest,
-or another external format like OVF.
-
-Some configuration items that might need to be changed:
-
-=over 4
-
-=item *
-
-name
-
-=item *
-
-UUID
-
-=item *
-
-path to block device(s)
-
-=item *
-
-network card MAC address
-
-=back
-
-=item I<block device(s)>
-
-One or more hard disk images, themselves containing files,
-directories, applications, kernels, configuration, etc.
-
-Some things inside the block devices that might need to be changed:
-
-=over 4
-
-=item *
-
-hostname and other net configuration
-
-=item *
-
-UUID
-
-=item *
-
-SSH host keys
-
-=item *
-
-Windows unique security ID (SID)
-
-=item *
-
-Puppet registration
-
-=back
-
-=back
-
-=head2 COPYING THE BLOCK DEVICE
-
-Starting with an original guest, you probably wish to copy the guest
-block device and its configuration to make a template.  Then once you
-are happy with the template, you will want to make many clones from
-it.
-
-                        virt-sysprep
-                             |
-                             v
- original guest --------> template ---------->
-                                      \------> cloned
-                                       \-----> guests
-                                        \---->
-
-You can, of course, just copy the block device on the host using
-L<cp(1)> or L<dd(1)>.
-
-                   dd                 dd
- original guest --------> template ---------->
-                                      \------> cloned
-                                       \-----> guests
-                                        \---->
-
-There are some smarter (and faster) ways too:
-
-=over 4
-
-=item *
-
-                          snapshot
-                template ---------->
-                            \------> cloned
-                             \-----> guests
-                              \---->
-
-Use the block device as a backing file and create a snapshot on top
-for each guest.  The advantage is that you don't need to copy the
-block device (very fast) and only changes are stored (less storage
-required).
-
-Note that writing to the backing file once you have created guests on
-top of it is not possible: you will corrupt the guests.
-
-Tools that can do this include:
-L<qemu-img(1)> (with the I<create -f qcow2 -o backing_file> option),
-L<lvcreate(8)> (I<--snapshot> option).  Some filesystems (such as
-btrfs) and most Network Attached Storage devices can also create cheap
-snapshots from files or LUNs.
-
-=item *
-
-Get your NAS to snapshot and/or duplicate the LUN.
-
-=item *
-
-Prepare your template using L<virt-sparsify(1)>.  See below.
-
-=back
-
-=head2 VIRT-CLONE
-
-A separate tool, L<virt-clone(1)>, can be used to duplicate the block
-device and/or modify the external libvirt configuration of a guest.
-It will reset the name, UUID and MAC address of the guest in the
-libvirt XML.
-
-L<virt-clone(1)> does not use libguestfs and cannot look inside the
-disk image.  This was the original motivation to write virt-sysprep.
-
-=head2 SPARSIFY
-
-              virt-sparsify
- original guest --------> template
-
-L<virt-sparsify(1)> can be used to make the cloning template smaller,
-making it easier to compress and/or faster to copy.
-
-Notice that since virt-sparsify also copies the image, you can use it
-to make the initial copy (instead of C<dd>).
-
-=head2 RESIZE
-
-                         virt-resize
-                template ---------->
-                            \------> cloned
-                             \-----> guests
-                              \---->
-
-If you want to give people cloned guests, but let them pick the size
-of the guest themselves (eg. depending on how much they are prepared
-to pay for disk space), then instead of copying the template, you can
-run L<virt-resize(1)>.  Virt-resize performs a copy and resize, and
-thus is ideal for cloning guests from a template.
-
-=head1 SECURITY
-
-Although virt-sysprep removes some sensitive information from the
-guest, it does not pretend to remove all of it.  You should examine
-the L</OPERATIONS> above, and the implementation of the operations in
-the shell script.  You should also examine the guest afterwards.
-
-Sensitive files are simply removed.  The data they contained may still
-exist on the disk, easily recovered with a hex editor or undelete
-tool.  Use L<virt-sparsify(1)> as one way to remove this content.  See
-also the L<scrub(1)> command to get rid of deleted content in
-directory entries and inodes.
-
-=head2 RANDOM SEED
-
-I<(This section applies to Linux guests only)>
-
-The virt-sysprep C<random-seed> operation writes a few bytes of
-randomness from the host into the guest's random seed file.
-
-If this is just done once and the guest is cloned from the same
-template, then each guest will start with the same entropy, and things
-like SSH host keys and TCP sequence numbers may be predictable.
-
-Therefore you should arrange to add more randomness I<after> cloning
-from a template too, which can be done by just enabling the
-C<random-seed> operation:
-
- cp template.img newguest.img
- virt-sysprep --enable=random-seed -a newguest.img
-
-=head2 SELINUX RELABELLING
-
-I<(This section applies to Linux guests using SELinux only)>
-
-If any new files are created by virt-sysprep, then virt-sysprep
-touches C</.autorelabel> so that these will be correctly labelled by
-SELinux the next time the guest is booted.  This process interrupts
-boot and can take some time.
-
-You can force relabelling for all guests by supplying the
-I<--selinux-relabel> option.
-
-You can disable relabelling entirely by supplying the
-I<--no-selinux-relabel> option.
-
-=head1 SHELL QUOTING
-
-Libvirt guest names can contain arbitrary characters, some of which
-have meaning to the shell such as C<#> and space.  You may need to
-quote or escape these characters on the command line.  See the shell
-manual page L<sh(1)> for details.
-
-=head1 EXIT STATUS
-
-This program returns 0 on success, or 1 if there was an error.
-
-=head1 SEE ALSO
-
-L<guestfs(3)>,
-L<guestfish(1)>,
-L<virt-clone(1)>,
-L<virt-rescue(1)>,
-L<virt-resize(1)>,
-L<virt-sparsify(1)>,
-L<virsh(1)>,
-L<lvcreate(8)>,
-L<qemu-img(1)>,
-L<scrub(1)>,
-L<http://libguestfs.org/>,
-L<http://libvirt.org/>.
-
-=head1 AUTHOR
-
-Richard W.M. Jones L<http://people.redhat.com/~rjones/>
-
-=head1 COPYRIGHT
-
-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.
diff --git a/configure.ac b/configure.ac
index b8cc803..6d34460 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1149,8 +1149,6 @@ AC_CONFIG_HEADERS([config.h])
 dnl http://www.mail-archive.com/automake@gnu.org/msg10204.html
 AC_CONFIG_FILES([appliance/libguestfs-make-fixed-appliance],
                 [chmod +x appliance/libguestfs-make-fixed-appliance])
-AC_CONFIG_FILES([clone/virt-sysprep],
-                [chmod +x clone/virt-sysprep])
 AC_CONFIG_FILES([podwrapper.sh],
                 [chmod +x podwrapper.sh])
 AC_CONFIG_FILES([run],
@@ -1159,7 +1157,6 @@ AC_CONFIG_FILES([Makefile
                  align/Makefile
                  appliance/Makefile
                  cat/Makefile
-                 clone/Makefile
                  csharp/Makefile
                  daemon/Makefile
                  df/Makefile
@@ -1201,6 +1198,7 @@ AC_CONFIG_FILES([Makefile
                  ruby/examples/Makefile
                  sparsify/Makefile
                  src/Makefile
+                 sysprep/Makefile
                  test-tool/Makefile
                  tests/c-api/Makefile
                  tests/data/Makefile
diff --git a/contrib/make-check-on-installed.pl b/contrib/make-check-on-installed.pl
index dc12dc5..83c408d 100755
--- a/contrib/make-check-on-installed.pl
+++ b/contrib/make-check-on-installed.pl
@@ -80,7 +80,7 @@ my %mapping = (
     '/bin/virt-rescue$' => "rescue",
     '/bin/virt-resize$' => "resize",
     '/bin/virt-sparsify$' => "sparsify",
-    '/bin/virt-sysprep$' => "clone",
+    '/bin/virt-sysprep$' => "sysprep",
     '/bin/virt-tar$' => "tools",
     '/bin/virt-tar-in$' => "fish",
     '/bin/virt-tar-out$' => "fish",
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 05f5c74..e2377b8 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -3008,11 +3008,6 @@ The libguestfs appliance, build scripts and so on.
 The L<virt-cat(1)>, L<virt-filesystems(1)> and L<virt-ls(1)> commands
 and documentation.
 
-=item C<clone>
-
-Tools for cloning virtual machines.  Currently contains
-L<virt-sysprep(1)> command and documentation.
-
 =item C<contrib>
 
 Outside contributions, experimental parts.
@@ -3091,6 +3086,10 @@ L<virt-sparsify(1)> command and documentation.
 
 Source code to the C library.
 
+=item C<sysprep>
+
+L<virt-sysprep(1)> command and documentation.
+
 =item C<test-tool>
 
 Test tool for end users to test if their qemu/kernel combination
diff --git a/sysprep/Makefile.am b/sysprep/Makefile.am
new file mode 100644
index 0000000..1675071
--- /dev/null
+++ b/sysprep/Makefile.am
@@ -0,0 +1,126 @@
+# libguestfs virt-sysprep tool
+# 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.
+
+include $(top_srcdir)/subdir-rules.mk
+
+SOURCES =
+
+EXTRA_DIST = \
+	$(SOURCES) \
+	test-virt-sysprep.sh \
+	virt-sysprep.pod
+
+CLEANFILES = stamp-virt-sysprep.pod
+
+if HAVE_OCAML
+
+# Alphabetical order.
+SOURCES += \
+	main.ml \
+	sysprep_operation.ml \
+	sysprep_operation.mli \
+	sysprep_operation_hostname.ml \
+	sysprep_operation_utmp.ml \
+	utils.ml
+
+# Note this list must be in dependency order.
+OBJECTS = \
+	utils.cmx \
+	sysprep_operation.cmx \
+	sysprep_operation_hostname.cmx \
+	sysprep_operation_utmp.cmx \
+	main.cmx
+
+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 unix -I $(top_builddir)/src/.libs -I $(top_builddir)/ocaml
+
+OCAMLCFLAGS = -g -warn-error CDEFLMPSUVYZX $(OCAMLPACKAGES)
+OCAMLOPTFLAGS = $(OCAMLCFLAGS)
+
+virt-sysprep: $(OBJECTS)
+	$(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) \
+	  mlguestfs.cmxa -linkpkg $^ -cclib -lncurses -o $@
+
+.mli.cmi:
+	$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
+.ml.cmo:
+	$(OCAMLFIND) ocamlc $(OCAMLCFLAGS) -c $< -o $@
+.ml.cmx:
+	$(OCAMLFIND) ocamlopt $(OCAMLCFLAGS) -c $< -o $@
+
+# Manual pages and HTML files for the website.
+man_MANS = virt-sysprep.1
+noinst_DATA = $(top_builddir)/html/virt-sysprep.1.html
+
+virt-sysprep.1 $(top_builddir)/html/virt-sysprep.1.html: stamp-virt-sysprep.pod
+
+stamp-virt-sysprep.pod: virt-sysprep.pod sysprep-extra-options.pod sysprep-operations.pod
+	$(top_builddir)/podwrapper.sh \
+	  --man virt-sysprep.1 \
+          --insert sysprep-extra-options.pod:@EXTRA_OPTIONS@ \
+          --insert sysprep-operations.pod:@OPERATIONS@ \
+	  --html $(top_builddir)/html/virt-sysprep.1.html \
+	  $<
+	touch $@
+
+sysprep-extra-options.pod: virt-sysprep
+	rm -f $@ $@-t
+	./$< --dump-pod-options > $@-t
+	mv $@-t $@
+
+sysprep-operations.pod: virt-sysprep
+	rm -f $@ $@-t
+	./$< --dump-pod > $@-t
+	mv $@-t $@
+
+# Tests.
+
+random_val := $(shell awk 'BEGIN{srand(); print 1+int(255*rand())}' < /dev/null)
+
+TESTS_ENVIRONMENT = \
+	MALLOC_PERTURB_=$(random_val) \
+	$(top_builddir)/run
+
+if ENABLE_APPLIANCE
+TESTS = test-virt-sysprep.sh
+endif ENABLE_APPLIANCE
+
+# 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
+
+.PHONY: depend docs
+
+# Parallel builds don't obey dependencies for some reason we
+# don't understand.
+.NOTPARALLEL:
diff --git a/sysprep/main.ml b/sysprep/main.ml
new file mode 100644
index 0000000..67ef047
--- /dev/null
+++ b/sysprep/main.ml
@@ -0,0 +1,232 @@
+(* 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 Unix
+open Printf
+
+open Utils
+
+module G = Guestfs
+
+(* Finalize the list of operations modules. *)
+let () = Sysprep_operation.bake ()
+
+(* Command line argument parsing. *)
+let prog = Filename.basename Sys.executable_name
+
+let debug_gc, operations, g, selinux_relabel =
+  let debug_gc = ref false in
+  let domain = ref None in
+  let dryrun = ref false in
+  let files = ref [] in
+  let format = ref "auto" in
+  let libvirturi = ref "" in
+  let operations = ref None in
+  let selinux_relabel = ref `Auto in
+  let trace = ref false in
+  let verbose = ref false 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;
+    exit 0
+  and add_file file =
+    let format = match !format with "auto" -> None | fmt -> Some fmt in
+    files := (file, format) :: !files
+  and set_domain dom =
+    if !domain <> None then (
+      eprintf "%s: --domain option can only be given once\n" prog;
+      exit 1
+    );
+    domain := Some dom
+  and dump_pod () =
+    Sysprep_operation.dump_pod ();
+    exit 0
+  and dump_pod_options () =
+    Sysprep_operation.dump_pod_options ();
+    exit 0
+  and set_enable ops =
+    if !operations <> None then (
+      eprintf "%s: --enable option can only be given once\n" prog;
+      exit 1
+    );
+    if ops = "" then (
+      eprintf "%s: you cannot pass an empty argument to --enable\n" prog;
+      exit 1
+    );
+    let ops = string_split "," ops in
+    let opset = List.fold_left (
+      fun opset op_name ->
+        try Sysprep_operation.add_to_set op_name opset
+        with Not_found ->
+          eprintf "%s: --enable: '%s' is not a known operation\n" prog op_name;
+          exit 1
+    ) Sysprep_operation.empty_set ops in
+    operations := Some opset
+  and force_selinux_relabel () =
+    selinux_relabel := `Force
+  and no_force_selinux_relabel () =
+    selinux_relabel := `Never
+  and list_operations () =
+    Sysprep_operation.list_operations ();
+    exit 0
+  in
+
+  let argspec = Arg.align [
+    "-a",        Arg.String add_file,       "file Add disk image file";
+    "--add",     Arg.String add_file,       "file Add disk image file";
+    "-c",        Arg.Set_string libvirturi, "uri Set libvirt URI";
+    "--connect", Arg.Set_string libvirturi, "uri Set libvirt URI";
+    "--debug-gc", Arg.Set debug_gc,         " Debug GC and memory allocations (internal)";
+    "-d",        Arg.String set_domain,     "domain Set libvirt guest name";
+    "--domain",  Arg.String set_domain,     "domain Set libvirt guest name";
+    "-n",        Arg.Set dryrun,            " Perform a dry run";
+    "--dryrun",  Arg.Set dryrun,            " Perform a dry run";
+    "--dry-run", Arg.Set dryrun,            " Perform a dry run";
+    "--dump-pod", Arg.Unit dump_pod,        " Dump POD (internal)";
+    "--dump-pod-options", Arg.Unit dump_pod_options, " Dump POD for options (internal)";
+    "--enable",  Arg.String set_enable,     "operations Enable specific operations";
+    "--format",  Arg.Set_string format,     "format Set format (default: auto)";
+    "--list-operations", Arg.Unit list_operations, " List supported operations";
+    "--selinux-relabel", Arg.Unit force_selinux_relabel, " Force SELinux relabel";
+    "--no-selinux-relabel", Arg.Unit no_force_selinux_relabel, " Never do SELinux relabel";
+    "-v",        Arg.Set verbose,           " Enable debugging messages";
+    "--verbose", Arg.Set verbose,           " -\"-";
+    "-V",        Arg.Unit display_version,  " Display version and exit";
+    "--version", Arg.Unit display_version,  " -\"-";
+    "-x",        Arg.Set trace,             " Enable tracing of libguestfs calls";
+  ] in
+  let anon_fun _ = raise (Arg.Bad "extra parameter on the command line") in
+  let usage_msg =
+    sprintf "\
+%s: reset or unconfigure a virtual machine so clones can be made
+
+ virt-sysprep [--options] -d domname
+
+ virt-sysprep [--options] -a disk.img [-a disk.img ...]
+
+A short summary of the options is given below.  For detailed help please
+read the man page virt-sysprep(1).
+"
+      prog in
+  Arg.parse argspec anon_fun usage_msg;
+
+  (* Check -a and -d options. *)
+  let files = !files in
+  let domain = !domain in
+  let libvirturi = match !libvirturi with "" -> None | s -> Some s in
+  let add =
+    match files, domain with
+    | [], None ->
+      eprintf "%s: you must give either -a or -d options\n" prog;
+      eprintf "Read virt-sysprep(1) man page for further information.\n";
+      exit 1
+    | [], Some dom ->
+      fun (g : Guestfs.guestfs) readonly ->
+        let allowuuid = true in
+        let readonlydisk = "ignore" (* ignore CDs, data drives *) in
+        ignore (g#add_domain ~readonly ?libvirturi ~allowuuid ~readonlydisk dom)
+    | _, Some _ ->
+      eprintf "%s: you cannot give -a and -d options together\n" prog;
+      eprintf "Read virt-sysprep(1) man page for further information.\n";
+      exit 1
+    | files, None ->
+      fun g readonly ->
+        List.iter (
+          fun (file, format) ->
+            g#add_drive_opts ~readonly ?format file
+        ) files
+  in
+
+  (* Dereference the rest of the args. *)
+  let debug_gc = !debug_gc in
+  let dryrun = !dryrun in
+  let operations = !operations in
+  let selinux_relabel = !selinux_relabel in
+  let trace = !trace in
+  let verbose = !verbose in
+
+  (* Connect to libguestfs. *)
+  let g = new G.guestfs () in
+  if trace then g#set_trace true;
+  if verbose then g#set_verbose true;
+  add g dryrun;
+  g#launch ();
+
+  debug_gc, operations, g, selinux_relabel
+
+let () =
+  (* Inspection. *)
+  match Array.to_list (g#inspect_os ()) with
+  | [] ->
+    eprintf "%s: no operating systems were found in the guest image\n" prog;
+    exit 1
+  | roots ->
+    List.iter (
+      fun root ->
+        (* Mount up the disks, like guestfish -i.
+         * See [ocaml/examples/inspect_vm.ml].
+         *)
+        let mps = g#inspect_get_mountpoints root in
+        let cmp (a,_) (b,_) = compare (String.length a) (String.length b) in
+        let mps = List.sort cmp mps in
+        List.iter (
+          fun (mp, dev) ->
+            try g#mount dev mp
+            with Guestfs.Error msg -> eprintf "%s (ignored)\n" msg
+        ) mps;
+
+        (* Perform the operations. *)
+        let flags = Sysprep_operation.perform_operations ?operations g root in
+
+        (* Parse flags. *)
+        let relabel = ref false in
+        List.iter (function
+        | `Created_files -> relabel := true
+        ) flags;
+
+        (* SELinux relabel? *)
+        let relabel =
+          match selinux_relabel, !relabel with
+          | `Force, _ -> true
+          | `Never, _ -> false
+          | `Auto, relabel -> relabel in
+        if relabel then (
+          let typ = g#inspect_get_type root in
+          let distro = g#inspect_get_distro root in
+          match typ, distro with
+          | "linux", ("fedora"|"rhel"|"redhat-based"
+                         |"centos"|"scientificlinux") ->
+            g#touch "/.autorelabel"
+          | _ -> ()
+        );
+
+        (* Unmount everything in this guest. *)
+        g#umount_all ()
+    ) roots
+
+(* Finished. *)
+let () =
+  g#close ();
+
+  if debug_gc then
+    Gc.compact ();
+
+  exit 0
diff --git a/sysprep/sysprep_operation.ml b/sysprep/sysprep_operation.ml
new file mode 100644
index 0000000..8c8bd48
--- /dev/null
+++ b/sysprep/sysprep_operation.ml
@@ -0,0 +1,164 @@
+(* 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
+
+type flag = [ `Created_files ]
+
+type operation = {
+  name : string;
+  pod_description : string;
+  extra_args : ((Arg.key * Arg.spec * Arg.doc) * string) list;
+  perform : Guestfs.guestfs -> string -> flag list;
+}
+
+let ops = ref []
+
+let register_operation op = ops := op :: !ops
+
+let baked = ref false
+let rec bake () =
+  let ops' = List.sort (fun { name = a } { name = b } -> compare a b) !ops in
+  List.iter check ops';
+  ops := ops';
+  baked := true
+and check op =
+  let n = String.length op.name in
+  if n = 0 then (
+    eprintf "virt-sysprep: operation name is an empty string\n";
+    exit 1;
+  );
+  for i = 0 to n-1 do
+    match String.unsafe_get op.name i with
+    | 'a'..'z' | 'A'..'Z' | '0'..'9' | '-' -> ()
+    | c ->
+      eprintf "virt-sysprep: disallowed character (%c) in operation name\n" c;
+      exit 1
+  done;
+  let n = String.length op.pod_description in
+  if n = 0 then (
+    eprintf "virt-sysprep: operation %s has no POD\n" op.name;
+    exit 1
+  );
+  if op.pod_description.[n-1] = '\n' then (
+    eprintf "virt-sysprep: POD for %s must not end with newline\n" op.name;
+    exit 1
+  )
+
+(* These internal functions are used to generate the man page. *)
+let dump_pod () =
+  assert !baked;
+
+  List.iter (
+    fun op ->
+      printf "=head2 B<%s>\n" op.name;
+      printf "\n";
+      printf "%s\n\n" op.pod_description
+  ) !ops
+
+(* 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 assert false
+    else if 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 dump_pod_options () =
+  assert !baked;
+
+  let args = List.map (
+    fun { name = op_name; extra_args = extra_args } ->
+      List.map (fun ea -> op_name, ea) extra_args
+  ) !ops 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 *)
+  ) args in
+
+  let args = List.sort (
+    fun (a, _) (b, _) ->
+      compare (skip_dashes a) (skip_dashes b)
+  ) args in
+
+  List.iter (
+    fun (arg_name, (op_name, heading, pod)) ->
+      printf "=item %s\n" heading;
+      printf "(see C<%s> below)\n" op_name;
+      printf "\n";
+      printf "%s\n\n" pod
+  ) args
+
+let list_operations () =
+  assert !baked;
+
+  (* For compatibility with old shell version, list just the operation
+   * names, sorted.
+   *)
+  List.iter (fun op -> print_endline op.name ) !ops
+
+module OperationSet = Set.Make (
+  struct
+    type t = operation
+    let compare a b = compare a.name b.name
+  end
+)
+type set = OperationSet.t
+
+let empty_set = OperationSet.empty
+
+let add_to_set name set =
+  assert !baked;
+
+  let op = List.find (fun { name = n } -> name = n) !ops in
+  OperationSet.add op set
+
+let perform_operations ?operations g root =
+  assert !baked;
+
+  let ops =
+    match operations with
+    | None -> !ops (* all operations *)
+    | Some opset -> (* just the operation names listed *)
+      OperationSet.elements opset in
+
+  let flags = List.map (fun op -> op.perform g root) ops in
+
+  List.flatten flags
diff --git a/sysprep/sysprep_operation.mli b/sysprep/sysprep_operation.mli
new file mode 100644
index 0000000..16d9285
--- /dev/null
+++ b/sysprep/sysprep_operation.mli
@@ -0,0 +1,96 @@
+(* 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.
+ *)
+
+(** Structure used to describe sysprep operations. *)
+
+type flag = [ `Created_files ]
+
+type operation = {
+  name : string;
+  (** Operation name, also used to enable the operation on the command
+      line.  Must contain only alphanumeric and '-' (dash)
+      character. *)
+
+  pod_description : string;
+  (** POD-format description, used for the man page. *)
+
+  extra_args : ((Arg.key * Arg.spec * Arg.doc) * string) 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.
+
+      You can decide the types of the arguments, whether they are
+      mandatory etc. *)
+
+  perform : Guestfs.guestfs -> string -> flag list;
+  (** The function which is called to perform this operation, when
+      enabled.
+
+      The parameters are [g] (libguestfs handle) and [root] (the
+      operating system root filesystem).  Inspection has been performed
+      already on this handle so if the operation depends on OS type,
+      call [g#inspect_get_type], [g#inspect_get_distro] etc. in order to
+      determine that.  The guest operating system's disks have been
+      mounted up, and this function must not unmount them.
+
+      In the rare case of a multiboot operating system, it is possible
+      for this function to be called multiple times.
+
+      On success, the function can return a list of flags (or an
+      empty list).  See {!flag}.
+
+      On error the function should raise an exception.  The function
+      also needs to be careful to {i suppress} exceptions for things
+      which are not errors, eg. deleting non-existent files. *)
+}
+
+val register_operation : operation -> unit
+(** Register an operation. *)
+
+val bake : unit -> unit
+(** 'Bake' is called after all modules have been registered.  We
+    finalize the list of operations, sort it, and run some checks. *)
+
+val dump_pod : unit -> unit
+(** Dump the perldoc (POD) for the manual page
+    (implements [--dump-pod]). *)
+
+val dump_pod_options : unit -> unit
+(** Dump the perldoc (POD) for the [extra_args]
+    (implements [--dump-pod-options]). *)
+
+val list_operations : unit -> unit
+(** List supported operations
+    (implements [--list-operations]). *)
+
+type set
+(** A (sub-)set of operations. *)
+
+val empty_set : set
+(** Empty set of operations. *)
+
+val add_to_set : string -> set -> set
+(** [add_to_set name set] adds the operation named [name] to [set].
+
+    Note that this will raise [Not_found] if [name] is not
+    a valid operation name. *)
+
+val perform_operations : ?operations:set -> Guestfs.guestfs -> string -> flag list
+(** Perform all operations, or the subset listed in the [operations] set. *)
diff --git a/sysprep/sysprep_operation_hostname.ml b/sysprep/sysprep_operation_hostname.ml
new file mode 100644
index 0000000..1472a1c
--- /dev/null
+++ b/sysprep/sysprep_operation_hostname.ml
@@ -0,0 +1,69 @@
+(* 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 Utils
+open Sysprep_operation
+
+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
+  match typ, distro with
+  | "linux", ("fedora"|"rhel") ->
+    (* Replace HOSTNAME=... entry.  The code assumes it's a small,
+     * plain text file.
+     *)
+    let filename = "/etc/sysconfig/network" in
+    let lines = Array.to_list (g#read_lines filename) in
+    let lines = List.filter (
+      fun line -> not (string_prefix line "HOSTNAME=")
+    ) lines in
+    let file =
+      String.concat "\n" lines ^
+      sprintf "\nHOSTNAME=%s\n" !hostname in
+    g#write filename file;
+    [ `Created_files ]
+
+  | "linux", ("debian"|"ubuntu") ->
+    g#write "/etc/hostname" !hostname;
+    [ `Created_files ]
+
+  | _ -> []
+
+let hostname_op = {
+  name = "hostname";
+  pod_description = "\
+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, "hostname New hostname"),
+    "\
+Change the hostname.  If not given, defaults to C<localhost.localdomain>."
+  ];
+  perform = hostname_perform;
+}
+
+let () = register_operation hostname_op
diff --git a/sysprep/sysprep_operation_utmp.ml b/sysprep/sysprep_operation_utmp.ml
new file mode 100644
index 0000000..69867e1
--- /dev/null
+++ b/sysprep/sysprep_operation_utmp.ml
@@ -0,0 +1,43 @@
+(* 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 Sysprep_operation
+
+module G = Guestfs
+
+let utmp_perform g root =
+  let typ = g#inspect_get_type root in
+  if typ <> "windows" then (
+    try g#rm "/var/run/utmp"
+    with G.Error _ -> ()
+  );
+  []
+
+let utmp_op = {
+  name = "utmp";
+  pod_description = "\
+Remove the utmp file.
+
+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 = utmp_perform;
+}
+
+let () = register_operation utmp_op
diff --git a/sysprep/test-virt-sysprep.sh b/sysprep/test-virt-sysprep.sh
new file mode 100755
index 0000000..e0bae2d
--- /dev/null
+++ b/sysprep/test-virt-sysprep.sh
@@ -0,0 +1,26 @@
+#!/bin/bash -
+# libguestfs virt-sysprep test script
+# Copyright (C) 2011-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.
+
+export LANG=C
+set -e
+
+if [ ! -w /dev/fuse ]; then
+    echo "SKIPPING virt-sysprep test, because there is no /dev/fuse."
+    exit 0
+fi
+
diff --git a/sysprep/utils.ml b/sysprep/utils.ml
new file mode 100644
index 0000000..7a4cbef
--- /dev/null
+++ b/sysprep/utils.ml
@@ -0,0 +1,69 @@
+(* virt-sysprep
+ * 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 Printf
+
+module G = Guestfs
+
+let (//) = Filename.concat
+
+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 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]
+    )
diff --git a/sysprep/virt-sysprep.pod b/sysprep/virt-sysprep.pod
new file mode 100755
index 0000000..ca1b37b
--- /dev/null
+++ b/sysprep/virt-sysprep.pod
@@ -0,0 +1,419 @@
+=encoding utf8
+
+=head1 NAME
+
+virt-sysprep - Reset or unconfigure a virtual machine so clones can be made
+
+=head1 SYNOPSIS
+
+ virt-sysprep [--options] -d domname
+
+ virt-sysprep [--options] -a disk.img [-a disk.img ...]
+
+=head1 DESCRIPTION
+
+Virt-sysprep "resets" or "unconfigures" a virtual machine so that
+clones can be made from it.  Steps in this process include removing
+SSH host keys, removing persistent network MAC configuration, and
+removing user accounts.  Each step can be enabled or disabled as
+required.
+
+Virt-sysprep modifies the guest or disk image I<in place>.  The guest
+must be shut down.  If you want to preserve the existing contents of
+the guest, you I<must copy or clone the disk first>.
+See L</COPYING AND CLONING> below.
+
+You do I<not> need to run virt-sysprep as root.  In fact we'd
+generally recommend that you don't.  The time you might want to run it
+as root is when you need root in order to access the disk image, but
+even in this case it would be better to change the permissions on the
+disk image to be writable as the non-root user running virt-sysprep.
+
+"Sysprep" stands for "system preparation" tool.  The name comes from
+the Microsoft program C<sysprep.exe> which is used to unconfigure
+Windows machines in preparation for cloning them.  Having said that,
+virt-sysprep does I<not> currently work on Microsoft Windows guests.
+We plan to support Windows sysprepping in a future version, and we
+already have code to do it.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display brief help.
+
+=item B<-a> file
+
+=item B<--add> file
+
+Add I<file> which should be a disk image from a virtual machine.
+
+The format of the disk image is auto-detected.  To override this and
+force a particular format use the I<--format> option.
+
+=item B<-c> URI
+
+=item B<--connect> URI
+
+If using libvirt, connect to the given I<URI>.  If omitted, then we
+connect to the default libvirt hypervisor.
+
+If you specify guest block devices directly (I<-a>), then libvirt is
+not used at all.
+
+=item B<-d> guest
+
+=item B<--domain> guest
+
+Add all the disks from the named libvirt guest.  Domain UUIDs can be
+used instead of names.
+
+=item B<-n>
+
+=item B<--dry-run>
+
+Perform a read-only "dry run" on the guest.  This runs the sysprep
+operation, but throws away any changes to the disk at the end.
+
+=item B<--enable> operations
+
+Choose which sysprep operations to perform.  Give a comma-separated
+list of operations, for example:
+
+ --enable ssh-hostkeys,udev-persistent-net
+
+would enable ONLY C<ssh-hostkeys> and C<udev-persistent-net> operations.
+
+If the I<--enable> option is not given, then we default to trying all
+possible sysprep operations.  But some sysprep operations are skipped
+for some guest types.
+
+Use I<--list-operations> to list operations supported by a particular
+version of virt-sysprep.
+
+See L</OPERATIONS> below for a list and an explanation of each
+operation.
+
+=item B<--format> raw|qcow2|..
+
+=item B<--format> auto
+
+The default for the I<-a> option is to auto-detect the format of the
+disk image.  Using this forces the disk format for I<-a> options which
+follow on the command line.  Using I<--format auto> switches back to
+auto-detection for subsequent I<-a> options.
+
+For example:
+
+ virt-sysprep --format raw -a disk.img
+
+forces raw format (no auto-detection) for C<disk.img>.
+
+ virt-sysprep --format raw -a disk.img --format auto -a another.img
+
+forces raw format (no auto-detection) for C<disk.img> and reverts to
+auto-detection for C<another.img>.
+
+If you have untrusted raw-format guest disk images, you should use
+this option to specify the disk format.  This avoids a possible
+security problem with malicious guests (CVE-2010-3851).
+
+=item B<--list-operations>
+
+List the operations supported by the virt-sysprep program.
+
+=item B<--selinux-relabel>
+
+=item B<--no-selinux-relabel>
+
+I<--selinux-relabel> forces SELinux relabelling next time the guest
+boots.  I<--no-selinux-relabel> disables relabelling.
+
+The default is to try to detect if SELinux relabelling is required.
+See L</SELINUX RELABELLING> below for more details.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable verbose messages for debugging.
+
+=item B<-V>
+
+=item B<--version>
+
+Display version number and exit.
+
+=item B<-x>
+
+Enable tracing of libguestfs API calls.
+
+ at EXTRA_OPTIONS@
+
+=back
+
+=head1 OPERATIONS
+
+ at OPERATIONS@
+
+=head1 COPYING AND CLONING
+
+Virt-sysprep can be used as part of a process of cloning guests, or to
+prepare a template from which guests can be cloned.  There are many
+different ways to achieve this using the virt tools, and this section
+is just an introduction.
+
+A virtual machine (when switched off) consists of two parts:
+
+=over 4
+
+=item I<configuration>
+
+The configuration or description of the guest.  eg. The libvirt
+XML (see C<virsh dumpxml>), the running configuration of the guest,
+or another external format like OVF.
+
+Some configuration items that might need to be changed:
+
+=over 4
+
+=item *
+
+name
+
+=item *
+
+UUID
+
+=item *
+
+path to block device(s)
+
+=item *
+
+network card MAC address
+
+=back
+
+=item I<block device(s)>
+
+One or more hard disk images, themselves containing files,
+directories, applications, kernels, configuration, etc.
+
+Some things inside the block devices that might need to be changed:
+
+=over 4
+
+=item *
+
+hostname and other net configuration
+
+=item *
+
+UUID
+
+=item *
+
+SSH host keys
+
+=item *
+
+Windows unique security ID (SID)
+
+=item *
+
+Puppet registration
+
+=back
+
+=back
+
+=head2 COPYING THE BLOCK DEVICE
+
+Starting with an original guest, you probably wish to copy the guest
+block device and its configuration to make a template.  Then once you
+are happy with the template, you will want to make many clones from
+it.
+
+                        virt-sysprep
+                             |
+                             v
+ original guest --------> template ---------->
+                                      \------> cloned
+                                       \-----> guests
+                                        \---->
+
+You can, of course, just copy the block device on the host using
+L<cp(1)> or L<dd(1)>.
+
+                   dd                 dd
+ original guest --------> template ---------->
+                                      \------> cloned
+                                       \-----> guests
+                                        \---->
+
+There are some smarter (and faster) ways too:
+
+=over 4
+
+=item *
+
+                          snapshot
+                template ---------->
+                            \------> cloned
+                             \-----> guests
+                              \---->
+
+Use the block device as a backing file and create a snapshot on top
+for each guest.  The advantage is that you don't need to copy the
+block device (very fast) and only changes are stored (less storage
+required).
+
+Note that writing to the backing file once you have created guests on
+top of it is not possible: you will corrupt the guests.
+
+Tools that can do this include:
+L<qemu-img(1)> (with the I<create -f qcow2 -o backing_file> option),
+L<lvcreate(8)> (I<--snapshot> option).  Some filesystems (such as
+btrfs) and most Network Attached Storage devices can also create cheap
+snapshots from files or LUNs.
+
+=item *
+
+Get your NAS to snapshot and/or duplicate the LUN.
+
+=item *
+
+Prepare your template using L<virt-sparsify(1)>.  See below.
+
+=back
+
+=head2 VIRT-CLONE
+
+A separate tool, L<virt-clone(1)>, can be used to duplicate the block
+device and/or modify the external libvirt configuration of a guest.
+It will reset the name, UUID and MAC address of the guest in the
+libvirt XML.
+
+L<virt-clone(1)> does not use libguestfs and cannot look inside the
+disk image.  This was the original motivation to write virt-sysprep.
+
+=head2 SPARSIFY
+
+              virt-sparsify
+ original guest --------> template
+
+L<virt-sparsify(1)> can be used to make the cloning template smaller,
+making it easier to compress and/or faster to copy.
+
+Notice that since virt-sparsify also copies the image, you can use it
+to make the initial copy (instead of C<dd>).
+
+=head2 RESIZE
+
+                         virt-resize
+                template ---------->
+                            \------> cloned
+                             \-----> guests
+                              \---->
+
+If you want to give people cloned guests, but let them pick the size
+of the guest themselves (eg. depending on how much they are prepared
+to pay for disk space), then instead of copying the template, you can
+run L<virt-resize(1)>.  Virt-resize performs a copy and resize, and
+thus is ideal for cloning guests from a template.
+
+=head1 SECURITY
+
+Although virt-sysprep removes some sensitive information from the
+guest, it does not pretend to remove all of it.  You should examine
+the L</OPERATIONS> above and the guest afterwards.
+
+Sensitive files are simply removed.  The data they contained may still
+exist on the disk, easily recovered with a hex editor or undelete
+tool.  Use L<virt-sparsify(1)> as one way to remove this content.  See
+also the L<scrub(1)> command to get rid of deleted content in
+directory entries and inodes.
+
+=head2 RANDOM SEED
+
+I<(This section applies to Linux guests only)>
+
+The virt-sysprep C<random-seed> operation writes a few bytes of
+randomness from the host into the guest's random seed file.
+
+If this is just done once and the guest is cloned from the same
+template, then each guest will start with the same entropy, and things
+like SSH host keys and TCP sequence numbers may be predictable.
+
+Therefore you should arrange to add more randomness I<after> cloning
+from a template too, which can be done by just enabling the
+C<random-seed> operation:
+
+ cp template.img newguest.img
+ virt-sysprep --enable random-seed -a newguest.img
+
+=head2 SELINUX RELABELLING
+
+I<(This section applies to Linux guests using SELinux only)>
+
+If any new files are created by virt-sysprep, then virt-sysprep
+touches C</.autorelabel> so that these will be correctly labelled by
+SELinux the next time the guest is booted.  This process interrupts
+boot and can take some time.
+
+You can force relabelling for all guests by supplying the
+I<--selinux-relabel> option.
+
+You can disable relabelling entirely by supplying the
+I<--no-selinux-relabel> option.
+
+=head1 SHELL QUOTING
+
+Libvirt guest names can contain arbitrary characters, some of which
+have meaning to the shell such as C<#> and space.  You may need to
+quote or escape these characters on the command line.  See the shell
+manual page L<sh(1)> for details.
+
+=head1 EXIT STATUS
+
+This program returns 0 on success, or 1 if there was an error.
+
+=head1 SEE ALSO
+
+L<guestfs(3)>,
+L<guestfish(1)>,
+L<virt-clone(1)>,
+L<virt-rescue(1)>,
+L<virt-resize(1)>,
+L<virt-sparsify(1)>,
+L<virsh(1)>,
+L<lvcreate(8)>,
+L<qemu-img(1)>,
+L<scrub(1)>,
+L<http://libguestfs.org/>,
+L<http://libvirt.org/>.
+
+=head1 AUTHOR
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2011-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.
diff --git a/tests/extra/Makefile.am b/tests/extra/Makefile.am
index b807048..baba4a3 100644
--- a/tests/extra/Makefile.am
+++ b/tests/extra/Makefile.am
@@ -28,10 +28,8 @@
 #
 # XXX Not tested:
 #
-# ../clone/virt-sysprep
-#   - hard to test because it's a shell script
-#
 # ../edit/virt-edit
+# ../sysprep/virt-sysprep
 #
 # Perl bindings
 # ../edit/virt-edit -e
-- 
1.7.9.3




More information about the Libguestfs mailing list