Add static network configuration option and make <Back> work.
[virt-p2v.git] / virt-p2v
1 #!/usr/bin/ocamlrun /usr/bin/ocaml
2 (* -*- tuareg -*- *)
3 (* virt-p2v is a script which performs a physical to
4  * virtual conversion of local disks.
5  *
6  * Copyright (C) 2007-2008 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
24 #load "unix.cma";;
25 #directory "+extlib";;
26 #load "extLib.cma";;
27 #directory "+pcre";;
28 #load "pcre.cma";;
29 #directory "+xml-light";;
30 #load "xml-light.cma";;
31
32 open Unix
33 open Printf
34 open ExtList
35 open ExtString
36
37 type state = { greeting : bool;
38                remote_host : string option; remote_port : string option;
39                remote_directory : string option;
40                remote_username : string option;
41                network : network option;
42                static_network_config : static_network_config option;
43                devices_to_send : string list option;
44                root_filesystem : partition option;
45                hypervisor : hypervisor option;
46                architecture : architecture option;
47                memory : int option; vcpus : int option;
48                mac_address : string option;
49              }
50 and network = Auto
51             | Shell
52             | QEMUUserNet
53             | Static
54 and partition = Part of string * string (* eg. "hda", "1" *)
55               | LV of string * string   (* eg. "VolGroup00", "LogVol00" *)
56 and hypervisor = Xen | QEMU | KVM
57 and architecture = I386 | X86_64 | IA64 | PPC | PPC64 | SPARC | SPARC64
58                  | OtherArch of string | UnknownArch
59 and static_network_config = string * string * string * string * string
60     (* interface, address, netmask, gateway, nameserver *)
61
62 (*----------------------------------------------------------------------*)
63 (* TO MAKE A CUSTOM virt-p2v SCRIPT, adjust the defaults in this section.
64  *
65  * If left as they are, then this will create a generic virt-p2v script
66  * which asks the user for each question.  If you set the defaults here
67  * then you will get a custom virt-p2v which is partially or even fully
68  * automated and won't ask the user any questions.
69  *
70  * Note that 'None' means 'no default' (ie. ask the user) whereas
71  * 'Some foo' means use 'foo' as the answer.
72  *)
73 let defaults = {
74   (* If greeting is true, wait for keypress after boot and during
75    * final verification.  Set to 'false' for less interactions.
76    *)
77   greeting = true;
78
79   (* These are now documented in the man page virt-p2v(1).
80    * 'None' means ask the user.
81    * After changing them, run './virt-p2v --test' to check syntax.
82    *)
83   remote_host = None;
84   remote_port = None;
85   remote_directory = None;
86   remote_username = None;
87   devices_to_send = None;
88   root_filesystem = None;
89   network = None;
90   static_network_config = None;
91   hypervisor = None;
92   architecture = None;
93   memory = None;
94   vcpus = None;
95   mac_address = None;
96 }
97 (* END OF CUSTOM virt-p2v SCRIPT SECTION.                               *)
98 (*----------------------------------------------------------------------*)
99
100 (* General helper functions. *)
101
102 let sort_uniq ?(cmp = compare) xs =     (* sort and uniq a list *)
103   let xs = List.sort ~cmp xs in
104   let rec loop = function
105     | [] -> [] | [x] -> [x]
106     | x1 :: x2 :: xs when x1 = x2 -> loop (x1 :: xs)
107     | x :: xs -> x :: loop xs
108   in
109   loop xs
110
111 let input_all_lines chan =
112   let lines = ref [] in
113   try
114     while true do lines := input_line chan :: !lines done; []
115   with
116     End_of_file -> List.rev !lines
117
118 let dev_of_partition = function
119   | Part (dev, partnum) -> sprintf "/dev/%s%s" dev partnum
120   | LV (vg, lv) -> sprintf "/dev/%s/%s" vg lv
121
122 let string_of_architecture = function
123   | I386 -> "i386"
124   | X86_64 -> "x86_64"
125   | IA64 -> "ia64"
126   | PPC -> "ppc"
127   | PPC64 -> "ppc64"
128   | SPARC -> "sparc"
129   | SPARC64 -> "sparc64"
130   | OtherArch arch -> arch
131   | UnknownArch -> ""
132
133 type dialog_status = Yes of string list | No | Help | Back | Error
134
135 type ask_result = Next of state | Prev | Ask_again
136
137 type nature = LinuxSwap
138             | LinuxRoot of architecture * linux_distro
139             | WindowsRoot               (* Windows C: *)
140             | LinuxBoot                 (* Linux /boot *)
141             | NotRoot                   (* mountable, but not / or /boot *)
142             | UnknownNature
143 and linux_distro = RHEL of int * int
144                  | Fedora of int
145                  | Debian of int * int
146                  | OtherLinux
147
148 let rec string_of_nature = function
149   | LinuxSwap -> "Linux swap"
150   | LinuxRoot (architecture, distro) ->
151       string_of_linux_distro distro ^ " " ^ string_of_architecture architecture
152   | WindowsRoot -> "Windows root"
153   | LinuxBoot -> "Linux /boot"
154   | NotRoot -> "Mountable non-root"
155   | UnknownNature -> "Unknown"
156 and string_of_linux_distro = function
157   | RHEL (a,b) -> sprintf "RHEL %d.%d" a b
158   | Fedora v -> sprintf "Fedora %d" v
159   | Debian (a,b) -> sprintf "Debian %d.%d" a b
160   | OtherLinux -> "Linux"
161
162 (* Dialog functions.
163  *
164  * Each function takes some common parameters (eg. ~title) and some
165  * dialog-specific parameters.
166  *
167  * Returns the exit status (Yes lines | No | Help | Back | Error).
168  *)
169 let msgbox, yesno, inputbox, radiolist, checklist, form =
170   (* Internal function to actually run the "dialog" shell command. *)
171   let run_dialog cparams params =
172     let params = cparams @ params in
173     eprintf "dialog %s\n%!"
174       (String.concat " " (List.map (sprintf "%S") params));
175
176     (* 'dialog' writes its output/result to stderr, so we need to take
177      * special steps to capture that - in other words, manual pipe/fork.
178      *)
179     let rfd, wfd = pipe () in
180     match fork () with
181     | 0 ->                              (* child, runs dialog *)
182         close rfd;
183         dup2 wfd stderr;                (* capture stderr to pipe *)
184         execvp "dialog" (Array.of_list ("dialog" :: params))
185     | pid ->                            (* parent *)
186         close wfd;
187         let chan = in_channel_of_descr rfd in
188         let result = input_all_lines chan in
189         close rfd;
190         eprintf "dialog result: %S\n%!" (String.concat "\n" result);
191         match snd (wait ()) with
192         | WEXITED 0 -> Yes result       (* something selected / entered *)
193         | WEXITED 1 -> No               (* cancel / no button *)
194         | WEXITED 2 -> Help             (* help pressed *)
195         | WEXITED 3 -> Back             (* back button *)
196         | WEXITED _ -> Error            (* error or Esc *)
197         | WSIGNALED i -> failwith (sprintf "dialog: killed by signal %d" i)
198         | WSTOPPED i -> failwith (sprintf "dialog: stopped by signal %d" i)
199   in
200
201   (* Handle the common parameters.  Note Continuation Passing Style. *)
202   let with_common cont ?(cancel=false) ?(backbutton=true) title =
203     let params = ["--title"; title] in
204     let params = if not cancel then "--nocancel" :: params else params in
205     let params =
206       if backbutton then "--extra-button" :: "--extra-label" :: "Back" :: params
207       else params in
208     cont params
209   in
210
211   (* Message box and yes/no box. *)
212   let rec msgbox =
213     with_common (
214       fun cparams text height width ->
215         run_dialog cparams
216           [ "--msgbox"; text; string_of_int height; string_of_int width ]
217     )
218   and yesno =
219     with_common (
220       fun cparams text height width ->
221         run_dialog cparams
222           [ "--yesno"; text; string_of_int height; string_of_int width ]
223     )
224
225   (* Simple input box. *)
226   and inputbox =
227     with_common (
228       fun cparams text height width default ->
229         run_dialog cparams
230           [ "--inputbox"; text; string_of_int height; string_of_int width;
231             default ]
232     )
233
234   (* Radio list and check list. *)
235   and radiolist =
236     with_common (
237       fun cparams text height width listheight items ->
238         let items = List.map (
239           function
240           | tag, item, true -> [ tag; item; "on" ]
241           | tag, item, false -> [ tag; item; "off" ]
242         ) items in
243         let items = List.concat items in
244         let items = "--single-quoted" ::
245           "--radiolist" :: text ::
246           string_of_int height :: string_of_int width ::
247           string_of_int listheight :: items in
248         run_dialog cparams items
249     )
250   and checklist =
251     with_common (
252       fun cparams text height width listheight items ->
253         let items = List.map (
254           function
255           | tag, item, true -> [ tag; item; "on" ]
256           | tag, item, false -> [ tag; item; "off" ]
257         ) items in
258         let items = List.concat items in
259         let items = "--separate-output" ::
260           "--checklist" :: text ::
261           string_of_int height :: string_of_int width ::
262           string_of_int listheight :: items in
263         run_dialog cparams items
264     )
265
266   (* Form. *)
267   and form =
268     with_common (
269       fun cparams text height width formheight items ->
270         let items = List.map (
271           fun (label, y, x, item, y', x', flen, ilen) ->
272             [ label; string_of_int y; string_of_int x; item;
273               string_of_int y'; string_of_int x';
274               string_of_int flen; string_of_int ilen ]
275         ) items in
276         let items = List.concat items in
277         let items = "--form" :: text ::
278           string_of_int height :: string_of_int width ::
279           string_of_int formheight :: items in
280         run_dialog cparams items
281     )
282   in
283   msgbox, yesno, inputbox, radiolist, checklist, form
284
285 (* Print failure dialog and exit. *)
286 let fail_dialog text =
287   let text = "\n" ^ text ^ "\n\nIf you want to report this error, there is a shell on [ALT] [F2], log in as root with no password.\n\nPlease provide the contents of /tmp/virt-p2v.log and output of the 'dmesg' command." in
288   ignore (msgbox "Error" text 17 50);
289   exit 1
290
291 (* Shell-safe quoting function.  In fact there's one in stdlib so use it. *)
292 let quote = Filename.quote
293
294 (* Run a shell command and check it returns 0. *)
295 let sh cmd =
296   eprintf "sh: %s\n%!" cmd;
297   if Sys.command cmd <> 0 then fail_dialog (sprintf "Command failed:\n\n%s" cmd)
298
299 let shfailok cmd =
300   eprintf "shfailok: %s\n%!" cmd;
301   ignore (Sys.command cmd)
302
303 let shwithstatus cmd =
304   eprintf "shwithstatus: %s\n%!" cmd;
305   Sys.command cmd
306
307 (* Same as `cmd` in shell.  Any error message will be in the logfile. *)
308 let shget cmd =
309   eprintf "shget: %s\n%!" cmd;
310   let chan = open_process_in cmd in
311   let lines = input_all_lines chan in
312   match close_process_in chan with
313   | WEXITED 0 -> Some lines             (* command succeeded *)
314   | WEXITED _ -> None                   (* command failed *)
315   | WSIGNALED i -> failwith (sprintf "shget: command killed by signal %d" i)
316   | WSTOPPED i -> failwith (sprintf "shget: command stopped by signal %d" i)
317
318 (* Start an interactive shell.  Need to juggle file descriptors a bit
319  * because bash write PS1 to stderr (currently directed to the logfile).
320  *)
321 let shell () =
322   match fork () with
323   | 0 ->                                (* child, runs bash *)
324       close stderr;
325       dup2 stdout stderr;
326       (* Sys.command runs 'sh -c' which blows away PS1, so set it late. *)
327       ignore (
328         Sys.command "PS1='\\u@\\h:\\w\\$ ' /bin/bash --norc --noprofile -i"
329       )
330   | _ ->                                (* parent, waits *)
331       eprintf "waiting for subshell to exit\n%!";
332       ignore (wait ())
333
334 (* Some true if is dir/file, Some false if not, None if not found. *)
335 let is_dir path =
336   try Some ((stat path).st_kind = S_DIR)
337   with Unix_error (ENOENT, "stat", _) -> None
338 let is_file path =
339   try Some ((stat path).st_kind = S_REG)
340   with Unix_error (ENOENT, "stat", _) -> None
341
342 (* Useful regular expression. *)
343 let whitespace = Pcre.regexp "[ \t]+"
344
345 (* Generate a predictable safe name containing only letters, numbers
346  * and underscores.  If passed a string with no letters or numbers,
347  * generates "_1", "_2", etc.
348  *)
349 let safe_name =
350   let next_anon =
351     let i = ref 0 in
352     fun () -> incr i; "_" ^ string_of_int !i
353   in
354   fun name ->
355     let is_safe = function 'a'..'z'|'A'..'Z'|'0'..'9' -> true | _ -> false in
356     let name = String.copy name in
357     let have_safe = ref false in
358     for i = 0 to String.length name - 1 do
359       if not (is_safe name.[i]) then name.[i] <- '_' else have_safe := true
360     done;
361     if !have_safe then name else next_anon ()
362
363 type block_device = string * int64      (* "hda" & size in bytes *)
364
365 (* Parse the output of 'lvs' to get list of LV names, sizes,
366  * corresponding PVs, etc.  Returns a list of (lvname, PVs, lvsize).
367  *)
368 let get_lvs =
369   let devname = Pcre.regexp "^/dev/(.+)\\(.+\\)$" in
370
371   function () ->
372     match
373     shget "lvs --noheadings -o vg_name,lv_name,devices,lv_size"
374     with
375     | None -> []
376     | Some lines ->
377         let lines = List.map (Pcre.split ~rex:whitespace) lines in
378         List.map (
379           function
380           | [vg; lv; pvs; lvsize]
381           | [_; vg; lv; pvs; lvsize] ->
382               let pvs = String.nsplit pvs "," in
383               let pvs = List.filter_map (
384                 fun pv ->
385                   try
386                     let subs = Pcre.exec ~rex:devname pv in
387                     Some (Pcre.get_substring subs 1)
388                   with
389                     Not_found ->
390                       eprintf "lvs: unexpected device name: %s\n%!" pv;
391                       None
392               ) pvs in
393               LV (vg, lv), pvs, lvsize
394           | line ->
395               failwith ("lvs: unexpected output: " ^ String.concat "," line)
396         ) lines
397
398 (* Get the partitions on a block device.
399  * eg. "sda" -> [Part ("sda","1"); Part ("sda", "2")]
400  *)
401 let get_partitions dev =
402   let rex = Pcre.regexp ("^" ^ dev ^ "(.+)$") in
403   let devdir = "/sys/block/" ^ dev in
404   let parts = Sys.readdir devdir in
405   let parts = Array.to_list parts in
406   let parts = List.filter (
407     fun name -> Some true = is_dir (devdir ^ "/" ^ name)
408   ) parts in
409   let parts = List.filter_map (
410     fun part ->
411       try
412         let subs = Pcre.exec ~rex part in
413         Some (Part (dev, Pcre.get_substring subs 1))
414       with
415         Not_found -> None
416   ) parts in
417   parts
418
419 (* Generate snapshot device name from device name. *)
420 let snapshot_name dev =
421   "snap" ^ (safe_name dev)
422
423 (* Perform a device-mapper snapshot with ramdisk overlay. *)
424 let snapshot =
425   let next_free_ram_disk =
426     let i = ref 0 in
427     fun () -> incr i; "/dev/ram" ^ string_of_int !i
428   in
429   fun origin_dev snapshot_dev ->
430     let ramdisk = next_free_ram_disk () in
431     let sectors =
432       let cmd = "blockdev --getsz " ^ quote ("/dev/" ^ origin_dev) in
433       let lines = shget cmd in
434       match lines with
435       | Some (sectors::_) -> Int64.of_string sectors
436       | Some [] | None ->
437           fail_dialog (sprintf "Snapshot failed - unable to read the size in sectors of block device %s" origin_dev) in
438
439     (* Create the snapshot origin device.  Called, eg. snap_sda1_org *)
440     sh (sprintf "dmsetup create %s_org --table='0 %Ld snapshot-origin /dev/%s'"
441           snapshot_dev sectors origin_dev);
442     (* Create the snapshot. *)
443     sh (sprintf "dmsetup create %s --table='0 %Ld snapshot /dev/mapper/%s_org %s n 64'"
444           snapshot_dev sectors snapshot_dev ramdisk)
445
446 (* Try to perform automatic network configuration, assuming a Fedora or
447  * RHEL-like root filesystem mounted on /mnt/root.
448  *)
449 let auto_network state =
450   (* Fedora gives an error if this file doesn't exist. *)
451   sh "touch /etc/resolv.conf";
452
453 (*
454   (* We can run /mnt/root/etc/init.d/network in a chroot environment,
455    * however this won't work directly because the architecture of the
456    * binaries under /mnt/root (eg. /mnt/root/sbin/ip) might not match
457    * the architecture of the live CD kernel.  In particular, a 32 bit
458    * live CD cannot run 64 bit binaries.  So we also have to bind-mount
459    * the live CD's /bin, /sbin, /lib etc. over the equivalents in
460    * /mnt/root.
461    *)
462   let bind dir =
463     if is_dir dir = Some true then
464       sh ("mount -o bind " ^ quote dir ^ " " ^ quote ("/mnt/root" ^ dir))
465   in
466   let unbind dir =
467     if is_dir dir = Some true then sh ("umount -l " ^ quote ("/mnt/root" ^ dir))
468   in
469   let dirs = [
470     "/bin"; "/sbin"; "/lib"; "/lib64";
471     "/usr/bin"; "/usr/sbin"; "/usr/lib"; "/usr/lib64";
472     "/proc"; "/sys"
473   ] in
474   List.iter bind dirs;
475   let status = shwithstatus "chroot /mnt/root /etc/init.d/network start" in
476   List.iter unbind dirs;
477 *)
478
479   (* Simpler way to do the above.
480    * NB. Lazy unmount is required because dhclient keeps its current
481    * directory open on /etc/sysconfig/network-scripts/
482    *)
483   sh "mount -o bind /mnt/root/etc /etc";
484   let status = shwithstatus "/etc/init.d/network start" in
485   sh "umount -l /etc";
486
487   (* Try to ping the remote host to see if this worked. *)
488   shfailok ("ping -c 3 " ^ Option.map_default quote "" state.remote_host);
489
490   if state.greeting then (
491     printf "\n\nDid automatic network configuration work?\n";
492     printf "Hint: If not sure, there is a shell on console [ALT] [F2]\n";
493     printf "    (y/n) %!";
494     let line = read_line () in
495     String.length line > 0 && (line.[0] = 'y' || line.[0] = 'Y')
496   )
497   else
498     (* Non-interactive: return the status of /etc/init.d/network start. *)
499     status = 0
500
501 (* Configure the network statically. *)
502 let static_network state =
503   match state.static_network_config with
504   | None -> false                       (* failed *)
505   | Some (interface, address, netmask, gateway, nameserver) ->
506       let do_cmd_or_exit cmd = if shwithstatus cmd <> 0 then raise Exit in
507       try
508         do_cmd_or_exit (sprintf "ifconfig %s %s netmask %s"
509                           (quote interface) (quote address) (quote netmask));
510         do_cmd_or_exit (sprintf "route add default gw %s %s"
511                           (quote gateway) (quote interface));
512         if nameserver <> "" then
513           do_cmd_or_exit (sprintf "echo nameserver %s > /etc/resolv.conf"
514                             (quote nameserver));
515         true                            (* succeeded *)
516       with
517         Exit -> false                   (* failed *)
518
519 let qemu_network () =
520   sh "ifconfig eth0 10.0.2.10 netmask 255.255.255.0";
521   sh "route add default gw 10.0.2.2 eth0";
522   sh "echo nameserver 10.0.2.3 > /etc/resolv.conf"
523
524 (* Map local device names to remote devices names.  At the moment we
525  * just change sd* to hd* (as device names appear under fullvirt).  In
526  * future, lots of complex possibilities.
527  *)
528 let remote_of_origin_dev =
529   let devsd = Pcre.regexp "^sd([[:alpha:]]+[[:digit:]]*)$" in
530   let devsd_subst = Pcre.subst "hd$1" in
531   fun dev ->
532     Pcre.replace ~rex:devsd ~itempl:devsd_subst dev
533
534 (* Rewrite /mnt/root/etc/fstab. *)
535 let rewrite_fstab state devices_to_send =
536   let filename = "/mnt/root/etc/fstab" in
537   if is_file filename = Some true then (
538     sh ("cp " ^ quote filename ^ " " ^ quote (filename ^ ".p2vsaved"));
539
540     let chan = open_in filename in
541     let lines = input_all_lines chan in
542     close_in chan;
543     let lines = List.map (Pcre.split ~rex:whitespace) lines in
544     let lines = List.map (
545       function
546       | dev :: rest when String.starts_with dev "/dev/" ->
547           let dev = String.sub dev 5 (String.length dev - 5) in
548           let dev = remote_of_origin_dev dev in
549           let dev = "/dev/" ^ dev in
550           dev :: rest
551       | line -> line
552     ) lines in
553
554     let chan = open_out filename in
555     List.iter (
556       function
557       | [dev; mountpoint; fstype; options; freq; passno] ->
558           fprintf chan "%-23s %-23s %-7s %-15s %s %s\n"
559             dev mountpoint fstype options freq passno
560       | line ->
561           output_string chan (String.concat " " line)
562     ) lines;
563     close_out chan
564   )
565
566 (* Main entry point. *)
567 let rec main ttyname =
568   (* Running from an init script.  We don't have much of a
569    * login environment, so set one up.
570    *)
571   putenv "PATH"
572     (String.concat ":"
573        ["/usr/sbin"; "/sbin"; "/usr/local/bin"; "/usr/kerberos/bin";
574         "/usr/bin"; "/bin"]);
575   putenv "HOME" "/root";
576   putenv "LOGNAME" "root";
577
578   (* We can safely write in /tmp (it's a synthetic live CD directory). *)
579   chdir "/tmp";
580
581   (* Set up logging to /tmp/virt-p2v.log. *)
582   let fd = openfile "virt-p2v.log" [ O_WRONLY; O_APPEND; O_CREAT ] 0o644 in
583   dup2 fd stderr;
584   close fd;
585
586   (* Log the start up time. *)
587   eprintf "\n\n**************************************************\n\n";
588   let tm = localtime (time ()) in
589   eprintf "virt-p2v-ng starting up at %04d-%02d-%02d %02d:%02d:%02d\n\n%!"
590     (tm.tm_year+1900) (tm.tm_mon+1) tm.tm_mday tm.tm_hour tm.tm_min tm.tm_sec;
591
592   (* Connect stdin/stdout to the tty. *)
593   (match ttyname with
594    | None -> ()
595    | Some ttyname ->
596        let fd = openfile ("/dev/" ^ ttyname) [ O_RDWR ] 0 in
597        dup2 fd stdin;
598        dup2 fd stdout;
599        close fd);
600   printf "virt-p2v starting up ...\n%!";
601
602   (* Disable screen blanking on tty. *)
603   sh "setterm -blank 0";
604
605   (* Check that the environment is a sane-looking live CD.  If not, bail. *)
606   if is_dir "/mnt/root" <> Some true then
607     fail_dialog
608       "You should only run this script from the live CD or a USB key.";
609
610   printf "virt-p2v detecting hard drives (this may take some time) ...\n%!";
611
612   (* Search for all non-removable block devices.  Do this early and bail
613    * if we can't find anything.  This is a list of strings, like "hda".
614    *)
615   let all_block_devices : block_device list =
616     let rex = Pcre.regexp "^[hs]d" in
617     let devices = Array.to_list (Sys.readdir "/sys/block") in
618     let devices = List.sort devices in
619     let devices = List.filter (fun d -> Pcre.pmatch ~rex d) devices in
620     eprintf "all_block_devices: block devices: %s\n%!"
621       (String.concat "; " devices);
622     (* Run blockdev --getsize64 on each, and reject any where this fails
623      * (probably removable devices).
624      *)
625     let devices = List.filter_map (
626       fun d ->
627         let cmd = "blockdev --getsize64 " ^ quote ("/dev/" ^ d) in
628         let lines = shget cmd in
629         match lines with
630         | Some (blksize::_) -> Some (d, Int64.of_string blksize)
631         | Some [] | None -> None
632     ) devices in
633     eprintf "all_block_devices: non-removable block devices: %s\n%!"
634       (String.concat "; "
635          (List.map (fun (d, b) -> sprintf "%s [%Ld]" d b) devices));
636     if devices = [] then
637       fail_dialog "No non-removable block devices (hard disks, etc.) could be found on this machine.";
638     devices in
639
640   (* Search for partitions and LVs (anything that could contain a
641    * filesystem directly).  We refer to these generically as
642    * "partitions".
643    *)
644   let all_partitions : partition list =
645     (* LVs & PVs. *)
646     let lvs, pvs =
647       let lvs = get_lvs () in
648       let pvs = List.map (fun (_, pvs, _) -> pvs) lvs in
649       let pvs = List.concat pvs in
650       let pvs = sort_uniq pvs in
651       eprintf "all_partitions: PVs: %s\n%!" (String.concat "; " pvs);
652       let lvs = List.map (fun (lvname, _, _) -> lvname) lvs in
653       eprintf "all_partitions: LVs: %s\n%!"
654         (String.concat "; " (List.map dev_of_partition lvs));
655       lvs, pvs in
656
657     (* Partitions (eg. "sda1", "sda2"). *)
658     let parts =
659       let parts = List.map fst all_block_devices in
660       let parts = List.map get_partitions parts in
661       let parts = List.concat parts in
662       eprintf "all_partitions: all partitions: %s\n%!"
663         (String.concat "; " (List.map dev_of_partition parts));
664
665       (* Remove any partitions which are PVs. *)
666       let parts = List.filter (
667         function
668         | Part (dev, partnum) -> not (List.mem (dev ^ partnum) pvs)
669         | LV _ -> assert false
670       ) parts in
671       parts in
672     eprintf "all_partitions: partitions after removing PVs: %s\n%!"
673       (String.concat "; " (List.map dev_of_partition parts));
674
675     (* Concatenate LVs & Parts *)
676     lvs @ parts in
677
678   (* Try to determine the nature of each partition.
679    * Root? Swap? Architecture? etc.
680    *)
681   let all_partitions : (partition * nature) list =
682     (* Output of 'file' command for Linux swap file. *)
683     let swap = Pcre.regexp "Linux.*swap.*file" in
684     (* Contents of /etc/redhat-release. *)
685     let rhel = Pcre.regexp "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)(?:\\.(\\d+))?" in
686     let fedora = Pcre.regexp "Fedora.*release (\\d+)" in
687     (* Contents of /etc/debian_version. *)
688     let debian = Pcre.regexp "^(\\d+)\\.(\\d+)" in
689     (* Output of 'file' on certain executables. *)
690     let i386 = Pcre.regexp ", Intel 80386," in
691     let x86_64 = Pcre.regexp ", x86-64," in
692     let itanic = Pcre.regexp ", IA-64," in
693
694     (* Examine the filesystem mounted on 'mnt' to determine the
695      * operating system, and, if Linux, the distro.
696      *)
697     let detect_os mnt =
698       if is_dir (mnt ^ "/Windows") = Some true &&
699         is_file (mnt ^ "/autoexec.bat") = Some true then
700           WindowsRoot
701       else if is_dir (mnt ^ "/etc") = Some true &&
702         is_dir (mnt ^ "/sbin") = Some true &&
703         is_dir (mnt ^ "/var") = Some true then (
704           if is_file (mnt ^ "/etc/redhat-release") = Some true then (
705             let chan = open_in (mnt ^ "/etc/redhat-release") in
706             let lines = input_all_lines chan in
707             close_in chan;
708
709             match lines with
710             | [] -> (* empty /etc/redhat-release ...? *)
711                 LinuxRoot (UnknownArch, OtherLinux)
712             | line::_ -> (* try to detect OS from /etc/redhat-release *)
713                 try
714                   let subs = Pcre.exec ~rex:rhel line in
715                   let major = int_of_string (Pcre.get_substring subs 1) in
716                   let minor =
717                     try int_of_string (Pcre.get_substring subs 2)
718                     with Not_found -> 0 in
719                   LinuxRoot (UnknownArch, RHEL (major, minor))
720                 with
721                   Not_found | Failure "int_of_string" ->
722                     try
723                       let subs = Pcre.exec ~rex:fedora line in
724                       let version = int_of_string (Pcre.get_substring subs 1) in
725                       LinuxRoot (UnknownArch, Fedora version)
726                     with
727                       Not_found | Failure "int_of_string" ->
728                         LinuxRoot (UnknownArch, OtherLinux)
729           )
730           else if is_file (mnt ^ "/etc/debian_version") = Some true then (
731             let chan = open_in (mnt ^ "/etc/debian_version") in
732             let lines = input_all_lines chan in
733             close_in chan;
734
735             match lines with
736             | [] -> (* empty /etc/debian_version ...? *)
737                 LinuxRoot (UnknownArch, OtherLinux)
738             | line::_ -> (* try to detect version from /etc/debian_version *)
739                 try
740                   let subs = Pcre.exec ~rex:debian line in
741                   let major = int_of_string (Pcre.get_substring subs 1) in
742                   let minor = int_of_string (Pcre.get_substring subs 2) in
743                   LinuxRoot (UnknownArch, Debian (major, minor))
744                 with
745                   Not_found | Failure "int_of_string" ->
746                     LinuxRoot (UnknownArch, OtherLinux)
747           )
748           else
749             LinuxRoot (UnknownArch, OtherLinux)
750         ) else if is_dir (mnt ^ "/grub") = Some true &&
751           is_file (mnt ^ "/grub/stage1") = Some true then (
752             LinuxBoot
753         ) else
754           NotRoot (* mountable, but not a root filesystem *)
755     in
756
757     (* Examine the Linux root filesystem mounted on 'mnt' to
758      * determine the architecture. We do this by looking at some
759      * well-known binaries that we expect to be there.
760      *)
761     let detect_architecture mnt =
762       let cmd = "file -bL " ^ quote (mnt ^ "/sbin/init") in
763       match shget cmd with
764       | Some (str::_) when Pcre.pmatch ~rex:i386 str -> I386
765       | Some (str::_) when Pcre.pmatch ~rex:x86_64 str -> X86_64
766       | Some (str::_) when Pcre.pmatch ~rex:itanic str -> IA64
767       | _ -> UnknownArch
768     in
769
770     List.map (
771       fun part ->
772         let dev = dev_of_partition part in (* Get /dev device. *)
773
774         let nature =
775           (* Use 'file' command to detect if it is swap. *)
776           let cmd = "file -sbL " ^ quote dev in
777           match shget cmd with
778           | Some (str::_) when Pcre.pmatch ~rex:swap str -> LinuxSwap
779           | _ ->
780               (* Blindly try to mount the device. *)
781               let cmd = "mount -o ro " ^ quote dev ^ " /mnt/root" in
782               match shwithstatus cmd with
783               | 0 ->
784                   let os = detect_os "/mnt/root" in
785                   let nature =
786                     match os with
787                     | LinuxRoot (UnknownArch, distro) ->
788                         let architecture = detect_architecture "/mnt/root" in
789                         LinuxRoot (architecture, distro)
790                     | os -> os in
791                   sh "umount /mnt/root";
792                   nature
793
794               | _ -> UnknownNature (* not mountable *)
795
796         in
797
798         eprintf "partition detection: %s is %s\n%!"
799           dev (string_of_nature nature);
800
801         (part, nature)
802     ) all_partitions
803   in
804
805   printf "virt-p2v finished detecting hard drives\n%!";
806
807   (* Dialogs. *)
808   let ask_greeting state =
809     ignore (msgbox "virt-p2v" "\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.\n\nExtra information is logged in /tmp/virt-p2v.log but this file disappears when the machine reboots." 18 50);
810     Next state
811   in
812
813   let ask_hostname state =
814     match
815     inputbox "Remote host" "Remote host" 10 50
816       (Option.default "" state.remote_host)
817     with
818     | Yes [] -> Ask_again
819     | Yes (hostname::_) -> Next { state with remote_host = Some hostname }
820     | No | Help | Error -> Ask_again
821     | Back -> Prev
822   in
823
824   let ask_port state =
825     match
826     inputbox "Remote port" "Remote port" 10 50
827       (Option.default "22" state.remote_port)
828     with
829     | Yes ([]|""::_) -> Next { state with remote_port = Some "22" }
830     | Yes (port::_) -> Next { state with remote_port = Some port }
831     | No | Help | Error -> Ask_again
832     | Back -> Prev
833   in
834
835   let ask_directory state =
836     let default_dir = "/var/lib/xen/images" in
837     match
838     inputbox "Remote directory" "Remote directory" 10 50
839       (Option.default default_dir state.remote_directory)
840     with
841     | Yes ([]|""::_) -> Next { state with remote_directory = Some default_dir }
842     | Yes (dir::_) -> Next { state with remote_directory = Some dir }
843     | No | Help | Error -> Ask_again
844     | Back -> Prev
845   in
846
847   let ask_username state =
848     let default_username = "root" in
849     match
850     inputbox "Remote username" "Remote username for ssh access to server" 10 50
851       (Option.default default_username state.remote_username)
852     with
853     | Yes ([]|""::_) ->
854         Next { state with remote_username = Some default_username }
855     | Yes (user::_) -> Next { state with remote_username = Some user }
856     | No | Help | Error -> Ask_again
857     | Back -> Prev
858   in
859
860   let ask_network state =
861     match
862     radiolist "Network configuration" "Network configuration" 12 50 4 [
863       "auto", "Automatic configuration", state.network = Some Auto;
864       "ask", "Ask for fixed IP address and gateway",
865         state.network = Some Static;
866       "sh", "Configure from the shell", state.network = Some Shell;
867       "qemu", "QEMU user network (for developers only)",
868         state.network = Some QEMUUserNet
869     ]
870     with
871     | Yes ("auto"::_) -> Next { state with network = Some Auto }
872     | Yes ("ask"::_) -> Next { state with network = Some Static }
873     | Yes ("sh"::_) -> Next { state with network = Some Shell }
874     | Yes ("qemu"::_) -> Next { state with network = Some QEMUUserNet }
875     | Yes _ | No | Help | Error -> Ask_again
876     | Back -> Prev
877   in
878
879   let ask_static_network_config state =
880     let interface, address, netmask, gateway, nameserver =
881       match state.static_network_config with
882       | Some (a,b,c,d,e) -> a,b,c,d,e
883       | None -> "eth0","","","","" in
884     match
885     form "Static network configuration" "Static network configuration"
886       13 50 5 [
887         "Interface",  1, 0, interface,  1, 12, 8,  0;
888         "Address",    2, 0, address,    2, 12, 16, 0;
889         "Netmask",    3, 0, netmask,    3, 12, 16, 0;
890         "Gateway",    4, 0, gateway,    4, 12, 16, 0;
891         "Nameserver", 5, 0, nameserver, 5, 12, 16, 0;
892       ]
893     with
894     | Yes (interface::address::netmask::gateway::nameserver::_) ->
895         Next { state with
896                  static_network_config = Some (interface, address, netmask,
897                                                gateway, nameserver) }
898     | Yes _ | No | Help | Error -> Ask_again
899     | Back -> Prev
900   in
901
902   let ask_devices state =
903     let selected_devices = Option.default [] state.devices_to_send in
904     let devices = List.map (
905       fun (dev, blksize) ->
906         (dev,
907          sprintf "/dev/%s (%.3f GB)" dev
908            ((Int64.to_float blksize) /. (1024.*.1024.*.1024.)),
909          List.mem dev selected_devices)
910     ) all_block_devices in
911     match
912     checklist "Devices" "Pick devices to send" 15 50 8 devices
913     with
914     | Yes [] | No | Help | Error -> Ask_again
915     | Yes devices -> Next { state with devices_to_send = Some devices }
916     | Back -> Prev
917   in
918
919   let ask_root state =
920     let parts = List.mapi (
921       fun i (part, nature) ->
922         let descr =
923           match nature with
924           | LinuxSwap -> " (Linux swap)"
925           | LinuxRoot (_, RHEL (a,b)) -> sprintf " (RHEL %d.%d root)" a b
926           | LinuxRoot (_, Fedora v) -> sprintf " (Fedora %d root)" v
927           | LinuxRoot (_, Debian (a,b)) -> sprintf " (Debian %d.%d root)" a b
928           | LinuxRoot (_, OtherLinux) -> sprintf " (Linux root)"
929           | WindowsRoot -> " (Windows C:)"
930           | LinuxBoot -> " (Linux /boot)"
931           | NotRoot -> " (filesystem)"
932           | UnknownNature -> "" in
933         (string_of_int i,
934          dev_of_partition part ^ descr,
935          Some part = state.root_filesystem)
936     ) all_partitions in
937     match
938     radiolist "Root device"
939       "Pick partition containing the root (/) filesystem" 18 70 9
940       parts
941     with
942     | Yes (i::_) ->
943         let (part, _) = List.nth all_partitions (int_of_string i) in
944         Next { state with root_filesystem = Some part }
945     | Yes [] | No | Help | Error -> Ask_again
946     | Back -> Prev
947   in
948
949   let ask_hypervisor state =
950     match
951     radiolist "Hypervisor"
952       "Choose hypervisor / virtualization system"
953       11 50 4 [
954         "xen", "Xen", state.hypervisor = Some Xen;
955         "qemu", "QEMU", state.hypervisor = Some QEMU;
956         "kvm", "KVM", state.hypervisor = Some KVM;
957         "other", "Other", state.hypervisor = None
958       ]
959     with
960     | Yes ("xen"::_) -> Next { state with hypervisor = Some Xen }
961     | Yes ("qemu"::_) -> Next { state with hypervisor = Some QEMU }
962     | Yes ("kvm"::_) -> Next { state with hypervisor = Some KVM }
963     | Yes _ -> Next { state with hypervisor = None }
964     | No | Help | Error -> Ask_again
965     | Back -> Prev
966   in
967
968   let ask_architecture state =
969     match
970     radiolist "Architecture" "Machine architecture" 16 50 8 [
971       "i386", "i386 and up (32 bit)", state.architecture = Some I386;
972       "x86_64", "x86-64 (64 bit)", state.architecture = Some X86_64;
973       "ia64", "Itanium IA64", state.architecture = Some IA64;
974       "ppc", "PowerPC (32 bit)", state.architecture = Some PPC;
975       "ppc64", "PowerPC (64 bit)", state.architecture = Some PPC64;
976       "sparc", "SPARC (32 bit)", state.architecture = Some SPARC;
977       "sparc64", "SPARC (64 bit)", state.architecture = Some SPARC64;
978       "auto", "Auto-detect",
979         state.architecture = None || state.architecture = Some UnknownArch;
980     ]
981     with
982     | Yes ("i386" :: _) -> Next { state with architecture = Some I386 }
983     | Yes ("x86_64" :: _) -> Next { state with architecture = Some X86_64 }
984     | Yes ("ia64" :: _) -> Next { state with architecture = Some IA64 }
985     | Yes ("ppc" :: _) -> Next { state with architecture = Some PPC }
986     | Yes ("ppc64" :: _) -> Next { state with architecture = Some PPC64 }
987     | Yes ("sparc" :: _) -> Next { state with architecture = Some SPARC }
988     | Yes ("sparc64" :: _) -> Next { state with architecture = Some SPARC64 }
989     | Yes _ -> Next { state with architecture = Some UnknownArch }
990     | No | Help | Error -> Ask_again
991     | Back -> Prev
992   in
993
994   let ask_memory state =
995     match
996     inputbox "Memory" "Memory (MB). Leave blank to use same as physical server."
997       10 50
998       (Option.map_default string_of_int "" state.memory)
999     with
1000     | Yes (""::_ | []) -> Next { state with memory = Some 0 }
1001     | Yes (mem::_) ->
1002         let mem = try int_of_string mem with Failure "int_of_string" -> -1 in
1003         if mem < 0 || (mem > 0 && mem < 64) then Ask_again
1004         else Next { state with memory = Some mem }
1005     | No | Help | Error -> Ask_again
1006     | Back -> Prev
1007   in
1008
1009   let ask_vcpus state =
1010     match
1011     inputbox "VCPUs" "Virtual CPUs. Leave blank to use same as physical server."
1012       10 50
1013       (Option.map_default string_of_int "" state.vcpus)
1014     with
1015     | Yes (""::_ | []) -> Next { state with vcpus = Some 0 }
1016     | Yes (vcpus::_) ->
1017         let vcpus =
1018           try int_of_string vcpus with Failure "int_of_string" -> -1 in
1019         if vcpus < 0 then Ask_again
1020         else Next { state with vcpus = Some vcpus }
1021     | No | Help | Error -> Ask_again
1022     | Back -> Prev
1023   in
1024
1025   let ask_mac_address state =
1026     match
1027     inputbox "MAC address"
1028       "Network MAC address. Leave blank to use a random address." 10 50
1029       (Option.default "" state.mac_address)
1030     with
1031     | Yes (""::_ | []) -> Next { state with mac_address = Some "" }
1032     | Yes (mac :: _) -> Next { state with mac_address = Some mac }
1033     | No | Help | Error -> Ask_again
1034     | Back -> Prev
1035   in
1036
1037   let ask_verify state =
1038     match
1039     yesno "Verify and proceed"
1040       (sprintf "\nPlease verify the settings below and click [OK] to proceed, or the [Back] button to return to a previous step.
1041
1042 Host:port:    %s : %s
1043 Directory:    %s
1044 Network:      %s
1045 Send devices: %s
1046 Root (/) dev: %s
1047 Hypervisor:   %s
1048 Architecture: %s
1049 Memory:       %s
1050 VCPUs:        %s
1051 MAC address:  %s"
1052          (Option.default "" state.remote_host)
1053          (Option.default "" state.remote_port)
1054          (Option.default "" state.remote_directory)
1055          (match state.network with
1056           | Some Auto -> "Auto-configure" | Some Shell -> "Shell"
1057           | Some Static -> "Static" | Some QEMUUserNet -> "QEMU user net"
1058           | None -> "")
1059          (String.concat "," (Option.default [] state.devices_to_send))
1060          (Option.map_default dev_of_partition "" state.root_filesystem)
1061          (match state.hypervisor with
1062           | Some Xen -> "Xen" | Some QEMU -> "QEMU" | Some KVM -> "KVM"
1063           | None -> "Other / not set")
1064          (match state.architecture with
1065           | Some UnknownArch -> "Auto-detect"
1066           | Some arch -> string_of_architecture arch | None -> "")
1067          (match state.memory with
1068           | Some 0 -> "Same as physical"
1069           | Some mem -> string_of_int mem ^ " MB" | None -> "")
1070          (match state.vcpus with
1071           | Some 0 -> "Same as physical"
1072           | Some vcpus -> string_of_int vcpus | None -> "")
1073          (match state.mac_address with
1074           | Some "" -> "Random" | Some mac -> mac | None -> "")
1075       )
1076       21 50
1077     with
1078     | Yes _ -> Next state
1079     | Back -> Prev
1080     | No | Help | Error -> Ask_again
1081   in
1082
1083   (* This is the list of dialogs, in order.  The user can go forwards or
1084    * backwards through them.
1085    *
1086    * The second parameter in each tuple is true if we need to skip
1087    * this dialog statically (info already supplied in 'defaults' above).
1088    *
1089    * The third parameter in each tuple is a function that tests whether
1090    * this dialog should be skipped, given other parts of the current state.
1091    *)
1092   let dlgs =
1093     let dont_skip _ = false in
1094     [|
1095     ask_greeting,      not defaults.greeting,             dont_skip;
1096     ask_hostname,      defaults.remote_host <> None,      dont_skip;
1097     ask_port,          defaults.remote_port <> None,      dont_skip;
1098     ask_directory,     defaults.remote_directory <> None, dont_skip;
1099     ask_username,      defaults.remote_username <> None,  dont_skip;
1100     ask_network,       defaults.network <> None,          dont_skip;
1101     ask_static_network_config,
1102       defaults.static_network_config <> None,
1103       (function { network = Some Static } -> false | _ -> true);
1104     ask_devices,       defaults.devices_to_send <> None,  dont_skip;
1105     ask_root,          defaults.root_filesystem <> None,  dont_skip;
1106     ask_hypervisor,    defaults.hypervisor <> None,       dont_skip;
1107     ask_architecture,  defaults.architecture <> None,     dont_skip;
1108     ask_memory,        defaults.memory <> None,           dont_skip;
1109     ask_vcpus,         defaults.vcpus <> None,            dont_skip;
1110     ask_mac_address,   defaults.mac_address <> None,      dont_skip;
1111     ask_verify,        not defaults.greeting,             dont_skip;
1112   |] in
1113
1114   (* Loop through the dialogs until we reach the end. *)
1115   let rec loop ?(back=false) posn state =
1116     eprintf "dialog loop: posn = %d, back = %b\n%!" posn back;
1117     if posn >= Array.length dlgs then state (* Finished all dialogs. *)
1118     else if posn < 0 then loop 0 state
1119     else (
1120       let dlg, skip_static, skip_dynamic = dlgs.(posn) in
1121       if skip_static || skip_dynamic state then
1122         (* Skip this dialog. *)
1123         loop ~back (if back then posn-1 else posn+1) state
1124       else (
1125         (* Run dialog. *)
1126         match dlg state with
1127         | Next new_state -> loop (posn+1) new_state (* Forwards. *)
1128         | Ask_again -> loop posn state  (* Repeat the question. *)
1129         | Prev -> loop ~back:true (posn-1) state (* Backwards / back button. *)
1130       )
1131     )
1132   in
1133   let state = loop 0 defaults in
1134
1135   eprintf "finished dialog loop\n%!";
1136
1137   (* Switch LVM config. *)
1138   sh "vgchange -a n";
1139   putenv "LVM_SYSTEM_DIR" "/etc/lvm.new"; (* see lvm(8) *)
1140   sh "rm -f /etc/lvm/cache/.cache";
1141   sh "rm -f /etc/lvm.new/cache/.cache";
1142
1143   (* Snapshot the block devices to send. *)
1144   let devices_to_send = Option.get state.devices_to_send in
1145   let devices_to_send =
1146     List.map (
1147       fun origin_dev ->
1148         let snapshot_dev = snapshot_name origin_dev in
1149         snapshot origin_dev snapshot_dev;
1150         (origin_dev, snapshot_dev)
1151     ) devices_to_send in
1152
1153   (* Run kpartx on the snapshots. *)
1154   List.iter (
1155     fun (origin, snapshot) ->
1156       shfailok ("kpartx -a " ^ quote ("/dev/mapper/" ^ snapshot))
1157   ) devices_to_send;
1158
1159   (* Rescan for LVs. *)
1160   sh "vgscan";
1161   sh "vgchange -a y";
1162
1163   (* Mount the root filesystem under /mnt/root. *)
1164   let root_filesystem = Option.get state.root_filesystem in
1165   (match root_filesystem with
1166    | Part (dev, partnum) ->
1167        let dev = dev ^ partnum in
1168        let snapshot_dev = snapshot_name dev in
1169        sh ("mount " ^ quote ("/dev/mapper/" ^ snapshot_dev) ^ " /mnt/root")
1170
1171    | LV (vg, lv) ->
1172        (* The LV will be backed by a snapshot device, so just mount
1173         * directly.
1174         *)
1175        sh ("mount " ^ quote ("/dev/" ^ vg ^ "/" ^ lv) ^ " /mnt/root")
1176   );
1177
1178   (* See if we can do network configuration. *)
1179   let network = Option.get state.network in
1180   (match network with
1181    | Shell ->
1182        printf "Network configuration.\n\n";
1183        printf "Please configure the network from this shell.\n\n";
1184        printf "When you have finished, exit the shell with ^D or exit.\n\n%!";
1185        shell ()
1186
1187    | Static ->
1188        printf "Trying static network configuration.\n\n%!";
1189        if not (static_network state) then (
1190          printf "\nAuto-configuration failed.  Starting a shell.\n\n";
1191          printf "Please configure the network from this shell.\n\n";
1192          printf "When you have finished, exit the shell with ^D or exit.\n\n";
1193          shell ()
1194        )
1195
1196    | Auto ->
1197        printf
1198          "Trying network auto-configuration from root filesystem ...\n\n%!";
1199        if not (auto_network state) then (
1200          printf "\nAuto-configuration failed.  Starting a shell.\n\n";
1201          printf "Please configure the network from this shell.\n\n";
1202          printf "When you have finished, exit the shell with ^D or exit.\n\n";
1203          shell ()
1204        )
1205    | QEMUUserNet ->
1206        printf "Trying QEMU network configuration.\n\n%!";
1207        qemu_network ()
1208   );
1209
1210   (* Work out what devices will be called at the remote end. *)
1211   let devices_to_send = List.map (
1212     fun (origin_dev, snapshot_dev) ->
1213       let remote_dev = remote_of_origin_dev origin_dev in
1214       (origin_dev, snapshot_dev, remote_dev)
1215   ) devices_to_send in
1216
1217   (* Modify files on the root filesystem. *)
1218   rewrite_fstab state devices_to_send;
1219   (* XXX Other files to rewrite? *)
1220
1221   (* Unmount the root filesystem and sync disks. *)
1222   sh "umount /mnt/root";
1223   sh "sync";                            (* Ugh, should be in stdlib. *)
1224
1225   (* Get architecture of root filesystem, detected previously. *)
1226   let system_architecture =
1227     try
1228       (match List.assoc root_filesystem all_partitions with
1229        | LinuxRoot (arch, _) -> arch
1230        | _ -> raise Not_found
1231       )
1232     with
1233       Not_found ->
1234         (* None was detected before, so assume same as live CD. *)
1235         let arch = shget "uname -m" in
1236         match arch with
1237         | Some (("i386"|"i486"|"i586"|"i686")::_) -> I386
1238         | Some ("x86_64"::_) -> X86_64
1239         | Some ("ia64"::_) -> IA64
1240         | _ -> I386 (* probably wrong XXX *) in
1241
1242   (* Autodetect system memory. *)
1243   let system_memory =
1244     let mem = shget "head -1 /proc/meminfo | awk '{print $2/1024}'" in
1245     match mem with
1246     | Some (mem::_) -> int_of_float (float_of_string mem)
1247     | _ -> 256 in
1248
1249   (* Autodetect system # pCPUs. *)
1250   let system_nr_cpus =
1251     let cpus =
1252       shget "grep ^processor /proc/cpuinfo | tail -1 | awk '{print $3+1}'" in
1253     match cpus with
1254     | Some (cpus::_) -> int_of_string cpus
1255     | _ -> 1 in
1256
1257   let remote_host = Option.get state.remote_host in
1258   let remote_port = Option.get state.remote_port in
1259   let remote_directory = Option.get state.remote_directory in
1260   let remote_username = Option.get state.remote_username in
1261
1262   (* Functions to connect and disconnect from the remote system. *)
1263   let do_connect remote_name _ =
1264     let cmd = sprintf "ssh -C -l %s -p %s %s \"cat > %s/%s\""
1265       (quote remote_username) (quote remote_port) (quote remote_host)
1266       (quote remote_directory) (quote remote_name) in
1267     eprintf "connect: %s\n%!" cmd;
1268     let chan = open_process_out cmd in
1269     descr_of_out_channel chan, chan
1270   in
1271   let do_disconnect (_, chan) =
1272     match close_process_out chan with
1273     | WEXITED 0 -> ()           (* OK *)
1274     | WEXITED i -> failwith (sprintf "ssh: exited with error code %d" i)
1275     | WSIGNALED i -> failwith (sprintf "ssh: killed by signal %d" i)
1276     | WSTOPPED i -> failwith (sprintf "ssh: stopped by signal %d" i)
1277   in
1278
1279   (* XXX This is using the hostname derived from network configuration
1280    * above.  We might want to ask the user to choose.
1281    *)
1282   let hostname = safe_name (gethostname ()) in
1283   let basename =
1284     let date = sprintf "%04d%02d%02d%02d%02d"
1285       (tm.tm_year+1900) (tm.tm_mon+1) tm.tm_mday tm.tm_hour tm.tm_min in
1286     "p2v-" ^ hostname ^ "-" ^ date in
1287
1288   (* Work out what the image filenames will be at the remote end. *)
1289   let devices_to_send = List.map (
1290     fun (origin_dev, snapshot_dev, remote_dev) ->
1291       let remote_name = basename ^ "-" ^ remote_dev ^ ".img" in
1292       (origin_dev, snapshot_dev, remote_dev, remote_name)
1293   ) devices_to_send in
1294
1295   (* Write a configuration file.  Not sure if this is any better than
1296    * just 'sprintf-ing' bits of XML text together, but at least we will
1297    * always get well-formed XML.
1298    *
1299    * XXX For some of the stuff here we really should do a
1300    * virConnectGetCapabilities call to the remote host first.
1301    *
1302    * XXX There is a case for using virt-install to generate this XML.
1303    * When we start to incorporate libvirt access & storage API this
1304    * needs to be rethought.
1305    *)
1306   let conf_filename = basename ^ ".conf" in
1307
1308   let architecture =
1309     match state.architecture with
1310     | Some UnknownArch | None -> system_architecture
1311     | Some arch -> arch in
1312   let memory =
1313     match state.memory with
1314     | Some 0 | None -> system_memory
1315     | Some memory -> memory in
1316   let vcpus =
1317     match state.vcpus with
1318     | Some 0 | None -> system_nr_cpus
1319     | Some n -> n in
1320   let mac_address =
1321     match state.mac_address with
1322     | Some "" | None ->
1323         let random =
1324           List.map (sprintf "%02x") (
1325             List.map (fun _ -> Random.int 256) [0;0;0]
1326           ) in
1327         String.concat ":" ("00"::"16"::"3e"::random)
1328     | Some mac -> mac in
1329
1330   let xml =
1331     (* Shortcut to make "<name>value</name>". *)
1332     let leaf name value = Xml.Element (name, [], [Xml.PCData value]) in
1333     (* ... and the _other_ sort of leaf (god I hate XML). *)
1334     let tleaf name attribs = Xml.Element (name, attribs, []) in
1335
1336     (* Standard stuff for every domain. *)
1337     let name = leaf "name" hostname in
1338     let memory = leaf "memory" (string_of_int (memory * 1024)) in
1339     let vcpu = leaf "vcpu" (string_of_int vcpus) in
1340
1341     (* Top-level stuff which differs for each HV type (isn't this supposed
1342      * to be portable ...)
1343      *)
1344     let extras =
1345       match state.hypervisor with
1346       | Some Xen ->
1347           [Xml.Element ("os", [],
1348                         [leaf "type" "hvm";
1349                          leaf "loader" "/usr/lib/xen/boot/hvmloader";
1350                          tleaf "boot" ["dev", "hd"]]);
1351            Xml.Element ("features", [],
1352                         [tleaf "pae" [];
1353                          tleaf "acpi" [];
1354                          tleaf "apic" []]);
1355            tleaf "clock" ["sync", "localtime"]]
1356       | Some KVM ->
1357           [Xml.Element ("os", [], [leaf "type" "hvm"]);
1358            tleaf "clock" ["sync", "localtime"]]
1359       | Some QEMU ->
1360           [Xml.Element ("os", [],
1361                         [Xml.Element ("type",
1362                                       ["arch",
1363                                        string_of_architecture architecture;
1364                                        "machine","pc"],
1365                                       [Xml.PCData "hvm"]);
1366                          tleaf "boot" ["dev", "hd"]])]
1367       | None ->
1368           [] in
1369
1370     (* <devices> section. *)
1371     let devices =
1372       let emulator =
1373         match state.hypervisor with
1374         | Some Xen ->
1375             [leaf "emulator" "/usr/lib64/xen/bin/qemu-dm"] (* XXX lib64? *)
1376         | Some QEMU ->
1377             [leaf "emulator" "/usr/bin/qemu"]
1378         | Some KVM ->
1379             [leaf "emulator" "/usr/bin/qemu-kvm"]
1380         | None ->
1381             [] in
1382       let interface =
1383         Xml.Element ("interface", ["type", "user"],
1384                      [tleaf "mac" ["address", mac_address]]) in
1385       (* XXX should have an option for Xen bridging:
1386         Xml.Element (
1387         "interface", ["type","bridge"],
1388         [tleaf "source" ["bridge","xenbr0"];
1389         tleaf "mac" ["address",mac_address];
1390         tleaf "script" ["path","vif-bridge"]])*)
1391       let graphics = tleaf "graphics" ["type", "vnc"] in
1392
1393       let disks = List.map (
1394         fun (_, _, remote_dev, remote_name) ->
1395           Xml.Element (
1396             "disk", ["type", "file";
1397                      "device", "disk"],
1398             [tleaf "source" ["file", remote_directory ^ "/" ^ remote_name];
1399              tleaf "target" ["dev", remote_dev]]
1400           )
1401       ) devices_to_send in
1402
1403       Xml.Element (
1404         "devices", [],
1405         emulator @ interface :: graphics :: disks
1406       ) in
1407
1408     (* Put it all together in <domain type='foo'>. *)
1409     Xml.Element (
1410       "domain",
1411       (match state.hypervisor with
1412        | Some Xen -> ["type", "xen"]
1413        | Some QEMU -> ["type", "qemu"]
1414        | Some KVM -> ["type", "kvm"]
1415        | None -> []),
1416       name :: memory :: vcpu :: extras @ [devices]
1417     ) in
1418
1419   (* Convert XML configuration file to a string, then send it to the
1420    * remote server.
1421    *)
1422   let () =
1423     let xml = Xml.to_string_fmt xml in
1424
1425     let conn_arg =
1426       match state.hypervisor with
1427       | Some Xen | None -> ""
1428       | Some QEMU | Some KVM -> " -c qemu:///system" in
1429     let xml = sprintf "\
1430 <!--
1431   This is a libvirt configuration file.
1432
1433   To start the domain, do:
1434     virsh%s define %s
1435     virsh%s start %s
1436 -->\n\n" conn_arg conf_filename conn_arg hostname ^ xml in
1437
1438     let xml_len = String.length xml in
1439     eprintf "length of configuration file is %d bytes\n%!" xml_len;
1440
1441     let (sock,_) as conn = do_connect conf_filename (Int64.of_int xml_len) in
1442     (* In OCaml this actually loops calling write(2) *)
1443     ignore (write sock xml 0 xml_len);
1444     do_disconnect conn in
1445
1446   (* Send the device snapshots to the remote host. *)
1447   (* XXX This code should be made more robust against both network
1448    * errors and local I/O errors.  Also should allow the user several
1449    * attempts to connect, or let them go back to the dialog stage.
1450    *)
1451   List.iter (
1452     fun (origin_dev, snapshot_dev, remote_dev, remote_name) ->
1453       eprintf "sending %s as %s\n%!" origin_dev remote_name;
1454
1455       let size =
1456         try List.assoc origin_dev all_block_devices
1457         with Not_found -> assert false (* internal error *) in
1458
1459       printf "Sending /dev/%s (%.3f GB) to remote machine\n%!" origin_dev
1460         ((Int64.to_float size) /. (1024.*.1024.*.1024.));
1461
1462       (* Open the snapshot device. *)
1463       let fd = openfile ("/dev/mapper/" ^ snapshot_dev) [O_RDONLY] 0 in
1464
1465       (* Now connect. *)
1466       let (sock,_) as conn = do_connect remote_name size in
1467
1468       (* Copy the data. *)
1469       let bufsize = 1024 * 1024 in
1470       let buffer = String.create bufsize in
1471       let start = gettimeofday () in
1472
1473       let rec copy bytes_sent last_printed_at =
1474         let n = read fd buffer 0 bufsize in
1475         if n > 0 then (
1476           ignore (write sock buffer 0 n);
1477
1478           let bytes_sent = Int64.add bytes_sent (Int64.of_int n) in
1479           let last_printed_at =
1480             let now = gettimeofday () in
1481             (* Print progress once per second. *)
1482             if now -. last_printed_at > 1. then (
1483               let elapsed = Int64.to_float bytes_sent /. Int64.to_float size in
1484               let secs_elapsed = now -. start in
1485               printf "%.0f%%" (100. *. elapsed);
1486               (* After 60 seconds has elapsed, start printing estimates. *)
1487               if secs_elapsed >= 60. then (
1488                 let remaining = 1. -. elapsed in
1489                 let secs_remaining = (remaining /. elapsed) *. secs_elapsed in
1490                 if secs_remaining > 120. then
1491                   printf " (about %.0f minutes remaining)          "
1492                     (secs_remaining /. 60.)
1493                 else
1494                   printf " (about %.0f seconds remaining)          "
1495                     secs_remaining
1496               );
1497               printf "\r%!";
1498               now
1499             )
1500             else last_printed_at in
1501
1502           copy bytes_sent last_printed_at
1503         )
1504       in
1505       copy 0L start;
1506
1507       (* Disconnect. *)
1508       do_disconnect conn
1509   ) devices_to_send;
1510
1511   (* Clean up and reboot. *)
1512   ignore (
1513     msgbox "virt-p2v completed"
1514       (sprintf "\nThe physical to virtual migration is complete.\n\nPlease verify the disk image(s) and configuration file on the remote host, and then start up the virtual machine by doing:\n\ncd %s\nvirsh define %s\n\nWhen you press [OK] this machine will reboot."
1515          remote_directory conf_filename)
1516       17 50
1517   );
1518
1519   shfailok "eject";
1520   shfailok "reboot";
1521   exit 0
1522
1523 let usage () =
1524   eprintf "usage: virt-p2v [--test] [ttyname]\n%!";
1525   exit 2
1526
1527 (* Make sure that exceptions from 'main' get printed out on stdout
1528  * as well as stderr, since stderr is probably redirected to the
1529  * logfile, and so not visible to the user.
1530  *)
1531 let handle_exn f arg =
1532   try f arg
1533   with exn -> print_endline (Printexc.to_string exn); raise exn
1534
1535 (* Test harness for the Makefile.  The Makefile invokes this script as
1536  * 'virt-p2v --test' just to check it compiles.  When it is running
1537  * from the actual live CD, there is a single parameter which is the
1538  * tty name (so usually 'virt-p2v tty1').
1539  *)
1540 let () =
1541   match Array.to_list Sys.argv with
1542   | [ _; ("--help"|"-help"|"-?"|"-h") ] -> usage ();
1543   | [ _; "--test" ] -> ()               (* Makefile test - do nothing. *)
1544   | [ _; ttyname ] ->                   (* Run main with ttyname. *)
1545       handle_exn main (Some ttyname)
1546   | [ _ ] ->                            (* Interactive - no ttyname. *)
1547       handle_exn main None
1548   | _ -> usage ()
1549
1550 (* This file must end with a newline *)