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>
11 export PATH=/usr/sbin:/sbin:/usr/local/bin:/usr/kerberos/bin:/usr/bin:/bin
13 # The defaults here make a generic virt-p2v.sh script, but if you want
14 # to build a partially-/fully-automatic P2V solution, then you can set
15 # these variables to something, and the script won't ask the user for
18 # Use 'greeting=no' to suppress greeting and final verification screens.
24 # can be 'ssh' or 'tcp'
25 override_remote_transport=
27 # eg. override_remote_directory=/var/lib/xen/images
28 # (only if override_remote_transport is 'ssh')
29 override_remote_directory=
31 # list of local physical devices to send, separated by spaces,
33 # this is usually a list of whole disk devices (eg. "sda")
34 override_devices_to_send=""
36 # the root filesystem containing /etc/fstab (eg. "sda3" or
37 # "VolGroup00/LogVol00")
38 override_root_filesystem=""
40 # network configuration
41 # - if empty, ask user
42 # - "auto" means try to autoconfigure from root filesystem
43 # (XXX needs to contain more ways to override in future)
46 #----------------------------------------------------------------------
50 echo "$@" >> /tmp/virt-p2v.log
53 #----------------------------------------------------------------------
56 # 'matches_regexp regexp string' returns true if string matches regexp.
57 # It uses egrep for extended regexps.
58 function matches_regexp {
60 echo "$@" | grep -Esq "$re"
63 # 'string_contains needle haystack' returns true if needle in haystack.
64 function string_contains {
65 echo "$2" | grep -Fsq "$1"
68 # 'word_in_list word ...'. '...' (list of parameters) is a list of
69 # words. Returns true if 'word' is in the list.
70 function word_in_list {
71 local word="$1"; shift
74 [ "$w" = "$word" ] && return 0
79 #----------------------------------------------------------------------
82 # Use the function read_line instead of the shell built-in read.
83 # It reads from the console.
85 read "$*" </dev/console
88 # Launch a bash subshell connected to the console.
90 PS1='\u@\h:\w\$ ' bash </dev/console >/dev/console 2>&1
93 #----------------------------------------------------------------------
94 # Device mapper snapshotting.
98 # Create a device-mapper snapshot of a device with ramdisk overlay.
101 # creates a snapshot of /dev/sda1 called /dev/mapper/snap
105 # Next free ramdisk (/dev/ram$i)
106 local i=$next_ramdisk
107 next_ramdisk=$(($next_ramdisk+1))
109 # Get size of the device in sectors.
110 local sectors=`blockdev --getsize /dev/$dev`
112 dmsetup create ${name}_org \
113 --table="0 $sectors snapshot-origin /dev/$dev"
114 if [ $? -ne 0 ]; then exit 1; fi
115 dmsetup create $name \
116 --table="0 $sectors snapshot /dev/mapper/${name}_org /dev/ram$i n 64"
117 if [ $? -ne 0 ]; then exit 1; fi
120 # Drop an existing snapshot created by snapshot function.
123 # drops a snapshot called /dev/mapper/snap
124 function drop_snapshot {
127 dmsetup remove /dev/mapper/$name
128 dmsetup remove /dev/mapper/${name}_org
131 #----------------------------------------------------------------------
132 # Searching for devices, partitions and LVs.
134 # Convert "/dev/foo" into "foo". Returns $device.
135 function strip_slash_dev {
136 device=$(echo "$1" | sed 's|^/dev/||')
139 # The 'lvs' utility returns devices like '/dev/sda2(0)'. I've no
140 # idea what the number in parentheses is, but remove /dev/ and the
142 function device_of_lvs_device {
144 device=$(echo "$device" | sed 's|(.*)$||')
147 # All functions in this section assume that physical block devices
148 # (things corresponding to hard disks in the system) are called
149 # hd[a-z]+ or sd[a-z]+
151 # Get list of physical block devices. Sets variable $devices
152 # to something like "sda sdb".
153 function search_devices {
154 devices=$(cd /sys/block && /bin/ls -d [hs]d*)
157 # Get list of partitions from a physical block device. Sets
158 # variable $partitions to something like "sda1 sda2 sda3".
159 # See also: search_parts
160 function get_partitions {
161 partitions=$(cd /sys/block/"$1" && /bin/ls -d "$1"*)
164 # Given a partition (eg. "sda1" or "VolGroup00/LogVol00") return
165 # the physical block device which contains it. In the case where
166 # we are given an LV, this is supposed to do the right thing and
167 # go back through the layers until it gets to the physical block
168 # device. Returns $device set to something like "sda".
169 function block_device_of_part {
170 local part="$1" vg_name lv_name pv
172 if matches_regexp '^[hs]d[a-z]+[0-9]*$' "$part"; then
173 device=$(echo "$part" | sed 's|[0-9]*$||')
177 # Not a partition name, so it's a LV name. Ask lvs to
179 lvs --noheadings -o vg_name,lv_name,devices > lvs
180 while read vg_name lv_name pv; do
181 if [ "$vg_name/$lv_name" = "$part" \
182 -o "mapper/$vg_name-$lv_name" = "$part" ]; then
183 device_of_lvs_device "$pv" ;# sets $device to (eg.) "sda1"
184 block_device_of_part "$device"
189 # Help ... block device not found.
190 log block_device_of_part: block device cannot be resolved: $part
194 # search_parts $devices examines the list of devices and looks for
195 # partitions or LVs on just those devices. Sets variable $parts to
196 # something like "sda1 VolGroup00/LogVol00".
197 function search_parts {
198 local vg_name lv_name pv pvs="" device partition partitions
202 lvs --noheadings -o vg_name,lv_name,devices > lvs
203 while read vg_name lv_name pv; do
204 # Get just the partition name (eg. "sda2").
205 device_of_lvs_device "$pv"; pv="$device"
206 # Get just the block device name (eg. "sda").
207 block_device_of_part "$pv"
209 log search_parts: pv $pv device $device lv $vg_name/$lv_name
211 # A device in our list of devices to consider?
212 if word_in_list $device "$@"; then
213 log search_parts: adding $vg_name/$lv_name
214 parts="$parts $vg_name/$lv_name"
219 log search_parts: after lvs, parts $parts pvs $pvs
221 # Look for non-LVM partitions, but ignore any which are PVs
222 # as identified above.
223 for device in "$@"; do
224 get_partitions "$device"
225 for partition in $partitions; do
226 if ! word_in_list $partition $pvs; then
227 log search_parts: adding $partition
228 parts="$parts $partition"
233 # $parts is returned.
236 #----------------------------------------------------------------------
237 # Network configuration functions.
239 # `auto_network $fs' tries to configure the network from the
240 # root filesystem. Returns true or false.
241 function auto_network {
242 # Make sure this file exists, otherwise Fedora gives a warning.
243 touch /etc/resolv.conf
247 mv network network.saved
248 mv networking networking.saved
249 mv network-scripts network-scripts.saved
251 # Originally I symlinked these, but that causes dhclient to
252 # keep open /mnt/root (as its cwd is in network-scripts subdir).
253 # So now we will copy them recursively instead.
254 cp -r /mnt/root/etc/sysconfig/network .
255 cp -r /mnt/root/etc/sysconfig/networking .
256 cp -r /mnt/root/etc/sysconfig/network-scripts .
258 /etc/init.d/network start
261 rm -rf network networking network-scripts
262 mv network.saved network
263 mv networking.saved networking
264 mv network-scripts.saved network-scripts
268 ping -c 3 $remote_host
270 if [ "$greeting" != "no" ]; then
271 echo "Did automatic network configuration work?"
272 echo "(Hint: if not sure, there is a shell on console [ALT] [F2])"
275 read_line line </dev/console
276 if [ "$line" = "y" -o "$line" = "yes" ]; then return 0; fi
280 # In non-interactive mode, return the status of /etc/init.d/network.
285 #----------------------------------------------------------------------
286 # General script setup.
288 # We can safely write files into /tmp without modifying anything.
291 #----------------------------------------------------------------------
292 # Dialog with the user.
295 log virt-p2v starting up at `date`
297 if [ "$greeting" != "no" ]; then
300 --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
303 # Get configuration from the user.
305 # To make the [Back] button work, we make this into a looping state
306 # machine. Each state asks a question and jumps to the next state
307 # (unless [Back] is pressed, in which case it jumps back to the previous
308 # state). Finally the 'exit' state causes us to quit the loop.
311 remote_directory=/var/lib/xen/images
314 while [ "$state" != "exit" ]; do
315 log next state = $state
318 if [ -n "$override_remote_transport" ]; then
319 remote_transport="$override_remote_transport"
322 case "$remote_transport" in
323 tcp) ssh_on=off; tcp_on=on;;
324 *) ssh_on=on; tcp_on=off;;
328 --radiolist "Connection type" 10 50 2 \
329 "ssh" "SSH (secure shell - recommended)" $ssh_on \
330 "tcp" "TCP socket" $tcp_on \
332 remote_transport=`cat line`
337 if [ -n "$override_remote_host" ]; then
338 remote_host="$override_remote_host"
342 --extra-button --extra-label "Back" --nocancel \
343 --inputbox "Remote host" 10 50 "$remote_host" \
345 if [ $? -eq 3 ]; then state=transport
347 remote_host=`cat line`
348 if [ -n "$remote_host" ]; then state=port; fi
349 # else stay in same state and demand a hostname!
354 if [ -n "$override_remote_port" ]; then
355 remote_port="$override_remote_port"
359 --extra-button --extra-label "Back" --nocancel \
360 --inputbox "Remote port" 10 50 "$remote_port" \
362 if [ $? -eq 3 ]; then state=hostname
364 remote_port=`cat line`
370 if [ "$remote_transport" = "tcp" ]; then
372 elif [ -n "$override_remote_directory" ]; then
373 remote_directory="$override_remote_directory"
377 --extra-button --extra-label "Back" --nocancel \
378 --inputbox "Remote directory containing guest images" \
380 "$remote_directory" \
382 if [ $? -eq 3 ]; then state=port
384 remote_directory=`cat line`
390 # Block devices configuration.
392 if [ -n "$override_devices_to_send" ]; then
393 devices_to_send="$override_devices_to_send"
396 # Returns the list of physical devices in $devices
399 log devices returned by search_devices: $devices
402 for d in $devices; do
403 if word_in_list $d $devices_to_send; then
408 gigs=$(($(blockdev --getsize /dev/$d)/2/1024/1024))
409 deviceslist="$deviceslist $d /dev/$d(${gigs}GB) $stat"
413 --extra-button --extra-label "Back" --nocancel \
415 --checklist "Pick disks to send" 15 50 8 \
418 if [ $? -eq 3 ]; then state=directory
420 devices_to_send=`cat line`
428 if [ -n "$override_root_filesystem" ]; then
429 root_filesystem="$override_root_filesystem"
432 # Returns the list of possible partitions / LVs in $parts
433 search_parts $devices_to_send
435 log partitions returned by search_parts: $parts
439 if word_in_list $r $root_filesystem; then
444 partslist="$partslist $r /dev/$r $stat"
448 --extra-button --extra-label "Back" --nocancel \
450 --radiolist "Pick partition containing the root (/) filesystem" 10 70 5 \
453 if [ $? -eq 3 ]; then state=devices
455 root_filesystem=`cat line`
461 # Network configuration.
463 if [ -n "$override_network" ]; then
464 network="$override_network"
468 --extra-button --extra-label "Back" --nocancel \
469 --radiolist "Network configuration" 10 70 5 \
470 "auto" "Auto-configure from root filesystem" on \
471 "ask" "Manual configuration" off \
472 "sh" "Configure from the shell" off \
474 if [ $? -eq 3 ]; then state=root
482 # Verify configuration.
484 if [ "$greeting" = "no" ]; then
489 --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?" \
491 if [ $? -eq 1 ]; then
499 echo "Invalid state: $state"
507 # De-activate all volume groups and switch to new dm-only LVM config.
508 log deactivating volume groups
511 mv /etc/lvm/lvm.conf /etc/lvm/lvm.conf.old
512 mv /etc/lvm/lvm.conf.new /etc/lvm/lvm.conf
513 rm -f /etc/lvm/cache/.cache
515 # Snapshot the block devices.
516 for d in $devices_to_send; do
517 log snapshotting block device /dev/$d ...
521 # The block devices are whole disks. Use kpartx to repartition them.
522 log running kpartx -a /dev/mapper/snap_$d ...
523 kpartx -a /dev/mapper/snap_$d
531 # Mount the root filesystem on /mnt/root. If it's a physical
532 # device then we want to mount (eg) /dev/mapper/snap_sda2.
533 # If it's a LVM device then we can just mount the LVM partition.
535 log mount $root_filesystem as /mnt/root
537 if [ -f /dev/mapper/snap_$root_filesystem ]; then
538 mount /dev/mapper/snap_$root_filesystem /mnt/root
540 mount /dev/$root_filesystem /mnt/root
543 # Now see if we can get a network configuration.
544 log network configuration $network
548 echo "Network configuration"
550 echo "Please configure the network from this shell."
552 echo "When finished, exit with ^D or exit"
558 # XXX Not implemented
559 echo "Sorry, we didn't implement this one yet."
564 echo "Trying to auto-configure network from /dev/$network ..."
566 if ! auto_network "$network"; then
567 echo "Auto-configuration failed. Starting a shell."
579 #----------------------------------------------------------------------
585 # This file must end with a newline