Version 0.5 -- fixed tty problems, added virt-p2v-unpack script.
[virt-p2v.git] / virt-p2v.sh
1 #!/bin/bash
2 #
3 # virt-p2v.sh is a shell script which performs a physical to
4 # virtual conversion of local disks.
5 #
6 # Copyright (C) 2007 Red Hat Inc.
7 # Written by Richard W.M. Jones <rjones@redhat.com>
8 #
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 #
23 # $Id$
24
25 # Because we're running from a start-up script, we don't have much
26 # of a login environment, so set one up.
27 export PATH=/usr/sbin:/sbin:/usr/local/bin:/usr/kerberos/bin:/usr/bin:/bin
28 export HOME=/root
29 export LOGNAME=root
30
31 # The defaults here make a generic virt-p2v.sh script, but if you want
32 # to build a partially-/fully-automatic P2V solution, then you can set
33 # these variables to something, and the script won't ask the user for
34 # input.
35
36 # Use 'greeting=no' to suppress greeting and final verification screens.
37 greeting=
38
39 override_remote_host=
40 override_remote_port=
41
42 # can be 'ssh' or 'tcp'
43 override_remote_transport=
44
45 # eg. override_remote_directory=/var/lib/xen/images
46 # (only if override_remote_transport is 'ssh')
47 override_remote_directory=
48
49 # list of local physical devices to send, separated by spaces,
50 # if empty ask user.
51 # this is usually a list of whole disk devices (eg. "sda")
52 override_devices_to_send=""
53
54 # the root filesystem containing /etc/fstab (eg. "sda3" or
55 # "VolGroup00/LogVol00")
56 override_root_filesystem=""
57
58 # network configuration
59 #  - if empty, ask user
60 #  - "auto" means try to autoconfigure from root filesystem
61 # (XXX needs to contain more ways to override in future)
62 override_network=""
63
64 #----------------------------------------------------------------------
65 # Logging.
66
67 function log {
68     echo "$@" >> /tmp/virt-p2v.log
69 }
70
71 #----------------------------------------------------------------------
72 # Helper functions.
73
74 # 'matches_regexp regexp string' returns true if string matches regexp.
75 # It uses egrep for extended regexps.
76 function matches_regexp {
77     local re="$1"; shift
78     echo "$@" | grep -Esq "$re"
79 }
80
81 # 'string_contains needle haystack' returns true if needle in haystack.
82 function string_contains {
83     echo "$2" | grep -Fsq "$1"
84 }
85
86 # 'word_in_list word ...'.  '...' (list of parameters) is a list of
87 # words.  Returns true if 'word' is in the list.
88 function word_in_list {
89     local word="$1"; shift
90     local w
91     for w in "$@"; do
92         [ "$w" = "$word" ] && return 0
93     done
94     return 1
95 }
96
97 #----------------------------------------------------------------------
98 # I/O functions
99
100 # Use the function read_line instead of the shell built-in read.
101 # It reads from the console.
102 function read_line {
103     read "$*"
104 }
105
106 # Launch a bash subshell connected to the console.
107 function shell {
108     PS1='\u@\h:\w\$ ' bash
109 }
110
111 #----------------------------------------------------------------------
112 # Device mapper snapshotting.
113
114 next_ramdisk=1
115
116 # Create a device-mapper snapshot of a device with ramdisk overlay.
117 # Example:
118 #   snapshot sda1 snap
119 # creates a snapshot of /dev/sda1 called /dev/mapper/snap
120 function snapshot {
121     local dev=$1 name=$2
122
123     # Next free ramdisk (/dev/ram$i)
124     local i=$next_ramdisk
125     next_ramdisk=$(($next_ramdisk+1))
126
127     # Get size of the device in sectors.
128     local sectors=`blockdev --getsize /dev/$dev`
129
130     dmsetup create ${name}_org \
131         --table="0 $sectors snapshot-origin /dev/$dev"
132     if [ $? -ne 0 ]; then exit 1; fi
133     dmsetup create $name \
134         --table="0 $sectors snapshot /dev/mapper/${name}_org /dev/ram$i n 64"
135     if [ $? -ne 0 ]; then exit 1; fi
136 }
137
138 # Drop an existing snapshot created by snapshot function.
139 # Example:
140 #   drop_snapshot snap
141 # drops a snapshot called /dev/mapper/snap
142 function drop_snapshot {
143     local name=$1
144
145     dmsetup remove /dev/mapper/$name
146     dmsetup remove /dev/mapper/${name}_org
147 }
148
149 #----------------------------------------------------------------------
150 # Searching for devices, partitions and LVs.
151
152 # Convert "/dev/foo" into "foo".  Returns $device.
153 function strip_slash_dev {
154     device=$(echo "$1" | sed 's|^/dev/||')
155 }
156
157 # The 'lvs' utility returns devices like '/dev/sda2(0)'.  I've no
158 # idea what the number in parentheses is, but remove /dev/ and the
159 # strange number.
160 function device_of_lvs_device {
161     strip_slash_dev "$1"
162     device=$(echo "$device" | sed 's|(.*)$||')
163 }
164
165 # All functions in this section assume that physical block devices
166 # (things corresponding to hard disks in the system) are called
167 # hd[a-z]+ or sd[a-z]+
168
169 # Get list of physical block devices.  Sets variable $devices
170 # to something like "sda sdb".
171 function search_devices {
172     devices=$(cd /sys/block && /bin/ls -d [hs]d*)
173 }
174
175 # Get list of partitions from a physical block device.  Sets
176 # variable $partitions to something like "sda1 sda2 sda3".
177 # See also: search_parts
178 function get_partitions {
179     partitions=$(cd /sys/block/"$1" && /bin/ls -d "$1"*)
180 }
181
182 # Given a partition (eg. "sda1" or "VolGroup00/LogVol00") return
183 # the physical block device which contains it.  In the case where
184 # we are given an LV, this is supposed to do the right thing and
185 # go back through the layers until it gets to the physical block
186 # device.  Returns $device set to something like "sda".
187 function block_device_of_part {
188     local part="$1" vg_name lv_name pv
189
190     if matches_regexp '^[hs]d[a-z]+[0-9]*$' "$part"; then
191         device=$(echo "$part" | sed 's|[0-9]*$||')
192         return 0
193     fi
194
195     # Not a partition name, so it's a LV name.  Ask lvs to
196     # do the hard work.
197     lvs --noheadings -o vg_name,lv_name,devices > lvs
198     while read vg_name lv_name pv; do
199         if [ "$vg_name/$lv_name" = "$part" \
200             -o "mapper/$vg_name-$lv_name" = "$part" ]; then
201             device_of_lvs_device "$pv"  ;# sets $device to (eg.) "sda1"
202             block_device_of_part "$device"
203             return 0
204         fi
205     done < lvs
206
207     # Help ... block device not found.
208     log block_device_of_part: block device cannot be resolved: $part
209     device="$part"
210 }
211
212 # search_parts $devices examines the list of devices and looks for
213 # partitions or LVs on just those devices.  Sets variable $parts to
214 # something like "sda1 VolGroup00/LogVol00".
215 function search_parts {
216     local vg_name lv_name pv pvs="" device partition partitions
217
218     parts=""
219
220     lvs --noheadings -o vg_name,lv_name,devices > lvs
221     while read vg_name lv_name pv; do
222         # Get just the partition name (eg. "sda2").
223         device_of_lvs_device "$pv"; pv="$device"
224         # Get just the block device name (eg. "sda").
225         block_device_of_part "$pv"
226
227         log search_parts: pv $pv device $device lv $vg_name/$lv_name
228
229         # A device in our list of devices to consider?
230         if word_in_list $device "$@"; then
231             log search_parts: adding $vg_name/$lv_name
232             parts="$parts $vg_name/$lv_name"
233             pvs="$pvs $pv"
234         fi
235     done < lvs
236
237     log search_parts: after lvs, parts $parts pvs $pvs
238
239     # Look for non-LVM partitions, but ignore any which are PVs
240     # as identified above.
241     for device in "$@"; do
242         get_partitions "$device"
243         for partition in $partitions; do
244             if ! word_in_list $partition $pvs; then
245                 log search_parts: adding $partition
246                 parts="$parts $partition"
247             fi
248         done
249     done
250
251     # $parts is returned.
252 }
253
254 #----------------------------------------------------------------------
255 # Network configuration functions.
256
257 # `auto_network' tries to configure the network from the
258 # root filesystem.  Returns true or false.
259 function auto_network {
260     # Make sure this file exists, otherwise Fedora gives a warning.
261     touch /etc/resolv.conf
262
263     pushd /etc/sysconfig
264
265     mv network network.saved
266     mv networking networking.saved
267     mv network-scripts network-scripts.saved
268
269     # Originally I symlinked these, but that causes dhclient to
270     # keep open /mnt/root (as its cwd is in network-scripts subdir).
271     # So now we will copy them recursively instead.
272     cp -r /mnt/root/etc/sysconfig/network .
273     cp -r /mnt/root/etc/sysconfig/networking .
274     cp -r /mnt/root/etc/sysconfig/network-scripts .
275
276     /etc/init.d/network start
277     local status=$?
278
279     rm -rf network networking network-scripts
280     mv network.saved network
281     mv networking.saved networking
282     mv network-scripts.saved network-scripts
283
284     popd
285
286     ping -c 3 $remote_host
287
288     if [ "$greeting" != "no" ]; then
289         echo "Did automatic network configuration work?"
290         echo "(Hint: if not sure, there is a shell on console [ALT] [F2])"
291         echo -n "    (y/n) "
292         local line
293         read_line line
294         if [ "$line" = "y" -o "$line" = "yes" ]; then return 0; fi
295         return 1
296     fi
297
298     # In non-interactive mode, return the status of /etc/init.d/network.
299     return $status
300 }
301
302
303 #----------------------------------------------------------------------
304 # General script setup.
305
306 log
307 log virt-p2v starting up at `date`
308
309 # The first and only parameter must be the tty.  Connect
310 # stdin/stdout/stderr to this device.
311 if [ -n "$1" ]; then
312     log connecting to /dev/$1
313     exec </dev/$1 &>/dev/$1
314 fi
315
316 # We can safely write files into /tmp without modifying anything.
317 cd /tmp
318
319 #----------------------------------------------------------------------
320 # Dialog with the user.
321
322 if [ "$greeting" != "no" ]; then
323     dialog \
324         --title "virt-p2v" \
325         --msgbox "\nWelcome to virt-p2v, a live CD for migrating a physical machine to a virtualized host.\n\nTo continue press the Return key.\n\nTo get a shell you can use [ALT] [F2] and log in as root with no password." 17 50
326 fi
327
328 # Get configuration from the user.
329
330 # To make the [Back] button work, we make this into a looping state
331 # machine.  Each state asks a question and jumps to the next state
332 # (unless [Back] is pressed, in which case it jumps back to the previous
333 # state).  Finally the 'exit' state causes us to quit the loop.
334
335 remote_port=22
336 remote_directory=/var/lib/xen/images
337 state=transport
338
339 while [ "$state" != "exit" ]; do
340     log next state = $state
341     case "$state" in
342         transport)
343             if [ -n "$override_remote_transport" ]; then
344                 remote_transport="$override_remote_transport"
345                 state=hostname
346             else
347                 case "$remote_transport" in
348                     tcp) ssh_on=off; tcp_on=on;;
349                     *) ssh_on=on; tcp_on=off;;
350                 esac
351                 dialog \
352                     --nocancel \
353                     --radiolist "Connection type" 10 50 2 \
354                     "ssh" "SSH (secure shell - recommended)" $ssh_on \
355                     "tcp" "TCP socket" $tcp_on \
356                     2> line
357                 remote_transport=`cat line`
358                 state=hostname
359             fi
360             ;;
361         hostname)
362             if [ -n "$override_remote_host" ]; then
363                 remote_host="$override_remote_host"
364                 state=port
365             else
366                 dialog \
367                     --extra-button --extra-label "Back" --nocancel \
368                     --inputbox "Remote host" 10 50 "$remote_host" \
369                     2> line
370                 if [ $? -eq 3 ]; then state=transport
371                 else
372                     remote_host=`cat line`
373                     if [ -n "$remote_host" ]; then state=port; fi
374                     # else stay in same state and demand a hostname!
375                 fi
376             fi
377             ;;
378         port)
379             if [ -n "$override_remote_port" ]; then
380                 remote_port="$override_remote_port"
381                 state=directory
382             else
383                 dialog \
384                     --extra-button --extra-label "Back" --nocancel \
385                     --inputbox "Remote port" 10 50 "$remote_port" \
386                     2> line
387                 if [ $? -eq 3 ]; then state=hostname
388                 else
389                     remote_port=`cat line`
390                     state=directory
391                 fi
392             fi
393             ;;
394         directory)
395             if [ "$remote_transport" = "tcp" ]; then
396                 state=devices
397             elif [ -n "$override_remote_directory" ]; then
398                 remote_directory="$override_remote_directory"
399                 state=devices
400             else
401                 dialog \
402                     --extra-button --extra-label "Back" --nocancel \
403                     --inputbox "Remote directory containing guest images" \
404                     10 50 \
405                     "$remote_directory" \
406                     2> line
407                 if [ $? -eq 3 ]; then state=port
408                 else
409                     remote_directory=`cat line`
410                     state=devices
411                 fi
412             fi
413             ;;
414
415         # Block devices configuration.
416         devices)
417             if [ -n "$override_devices_to_send" ]; then
418                 devices_to_send="$override_devices_to_send"
419                 state=root
420             else
421                 # Returns the list of physical devices in $devices
422                 search_devices
423
424                 log devices returned by search_devices: $devices
425
426                 deviceslist=""
427                 for d in $devices; do
428                     if word_in_list $d $devices_to_send; then
429                         stat=on
430                     else
431                         stat=off
432                     fi
433                     gigs=$(($(blockdev --getsize /dev/$d)/2/1024/1024))
434                     deviceslist="$deviceslist $d /dev/$d(${gigs}GB) $stat"
435                 done
436
437                 dialog \
438                     --extra-button --extra-label "Back" --nocancel \
439                     --single-quoted \
440                     --checklist "Pick disks to send" 15 50 8 \
441                     $deviceslist \
442                     2> line
443                 if [ $? -eq 3 ]; then state=directory
444                 else
445                     devices_to_send=`cat line`
446                     state=root
447                 fi
448             fi
449             ;;
450
451         # Root filesystem.
452         root)
453             if [ -n "$override_root_filesystem" ]; then
454                 root_filesystem="$override_root_filesystem"
455                 state=network
456             else
457                 # Returns the list of possible partitions / LVs in $parts
458                 search_parts $devices_to_send
459
460                 log partitions returned by search_parts: $parts
461
462                 partslist=""
463                 for r in $parts; do
464                     if word_in_list $r $root_filesystem; then
465                         stat=on
466                     else
467                         stat=off
468                     fi
469                     partslist="$partslist $r /dev/$r $stat"
470                 done
471
472                 dialog \
473                     --extra-button --extra-label "Back" --nocancel \
474                     --single-quoted \
475                     --radiolist "Pick partition containing the root (/) filesystem" 10 70 5 \
476                     $partslist \
477                     2> line
478                 if [ $? -eq 3 ]; then state=devices
479                 else
480                     root_filesystem=`cat line`
481                     state=network
482                 fi
483             fi
484             ;;
485
486         # Network configuration.
487         network)
488             if [ -n "$override_network" ]; then
489                 network="$override_network"
490                 state=verify
491             else
492                 dialog \
493                     --extra-button --extra-label "Back" --nocancel \
494                     --radiolist "Network configuration" 10 70 5 \
495                     "auto" "Auto-configure from root filesystem" on \
496                     "ask" "Manual configuration" off \
497                     "sh" "Configure from the shell" off \
498                     2> line
499                 if [ $? -eq 3 ]; then state=root
500                 else
501                     network=`cat line`
502                     state=verify
503                 fi
504             fi
505             ;;
506
507         # Verify configuration.
508         verify)
509             if [ "$greeting" = "no" ]; then
510                 state=exit
511             else
512                 dialog \
513                     --title "Summary" \
514                     --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?" \
515                     18 70
516                 if [ $? -eq 1 ]; then
517                     state=transport
518                 else
519                     state=exit
520                 fi
521             fi
522             ;;
523         *)
524             echo "Invalid state: $state"
525             state=transport
526             ;;
527     esac
528 done
529
530 clear
531
532 #----------------------------------------------------------------------
533 # De-activate all volume groups and switch to new dm-only LVM config.
534 log deactivating volume groups
535
536 vgchange -a n
537 mv /etc/lvm/lvm.conf /etc/lvm/lvm.conf.old
538 mv /etc/lvm/lvm.conf.new /etc/lvm/lvm.conf
539 rm -f /etc/lvm/cache/.cache
540
541 # Snapshot the block devices.
542 for d in $devices_to_send; do
543     log snapshotting block device /dev/$d ...
544
545     snapshot $d snap_$d
546
547     # The block devices are whole disks.  Use kpartx to repartition them.
548     log running kpartx -a /dev/mapper/snap_$d ...
549     kpartx -a /dev/mapper/snap_$d
550 done
551
552 # Rescan for LVs.
553 log running vgscan
554 vgscan
555 vgchange -a y
556
557 # Mount the root filesystem on /mnt/root.  If it's a physical
558 # device then we want to mount (eg) /dev/mapper/snap_sda2.
559 # If it's a LVM device then we can just mount the LVM partition.
560
561 log mount $root_filesystem as /mnt/root
562
563 if [ -f /dev/mapper/snap_$root_filesystem ]; then
564     mount /dev/mapper/snap_$root_filesystem /mnt/root
565 else
566     mount /dev/$root_filesystem /mnt/root
567 fi
568
569 #----------------------------------------------------------------------
570 # Now see if we can get a network configuration.
571 log network configuration $network
572
573 case "$network" in
574     sh)
575         echo "Network configuration"
576         echo
577         echo "Please configure the network from this shell."
578         echo
579         echo "When finished, exit with ^D or exit"
580         echo
581         shell
582         ;;
583
584     ask)
585         # XXX Not implemented
586         echo "Sorry, we didn't implement this one yet."
587         shell
588         ;;
589
590     auto)
591         echo "Trying to auto-configure network from root filesystem ..."
592         echo
593         if ! auto_network; then
594             echo "Auto-configuration failed.  Starting a shell."
595             echo
596             shell
597         fi
598 esac
599
600 #----------------------------------------------------------------------
601 # Rewrite /mnt/root/etc/fstab
602
603 log rewriting /etc/fstab
604
605 cp /mnt/root/etc/fstab /mnt/root/etc/fstab.p2vsaved
606 while read dev mountpoint fstype options freq passno; do
607     # If device is /dev/sd* then on the target fullvirt machine
608     # it will be /dev/sd*
609     if matches_regexp "^/dev/sd[[:alpha:]]+[[:digit:]]+$" "$dev"; then
610         dev=`echo $dev | sed 's|^/dev/sd|/dev/hd|'`
611     fi
612
613     # Print out again with all the fields nicely lined up.
614     printf "%-23s %-23s %-7s %-15s %d %d\n" \
615         "$dev" "$mountpoint" "$fstype" "$options" "$freq" "$passno"
616 done < /mnt/root/etc/fstab.p2vsaved > /mnt/root/etc/fstab
617
618 #----------------------------------------------------------------------
619 # XXX Look for other files which might need to be changed here ...
620
621
622
623 # We've now finished with /mnt/root (the real root filesystem),
624 # so unmount it and synch everything.
625 umount /mnt/root
626 sync
627
628 #----------------------------------------------------------------------
629 # Send the device snapshots (underlying device + changes in ramdisk)
630 # to the remote server.
631
632 log sending disks
633
634 # XXX Effectively this is using the hostname derived from network
635 # configuration, but we might want to ask the user instead.
636 # XXX How do we ensure that we won't overwrite target files?  Currently
637 # tries to use the current date as a uniquifier.
638
639 # Names will be something like
640 # p2v-oirase-200709011249-hda.img
641 basename=p2v-`hostname -s|tr -cd '[0-9a-zA-Z]'`-`date +'%Y%m%d%H%M'`
642
643 for dev in $devices_to_send; do
644     rdev=`echo $dev | sed 's|^sd|hd|'`
645     name="$basename-$rdev.img"
646     log sending $dev to $name
647
648     sectors=`blockdev --getsize /dev/mapper/snap_$dev`
649
650     gigs=$(($sectors/2/1024/1024))
651     echo "Sending /dev/$dev (${gigs} GB) to remote machine"
652
653     dd if=/dev/mapper/snap_$dev | gzip --best |
654     case "$remote_transport" in
655         ssh)
656             ssh -p "$remote_port" "$remote_host" \
657                 "zcat > $remote_directory/$name"
658             ;;
659         tcp)
660             echo "p2v $name $sectors" > header
661             echo > newline
662             cat header - newline | nc "$remote_host" "$remote_port"
663             ;;
664     esac
665 done
666
667
668 #----------------------------------------------------------------------
669 # Clean up.
670
671 #for d in $devices_to_send; do
672 #    kpartx -d /dev/mapper/snap_$d
673 #    drop_snapshot snap_$d
674 #done
675
676 # This file must end with a newline
677