Added GPL, updated MANIFEST.
[virt-p2v.git] / virt-p2v.sh
index d472578..274b087 100755 (executable)
@@ -6,6 +6,20 @@
 # Copyright (C) 2007 Red Hat Inc.
 # Written by Richard W.M. Jones <rjones@redhat.com>
 #
+# 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+#
 # $Id$
 
 export PATH=/usr/sbin:/sbin:/usr/local/bin:/usr/kerberos/bin:/usr/bin:/bin
@@ -28,22 +42,18 @@ override_remote_transport=
 # (only if override_remote_transport is 'ssh')
 override_remote_directory=
 
-# list of local physical devices to examine, separated by spaces,
+# list of local physical devices to send, separated by spaces,
 # if empty ask user.
 # this is usually a list of whole disk devices (eg. "sda")
-override_physical_devices=""
+override_devices_to_send=""
 
-# list of filesystems to send (eg. "sda1 sda2 VolGroup00/LogVol00")
-override_filesystems_to_send=""
-
-# list of root filesystems to examine (eg. "sda3" or
+# the root filesystem containing /etc/fstab (eg. "sda3" or
 # "VolGroup00/LogVol00")
-override_root_filesystems=""
+override_root_filesystem=""
 
 # network configuration
 #  - if empty, ask user
-#  - if contains a filesystem name (eg. "VolGroup00/LogVol00") try to
-#    auto-configure network from that
+#  - "auto" means try to autoconfigure from root filesystem
 # (XXX needs to contain more ways to override in future)
 override_network=""
 
@@ -97,6 +107,8 @@ function shell {
 #----------------------------------------------------------------------
 # Device mapper snapshotting.
 
+next_ramdisk=1
+
 # Create a device-mapper snapshot of a device with ramdisk overlay.
 # Example:
 #   snapshot sda1 snap
@@ -104,6 +116,10 @@ function shell {
 function snapshot {
     local dev=$1 name=$2
 
+    # Next free ramdisk (/dev/ram$i)
+    local i=$next_ramdisk
+    next_ramdisk=$(($next_ramdisk+1))
+
     # Get size of the device in sectors.
     local sectors=`blockdev --getsize /dev/$dev`
 
@@ -111,7 +127,7 @@ function snapshot {
        --table="0 $sectors snapshot-origin /dev/$dev"
     if [ $? -ne 0 ]; then exit 1; fi
     dmsetup create $name \
-       --table="0 $sectors snapshot /dev/mapper/${name}_org /dev/ram1 n 64"
+       --table="0 $sectors snapshot /dev/mapper/${name}_org /dev/ram$i n 64"
     if [ $? -ne 0 ]; then exit 1; fi
 }
 
@@ -120,6 +136,8 @@ function snapshot {
 #   drop_snapshot snap
 # drops a snapshot called /dev/mapper/snap
 function drop_snapshot {
+    local name=$1
+
     dmsetup remove /dev/mapper/$name
     dmsetup remove /dev/mapper/${name}_org
 }
@@ -232,11 +250,9 @@ function search_parts {
 #----------------------------------------------------------------------
 # Network configuration functions.
 
-# `auto_network $fs' mounts /dev/$fs read-only on /mnt/root and then
-# tries to configure the network from it.  Returns true or false.
+# `auto_network' tries to configure the network from the
+# root filesystem.  Returns true or false.
 function auto_network {
-    if ! mount -o ro /dev/$1 /mnt; then return 1; fi
-
     # Make sure this file exists, otherwise Fedora gives a warning.
     touch /etc/resolv.conf
 
@@ -247,13 +263,11 @@ function auto_network {
     mv network-scripts network-scripts.saved
 
     # Originally I symlinked these, but that causes dhclient to
-    # keep open /mnt (as its cwd is in network-scripts subdir).
+    # keep open /mnt/root (as its cwd is in network-scripts subdir).
     # So now we will copy them recursively instead.
-    cp -r /mnt/etc/sysconfig/network .
-    cp -r /mnt/etc/sysconfig/networking .
-    cp -r /mnt/etc/sysconfig/network-scripts .
-    # This also means we can umount /mnt early.
-    umount /mnt
+    cp -r /mnt/root/etc/sysconfig/network .
+    cp -r /mnt/root/etc/sysconfig/networking .
+    cp -r /mnt/root/etc/sysconfig/network-scripts .
 
     /etc/init.d/network start
     local status=$?
@@ -389,9 +403,9 @@ while [ "$state" != "exit" ]; do
 
        # Block devices configuration.
        devices)
-           if [ -n "$override_physical_devices" ]; then
-               physical_devices="$override_physical_devices"
-               state=filesystems
+           if [ -n "$override_devices_to_send" ]; then
+               devices_to_send="$override_devices_to_send"
+               state=root
            else
                # Returns the list of physical devices in $devices
                search_devices
@@ -400,7 +414,7 @@ while [ "$state" != "exit" ]; do
 
                deviceslist=""
                for d in $devices; do
-                   if word_in_list $d $physical_devices; then
+                   if word_in_list $d $devices_to_send; then
                        stat=on
                    else
                        stat=off
@@ -412,58 +426,31 @@ while [ "$state" != "exit" ]; do
                dialog \
                    --extra-button --extra-label "Back" --nocancel \
                    --single-quoted \
-                   --checklist "Pick local disks to examine" 15 50 8 \
+                   --checklist "Pick disks to send" 15 50 8 \
                    $deviceslist \
                    2> line
                if [ $? -eq 3 ]; then state=directory
                else
-                   physical_devices=`cat line`
-                   state=filesystems
+                   devices_to_send=`cat line`
+                   state=root
                fi
            fi
            ;;
-       filesystems)
-           if [ -n "$override_filesystems_to_send" ]; then
-               filesystems_to_send="$override_filesystems_to_send"
-               state=roots
+
+       # Root filesystem.
+       root)
+           if [ -n "$override_root_filesystem" ]; then
+               root_filesystem="$override_root_filesystem"
+               state=network
            else
                # Returns the list of possible partitions / LVs in $parts
-               search_parts $physical_devices
+               search_parts $devices_to_send
 
                log partitions returned by search_parts: $parts
 
                partslist=""
-               for p in $parts; do
-                   if word_in_list $p $filesystems_to_send; then
-                       stat=on
-                   else
-                       stat=off
-                   fi
-                   gigs=$(($(blockdev --getsize /dev/$p)/2/1024/1024))
-                   partslist="$partslist $p /dev/$p(${gigs}GB) $stat"
-               done
-
-               dialog \
-                   --extra-button --extra-label "Back" --nocancel \
-                   --single-quoted \
-                   --checklist "Pick filesystems to send" 15 70 8 \
-                   $partslist \
-                   2> line
-               if [ $? -eq 3 ]; then state=devices
-               else
-                   filesystems_to_send=`cat line`
-                   state=roots
-               fi
-           fi
-           ;;
-       roots)
-           if [ -n "$override_root_filesystems" ]; then
-               root_filesystems="$override_root_filesystems"
-               state=network
-           else
-               partslist=""
-               for r in $filesystems_to_send; do
-                   if word_in_list $r $root_filesystems; then
+               for r in $parts; do
+                   if word_in_list $r $root_filesystem; then
                        stat=on
                    else
                        stat=off
@@ -474,12 +461,12 @@ while [ "$state" != "exit" ]; do
                dialog \
                    --extra-button --extra-label "Back" --nocancel \
                    --single-quoted \
-                   --checklist "Pick partition(s) containing a root (/) filesystem" 10 70 5 \
+                   --radiolist "Pick partition containing the root (/) filesystem" 10 70 5 \
                    $partslist \
                    2> line
-               if [ $? -eq 3 ]; then state=filesystems
+               if [ $? -eq 3 ]; then state=devices
                else
-                   root_filesystems=`cat line`
+                   root_filesystem=`cat line`
                    state=network
                fi
            fi
@@ -491,24 +478,14 @@ while [ "$state" != "exit" ]; do
                network="$override_network"
                state=verify
            else
-               partslist=""
-               for r in $root_filesystems; do
-                   if [ "$r" = "$network" ]; then
-                       stat=on
-                   else
-                       stat=off
-                   fi
-                   partslist="$partslist $r Auto-configure $stat"
-               done
-
                dialog \
                    --extra-button --extra-label "Back" --nocancel \
                    --radiolist "Network configuration" 10 70 5 \
-                   $partslist \
+                   "auto" "Auto-configure from root filesystem" on \
                    "ask" "Manual configuration" off \
                    "sh" "Configure from the shell" off \
                    2> line
-               if [ $? -eq 3 ]; then state=roots
+               if [ $? -eq 3 ]; then state=root
                else
                    network=`cat line`
                    state=verify
@@ -523,8 +500,8 @@ while [ "$state" != "exit" ]; do
            else
                dialog \
                    --title "Summary" \
-                   --yesno "Transport: $remote_transport\nRemote host: $remote_host\nRemote port: $remote_port\nRemote directory (ssh only): $remote_directory\nFilesystems to send: $filesystems_to_send\nRoot filesystem(s): $root_filesystems\nNetwork configuration: $network\n\nProceed with these settings?" \
-                   23 70
+                   --yesno "Transport: $remote_transport\nRemote host: $remote_host\nRemote port: $remote_port\nRemote directory (ssh only): $remote_directory\nDisks to send: $devices_to_send\nRoot filesystem: $root_filesystem\nNetwork configuration: $network\n\nProceed with these settings?" \
+                   18 70
                if [ $? -eq 1 ]; then
                    state=transport
                else
@@ -541,6 +518,44 @@ done
 
 clear
 
+#----------------------------------------------------------------------
+# De-activate all volume groups and switch to new dm-only LVM config.
+log deactivating volume groups
+
+vgchange -a n
+mv /etc/lvm/lvm.conf /etc/lvm/lvm.conf.old
+mv /etc/lvm/lvm.conf.new /etc/lvm/lvm.conf
+rm -f /etc/lvm/cache/.cache
+
+# Snapshot the block devices.
+for d in $devices_to_send; do
+    log snapshotting block device /dev/$d ...
+
+    snapshot $d snap_$d
+
+    # The block devices are whole disks.  Use kpartx to repartition them.
+    log running kpartx -a /dev/mapper/snap_$d ...
+    kpartx -a /dev/mapper/snap_$d
+done
+
+# Rescan for LVs.
+log running vgscan
+vgscan
+vgchange -a y
+
+# Mount the root filesystem on /mnt/root.  If it's a physical
+# device then we want to mount (eg) /dev/mapper/snap_sda2.
+# If it's a LVM device then we can just mount the LVM partition.
+
+log mount $root_filesystem as /mnt/root
+
+if [ -f /dev/mapper/snap_$root_filesystem ]; then
+    mount /dev/mapper/snap_$root_filesystem /mnt/root
+else
+    mount /dev/$root_filesystem /mnt/root
+fi
+
+#----------------------------------------------------------------------
 # Now see if we can get a network configuration.
 log network configuration $network
 
@@ -550,10 +565,6 @@ case "$network" in
        echo
        echo "Please configure the network from this shell."
        echo
-       echo "You can modify any file under /etc (especially /etc/sysconfig)."
-       echo "No changes are made to the system disks unless you mount them"
-       echo "and explicitly modify them."
-       echo
        echo "When finished, exit with ^D or exit"
        echo
        shell
@@ -565,30 +576,90 @@ case "$network" in
        shell
        ;;
 
-    *)
-       echo "Trying to auto-configure network from /dev/$network ..."
+    auto)
+       echo "Trying to auto-configure network from root filesystem ..."
        echo
-       if ! auto_network "$network"; then
+       if ! auto_network; then
            echo "Auto-configuration failed.  Starting a shell."
            echo
            shell
        fi
 esac
 
-# Mount the root filesystem(s) using device-mapper snapshots.
-# The snapshots are called snap0, snap1, etc. with the number
-# corresponding to its index in $root_filesystems array.
+#----------------------------------------------------------------------
+# Rewrite /mnt/root/etc/fstab
 
+log rewriting /etc/fstab
 
+cp /mnt/root/etc/fstab /mnt/root/etc/fstab.p2vsaved
+while read dev mountpoint fstype options freq passno; do
+    # If device is /dev/sd* then on the target fullvirt machine
+    # it will be /dev/sd*
+    if matches_regexp "^/dev/sd[[:alpha:]]+[[:digit:]]+$" "$dev"; then
+       dev=`echo $dev | sed 's|^/dev/sd|/dev/hd|'`
+    fi
 
+    # Print out again with all the fields nicely lined up.
+    printf "%-23s %-23s %-7s %-15s %d %d\n" \
+       "$dev" "$mountpoint" "$fstype" "$options" "$freq" "$passno"
+done < /mnt/root/etc/fstab.p2vsaved > /mnt/root/etc/fstab
+
+#----------------------------------------------------------------------
+# XXX Look for other files which might need to be changed here ...
 
 
 
+# We've now finished with /mnt/root (the real root filesystem),
+# so unmount it and synch everything.
+umount /mnt/root
+sync
+
+#----------------------------------------------------------------------
+# Send the device snapshots (underlying device + changes in ramdisk)
+# to the remote server.
+
+log sending disks
+
+# XXX Effectively this is using the hostname derived from network
+# configuration, but we might want to ask the user instead.
+# XXX How do we ensure that we won't overwrite target files?  Currently
+# tries to use the current date as a uniquifier.
 
+# Names will be something like
+# p2v-oirase-200709011249-hda.img
+basename=p2v-`hostname -s|tr -cd '[0-9a-zA-Z]'`-`date +'%Y%m%d%H%M'`
 
+for dev in $devices_to_send; do
+    rdev=`echo $dev | sed 's|^sd|hd|'`
+    name="$basename-$rdev.img"
+    log sending $dev to $name
 
+    sectors=`blockdev --getsize /dev/mapper/snap_$dev`
 
+    gigs=$(($sectors/2/1024/1024))
+    echo "Sending /dev/$dev (${gigs} GB) to remote machine"
+
+    dd if=/dev/mapper/snap_$dev |
+    case "$remote_transport" in
+       ssh)
+           ssh -p "$remote_port" "$remote_host" \
+               "cat > $remote_directory/$name"
+           ;;
+       tcp)
+           echo "p2v $name $sectors" > header
+           cat header - | nc "$remote_host" "$remote_port"
+           ;;
+    esac
+done
+
+
+#----------------------------------------------------------------------
+# Clean up.
 
+#for d in $devices_to_send; do
+#    kpartx -d /dev/mapper/snap_$d
+#    drop_snapshot snap_$d
+#done
 
 # This file must end with a newline