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