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