From 026f616650c41e42b7c3058a8637fe2d76bfb793 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Sat, 31 Mar 2012 11:44:00 +0100 Subject: Rewrite virt-sysprep. --- .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 +- po-docs/ja/Makefile.am | 9 + po-docs/podfiles | 4 +- po-docs/uk/Makefile.am | 9 + src/guestfs.pod | 9 +- sysprep/Makefile.am | 150 +++++++ sysprep/main.ml | 232 ++++++++++ sysprep/sysprep_operation.ml | 183 ++++++++ sysprep/sysprep_operation.mli | 99 +++++ sysprep/sysprep_operation_cron_spool.ml | 34 ++ sysprep/sysprep_operation_dhcp_client_state.ml | 39 ++ sysprep/sysprep_operation_dhcp_server_state.ml | 34 ++ sysprep/sysprep_operation_hostname.ml | 69 +++ sysprep/sysprep_operation_logfiles.ml | 53 +++ sysprep/sysprep_operation_mail_spool.ml | 39 ++ sysprep/sysprep_operation_net_hwaddr.ml | 54 +++ sysprep/sysprep_operation_random_seed.ml | 57 +++ sysprep/sysprep_operation_rhn_systemid.ml | 40 ++ sysprep/sysprep_operation_smolt_uuid.ml | 44 ++ sysprep/sysprep_operation_ssh_hostkeys.ml | 51 +++ sysprep/sysprep_operation_udev_persistent_net.ml | 46 ++ sysprep/sysprep_operation_utmp.ml | 43 ++ sysprep/sysprep_operation_yum_uuid.ml | 42 ++ sysprep/test-virt-sysprep.sh | 33 ++ sysprep/utils.ml | 71 +++ sysprep/virt-sysprep.pod | 435 +++++++++++++++++++ tests/extra/Makefile.am | 4 +- 34 files changed, 1883 insertions(+), 1051 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_cron_spool.ml create mode 100644 sysprep/sysprep_operation_dhcp_client_state.ml create mode 100644 sysprep/sysprep_operation_dhcp_server_state.ml create mode 100644 sysprep/sysprep_operation_hostname.ml create mode 100644 sysprep/sysprep_operation_logfiles.ml create mode 100644 sysprep/sysprep_operation_mail_spool.ml create mode 100644 sysprep/sysprep_operation_net_hwaddr.ml create mode 100644 sysprep/sysprep_operation_random_seed.ml create mode 100644 sysprep/sysprep_operation_rhn_systemid.ml create mode 100644 sysprep/sysprep_operation_smolt_uuid.ml create mode 100644 sysprep/sysprep_operation_ssh_hostkeys.ml create mode 100644 sysprep/sysprep_operation_udev_persistent_net.ml create mode 100644 sysprep/sysprep_operation_utmp.ml create mode 100644 sysprep/sysprep_operation_yum_uuid.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 14d5c758..337ff751 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 de2d4caf..5f104fa9 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 4d586c67..00000000 --- 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 32173897..00000000 --- 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 d5055327..00000000 --- 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 5cab3eba..00000000 --- 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. The guest -must be shut down. If you want to preserve the existing contents of -the guest, you I. -See L below. - -You do I 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 which is used to unconfigure -Windows machines in preparation for cloning them. Having said that, -virt-sysprep does I 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 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. 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 and C 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 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. - - virt-sysprep --format=raw -a disk.img --format -a another.img - -forces raw format (no auto-detection) for C and reverts to -auto-detection for C. - -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 operation below. -If not given, defaults to C. - -=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 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 given, then -I, 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. - -=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 files. - -=head2 random-seed - -Write some random bytes from the host into the random seed file of -the guest. - -See L 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 - -The configuration or description of the guest. eg. The libvirt -XML (see C), 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 - -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 or L. - - 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 (with the I option), -L (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. See below. - -=back - -=head2 VIRT-CLONE - -A separate tool, L, 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 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 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
). - -=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 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 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 as one way to remove this content. See -also the L 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 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 cloning -from a template too, which can be done by just enabling the -C 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 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 for details. - -=head1 EXIT STATUS - -This program returns 0 on success, or 1 if there was an error. - -=head1 SEE ALSO - -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L, -L. - -=head1 AUTHOR - -Richard W.M. Jones L - -=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 b8cc8030..6d344602 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 dc12dc5e..83c408d4 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/po-docs/ja/Makefile.am b/po-docs/ja/Makefile.am index 2aa26bf8..a3350219 100644 --- a/po-docs/ja/Makefile.am +++ b/po-docs/ja/Makefile.am @@ -70,6 +70,15 @@ guestfish.1: guestfish.pod guestfish-actions.pod guestfish-commands.pod --insert $(srcdir)/guestfish-commands.pod:@FISH_COMMANDS@ \ $< +virt-sysprep.1: 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 $@ + %.1: %.pod $(top_builddir)/podwrapper.sh --man $@ $< diff --git a/po-docs/podfiles b/po-docs/podfiles index d207ba32..a9139845 100644 --- a/po-docs/podfiles +++ b/po-docs/podfiles @@ -3,7 +3,6 @@ ../cat/virt-cat.pod ../cat/virt-filesystems.pod ../cat/virt-ls.pod -../clone/virt-sysprep.pod ../df/virt-df.pod ../edit/virt-edit.pod ../erlang/examples/guestfs-erlang.pod @@ -32,6 +31,9 @@ ../src/guestfs-availability.pod ../src/guestfs-structs.pod ../src/guestfs.pod +../sysprep/sysprep-extra-options.pod +../sysprep/sysprep-operations.pod +../sysprep/virt-sysprep.pod ../test-tool/libguestfs-test-tool.pod ../tools/virt-list-filesystems.pl ../tools/virt-list-partitions.pl diff --git a/po-docs/uk/Makefile.am b/po-docs/uk/Makefile.am index 2aa26bf8..a3350219 100644 --- a/po-docs/uk/Makefile.am +++ b/po-docs/uk/Makefile.am @@ -70,6 +70,15 @@ guestfish.1: guestfish.pod guestfish-actions.pod guestfish-commands.pod --insert $(srcdir)/guestfish-commands.pod:@FISH_COMMANDS@ \ $< +virt-sysprep.1: 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 $@ + %.1: %.pod $(top_builddir)/podwrapper.sh --man $@ $< diff --git a/src/guestfs.pod b/src/guestfs.pod index 05f5c746..e2377b82 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -3008,11 +3008,6 @@ The libguestfs appliance, build scripts and so on. The L, L and L commands and documentation. -=item C - -Tools for cloning virtual machines. Currently contains -L command and documentation. - =item C Outside contributions, experimental parts. @@ -3091,6 +3086,10 @@ L command and documentation. Source code to the C library. +=item C + +L command and documentation. + =item C 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 00000000..034252e0 --- /dev/null +++ b/sysprep/Makefile.am @@ -0,0 +1,150 @@ +# 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_cron_spool.ml \ + sysprep_operation_dhcp_client_state.ml \ + sysprep_operation_dhcp_server_state.ml \ + sysprep_operation_hostname.ml \ + sysprep_operation_logfiles.ml \ + sysprep_operation_mail_spool.ml \ + sysprep_operation_net_hwaddr.ml \ + sysprep_operation_random_seed.ml \ + sysprep_operation_rhn_systemid.ml \ + sysprep_operation_smolt_uuid.ml \ + sysprep_operation_ssh_hostkeys.ml \ + sysprep_operation_udev_persistent_net.ml \ + sysprep_operation_utmp.ml \ + sysprep_operation_yum_uuid.ml \ + utils.ml + +# Note this list must be in dependency order. +OBJECTS = \ + utils.cmx \ + sysprep_operation.cmx \ + sysprep_operation_cron_spool.cmx \ + sysprep_operation_dhcp_client_state.cmx \ + sysprep_operation_dhcp_server_state.cmx \ + sysprep_operation_hostname.cmx \ + sysprep_operation_logfiles.cmx \ + sysprep_operation_mail_spool.cmx \ + sysprep_operation_net_hwaddr.cmx \ + sysprep_operation_random_seed.cmx \ + sysprep_operation_rhn_systemid.cmx \ + sysprep_operation_smolt_uuid.cmx \ + sysprep_operation_ssh_hostkeys.cmx \ + sysprep_operation_udev_persistent_net.cmx \ + sysprep_operation_utmp.cmx \ + sysprep_operation_yum_uuid.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 00000000..d06b0d2a --- /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"; + ] @ Sysprep_operation.extra_args () 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 00000000..316b35be --- /dev/null +++ b/sysprep/sysprep_operation.ml @@ -0,0 +1,183 @@ +(* 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 [] + +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 = + let op = List.find (fun { name = n } -> name = n) !ops in + OperationSet.add op set + +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 + check_no_dupes ops'; + List.iter check ops'; + ops := ops'; + baked := true +and check_no_dupes ops = + ignore ( + List.fold_left ( + fun opset op -> + if OperationSet.mem op opset then ( + eprintf "virt-sysprep: duplicate operation name (%s)\n" op.name; + exit 1 + ); + add_to_set op.name opset + ) empty_set ops + ) +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 + ) + +let extra_args () = + assert !baked; + + List.flatten ( + List.map (fun { extra_args = extra_args } -> + List.map fst extra_args + ) !ops + ) + +(* 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 + +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 00000000..5fe035f6 --- /dev/null +++ b/sysprep/sysprep_operation.mli @@ -0,0 +1,99 @@ +(* 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 extra_args : unit -> (Arg.key * Arg.spec * Arg.doc) list +(** Get the list of extra arguments for the command line. *) + +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_cron_spool.ml b/sysprep/sysprep_operation_cron_spool.ml new file mode 100644 index 00000000..e67688bf --- /dev/null +++ b/sysprep/sysprep_operation_cron_spool.ml @@ -0,0 +1,34 @@ +(* 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 cron_spool_perform g root = + Array.iter g#rm_rf (g#glob_expand "/var/spool/cron/*"); + [] + +let cron_spool_op = { + name = "cron-spool"; + pod_description = "Remove user at-jobs and cron-jobs."; + extra_args = []; + perform = cron_spool_perform; +} + +let () = register_operation cron_spool_op diff --git a/sysprep/sysprep_operation_dhcp_client_state.ml b/sysprep/sysprep_operation_dhcp_client_state.ml new file mode 100644 index 00000000..e3e87cb2 --- /dev/null +++ b/sysprep/sysprep_operation_dhcp_client_state.ml @@ -0,0 +1,39 @@ +(* 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 dhcp_client_state_perform g root = + let typ = g#inspect_get_type root in + if typ = "linux" then ( + List.iter ( + fun glob -> Array.iter g#rm_rf (g#glob_expand glob) + ) [ "/var/lib/dhclient/*"; "/var/lib/dhcp/*" (* RHEL 3 *) ] + ); + [] + +let dhcp_client_state_op = { + name = "dhcp-client-state"; + pod_description = "Remove DHCP client leases."; + extra_args = []; + perform = dhcp_client_state_perform; +} + +let () = register_operation dhcp_client_state_op diff --git a/sysprep/sysprep_operation_dhcp_server_state.ml b/sysprep/sysprep_operation_dhcp_server_state.ml new file mode 100644 index 00000000..c5251ce4 --- /dev/null +++ b/sysprep/sysprep_operation_dhcp_server_state.ml @@ -0,0 +1,34 @@ +(* 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 dhcp_server_state_perform g root = + Array.iter g#rm_rf (g#glob_expand "/var/lib/dhcpd/*"); + [] + +let dhcp_server_state_op = { + name = "dhcp-server-state"; + pod_description = "Remove DHCP server leases."; + extra_args = []; + perform = dhcp_server_state_perform; +} + +let () = register_operation dhcp_server_state_op diff --git a/sysprep/sysprep_operation_hostname.ml b/sysprep/sysprep_operation_hostname.ml new file mode 100644 index 00000000..1472a1c4 --- /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."; + extra_args = [ + ("--hostname", Arg.Set_string hostname, "hostname New hostname"), + "\ +Change the hostname. If not given, defaults to C." + ]; + perform = hostname_perform; +} + +let () = register_operation hostname_op diff --git a/sysprep/sysprep_operation_logfiles.ml b/sysprep/sysprep_operation_logfiles.ml new file mode 100644 index 00000000..910a9fb0 --- /dev/null +++ b/sysprep/sysprep_operation_logfiles.ml @@ -0,0 +1,53 @@ +(* 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 logfiles_perform g root = + let typ = g#inspect_get_type root in + if typ = "linux" then ( + List.iter ( + fun glob -> Array.iter g#rm_rf (g#glob_expand glob) + ) [ + "/var/log/*.log*"; + "/var/log/audit/*"; + "/var/log/btmp*"; + "/var/log/cron*"; + "/var/log/dmesg*"; + "/var/log/lastlog*"; + "/var/log/maillog*"; + "/var/log/mail/*"; + "/var/log/messages*"; + "/var/log/secure*"; + "/var/log/spooler*"; + "/var/log/tallylog*"; + "/var/log/wtmp*"; + ] + ); + [] + +let logfiles_op = { + name = "logfiles"; + pod_description = "Remove many log files."; + extra_args = []; + perform = logfiles_perform; +} + +let () = register_operation logfiles_op diff --git a/sysprep/sysprep_operation_mail_spool.ml b/sysprep/sysprep_operation_mail_spool.ml new file mode 100644 index 00000000..74f2d941 --- /dev/null +++ b/sysprep/sysprep_operation_mail_spool.ml @@ -0,0 +1,39 @@ +(* 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 mail_spool_perform g root = + List.iter ( + fun glob -> Array.iter g#rm_rf (g#glob_expand glob) + ) [ + "/var/spool/mail/*"; + "/var/mail/*"; + ]; + [] + +let mail_spool_op = { + name = "mail-spool"; + pod_description = "Remove email from the local mail spool directory."; + extra_args = []; + perform = mail_spool_perform; +} + +let () = register_operation mail_spool_op diff --git a/sysprep/sysprep_operation_net_hwaddr.ml b/sysprep/sysprep_operation_net_hwaddr.ml new file mode 100644 index 00000000..8aa102a9 --- /dev/null +++ b/sysprep/sysprep_operation_net_hwaddr.ml @@ -0,0 +1,54 @@ +(* 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 Utils +open Sysprep_operation + +module G = Guestfs + +let net_hwaddr_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") -> + let filenames = g#glob_expand "/etc/sysconfig/network-scripts/ifcfg-*" in + Array.iter ( + fun filename -> + (* Replace HWADDR=... entry. *) + let lines = Array.to_list (g#read_lines filename) in + let lines = List.filter ( + fun line -> not (string_prefix line "HWADDR=") + ) lines in + let file = String.concat "\n" lines ^ "\n" in + g#write filename file + ) filenames; + + if filenames <> [||] then [ `Created_files ] else [] + + | _ -> [] + +let net_hwaddr_op = { + name = "net-hwaddr"; + pod_description = "\ +Remove HWADDR (hard-coded MAC address) configuration. For Fedora and +Red Hat Enterprise Linux, this is removed from C files."; + extra_args = []; + perform = net_hwaddr_perform; +} + +let () = register_operation net_hwaddr_op diff --git a/sysprep/sysprep_operation_random_seed.ml b/sysprep/sysprep_operation_random_seed.ml new file mode 100644 index 00000000..989ffe82 --- /dev/null +++ b/sysprep/sysprep_operation_random_seed.ml @@ -0,0 +1,57 @@ +(* 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 random_seed_perform g root = + let typ = g#inspect_get_type root in + if typ = "linux" then ( + let files = [ + "/var/lib/random-seed"; (* Fedora *) + "/var/lib/urandom/random-seed"; (* Debian *) + ] in + List.iter ( + fun file -> + if g#is_file file then ( + (* Get 8 bytes of randomness from the host. *) + let chan = open_in "/dev/urandom" in + let buf = String.create 8 in + really_input chan buf 0 8; + close_in chan; + + g#write file buf + ) + ) files; + [ `Created_files ] + ) + else [] + +let random_seed_op = { + name = "random-seed"; + pod_description = "\ +Write some random bytes from the host into the random seed file of the +guest. + +See L below."; + extra_args = []; + perform = random_seed_perform; +} + +let () = register_operation random_seed_op diff --git a/sysprep/sysprep_operation_rhn_systemid.ml b/sysprep/sysprep_operation_rhn_systemid.ml new file mode 100644 index 00000000..35849e41 --- /dev/null +++ b/sysprep/sysprep_operation_rhn_systemid.ml @@ -0,0 +1,40 @@ +(* 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 rhn_systemid_perform g root = + let typ = g#inspect_get_type root in + let distro = g#inspect_get_distro root in + + match typ, distro with + | "linux", "rhel" -> + (try g#rm "/etc/sysconfig/rhn/systemid" with G.Error _ -> ()); + [] + | _ -> [] + +let rhn_systemid_op = { + name = "rhn-systemid"; + pod_description = "Remove the RHN system ID."; + extra_args = []; + perform = rhn_systemid_perform; +} + +let () = register_operation rhn_systemid_op diff --git a/sysprep/sysprep_operation_smolt_uuid.ml b/sysprep/sysprep_operation_smolt_uuid.ml new file mode 100644 index 00000000..a85aa9b3 --- /dev/null +++ b/sysprep/sysprep_operation_smolt_uuid.ml @@ -0,0 +1,44 @@ +(* 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 smolt_uuid_perform g root = + let typ = g#inspect_get_type root in + if typ = "linux" then ( + let files = [ "/etc/sysconfig/hw-uuid"; + "/etc/smolt/uuid"; + "/etc/smolt/hw-uuid" ] in + List.iter ( + fun file -> try g#rm file with G.Error _ -> () + ) files; + + [] + ) + else [] + +let smolt_uuid_op = { + name = "smolt-uuid"; + pod_description = "Remove the Smolt hardware UUID."; + extra_args = []; + perform = smolt_uuid_perform; +} + +let () = register_operation smolt_uuid_op diff --git a/sysprep/sysprep_operation_ssh_hostkeys.ml b/sysprep/sysprep_operation_ssh_hostkeys.ml new file mode 100644 index 00000000..8da405b4 --- /dev/null +++ b/sysprep/sysprep_operation_ssh_hostkeys.ml @@ -0,0 +1,51 @@ +(* 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 ssh_hostkeys_perform g root = + let typ = g#inspect_get_type root in + if typ <> "windows" then ( + let files = g#glob_expand "/etc/ssh/*_host_*" in + Array.iter g#rm files; + [] + ) + else [] + +let ssh_hostkeys_op = { + name = "ssh-hostkeys"; + pod_description = "\ +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!"; + extra_args = []; + perform = ssh_hostkeys_perform; +} + +let () = register_operation ssh_hostkeys_op diff --git a/sysprep/sysprep_operation_udev_persistent_net.ml b/sysprep/sysprep_operation_udev_persistent_net.ml new file mode 100644 index 00000000..e54e1404 --- /dev/null +++ b/sysprep/sysprep_operation_udev_persistent_net.ml @@ -0,0 +1,46 @@ +(* 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 udev_persistent_net_perform g root = + let typ = g#inspect_get_type root in + if typ = "linux" then ( + (try g#rm "/etc/udev/rules.d/70-persistent-net.rules" + with G.Error _ -> ()); + [] + ) + else [] + +let udev_persistent_net_op = { + name = "udev-persistent-net"; + pod_description = "\ +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."; + extra_args = []; + perform = udev_persistent_net_perform; +} + +let () = register_operation udev_persistent_net_op diff --git a/sysprep/sysprep_operation_utmp.ml b/sysprep/sysprep_operation_utmp.ml new file mode 100644 index 00000000..69867e15 --- /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/sysprep_operation_yum_uuid.ml b/sysprep/sysprep_operation_yum_uuid.ml new file mode 100644 index 00000000..396cac54 --- /dev/null +++ b/sysprep/sysprep_operation_yum_uuid.ml @@ -0,0 +1,42 @@ +(* 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 yum_uuid_perform g root = + let packager = g#inspect_get_package_management root in + if packager = "yum" then ( + (try g#rm "/var/lib/yum/uuid" with G.Error _ -> ()); + [] + ) + else [] + +let yum_uuid_op = { + name = "yum-uuid"; + pod_description = "\ +Remove the yum UUID. + +Yum creates a fresh UUID the next time it runs when it notices that the +original UUID has been erased."; + extra_args = []; + perform = yum_uuid_perform; +} + +let () = register_operation yum_uuid_op diff --git a/sysprep/test-virt-sysprep.sh b/sysprep/test-virt-sysprep.sh new file mode 100755 index 00000000..9b800542 --- /dev/null +++ b/sysprep/test-virt-sysprep.sh @@ -0,0 +1,33 @@ +#!/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 + +qemu-img create -f qcow2 -o backing_file=../tests/guests/fedora.img test.img + +./virt-sysprep -a test.img + +rm -f test.img diff --git a/sysprep/utils.ml b/sysprep/utils.ml new file mode 100644 index 00000000..dfac57d4 --- /dev/null +++ b/sysprep/utils.ml @@ -0,0 +1,71 @@ +(* 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 () = Random.self_init () + +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 00000000..8120c78d --- /dev/null +++ b/sysprep/virt-sysprep.pod @@ -0,0 +1,435 @@ +=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. The guest +must be shut down. If you want to preserve the existing contents of +the guest, you I. +See L below. + +You do I 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 which is used to unconfigure +Windows machines in preparation for cloning them. Having said that, +virt-sysprep does I 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 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. 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 and C 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 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. + + virt-sysprep --format raw -a disk.img --format auto -a another.img + +forces raw format (no auto-detection) for C and reverts to +auto-detection for C. + +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 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. + +@EXTRA_OPTIONS@ + +=back + +=head1 OPERATIONS + +If the I<--enable> option is I given, then +I, 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. + +@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 + +The configuration or description of the guest. eg. The libvirt +XML (see C), 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 + +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 or L. + + 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 (with the I option), +L (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. See below. + +=back + +=head2 VIRT-CLONE + +A separate tool, L, 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 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 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
). + +=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 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 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 as one way to remove this content. See +also the L 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 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 cloning +from a template too, which can be done by just enabling the +C 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 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 for details. + +=head1 EXIT STATUS + +This program returns 0 on success, or 1 if there was an error. + +=head1 SEE ALSO + +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L, +L. + +=head1 AUTHOR + +Richard W.M. Jones L + +=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 b807048b..baba4a33 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 -- cgit