virt-sysprep: Don't use xmlstarlet.
[libguestfs.git] / clone / virt-sysprep.in
index b73acfb..9150872 100644 (file)
@@ -21,9 +21,12 @@ 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,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"
@@ -31,18 +34,15 @@ if [ $? != 0 ]; then
 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
+selinux_relabel=auto
 
 usage ()
 {
@@ -59,17 +59,17 @@ usage ()
 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)
-            guestfish[i++]="-c"
-            guestfish[i++]="$2"
+            params[i++]="-c"
+            params[i++]="$2"
             shift 2;;
         -d|--domain)
-            guestfish[i++]="-d"
-            guestfish[i++]="$2"
+            params[i++]="-d"
+            params[i++]="$2"
             ((add_params++))
             shift 2;;
         --enable)
@@ -81,9 +81,9 @@ while true; do
             shift 2;;
         --format)
             if [ -n "$2" ]; then
-                guestfish[i++]="--format=$2"
+                params[i++]="--format=$2"
             else
-                guestfish[i++]="--format"
+                params[i++]="--format"
             fi
             shift 2;;
         --help)
@@ -94,15 +94,29 @@ while true; do
         --list-operations)
             enable=list
             shift;;
+        --selinux-relabel)
+            selinux_relabel=yes
+            shift;;
+        --no-selinux-relabel)
+            selinux_relabel=no
+            shift;;
         -v|--verbose)
-            guestfish[i++]="-v"
+            params[i++]="-v"
             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
@@ -117,23 +131,53 @@ done
 # 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
@@ -161,129 +205,194 @@ fi
 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)"
 
-# 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
-
 cleanup ()
 {
-    kill $GUESTFISH_PID >/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
-
-# Launch back-end, inspect for operating systems, and get the guest
-# root disk.
-root=$(guestfish --remote inspect-get-roots)
+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"
 
-if [ "$root" = "" ]; then
-    echo "$program: no operating system was found on this disk"
-    exit 1
-fi
+#----------------------------------------------------------------------
+# The sysprep operations.
 
-if [ "$verbose" = "yes" ]; then
-    echo root: "$root"
+if [ "$cron_spool" = "yes" ]; then
+    rm -rf $mnt/var/spool/cron/*
 fi
 
-# Get the guest type.
-type="$(guestfish --remote -- -inspect-get-type $root)"
-
-if [ "$type" = "linux" ]; then
-    distro="$(guestfish --remote -- -inspect-get-distro $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
 
-if [ "$type" = "windows" ]; then
-    systemroot="$(guestfish --remote -- -inspect-get-windows-systemroot $root)"
+if [ "$dhcp_server_state" = "yes" ]; then
+    case "$type" in
+        linux)
+            rm -rf $mnt/var/lib/dhcpd/*
+            ;;
+    esac
 fi
 
-#----------------------------------------------------------------------
-# Useful functions.
-
-# erase_line filename regex
-#
-# Erase line(s) in a file that match the given regex.
-erase_line ()
-{
-    guestfish --remote -- download "$1" "$tmpdir/file"
-    sed "/$2/d" < "$tmpdir/file" > "$tmpdir/file.1"
-    guestfish --remote -- upload "$tmpdir/file.1" "$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=$(guestfish --remote -- glob-expand "$1")
-    for f in $files; do
-        guestfish --remote -- 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=$(guestfish --remote -- is-file "$1")
-    if [ "$t" = "true" ]; then
-        guestfish --remote -- rm "$1"
-    fi
-}
-
-#----------------------------------------------------------------------
-# The sysprep operations.
-
 if [ "$hostname" = "yes" ]; then
     case "$type/$distro" in
         linux/fedora)
-            guestfish --remote -- \
-                download /etc/sysconfig/network "$tmpdir/network"
-            echo "HOSTNAME=$hostname_param" > "$tmpdir/network.1"
-            sed '/^HOSTNAME=/d' < "$tmpdir/network" >> "$tmpdir/network.1"
-            guestfish --remote -- \
-                upload "$tmpdir/network.1" /etc/sysconfig/network ;;
+            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)
-            guestfish --remote -- 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
+
+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)
-            # XXX these filenames can have spaces and untrusted chars in them!
-            files=$(guestfish --remote -- glob-expand '/etc/sysconfig/network-scripts/ifcfg-*')
-            for f in $files; do
-                erase_line "$f" "^HWADDR="
-            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
 
+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_files "/etc/ssh/*_host_*"
+    rm -rf $mnt/etc/ssh/*_host_*
 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 [ "$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.
 
-guestfish --remote umount-all
-guestfish --remote sync
-guestfish --remote exit
+# 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
+
+trap - EXIT ERR
 
 exit 0