3 # virt-p2v.sh is a shell script which performs a physical to
4 # virtual conversion of local disks.
6 # Copyright (C) 2007 Red Hat Inc.
7 # Written by Richard W.M. Jones <rjones@redhat.com>
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.
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.
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
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
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
36 # Use 'greeting=no' to suppress greeting and final verification screens.
42 # can be 'ssh' or 'tcp'
43 override_remote_transport=
45 # eg. override_remote_directory=/var/lib/xen/images
46 # (only if override_remote_transport is 'ssh')
47 override_remote_directory=
49 # list of local physical devices to send, separated by spaces,
51 # this is usually a list of whole disk devices (eg. "sda")
52 override_devices_to_send=""
54 # the root filesystem containing /etc/fstab (eg. "sda3" or
55 # "VolGroup00/LogVol00")
56 override_root_filesystem=""
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)
64 #----------------------------------------------------------------------
68 echo "$@" >> /tmp/virt-p2v.log
71 #----------------------------------------------------------------------
74 # 'matches_regexp regexp string' returns true if string matches regexp.
75 # It uses egrep for extended regexps.
76 function matches_regexp {
78 echo "$@" | grep -Esq "$re"
81 # 'string_contains needle haystack' returns true if needle in haystack.
82 function string_contains {
83 echo "$2" | grep -Fsq "$1"
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
92 [ "$w" = "$word" ] && return 0
97 #----------------------------------------------------------------------
100 # Use the function read_line instead of the shell built-in read.
101 # It reads from the console.
106 # Launch a bash subshell connected to the console.
108 PS1='\u@\h:\w\$ ' bash
111 #----------------------------------------------------------------------
112 # Device mapper snapshotting.
116 # Create a device-mapper snapshot of a device with ramdisk overlay.
119 # creates a snapshot of /dev/sda1 called /dev/mapper/snap
123 # Next free ramdisk (/dev/ram$i)
124 local i=$next_ramdisk
125 next_ramdisk=$(($next_ramdisk+1))
127 # Get size of the device in sectors.
128 local sectors=`blockdev --getsize /dev/$dev`
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
138 # Drop an existing snapshot created by snapshot function.
141 # drops a snapshot called /dev/mapper/snap
142 function drop_snapshot {
145 dmsetup remove /dev/mapper/$name
146 dmsetup remove /dev/mapper/${name}_org
149 #----------------------------------------------------------------------
150 # Searching for devices, partitions and LVs.
152 # Convert "/dev/foo" into "foo". Returns $device.
153 function strip_slash_dev {
154 device=$(echo "$1" | sed 's|^/dev/||')
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
160 function device_of_lvs_device {
162 device=$(echo "$device" | sed 's|(.*)$||')
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]+
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*)
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"*)
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
190 if matches_regexp '^[hs]d[a-z]+[0-9]*$' "$part"; then
191 device=$(echo "$part" | sed 's|[0-9]*$||')
195 # Not a partition name, so it's a LV name. Ask lvs to
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"
207 # Help ... block device not found.
208 log block_device_of_part: block device cannot be resolved: $part
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
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"
227 log search_parts: pv $pv device $device lv $vg_name/$lv_name
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"
237 log search_parts: after lvs, parts $parts pvs $pvs
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"
251 # $parts is returned.
254 # This generates a snapshot device name from a device name.
255 # eg. sda -> snap_sda
258 dname=`echo -n snap_"$1" | tr -cs '[:alnum:]' _`
261 #----------------------------------------------------------------------
262 # Network configuration functions.
264 # `auto_network' tries to configure the network from the
265 # root filesystem. Returns true or false.
266 function auto_network {
267 # Make sure this file exists, otherwise Fedora gives a warning.
268 touch /etc/resolv.conf
272 mv network network.saved
273 mv networking networking.saved
274 mv network-scripts network-scripts.saved
276 # Originally I symlinked these, but that causes dhclient to
277 # keep open /mnt/root (as its cwd is in network-scripts subdir).
278 # So now we will copy them recursively instead.
279 cp -r /mnt/root/etc/sysconfig/network .
280 cp -r /mnt/root/etc/sysconfig/networking .
281 cp -r /mnt/root/etc/sysconfig/network-scripts .
283 /etc/init.d/network start
286 rm -rf network networking network-scripts
287 mv network.saved network
288 mv networking.saved networking
289 mv network-scripts.saved network-scripts
293 ping -c 3 $remote_host
295 if [ "$greeting" != "no" ]; then
296 echo "Did automatic network configuration work?"
297 echo "(Hint: if not sure, there is a shell on console [ALT] [F2])"
301 if [ "$line" = "y" -o "$line" = "yes" ]; then return 0; fi
305 # In non-interactive mode, return the status of /etc/init.d/network.
310 #----------------------------------------------------------------------
311 # General script setup.
314 log virt-p2v starting up at `date`
316 # The first and only parameter must be the tty. Connect
317 # stdin/stdout/stderr to this device.
319 log connecting to /dev/$1
320 exec </dev/$1 &>/dev/$1
323 # We can safely write files into /tmp without modifying anything.
326 #----------------------------------------------------------------------
327 # Dialog with the user.
329 if [ "$greeting" != "no" ]; then
332 --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
335 # Get configuration from the user.
337 # To make the [Back] button work, we make this into a looping state
338 # machine. Each state asks a question and jumps to the next state
339 # (unless [Back] is pressed, in which case it jumps back to the previous
340 # state). Finally the 'exit' state causes us to quit the loop.
343 remote_directory=/var/lib/xen/images
346 while [ "$state" != "exit" ]; do
347 log next state = $state
350 if [ -n "$override_remote_transport" ]; then
351 remote_transport="$override_remote_transport"
354 case "$remote_transport" in
355 tcp) ssh_on=off; tcp_on=on;;
356 *) ssh_on=on; tcp_on=off;;
360 --radiolist "Connection type" 10 50 2 \
361 "ssh" "SSH (secure shell - recommended)" $ssh_on \
362 "tcp" "TCP socket" $tcp_on \
364 remote_transport=`cat line`
369 if [ -n "$override_remote_host" ]; then
370 remote_host="$override_remote_host"
374 --extra-button --extra-label "Back" --nocancel \
375 --inputbox "Remote host" 10 50 "$remote_host" \
377 if [ $? -eq 3 ]; then state=transport
379 remote_host=`cat line`
380 if [ -n "$remote_host" ]; then state=port; fi
381 # else stay in same state and demand a hostname!
386 if [ -n "$override_remote_port" ]; then
387 remote_port="$override_remote_port"
391 --extra-button --extra-label "Back" --nocancel \
392 --inputbox "Remote port" 10 50 "$remote_port" \
394 if [ $? -eq 3 ]; then state=hostname
396 remote_port=`cat line`
402 if [ "$remote_transport" = "tcp" ]; then
404 elif [ -n "$override_remote_directory" ]; then
405 remote_directory="$override_remote_directory"
409 --extra-button --extra-label "Back" --nocancel \
410 --inputbox "Remote directory containing guest images" \
412 "$remote_directory" \
414 if [ $? -eq 3 ]; then state=port
416 remote_directory=`cat line`
422 # Block devices configuration.
424 if [ -n "$override_devices_to_send" ]; then
425 devices_to_send="$override_devices_to_send"
428 # Returns the list of physical devices in $devices
431 log devices returned by search_devices: $devices
434 for d in $devices; do
435 if word_in_list $d $devices_to_send; then
440 log running blockdev --getsize /dev/$d
441 gigs=$(($(blockdev --getsize /dev/$d)/2/1024/1024))
442 log /dev/$d has size $gigs
443 deviceslist="$deviceslist $d /dev/$d(${gigs}GB) $stat"
445 log deviceslist="$deviceslist"
448 --extra-button --extra-label "Back" --nocancel \
450 --checklist "Pick disks to send" 15 50 8 \
453 if [ $? -eq 3 ]; then state=directory
455 devices_to_send=`cat line`
463 if [ -n "$override_root_filesystem" ]; then
464 root_filesystem="$override_root_filesystem"
467 # Returns the list of possible partitions / LVs in $parts
468 search_parts $devices_to_send
470 log partitions returned by search_parts: $parts
474 if word_in_list $r $root_filesystem; then
479 partslist="$partslist $r /dev/$r $stat"
483 --extra-button --extra-label "Back" --nocancel \
485 --radiolist "Pick partition containing the root (/) filesystem" 10 70 5 \
488 if [ $? -eq 3 ]; then state=devices
490 root_filesystem=`cat line`
496 # Network configuration.
498 if [ -n "$override_network" ]; then
499 network="$override_network"
503 --extra-button --extra-label "Back" --nocancel \
504 --radiolist "Network configuration" 10 70 5 \
505 "auto" "Auto-configure from root filesystem" on \
506 "ask" "Manual configuration" off \
507 "sh" "Configure from the shell" off \
509 if [ $? -eq 3 ]; then state=root
517 # Verify configuration.
519 if [ "$greeting" = "no" ]; then
524 --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?" \
526 if [ $? -eq 1 ]; then
534 echo "Invalid state: $state"
542 #----------------------------------------------------------------------
543 # De-activate all volume groups and switch to new dm-only LVM config.
544 log deactivating volume groups
547 mv /etc/lvm/lvm.conf /etc/lvm/lvm.conf.old
548 mv /etc/lvm/lvm.conf.new /etc/lvm/lvm.conf
549 rm -f /etc/lvm/cache/.cache
551 # Snapshot the block devices.
552 for d in $devices_to_send; do
554 log snapshotting block device /dev/$d to $dname ...
558 # The block devices are whole disks. Use kpartx to repartition them.
559 log running kpartx -a /dev/mapper/$dname ...
560 kpartx -a /dev/mapper/$dname
568 # Mount the root filesystem on /mnt/root. If it's a physical
569 # device then we want to mount (eg) /dev/mapper/snap_sda2.
570 # If it's a LVM device then we can just mount the LVM partition.
572 log mount $root_filesystem as /mnt/root
574 snap_name $root_filesystem
575 root_filesystem_dname="$dname"
577 if [ -b /dev/mapper/$root_filesystem_dname ]; then
578 mount /dev/mapper/$root_filesystem_dname /mnt/root
580 mount /dev/$root_filesystem /mnt/root
583 #----------------------------------------------------------------------
584 # Now see if we can get a network configuration.
585 log network configuration $network
589 echo "Network configuration"
591 echo "Please configure the network from this shell."
593 echo "When finished, exit with ^D or exit"
599 # XXX Not implemented
600 echo "Sorry, we didn't implement this one yet."
605 echo "Trying to auto-configure network from root filesystem ..."
607 if ! auto_network; then
608 echo "Auto-configuration failed. Starting a shell."
614 #----------------------------------------------------------------------
615 # Rewrite /mnt/root/etc/fstab
617 log rewriting /etc/fstab
619 cp /mnt/root/etc/fstab /mnt/root/etc/fstab.p2vsaved
620 while read dev mountpoint fstype options freq passno; do
621 # If device is /dev/sd* then on the target fullvirt machine
622 # it will be /dev/sd*
623 if matches_regexp "^/dev/sd[[:alpha:]]+[[:digit:]]+$" "$dev"; then
624 dev=`echo $dev | sed 's|^/dev/sd|/dev/hd|'`
627 # Print out again with all the fields nicely lined up.
628 printf "%-23s %-23s %-7s %-15s %d %d\n" \
629 "$dev" "$mountpoint" "$fstype" "$options" "$freq" "$passno"
630 done < /mnt/root/etc/fstab.p2vsaved > /mnt/root/etc/fstab
632 #----------------------------------------------------------------------
633 # XXX Look for other files which might need to be changed here ...
637 # We've now finished with /mnt/root (the real root filesystem),
638 # so unmount it and synch everything.
642 #----------------------------------------------------------------------
643 # Send the device snapshots (underlying device + changes in ramdisk)
644 # to the remote server.
648 # XXX Effectively this is using the hostname derived from network
649 # configuration, but we might want to ask the user instead.
650 # XXX How do we ensure that we won't overwrite target files? Currently
651 # tries to use the current date as a uniquifier.
653 # Names will be something like
654 # p2v-oirase-200709011249-hda.img
655 basename=p2v-`hostname -s|tr -cd '[0-9a-zA-Z]'`-`date +'%Y%m%d%H%M'`
657 for dev in $devices_to_send; do
658 rdev=`echo $dev | sed 's|^sd|hd|'`
659 name="$basename-$rdev.img"
660 log sending $dev to $name
664 sectors=`blockdev --getsize /dev/mapper/$dname`
666 gigs=$(($sectors/2/1024/1024))
667 echo "Sending /dev/$dev (${gigs} GB) to remote machine"
669 dd if=/dev/mapper/$dname | gzip --best |
670 case "$remote_transport" in
672 ssh -p "$remote_port" "$remote_host" \
673 "zcat > $remote_directory/$name"
676 echo "p2v $name $sectors" > header
678 cat header - newline | nc "$remote_host" "$remote_port"
684 #----------------------------------------------------------------------
687 #for d in $devices_to_send; do
689 # kpartx -d /dev/mapper/$dname
690 # drop_snapshot $dname
693 # This file must end with a newline