#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+# 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,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"
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 ()
{
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)
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)
--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
# 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
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
-
-# Helper.
-gf="guestfish --remote --"
-
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
-# Launch back-end, inspect for operating systems, and get the guest
-# root disk.
-root=$($gf inspect-get-roots)
+# 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="$($gf -inspect-get-type $root)"
-
-if [ "$type" = "linux" ]; then
- distro="$($gf -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="$($gf -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 ()
-{
- $gf download "$1" "$tmpdir/file"
- sed "/$2/d" < "$tmpdir/file" > "$tmpdir/file.1"
- $gf 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=$($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)
- $gf download /etc/sysconfig/network "$tmpdir/network"
- echo "HOSTNAME=$hostname_param" > "$tmpdir/network.1"
- sed '/^HOSTNAME=/d' < "$tmpdir/network" >> "$tmpdir/network.1"
- $gf 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)
- $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
+
+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=$($gf 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.
-$gf umount-all
-$gf sync
-$gf 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