virt-sysprep: Switch to using guestmount, add more features.
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 13 Oct 2011 14:37:56 +0000 (15:37 +0100)
committerRichard W.M. Jones <rjones@redhat.com>
Thu, 13 Oct 2011 21:36:56 +0000 (22:36 +0100)
This switches virt-sysprep to use guestmount instead of guestfish.
This makes the script a little bit easier to modify for sysadmins.

This commit also adds:

 - dhcp-client-state
 - dhcp-server-state
 - logfiles
 - random-seed
 - smolt-uuid
 - yum-uuid

SELinux relabelling, and a section on security in the manual page.

Makefile.am
README
TODO
clone/virt-sysprep.in
clone/virt-sysprep.pod

index 3d44c76..5170ec8 100644 (file)
@@ -38,9 +38,6 @@ SUBDIRS += fish
 # virt-tools in C.
 SUBDIRS += align cat df edit inspector rescue
 
 # virt-tools in C.
 SUBDIRS += align cat df edit inspector rescue
 
-# virt-tools in shell.
-SUBDIRS += clone
-
 # Language bindings.
 if HAVE_PERL
 SUBDIRS += perl perl/examples
 # Language bindings.
 if HAVE_PERL
 SUBDIRS += perl perl/examples
@@ -88,6 +85,11 @@ if HAVE_FUSE
 SUBDIRS += fuse
 endif
 
 SUBDIRS += fuse
 endif
 
+# virt-tools in shell.  This uses guestmount.
+if HAVE_FUSE
+SUBDIRS += clone
+endif
+
 # po-docs must come after tools, inspector.
 if HAVE_PO4A
 SUBDIRS += po-docs
 # po-docs must come after tools, inspector.
 if HAVE_PO4A
 SUBDIRS += po-docs
diff --git a/README b/README
index 396c8bb..9d18eeb 100644 (file)
--- a/README
+++ b/README
@@ -134,6 +134,12 @@ To build the Perl tools:
 
 - perl-libintl for translating perl code (optional)
 
 
 - perl-libintl for translating perl code (optional)
 
+To run virt-sysprep:
+
+- xmlstarlet (optional)
+
+- virt-sysprep also requires FUSE support since it uses guestmount
+
 
 Building
 ----------------------------------------------------------------------
 
 Building
 ----------------------------------------------------------------------
diff --git a/TODO b/TODO
index bb9af78..a82ad57 100644 (file)
--- a/TODO
+++ b/TODO
@@ -537,7 +537,6 @@ virt-sysprep ideas
 ------------------
 
  - touch /.unconfigured ?
 ------------------
 
  - touch /.unconfigured ?
- - smolt ID
  - Spacewalk / RHN ID
  - Kerberos keys
  - Puppet registration
  - Spacewalk / RHN ID
  - Kerberos keys
  - Puppet registration
@@ -549,11 +548,8 @@ virt-sysprep ideas
    . install a firstboot script      virt-sysprep --script=/tmp/foo.sh
    . run an external shell script
    . run external guestfish script   virt-sysprep --fish=/tmp/foo.fish
    . install a firstboot script      virt-sysprep --script=/tmp/foo.sh
    . run an external shell script
    . run external guestfish script   virt-sysprep --fish=/tmp/foo.fish
-   . rm /var/cache/apt/archives/*deb
- - log files (thanks Steve Grubb)
-   . as well as the obvious log files, also
-     utmp/wtmp/btmp/tallylog and pam_faillock's data files
- - RNG seed (Steve Grubb)
+   . rm /var/cache/apt/archives/*
+ - /var/run/* and pam_faillock's data files
  - homedirs/.ssh directory, especially /root/.ssh (Steve Grubb)
  - if drives are encrypted, then dm-crypt key should be changed
    and drives all re-encrypted
  - homedirs/.ssh directory, especially /root/.ssh (Steve Grubb)
  - if drives are encrypted, then dm-crypt key should be changed
    and drives all re-encrypted
@@ -564,20 +560,13 @@ virt-sysprep ideas
    may have picked up some certificates or things. This is an area
    that you would want to look into.
  - secure erase of inodes etc using scrub (Steve Grubb)
    may have picked up some certificates or things. This is an area
    that you would want to look into.
  - secure erase of inodes etc using scrub (Steve Grubb)
- - touch /.autorelabel if we create any new files (thanks Dan Berrange)
- - should we use guestmount instead of guestfish
-   and would that make it easier to run the tool inside VMs?
  - other directories that could require cleaning include:
  - other directories that could require cleaning include:
-    /var/lib/dhcpd/*
-    /var/lib/dhclient/*
     /var/cache/gdm/*
     /var/lib/fprint/*
     /var/run/*
     /var/spool/mail/*
     /var/spool/cron/*
     /var/lib/AccountService/users/*
     /var/cache/gdm/*
     /var/lib/fprint/*
     /var/run/*
     /var/spool/mail/*
     /var/spool/cron/*
     /var/lib/AccountService/users/*
-    /var/cache/yum/*
-    /var/lib/yum/*   (only /var/lib/yum/uuid)
     /var/lib/sss/db/*
     /var/lib/samba/*
     /var/lib/samba/*/*
     /var/lib/sss/db/*
     /var/lib/samba/*
     /var/lib/samba/*/*
index cf4ad96..981ed96 100644 (file)
@@ -21,9 +21,12 @@ unset CDPATH
 program="virt-sysprep"
 version="@PACKAGE_VERSION@"
 
 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 \
 TEMP=`getopt \
         -o a:c:d:vVx \
-        --long help,add:,connect:,domain:,enable:,format::,hostname:,list-operations,verbose,version \
+        --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"
         -n $program -- "$@"`
 if [ $? != 0 ]; then
     echo "$program: problem parsing the command line arguments"
@@ -31,18 +34,15 @@ if [ $? != 0 ]; then
 fi
 eval set -- "$TEMP"
 
 fi
 eval set -- "$TEMP"
 
-# This array accumulates the arguments we pass through to guestfish.
-declare -a guestfish
-guestfish[0]="guestfish"
-guestfish[1]="--rw"
-guestfish[2]="--listen"
-guestfish[3]="-i"
-i=4
+# This array accumulates the arguments we pass through to guestmount.
+declare -a params
+i=0
 
 verbose=
 add_params=0
 enable=
 hostname_param=localhost.localdomain
 
 verbose=
 add_params=0
 enable=
 hostname_param=localhost.localdomain
+selinux_relabel=auto
 
 usage ()
 {
 
 usage ()
 {
@@ -59,17 +59,17 @@ usage ()
 while true; do
     case "$1" in
         -a|--add)
 while true; do
     case "$1" in
         -a|--add)
-            guestfish[i++]="-a"
-            guestfish[i++]="$2"
+            params[i++]="-a"
+            params[i++]="$2"
             ((add_params++))
             shift 2;;
         -c|--connect)
             ((add_params++))
             shift 2;;
         -c|--connect)
-            guestfish[i++]="-c"
-            guestfish[i++]="$2"
+            params[i++]="-c"
+            params[i++]="$2"
             shift 2;;
         -d|--domain)
             shift 2;;
         -d|--domain)
-            guestfish[i++]="-d"
-            guestfish[i++]="$2"
+            params[i++]="-d"
+            params[i++]="$2"
             ((add_params++))
             shift 2;;
         --enable)
             ((add_params++))
             shift 2;;
         --enable)
@@ -81,9 +81,9 @@ while true; do
             shift 2;;
         --format)
             if [ -n "$2" ]; then
             shift 2;;
         --format)
             if [ -n "$2" ]; then
-                guestfish[i++]="--format=$2"
+                params[i++]="--format=$2"
             else
             else
-                guestfish[i++]="--format"
+                params[i++]="--format"
             fi
             shift 2;;
         --help)
             fi
             shift 2;;
         --help)
@@ -94,15 +94,29 @@ while true; do
         --list-operations)
             enable=list
             shift;;
         --list-operations)
             enable=list
             shift;;
+        --selinux-relabel)
+            selinux_relabel=yes
+            shift;;
+        --no-selinux-relabel)
+            selinux_relabel=no
+            shift;;
         -v|--verbose)
         -v|--verbose)
-            guestfish[i++]="-v"
+            params[i++]="-v"
             verbose=yes
             shift;;
         -V|--version)
             echo "$program $version"
             exit 0;;
         -x)
             verbose=yes
             shift;;
         -V|--version)
             echo "$program $version"
             exit 0;;
         -x)
-            guestfish[i++]="-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
             shift;;
         --)
             shift
@@ -117,23 +131,41 @@ done
 # enable all of these, although some of them are only done on certain
 # guest types (see details below).
 if [ -z "$enable" ]; then
 # enable all of these, although some of them are only done on certain
 # guest types (see details below).
 if [ -z "$enable" ]; then
+    dhcp_client_state=yes
+    dhcp_server_state=yes
     hostname=yes
     hostname=yes
+    logfiles=yes
     net_hwaddr=yes
     net_hwaddr=yes
+    random_seed=yes
+    smolt_uuid=yes
     ssh_hostkeys=yes
     udev_persistent_net=yes
     ssh_hostkeys=yes
     udev_persistent_net=yes
+    yum_uuid=yes
 elif [ "$enable" = "list" ]; then
 elif [ "$enable" = "list" ]; then
+    echo "dhcp-client-state"
+    echo "dhcp-server-state"
     echo "hostname"
     echo "hostname"
+    echo "logfiles"
     echo "net-hwaddr"
     echo "net-hwaddr"
+    echo "random-seed"
+    echo "smolt-uuid"
     echo "ssh-hostkeys"
     echo "udev-persistent-net"
     echo "ssh-hostkeys"
     echo "udev-persistent-net"
+    echo "yum-uuid"
     exit 0
 else
     for opt in $(echo "$enable" | sed 's/,/ /g'); do
         case "$opt" in
     exit 0
 else
     for opt in $(echo "$enable" | sed 's/,/ /g'); do
         case "$opt" in
+            dhcp-client-state)     dhcp_client_state=yes ;;
+            dhcp-server-state)     dhcp_server_state=yes ;;
             hostname)              hostname=yes ;;
             hostname)              hostname=yes ;;
+            logfiles)              logfiles=yes ;;
             net-hwaddr)            net_hwaddr=yes ;;
             net-hwaddr)            net_hwaddr=yes ;;
+            random-seed)           random_seed=yes ;;
+            smolt-uuid)            smolt_uuid=yes ;;
             ssh-hostkeys)          ssh_hostkeys=yes ;;
             udev-persistent-net)   udev_persistent_net=yes ;;
             ssh-hostkeys)          ssh_hostkeys=yes ;;
             udev-persistent-net)   udev_persistent_net=yes ;;
+            yum-uuid)              yum_uuid=yes ;;
             *)
                 echo "error: unknown --enable feature: $opt"
                 exit 1
             *)
                 echo "error: unknown --enable feature: $opt"
                 exit 1
@@ -161,126 +193,99 @@ fi
 set -e
 
 if [ "$verbose" = "yes" ]; then
 set -e
 
 if [ "$verbose" = "yes" ]; then
-    echo command: "${guestfish[@]}"
+    echo params: "${params[@]}"
 fi
 
 # Create a temporary directory for general purpose use during operations.
 tmpdir="$(mktemp -d)"
 
 fi
 
 # Create a temporary directory for general purpose use during operations.
 tmpdir="$(mktemp -d)"
 
-# Increase the amount of memory allocated to the appliance because
-# we're using augeas.  The user can override this by setting
-# $LIBGUESTFS_MEMSIZE before running the script.
-export LIBGUESTFS_MEMSIZE=${LIBGUESTFS_MEMSIZE:-2048}
-
-# Call guestfish.
-GUESTFISH_PID=
-eval $("${guestfish[@]}")
-if [ -z "$GUESTFISH_PID" ]; then
-    echo "$program: guestfish didn't start up, see error messages above"
-    exit 1
-fi
-
-# Helper.
-gf="guestfish --remote --"
-
 cleanup ()
 {
 cleanup ()
 {
-    $gf exit >/dev/null 2>&1 ||:
-    rm -rf "$tmpdir" ||:
+    if [ -d $tmpdir/mnt ]; then
+        fusermount -u $tmpdir/mnt >/dev/null 2>&1 ||:
+    fi
+    rm -rf $tmpdir ||:
 }
 trap cleanup EXIT ERR
 
 }
 trap cleanup EXIT ERR
 
-# Launch back-end, inspect for operating systems, and get the guest
-# root disk.
-root=$($gf inspect-get-roots)
-
-if [ "$root" = "" ]; then
-    echo "$program: no operating system was found on this disk"
-    exit 1
-fi
-
-if [ "$verbose" = "yes" ]; then
-    echo root: "$root"
-fi
-
-# Get the guest type.
-type="$($gf -inspect-get-type $root)"
+# Run virt-inspector and grab inspection information about this guest.
+virt-inspector "${params[@]}" > $tmpdir/xml
+xmlstarlet sel -t -c \
+    "string(/operatingsystems/operatingsystem[position()=1]/name)" \
+    $tmpdir/xml > $tmpdir/type
+xmlstarlet sel -t -c \
+    "string(/operatingsystems/operatingsystem[position()=1]/distro)" \
+    $tmpdir/xml > $tmpdir/distro ||:
+xmlstarlet sel -t -c \
+    "string(/operatingsystems/operatingsystem[position()=1]/package_format)" \
+    $tmpdir/xml > $tmpdir/package_format ||:
+xmlstarlet sel -t -c \
+    "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"
 
 
-if [ "$type" = "linux" ]; then
-    distro="$($gf -inspect-get-distro $root)"
-fi
+#----------------------------------------------------------------------
+# The sysprep operations.
 
 
-if [ "$type" = "windows" ]; then
-    systemroot="$($gf -inspect-get-windows-systemroot $root)"
+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
 
 fi
 
-# Start Augeas if it's a Linux guest.
-if [ "$type" = "linux" ]; then
-    $gf aug-init / 0
-    using_augeas=yes
+if [ "$dhcp_server_state" = "yes" ]; then
+    case "$type" in
+        linux)
+            rm -rf $mnt/var/lib/dhcpd/*
+            ;;
+    esac
 fi
 
 fi
 
-#----------------------------------------------------------------------
-# Useful functions.
-
-# erase_line filename regex
-#
-# Erase line(s) in a file that match the given regex.
-erase_line ()
-{
-    $gf download "$1" "$tmpdir/file"
-    sed "/$2/d" < "$tmpdir/file" > "$tmpdir/file.1"
-    $gf upload "$tmpdir/file.1" "$1"
-}
-
-# prepend_line filename line
-#
-# Prepend a line to a file (this is better than appending, because it
-# works even when the original file isn't terminated with a newline).
-prepend_line ()
-{
-    $gf download "$1" "$tmpdir/file"
-    echo "$2" > "$tmpdir/file.1"
-    cat "$tmpdir/file.1" "$tmpdir/file" >> "$tmpdir/file.2"
-    $gf upload "$tmpdir/file.2" "$1"
-}
-
-# rm_files wildcard
-#
-# Remove files.  Doesn't fail if no files exist.  Note the wildcard
-# parameter cannot contain spaces or characters that need special
-# quoting.
-rm_files ()
-{
-    files=$($gf glob-expand "$1")
-    for f in $files; do
-        $gf rm "$f"
-    done
-}
-
-# rm_file filename
-#
-# Remove a single file.  No error if the file doesn't exist or is not
-# a file.
-rm_file ()
-{
-    t=$($gf is-file "$1")
-    if [ "$t" = "true" ]; then
-        $gf rm "$1"
-    fi
-}
-
-#----------------------------------------------------------------------
-# The sysprep operations.
-
 if [ "$hostname" = "yes" ]; then
     case "$type/$distro" in
         linux/fedora)
 if [ "$hostname" = "yes" ]; then
     case "$type/$distro" in
         linux/fedora)
-            $gf aug-set /files/etc/sysconfig/network/HOSTNAME "$hostname_param"
-            augeas_save_needed=yes
+            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)
             ;;
         linux/debian|linux/ubuntu)
-            $gf write /etc/hostname "$hostname_param"
+            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
             ;;
     esac
 fi
@@ -288,35 +293,77 @@ fi
 if [ "$net_hwaddr" = "yes" ]; then
     case "$type/$distro" in
         linux/fedora)
 if [ "$net_hwaddr" = "yes" ]; then
     case "$type/$distro" in
         linux/fedora)
-            # XXX these filenames can have spaces and untrusted chars in them!
-            nodes=$( $gf aug-ls /files/etc/sysconfig/network-scripts |
-                     grep /files/etc/sysconfig/network-scripts/ifcfg- )
-            for node in $nodes; do
-                $gf -aug-rm "$node/HWADDR" >/dev/null
-                augeas_save_needed=yes
-            done
+            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
 
             ;;
     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/random of="$f" bs=8 count=1 conv=nocreat,notrunc 2>/dev/null
+    fi
+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
 if [ "$ssh_hostkeys" = "yes" -a "$type" != "windows" ]; then
-    rm_files "/etc/ssh/*_host_*"
+    rm -rf $mnt/etc/ssh/*_host_*
 fi
 
 if [ "$udev_persistent_net" = "yes" -a "$type" = "linux" ]; then
 fi
 
 if [ "$udev_persistent_net" = "yes" -a "$type" = "linux" ]; then
-    rm_file /etc/udev/rules.d/70-persistent-net.rules
+    rm -f $mnt/etc/udev/rules.d/70-persistent-net.rules
+fi
+
+if [ "$yum_uuid" = "yes" -a "$package_management" = "yum" ]; then
+    rm -f $mnt/var/lib/yum/uuid
 fi
 
 #----------------------------------------------------------------------
 # Clean up and close down.
 
 fi
 
 #----------------------------------------------------------------------
 # Clean up and close down.
 
-if [ "$using_augeas" = "yes" -a "$augeas_save_needed" = "yes" ]; then
-    $gf aug-save
-    $gf aug-close
-fi
+# 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
+
+fusermount -u $tmpdir/mnt
+rm -rf $tmpdir
 
 
-$gf umount-all
-$gf sync
-$gf exit
+trap - EXIT ERR
 
 exit 0
 
 exit 0
index cc8e44f..fa10b9e 100755 (executable)
@@ -26,6 +26,12 @@ 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.
 
 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,
 "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,
@@ -119,6 +125,16 @@ If not given, defaults to C<localhost.localdomain>.
 
 List the operations supported by the virt-sysprep program.
 
 
 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>
 =item B<-v>
 
 =item B<--verbose>
@@ -151,22 +167,45 @@ Use a comma-separated list, for example:
 To list the operations supported by the current version of
 virt-sysprep, use I<--list-operations>.
 
 To list the operations supported by the current version of
 virt-sysprep, use I<--list-operations>.
 
+=head2 dhcp-client-state
+
+Remove DHCP client leases.
+
+=head2 dhcp-server-state
+
+Remove DHCP server leases.
+
 =head2 hostname
 
 =head2 hostname
 
-This changes the hostname of the guest to the value given in the
+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>.
 
 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 net-hwaddr
 
 Remove HWADDR (hard-coded MAC address) configuration.  For Fedora and
 Red Hat Enterprise Linux, this is removed from C<ifcfg-*> files.
 
 =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 C</RANDOM SEED> below.
+
+=head2 smolt-uuid
+
+Remove the Smolt hardware UUID.
+
 =head2 ssh-hostkeys
 
 =head2 ssh-hostkeys
 
-This erases the SSH host keys in the guest.
+Remove the SSH host keys in the guest.
 
 The SSH host keys are regenerated (differently) next time the guest is
 booted.
 
 The SSH host keys are regenerated (differently) next time the guest is
 booted.
@@ -181,14 +220,21 @@ you a stark warning about the host key changing:
 
 =head2 udev-persistent-net
 
 
 =head2 udev-persistent-net
 
-This erases udev persistent net rules which map the guest's existing
-MAC address to a fixed ethernet device (eg. eth0).
+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.
 
 
 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 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
 =head1 COPYING AND CLONING
 
 Virt-sysprep can be used as part of a process of cloning guests, or to
@@ -356,6 +402,54 @@ 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.
 
 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
 =head1 SHELL QUOTING
 
 Libvirt guest names can contain arbitrary characters, some of which
@@ -376,8 +470,9 @@ L<virt-rescue(1)>,
 L<virt-resize(1)>,
 L<virt-sparsify(1)>,
 L<virsh(1)>,
 L<virt-resize(1)>,
 L<virt-sparsify(1)>,
 L<virsh(1)>,
-L<qemu-img(1)>,
 L<lvcreate(8)>,
 L<lvcreate(8)>,
+L<qemu-img(1)>,
+L<scrub(1)>,
 L<http://libguestfs.org/>,
 L<http://libvirt.org/>.
 
 L<http://libguestfs.org/>,
 L<http://libvirt.org/>.