fa2e7051e3cb3934a04b02b226971d18a4eb95d8
[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 # By Richard W.M. Jones <rjones@redhat.com>
7 #
8 # Copyright (C) 2007 Red Hat Inc.
9 # $Id$
10
11 export PATH=/usr/sbin:/sbin:/usr/local/bin:/usr/kerberos/bin:/usr/bin:/bin
12
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
16 # input.
17
18 # Use 'greeting=no' to suppress greeting and final verification screens.
19 greeting=
20
21 override_remote_host=
22 override_remote_port=
23
24 # can be 'ssh' or 'tcp'
25 override_remote_transport=
26
27 # eg. override_remote_directory=/var/lib/xen/images
28 # (only if override_remote_transport is 'ssh')
29 override_remote_directory=
30
31 # list of devices to send, separated by spaces, if empty ask user
32 # this is usually a list of whole disk devices (eg. "sda")
33 override_devices_to_send=""
34
35 # list of root filesystems to examine (eg. "sda3" or
36 # "VolGroup00/LogVol00")
37 override_root_filesystems=""
38
39 #----------------------------------------------------------------------
40 # Logging.
41
42 function log {
43     echo "$@" >> /tmp/virt-p2v.log
44 }
45
46 #----------------------------------------------------------------------
47 # Helper functions.
48
49 # 'matches_regexp regexp string' returns true if string matches regexp.
50 # It uses egrep for extended regexps.
51 function matches_regexp {
52     local re="$1"; shift
53     echo "$@" | grep -Esq "$re"
54 }
55
56 # 'string_contains needle haystack' returns true if needle in haystack.
57 function string_contains {
58     echo "$2" | grep -Fsq "$1"
59 }
60
61 # 'word_in_list word ...'.  '...' (list of parameters) is a list of
62 # words.  Returns true if 'word' is in the list.
63 function word_in_list {
64     local word="$1"; shift
65     local w
66     for w in "$@"; do
67         [ "$w" = "$word" ] && return 0
68     done
69     return 1
70 }
71
72 #----------------------------------------------------------------------
73 # Device mapper snapshotting.
74
75 # Create a device-mapper snapshot of a device with ramdisk overlay.
76 # Example:
77 #   snapshot /dev/sda1 snap
78 # creates a snapshot of /dev/sda1 called /dev/mapper/snap
79
80
81 # XXX Error checking.
82 function snapshot {
83     local dev=$1 name=$2
84
85     # Get size of the device in sectors.
86     local sectors=`blockdev --getsize $dev`
87
88     dmsetup create ${name}_org \
89         --table="0 $sectors snapshot-origin $dev"
90     dmsetup create $name \
91         --table="0 $sectors snapshot /dev/mapper/${name}_org /dev/ram1 n 64"
92 }
93
94 # Drop an existing snapshot created by snapshot function.
95 # Example:
96 #   drop_snapshot snap
97 # drops a snapshot called /dev/mapper/snap
98 function drop_snapshot {
99     dmsetup remove /dev/mapper/$name
100     dmsetup remove /dev/mapper/${name}_org
101 }
102
103 #----------------------------------------------------------------------
104 # Searching for devices, partitions and LVs.
105
106 # Convert "/dev/foo" into "foo".  Returns $device.
107 function strip_slash_dev {
108     device=$(echo "$1" | sed 's|^/dev/||')
109 }
110
111 # The 'lvs' utility returns devices like '/dev/sda2(0)'.  I've no
112 # idea what the number in parentheses is, but remove /dev/ and the
113 # strange number.
114 function device_of_lvs_device {
115     strip_slash_dev "$1"
116     device=$(echo "$device" | sed 's|(.*)$||')
117 }
118
119 # All functions in this section assume that physical block devices
120 # (things corresponding to hard disks in the system) are called
121 # hd[a-z]+ or sd[a-z]+
122
123 # Get list of physical block devices.  Sets variable $devices
124 # to something like "sda sdb".
125 function search_devices {
126     devices=$(cd /sys/block && /bin/ls -d [hs]d*)
127 }
128
129 # Get list of partitions from a physical block device.  Sets
130 # variable $partitions to something like "sda1 sda2 sda3".
131 # See also: search_parts
132 function get_partitions {
133     partitions=$(cd /sys/block/"$1" && /bin/ls -d "$1"*)
134 }
135
136 # Given a partition (eg. "sda1" or "VolGroup00/LogVol00") return
137 # the physical block device which contains it.  In the case where
138 # we are given an LV, this is supposed to do the right thing and
139 # go back through the layers until it gets to the physical block
140 # device.  Returns $device set to something like "sda".
141 function block_device_of_part {
142     local part="$1" vg_name lv_name pv
143
144     if matches_regexp '^[hs]d[a-z]+[0-9]*$' "$part"; then
145         device=$(echo "$part" | sed 's|[0-9]*$||')
146         return 0
147     fi
148
149     # Not a partition name, so it's a LV name.  Ask lvs to
150     # do the hard work.
151     lvs --noheadings -o vg_name,lv_name,devices > lvs
152     while read vg_name lv_name pv; do
153         if [ "$vg_name/$lv_name" = "$part" \
154             -o "mapper/$vg_name-$lv_name" = "$part" ]; then
155             device_of_lvs_device "$pv"  ;# sets $device to (eg.) "sda1"
156             block_device_of_part "$device"
157             return 0
158         fi
159     done < lvs
160
161     # Help ... block device not found.
162     log block_device_of_part: block device cannot be resolved: $part
163     device="$part"
164 }
165
166 # search_parts $devices examines the list of devices and looks for
167 # partitions or LVs on just those devices.  Sets variable $parts to
168 # something like "sda1 VolGroup00/LogVol00".
169 function search_parts {
170     local vg_name lv_name pv pvs="" device partition partitions
171
172     parts=""
173
174     lvs --noheadings -o vg_name,lv_name,devices > lvs
175     while read vg_name lv_name pv; do
176         # Get just the partition name (eg. "sda2").
177         device_of_lvs_device "$pv"; pv="$device"
178         # Get just the block device name (eg. "sda").
179         block_device_of_part "$pv"
180
181         log search_parts: pv $pv device $device lv $vg_name/$lv_name
182
183         # A device in our list of devices to consider?
184         if word_in_list $device "$@"; then
185             log search_parts: adding $vg_name/$lv_name
186             parts="$parts $vg_name/$lv_name"
187             pvs="$pvs $pv"
188         fi
189     done < lvs
190
191     log search_parts: after lvs, parts $parts pvs $pvs
192
193     # Look for non-LVM partitions, but ignore any which are PVs
194     # as identified above.
195     for device in "$@"; do
196         get_partitions "$device"
197         for partition in $partitions; do
198             if ! word_in_list $partition $pvs; then
199                 log search_parts: adding $partition
200                 parts="$parts $partition"
201             fi
202         done
203     done
204
205     # $parts is returned.
206 }
207
208 #----------------------------------------------------------------------
209 # General script setup.
210
211 # We can safely write files into /tmp without modifying anything.
212 cd /tmp
213
214 #----------------------------------------------------------------------
215 # Dialog with the user.
216
217 log
218 log virt-p2v starting up at `date`
219
220 if [ "$greeting" != "no" ]; then
221     dialog \
222         --title "virt-p2v" \
223         --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
224 fi
225
226 # Get configuration from the user.
227
228 # To make the [Back] button work, we make this into a looping state
229 # machine.  Each state asks a question and jumps to the next state
230 # (unless [Back] is pressed, in which case it jumps back to the previous
231 # state).  Finally the 'exit' state causes us to quit the loop.
232
233 remote_port=22
234 remote_directory=/var/lib/xen/images
235 state=transport
236
237 while [ "$state" != "exit" ]; do
238     log next state = $state
239     case "$state" in
240         transport)
241             if [ -n "$override_remote_transport" ]; then
242                 remote_transport="$override_remote_transport"
243                 state=hostname
244             else
245                 case "$remote_transport" in
246                     tcp) ssh_on=off; tcp_on=on;;
247                     *) ssh_on=on; tcp_on=off;;
248                 esac
249                 dialog \
250                     --nocancel \
251                     --radiolist "Connection type" 10 50 2 \
252                     "ssh" "SSH (secure shell - recommended)" $ssh_on \
253                     "tcp" "TCP socket" $tcp_on \
254                     2> line
255                 remote_transport=`cat line`
256                 state=hostname
257             fi
258             ;;
259         hostname)
260             if [ -n "$override_remote_host" ]; then
261                 remote_host="$override_remote_host"
262                 state=port
263             else
264                 dialog \
265                     --extra-button --extra-label "Back" --nocancel \
266                     --inputbox "Remote host" 10 50 "$remote_host" \
267                     2> line
268                 if [ $? -eq 3 ]; then state=transport
269                 else
270                     remote_host=`cat line`
271                     if [ -n "$remote_host" ]; then state=port; fi
272                     # else stay in same state and demand a hostname!
273                 fi
274             fi
275             ;;
276         port)
277             if [ -n "$override_remote_port" ]; then
278                 remote_port="$override_remote_port"
279                 state=directory
280             else
281                 dialog \
282                     --extra-button --extra-label "Back" --nocancel \
283                     --inputbox "Remote port" 10 50 "$remote_port" \
284                     2> line
285                 if [ $? -eq 3 ]; then state=hostname
286                 else
287                     remote_port=`cat line`
288                     state=directory
289                 fi
290             fi
291             ;;
292         directory)
293             if [ "$remote_transport" = "tcp" ]; then
294                 state=devices
295             elif [ -n "$override_remote_directory" ]; then
296                 remote_directory="$override_remote_directory"
297                 state=devices
298             else
299                 dialog \
300                     --extra-button --extra-label "Back" --nocancel \
301                     --inputbox "Remote directory containing guest images" \
302                     10 50 \
303                     "$remote_directory" \
304                     2> line
305                 if [ $? -eq 3 ]; then state=port
306                 else
307                     remote_directory=`cat line`
308                     state=devices
309                 fi
310             fi
311             ;;
312         devices)
313             if [ -n "$override_devices_to_send" ]; then
314                 devices_to_send="$override_devices_to_send"
315                 state=roots
316             else
317                 # Returns the list of possible devices in $devices
318                 search_devices
319
320                 log devices returned by search_devices: $devices
321
322                 deviceslist=""
323                 for d in $devices; do
324                     if word_in_list $d $devices_to_send; then
325                         stat=on
326                     else
327                         stat=off
328                     fi
329                     deviceslist="$deviceslist $d /dev/$d $stat"
330                 done
331
332                 dialog \
333                     --extra-button --extra-label "Back" --nocancel \
334                     --single-quoted \
335                     --checklist "Pick which local disk images to send" 10 50 5 \
336                     $deviceslist \
337                     2> line
338                 if [ $? -eq 3 ]; then state=directory
339                 else
340                     devices_to_send=`cat line`
341                     state=roots
342                     log user selected devices_to_send = $devices_to_send
343                 fi
344             fi
345             ;;
346         roots)
347             if [ -n "$override_root_filesystems" ]; then
348                 root_filesystems="$override_root_filesystems"
349                 state=verify
350             else
351                 # Returns the list of possible partitions / LVs in $parts
352                 search_parts $devices_to_send
353
354                 log filesystems returned by search_parts: $parts
355
356                 if [ -z "$parts" ]; then
357                     root_filesystems=""
358                     state=verify
359                 else
360                     partslist=""
361                     for r in $parts; do
362                         if word_in_list $r $root_filesystems; then
363                             stat=on
364                         else
365                             stat=off
366                         fi
367                         partslist="$partslist $r /dev/$r $stat"
368                     done
369
370                     dialog \
371                         --extra-button --extra-label "Back" --nocancel \
372                         --single-quoted \
373                         --checklist "Pick partition(s) containing root (/) filesystem" 10 70 5 \
374                         $partslist \
375                         2> line
376                     if [ $? -eq 3 ]; then state=devices
377                     else
378                         root_filesystems=`cat line`
379                         state=verify
380                         log user selected root_filesystems = $root_filesystems
381                     fi
382                 fi
383             fi
384             ;;
385         verify)
386             if [ "$greeting" = "no" ]; then
387                 state=exit
388             else
389                 dialog \
390                     --title "Summary" \
391                     --yesno "Transport: $remote_transport\nRemote host: $remote_host\nRemote port: $remote_port\nRemote directory (ssh only): $remote_directory\nDisks to send: $devices\nRoot filesystem(s): $root_filesystems\n\nProceed with these settings?" \
392                     23 70
393                 if [ $? -eq 1 ]; then
394                     state=transport
395                 else
396                     state=exit
397                 fi
398             fi
399             ;;
400         *)
401             echo "Invalid state: $state"
402             state=transport
403             ;;
404     esac
405 done
406
407 clear
408
409 # Mount the root filesystem(s) using device-mapper snapshots.
410
411
412
413
414
415
416
417
418 # This file must end with a newline
419