summaryrefslogtreecommitdiffstats
path: root/clone
diff options
context:
space:
mode:
authorRichard W.M. Jones <rjones@redhat.com>2011-10-07 19:22:11 +0100
committerRichard W.M. Jones <rjones@redhat.com>2011-10-08 13:38:30 +0100
commit1e35941f62bddafd6b88270b22b3afe4a5d37baa (patch)
tree7cf9a782549e2b6a6aa8bf3e3db0e1b353579dfd /clone
parent3a546663655abfcd6399d4fdb7febc6b0d83b822 (diff)
downloadlibguestfs-1e35941f62bddafd6b88270b22b3afe4a5d37baa.tar.gz
libguestfs-1e35941f62bddafd6b88270b22b3afe4a5d37baa.tar.xz
libguestfs-1e35941f62bddafd6b88270b22b3afe4a5d37baa.zip
New tool: virt-sysprep: system preparation for guests.
Diffstat (limited to 'clone')
-rw-r--r--clone/Makefile.am51
-rwxr-xr-xclone/test-virt-sysprep.sh35
-rw-r--r--clone/virt-sysprep.in289
-rwxr-xr-xclone/virt-sysprep.pod404
4 files changed, 779 insertions, 0 deletions
diff --git a/clone/Makefile.am b/clone/Makefile.am
new file mode 100644
index 00000000..20e1d52e
--- /dev/null
+++ b/clone/Makefile.am
@@ -0,0 +1,51 @@
+# 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., 675 Mass Ave, Cambridge, MA 02139, 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) \
+ LD_LIBRARY_PATH=$(top_builddir)/src/.libs \
+ LIBGUESTFS_PATH=$(top_builddir)/appliance \
+ TMPDIR=$(top_builddir)
+
+TESTS = test-virt-sysprep.sh
diff --git a/clone/test-virt-sysprep.sh b/clone/test-virt-sysprep.sh
new file mode 100755
index 00000000..897afb70
--- /dev/null
+++ b/clone/test-virt-sysprep.sh
@@ -0,0 +1,35 @@
+#!/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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+export LANG=C
+set -e
+
+rm -f test.img guestfish
+
+qemu-img create -f qcow2 -o backing_file=../images/fedora.img test.img
+
+cat <<'EOF' > guestfish
+#!/bin/sh -
+../run ../fish/guestfish "$@"
+EOF
+chmod +x guestfish
+PATH=.:$PATH
+
+./virt-sysprep -a test.img
+
+rm -f test.img guestfish
diff --git a/clone/virt-sysprep.in b/clone/virt-sysprep.in
new file mode 100644
index 00000000..b73acfb8
--- /dev/null
+++ b/clone/virt-sysprep.in
@@ -0,0 +1,289 @@
+#!/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., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+unset CDPATH
+program="virt-sysprep"
+version="@PACKAGE_VERSION@"
+
+TEMP=`getopt \
+ -o a:c:d:vVx \
+ --long help,add:,connect:,domain:,enable:,format::,hostname:,list-operations,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 guestfish.
+declare -a guestfish
+guestfish[0]="guestfish"
+guestfish[1]="--rw"
+guestfish[2]="--listen"
+guestfish[3]="-i"
+i=4
+
+verbose=
+add_params=0
+enable=
+hostname_param=localhost.localdomain
+
+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)
+ guestfish[i++]="-a"
+ guestfish[i++]="$2"
+ ((add_params++))
+ shift 2;;
+ -c|--connect)
+ guestfish[i++]="-c"
+ guestfish[i++]="$2"
+ shift 2;;
+ -d|--domain)
+ guestfish[i++]="-d"
+ guestfish[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
+ guestfish[i++]="--format=$2"
+ else
+ guestfish[i++]="--format"
+ fi
+ shift 2;;
+ --help)
+ usage 0;;
+ --hostname)
+ hostname_param="$2"
+ shift 2;;
+ --list-operations)
+ enable=list
+ shift;;
+ -v|--verbose)
+ guestfish[i++]="-v"
+ verbose=yes
+ shift;;
+ -V|--version)
+ echo "$program $version"
+ exit 0;;
+ -x)
+ guestfish[i++]="-x"
+ 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
+ hostname=yes
+ net_hwaddr=yes
+ ssh_hostkeys=yes
+ udev_persistent_net=yes
+elif [ "$enable" = "list" ]; then
+ echo "hostname"
+ echo "net-hwaddr"
+ echo "ssh-hostkeys"
+ echo "udev-persistent-net"
+ exit 0
+else
+ for opt in $(echo "$enable" | sed 's/,/ /g'); do
+ case "$opt" in
+ hostname) hostname=yes ;;
+ net-hwaddr) net_hwaddr=yes ;;
+ ssh-hostkeys) ssh_hostkeys=yes ;;
+ udev-persistent-net) udev_persistent_net=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 command: "${guestfish[@]}"
+fi
+
+# Create a temporary directory for general purpose use during operations.
+tmpdir="$(mktemp -d)"
+
+# Call guestfish.
+GUESTFISH_PID=
+eval $("${guestfish[@]}")
+if [ -z "$GUESTFISH_PID" ]; then
+ echo "$program: guestfish didn't start up, see error messages above"
+ exit 1
+fi
+
+cleanup ()
+{
+ kill $GUESTFISH_PID >/dev/null 2>&1 ||:
+ rm -rf "$tmpdir" ||:
+}
+trap cleanup EXIT
+
+# Launch back-end, inspect for operating systems, and get the guest
+# root disk.
+root=$(guestfish --remote inspect-get-roots)
+
+if [ "$root" = "" ]; then
+ echo "$program: no operating system was found on this disk"
+ exit 1
+fi
+
+if [ "$verbose" = "yes" ]; then
+ echo root: "$root"
+fi
+
+# Get the guest type.
+type="$(guestfish --remote -- -inspect-get-type $root)"
+
+if [ "$type" = "linux" ]; then
+ distro="$(guestfish --remote -- -inspect-get-distro $root)"
+fi
+
+if [ "$type" = "windows" ]; then
+ systemroot="$(guestfish --remote -- -inspect-get-windows-systemroot $root)"
+fi
+
+#----------------------------------------------------------------------
+# Useful functions.
+
+# erase_line filename regex
+#
+# Erase line(s) in a file that match the given regex.
+erase_line ()
+{
+ guestfish --remote -- download "$1" "$tmpdir/file"
+ sed "/$2/d" < "$tmpdir/file" > "$tmpdir/file.1"
+ guestfish --remote -- upload "$tmpdir/file.1" "$1"
+}
+
+# rm_files wildcard
+#
+# Remove files. Doesn't fail if no files exist. Note the wildcard
+# parameter cannot contain spaces or characters that need special
+# quoting.
+rm_files ()
+{
+ files=$(guestfish --remote -- glob-expand "$1")
+ for f in $files; do
+ guestfish --remote -- rm "$f"
+ done
+}
+
+# rm_file filename
+#
+# Remove a single file. No error if the file doesn't exist or is not
+# a file.
+rm_file ()
+{
+ t=$(guestfish --remote -- is-file "$1")
+ if [ "$t" = "true" ]; then
+ guestfish --remote -- rm "$1"
+ fi
+}
+
+#----------------------------------------------------------------------
+# The sysprep operations.
+
+if [ "$hostname" = "yes" ]; then
+ case "$type/$distro" in
+ linux/fedora)
+ guestfish --remote -- \
+ download /etc/sysconfig/network "$tmpdir/network"
+ echo "HOSTNAME=$hostname_param" > "$tmpdir/network.1"
+ sed '/^HOSTNAME=/d' < "$tmpdir/network" >> "$tmpdir/network.1"
+ guestfish --remote -- \
+ upload "$tmpdir/network.1" /etc/sysconfig/network ;;
+ linux/debian|linux/ubuntu)
+ guestfish --remote -- write /etc/hostname "$hostname_param"
+ esac
+fi
+
+if [ "$net_hwaddr" = "yes" ]; then
+ case "$type/$distro" in
+ linux/fedora)
+ # XXX these filenames can have spaces and untrusted chars in them!
+ files=$(guestfish --remote -- glob-expand '/etc/sysconfig/network-scripts/ifcfg-*')
+ for f in $files; do
+ erase_line "$f" "^HWADDR="
+ done
+ esac
+fi
+
+if [ "$ssh_hostkeys" = "yes" -a "$type" != "windows" ]; then
+ rm_files "/etc/ssh/*_host_*"
+fi
+
+if [ "$udev_persistent_net" = "yes" -a "$type" = "linux" ]; then
+ rm_file /etc/udev/rules.d/70-persistent-net.rules
+fi
+
+# Clean up and close down.
+
+guestfish --remote umount-all
+guestfish --remote sync
+guestfish --remote exit
+
+exit 0
diff --git a/clone/virt-sysprep.pod b/clone/virt-sysprep.pod
new file mode 100755
index 00000000..cc8e44f4
--- /dev/null
+++ b/clone/virt-sysprep.pod
@@ -0,0 +1,404 @@
+=encoding utf8
+
+=head1 NAME
+
+virt-sysprep - Reset or unconfigure a virtual machine so clones can be made
+
+=head1 SYNOPSIS
+
+ virt-sysprep [--options] -d domname
+
+ virt-sysprep [--options] -a disk.img [-a disk.img ...]
+
+=head1 DESCRIPTION
+
+Virt-sysprep "resets" or "unconfigures" a virtual machine so that
+clones can be made from it. Steps in this process include removing
+SSH host keys, removing persistent network MAC configuration, and
+removing user accounts. Each step can be enabled or disabled as
+required.
+
+Virt-sysprep is a simple shell script, allowing easy inspection or
+customization by the system administrator.
+
+Virt-sysprep modifies the guest or disk image I<in place>. The guest
+must be shut down. If you want to preserve the existing contents of
+the guest, you I<must copy or clone the disk first>.
+See L</COPYING AND CLONING> below.
+
+"Sysprep" stands for "system preparation" tool. The name comes from
+the Microsoft program C<sysprep.exe> which is used to unconfigure
+Windows machines in preparation for cloning them. Having said that,
+virt-sysprep does I<not> currently work on Microsoft Windows guests.
+We plan to support Windows sysprepping in a future version, and we
+already have code to do it.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--help>
+
+Display brief help.
+
+=item B<-a> file
+
+=item B<--add> file
+
+Add I<file> which should be a disk image from a virtual machine.
+
+The format of the disk image is auto-detected. To override this and
+force a particular format use the I<--format=..> option.
+
+=item B<-c> URI
+
+=item B<--connect> URI
+
+If using libvirt, connect to the given I<URI>. If omitted, then we
+connect to the default libvirt hypervisor.
+
+If you specify guest block devices directly (I<-a>), then libvirt is
+not used at all.
+
+=item B<-d> guest
+
+=item B<--domain> guest
+
+Add all the disks from the named libvirt guest. Domain UUIDs can be
+used instead of names.
+
+=item B<--enable=...>
+
+Choose which sysprep operations to perform. Give a comma-separated
+list of operations, for example:
+
+ --enable=ssh-hostkeys,udev-persistent-net
+
+would enable ONLY C<ssh-hostkeys> and C<udev-persistent-net> operations.
+
+If the I<--enable> option is not given, then we default to trying all
+possible sysprep operations. But some sysprep operations are skipped
+for some guest types.
+
+Use I<--list-operations> to list operations supported by a particular
+version of virt-sysprep.
+
+See L</OPERATIONS> below for a list and an explanation of each
+operation.
+
+=item B<--format=raw|qcow2|..>
+
+=item B<--format>
+
+The default for the I<-a> option is to auto-detect the format of the
+disk image. Using this forces the disk format for I<-a> options which
+follow on the command line. Using I<--format> with no argument
+switches back to auto-detection for subsequent I<-a> options.
+
+For example:
+
+ virt-sysprep --format=raw -a disk.img
+
+forces raw format (no auto-detection) for C<disk.img>.
+
+ virt-sysprep --format=raw -a disk.img --format -a another.img
+
+forces raw format (no auto-detection) for C<disk.img> and reverts to
+auto-detection for C<another.img>.
+
+If you have untrusted raw-format guest disk images, you should use
+this option to specify the disk format. This avoids a possible
+security problem with malicious guests (CVE-2010-3851).
+
+=item B<--hostname> newhostname
+
+Change the hostname. See the L</hostname> operation below.
+If not given, defaults to C<localhost.localdomain>.
+
+=item B<--list-operations>
+
+List the operations supported by the virt-sysprep program.
+
+=item B<-v>
+
+=item B<--verbose>
+
+Enable verbose messages for debugging.
+
+=item B<-V>
+
+=item B<--version>
+
+Display version number and exit.
+
+=item B<-x>
+
+Enable tracing of libguestfs API calls.
+
+=back
+
+=head1 OPERATIONS
+
+If the I<--enable> option is I<not> given, then all sysprep operations
+in the list below are enabled, although some are skipped depending on
+the type of guest.
+
+Operations can be individually enabled using the I<--enable> option.
+Use a comma-separated list, for example:
+
+ virt-sysprep --enable=ssh-hostkeys,udev-persistent-net [etc..]
+
+To list the operations supported by the current version of
+virt-sysprep, use I<--list-operations>.
+
+=head2 hostname
+
+This changes the hostname of the guest to the value given in the
+I<--hostname> parameter.
+
+If the I<--hostname> parameter is not given, then the hostname is
+changed to C<localhost.localdomain>.
+
+=head2 net-hwaddr
+
+Remove HWADDR (hard-coded MAC address) configuration. For Fedora and
+Red Hat Enterprise Linux, this is removed from C<ifcfg-*> files.
+
+=head2 ssh-hostkeys
+
+This erases 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
+
+This erases 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.
+
+=head1 COPYING AND CLONING
+
+Virt-sysprep can be used as part of a process of cloning guests, or to
+prepare a template from which guests can be cloned. There are many
+different ways to achieve this using the virt tools, and this section
+is just an introduction.
+
+A virtual machine (when switched off) consists of two parts:
+
+=over 4
+
+=item I<configuration>
+
+The configuration or description of the guest. eg. The libvirt
+XML (see C<virsh dumpxml>), the running configuration of the guest,
+or another external format like OVF.
+
+Some configuration items that might need to be changed:
+
+=over 4
+
+=item *
+
+name
+
+=item *
+
+UUID
+
+=item *
+
+path to block device(s)
+
+=item *
+
+network card MAC address
+
+=back
+
+=item I<block device(s)>
+
+One or more hard disk images, themselves containing files,
+directories, applications, kernels, configuration, etc.
+
+Some things inside the block devices that might need to be changed:
+
+=over 4
+
+=item *
+
+hostname and other net configuration
+
+=item *
+
+UUID
+
+=item *
+
+SSH host keys
+
+=item *
+
+Windows unique security ID (SID)
+
+=item *
+
+Puppet registration
+
+=back
+
+=back
+
+=head2 COPYING THE BLOCK DEVICE
+
+Starting with an original guest, you probably wish to copy the guest
+block device and its configuration to make a template. Then once you
+are happy with the template, you will want to make many clones from
+it.
+
+ virt-sysprep
+ |
+ v
+ original guest --------> template ---------->
+ \------> cloned
+ \-----> guests
+ \---->
+
+You can, of course, just copy the block device on the host using
+L<cp(1)> or L<dd(1)>.
+
+ dd dd
+ original guest --------> template ---------->
+ \------> cloned
+ \-----> guests
+ \---->
+
+There are some smarter (and faster) ways too:
+
+=over 4
+
+=item *
+
+ snapshot
+ template ---------->
+ \------> cloned
+ \-----> guests
+ \---->
+
+Use the block device as a backing file and create a snapshot on top
+for each guest. The advantage is that you don't need to copy the
+block device (very fast) and only changes are stored (less storage
+required).
+
+Note that writing to the backing file once you have created guests on
+top of it is not possible: you will corrupt the guests.
+
+Tools that can do this include:
+L<qemu-img(1)> (with the I<create -f qcow2 -o backing_file> option),
+L<lvcreate(8)> (I<--snapshot> option). Some filesystems (such as
+btrfs) and most Network Attached Storage devices can also create cheap
+snapshots from files or LUNs.
+
+=item *
+
+Get your NAS to snapshot and/or duplicate the LUN.
+
+=item *
+
+Prepare your template using L<virt-sparsify(1)>. See below.
+
+=back
+
+=head2 VIRT-CLONE
+
+A separate tool, L<virt-clone(1)>, can be used to duplicate the block
+device and/or modify the external libvirt configuration of a guest.
+It will reset the name, UUID and MAC address of the guest in the
+libvirt XML.
+
+L<virt-clone(1)> does not use libguestfs and cannot look inside the
+disk image. This was the original motivation to write virt-sysprep.
+
+=head2 SPARSIFY
+
+ virt-sparsify
+ original guest --------> template
+
+L<virt-sparsify(1)> can be used to make the cloning template smaller,
+making it easier to compress and/or faster to copy.
+
+Notice that since virt-sparsify also copies the image, you can use it
+to make the initial copy (instead of C<dd>).
+
+=head2 RESIZE
+
+ virt-resize
+ template ---------->
+ \------> cloned
+ \-----> guests
+ \---->
+
+If you want to give people cloned guests, but let them pick the size
+of the guest themselves (eg. depending on how much they are prepared
+to pay for disk space), then instead of copying the template, you can
+run L<virt-resize(1)>. Virt-resize performs a copy and resize, and
+thus is ideal for cloning guests from a template.
+
+=head1 SHELL QUOTING
+
+Libvirt guest names can contain arbitrary characters, some of which
+have meaning to the shell such as C<#> and space. You may need to
+quote or escape these characters on the command line. See the shell
+manual page L<sh(1)> for details.
+
+=head1 EXIT STATUS
+
+This program returns 0 on success, or 1 if there was an error.
+
+=head1 SEE ALSO
+
+L<guestfs(3)>,
+L<guestfish(1)>,
+L<virt-clone(1)>,
+L<virt-rescue(1)>,
+L<virt-resize(1)>,
+L<virt-sparsify(1)>,
+L<virsh(1)>,
+L<qemu-img(1)>,
+L<lvcreate(8)>,
+L<http://libguestfs.org/>,
+L<http://libvirt.org/>.
+
+=head1 AUTHOR
+
+Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+=head1 COPYRIGHT
+
+Copyright (C) 2011 Red Hat Inc.
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.