Non-canonical fstab lines need newline character after them.
[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           output_char chan '\n'
542     ) lines;
543     close_out chan
544   )
545
546 let () = Random.self_init ()
547
548 let random_mac_address () =
549   let random =
550     List.map (sprintf "%02x") (
551       List.map (fun _ -> Random.int 256) [0;0;0]
552     ) in
553   String.concat ":" ("00"::"16"::"3e"::random)
554
555 let random_uuid =
556   let hex = "0123456789abcdef" in
557   fun () ->
558   let str = String.create 32 in
559   for i = 0 to 31 do str.[i] <- hex.[Random.int 16] done;
560   str
561
562 (* Main entry point. *)
563 let rec main ttyname =
564   (* Running from an init script.  We don't have much of a
565    * login environment, so set one up.
566    *)
567   putenv "PATH"
568     (String.concat ":"
569        ["/usr/sbin"; "/sbin"; "/usr/local/bin"; "/usr/kerberos/bin";
570         "/usr/bin"; "/bin"]);
571   putenv "HOME" "/root";
572   putenv "LOGNAME" "root";
573
574   (* We can safely write in /tmp (it's a synthetic live CD directory). *)
575   chdir "/tmp";
576
577   (* Set up logging to /tmp/virt-p2v.log. *)
578   let fd = openfile "virt-p2v.log" [ O_WRONLY; O_APPEND; O_CREAT ] 0o644 in
579   dup2 fd stderr;
580   close fd;
581
582   (* Log the start up time. *)
583   eprintf "\n\n**************************************************\n\n";
584   let tm = localtime (time ()) in
585   eprintf "virt-p2v-ng starting up at %04d-%02d-%02d %02d:%02d:%02d\n\n%!"
586     (tm.tm_year+1900) (tm.tm_mon+1) tm.tm_mday tm.tm_hour tm.tm_min tm.tm_sec;
587
588   (* Connect stdin/stdout to the tty. *)
589   (match ttyname with
590    | None -> ()
591    | Some ttyname ->
592        let fd = openfile ("/dev/" ^ ttyname) [ O_RDWR ] 0 in
593        dup2 fd stdin;
594        dup2 fd stdout;
595        close fd);
596   printf "virt-p2v starting up ...\n%!";
597
598   (* Disable screen blanking on tty. *)
599   sh "setterm -blank 0";
600
601   (* Check that the environment is a sane-looking live CD.  If not, bail. *)
602   if is_dir "/mnt/root" <> Some true then
603     fail_dialog
604       "You should only run this script from the live CD or a USB key.";
605
606   printf "virt-p2v detecting hard drives (this may take some time) ...\n%!";
607
608   (* Search for all non-removable block devices.  Do this early and bail
609    * if we can't find anything.  This is a list of strings, like "hda".
610    *)
611   let all_block_devices : block_device list =
612     let rex = Pcre.regexp "^[hs]d" in
613     let devices = Array.to_list (Sys.readdir "/sys/block") in
614     let devices = List.sort devices in
615     let devices = List.filter (fun d -> Pcre.pmatch ~rex d) devices in
616     eprintf "all_block_devices: block devices: %s\n%!"
617       (String.concat "; " devices);
618     (* Run blockdev --getsize64 on each, and reject any where this fails
619      * (probably removable devices).
620      *)
621     let devices = List.filter_map (
622       fun d ->
623         let cmd = "blockdev --getsize64 " ^ quote ("/dev/" ^ d) in
624         let lines = shget cmd in
625         match lines with
626         | Some (blksize::_) -> Some (d, Int64.of_string blksize)
627         | Some [] | None -> None
628     ) devices in
629     eprintf "all_block_devices: non-removable block devices: %s\n%!"
630       (String.concat "; "
631          (List.map (fun (d, b) -> sprintf "%s [%Ld]" d b) devices));
632     if devices = [] then
633       fail_dialog "No non-removable block devices (hard disks, etc.) could be found on this machine.";
634     devices in
635
636   (* Search for partitions and LVs (anything that could contain a
637    * filesystem directly).  We refer to these generically as
638    * "partitions".
639    *)
640   let all_partitions : partition list =
641     (* LVs & PVs. *)
642     let lvs, pvs =
643       let lvs = get_lvs () in
644       let pvs = List.map (fun (_, pvs, _) -> pvs) lvs in
645       let pvs = List.concat pvs in
646       let pvs = sort_uniq pvs in
647       eprintf "all_partitions: PVs: %s\n%!" (String.concat "; " pvs);
648       let lvs = List.map (fun (lvname, _, _) -> lvname) lvs in
649       eprintf "all_partitions: LVs: %s\n%!"
650         (String.concat "; " (List.map dev_of_partition lvs));
651       lvs, pvs in
652
653     (* Partitions (eg. "sda1", "sda2"). *)
654     let parts =
655       let parts = List.map fst all_block_devices in
656       let parts = List.map get_partitions parts in
657       let parts = List.concat parts in
658       eprintf "all_partitions: all partitions: %s\n%!"
659         (String.concat "; " (List.map dev_of_partition parts));
660
661       (* Remove any partitions which are PVs. *)
662       let parts = List.filter (
663         function
664         | Part (dev, partnum) -> not (List.mem (dev ^ partnum) pvs)
665         | LV _ -> assert false
666       ) parts in
667       parts in
668     eprintf "all_partitions: partitions after removing PVs: %s\n%!"
669       (String.concat "; " (List.map dev_of_partition parts));
670
671     (* Concatenate LVs & Parts *)
672     lvs @ parts in
673
674   (* Try to determine the nature of each partition.
675    * Root? Swap? Architecture? etc.
676    *)
677   let all_partitions : (partition * nature) list =
678     (* Output of 'file' command for Linux swap file. *)
679     let swap = Pcre.regexp "Linux.*swap.*file" in
680     (* Contents of /etc/redhat-release. *)
681     let rhel = Pcre.regexp "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)(?:\\.(\\d+))?" in
682     let fedora = Pcre.regexp "Fedora.*release (\\d+)" in
683     (* Contents of /etc/debian_version. *)
684     let debian = Pcre.regexp "^(\\d+)\\.(\\d+)" in
685     (* Output of 'file' on certain executables. *)
686     let i386 = Pcre.regexp ", Intel 80386," in
687     let x86_64 = Pcre.regexp ", x86-64," in
688     let itanic = Pcre.regexp ", IA-64," in
689
690     (* Examine the filesystem mounted on 'mnt' to determine the
691      * operating system, and, if Linux, the distro.
692      *)
693     let detect_os mnt =
694       if is_dir (mnt ^ "/Windows") = Some true &&
695         is_file (mnt ^ "/autoexec.bat") = Some true then
696           WindowsRoot
697       else if is_dir (mnt ^ "/etc") = Some true &&
698         is_dir (mnt ^ "/sbin") = Some true &&
699         is_dir (mnt ^ "/var") = Some true then (
700           if is_file (mnt ^ "/etc/redhat-release") = Some true then (
701             let chan = open_in (mnt ^ "/etc/redhat-release") in
702             let lines = input_all_lines chan in
703             close_in chan;
704
705             match lines with
706             | [] -> (* empty /etc/redhat-release ...? *)
707                 LinuxRoot (UnknownArch, OtherLinux)
708             | line::_ -> (* try to detect OS from /etc/redhat-release *)
709                 try
710                   let subs = Pcre.exec ~rex:rhel line in
711                   let major = int_of_string (Pcre.get_substring subs 1) in
712                   let minor =
713                     try int_of_string (Pcre.get_substring subs 2)
714                     with Not_found -> 0 in
715                   LinuxRoot (UnknownArch, RHEL (major, minor))
716                 with
717                   Not_found | Failure "int_of_string" ->
718                     try
719                       let subs = Pcre.exec ~rex:fedora line in
720                       let version = int_of_string (Pcre.get_substring subs 1) in
721                       LinuxRoot (UnknownArch, Fedora version)
722                     with
723                       Not_found | Failure "int_of_string" ->
724                         LinuxRoot (UnknownArch, OtherLinux)
725           )
726           else if is_file (mnt ^ "/etc/debian_version") = Some true then (
727             let chan = open_in (mnt ^ "/etc/debian_version") in
728             let lines = input_all_lines chan in
729             close_in chan;
730
731             match lines with
732             | [] -> (* empty /etc/debian_version ...? *)
733                 LinuxRoot (UnknownArch, OtherLinux)
734             | line::_ -> (* try to detect version from /etc/debian_version *)
735                 try
736                   let subs = Pcre.exec ~rex:debian line in
737                   let major = int_of_string (Pcre.get_substring subs 1) in
738                   let minor = int_of_string (Pcre.get_substring subs 2) in
739                   LinuxRoot (UnknownArch, Debian (major, minor))
740                 with
741                   Not_found | Failure "int_of_string" ->
742                     LinuxRoot (UnknownArch, OtherLinux)
743           )
744           else
745             LinuxRoot (UnknownArch, OtherLinux)
746         ) else if is_dir (mnt ^ "/grub") = Some true &&
747           is_file (mnt ^ "/grub/stage1") = Some true then (
748             LinuxBoot
749         ) else
750           NotRoot (* mountable, but not a root filesystem *)
751     in
752
753     (* Examine the Linux root filesystem mounted on 'mnt' to
754      * determine the architecture. We do this by looking at some
755      * well-known binaries that we expect to be there.
756      *)
757     let detect_architecture mnt =
758       let cmd = "file -bL " ^ quote (mnt ^ "/sbin/init") in
759       match shget cmd with
760       | Some (str::_) when Pcre.pmatch ~rex:i386 str -> I386
761       | Some (str::_) when Pcre.pmatch ~rex:x86_64 str -> X86_64
762       | Some (str::_) when Pcre.pmatch ~rex:itanic str -> IA64
763       | _ -> UnknownArch
764     in
765
766     List.map (
767       fun part ->
768         let dev = dev_of_partition part in (* Get /dev device. *)
769
770         let nature =
771           (* Use 'file' command to detect if it is swap. *)
772           let cmd = "file -sbL " ^ quote dev in
773           match shget cmd with
774           | Some (str::_) when Pcre.pmatch ~rex:swap str -> LinuxSwap
775           | _ ->
776               (* Blindly try to mount the device. *)
777               let cmd = "mount -o ro " ^ quote dev ^ " /mnt/root" in
778               match shwithstatus cmd with
779               | 0 ->
780                   let os = detect_os "/mnt/root" in
781                   let nature =
782                     match os with
783                     | LinuxRoot (UnknownArch, distro) ->
784                         let architecture = detect_architecture "/mnt/root" in
785                         LinuxRoot (architecture, distro)
786                     | os -> os in
787                   sh "umount /mnt/root";
788                   nature
789
790               | _ -> UnknownNature (* not mountable *)
791
792         in
793
794         eprintf "partition detection: %s is %s\n%!"
795           dev (string_of_nature nature);
796
797         (part, nature)
798     ) all_partitions
799   in
800
801   printf "virt-p2v finished detecting hard drives\n%!";
802
803   (* Dialogs. *)
804   let ask_greeting state =
805     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);
806     Next state
807   in
808
809   let ask_hostname state =
810     match
811     inputbox "Remote host" "Remote host" 10 50
812       (Option.default "" state.remote_host)
813     with
814     | Yes [] -> Ask_again
815     | Yes (hostname::_) -> Next { state with remote_host = Some hostname }
816     | No | Help | Error -> Ask_again
817     | Back -> Prev
818   in
819
820   let ask_port state =
821     match
822     inputbox "Remote port" "Remote port" 10 50
823       (Option.default "22" state.remote_port)
824     with
825     | Yes ([]|""::_) -> Next { state with remote_port = Some "22" }
826     | Yes (port::_) -> Next { state with remote_port = Some port }
827     | No | Help | Error -> Ask_again
828     | Back -> Prev
829   in
830
831   let ask_directory state =
832     let default_dir = "/var/lib/xen/images" in
833     match
834     inputbox "Remote directory" "Remote directory" 10 50
835       (Option.default default_dir state.remote_directory)
836     with
837     | Yes ([]|""::_) -> Next { state with remote_directory = Some default_dir }
838     | Yes (dir::_) -> Next { state with remote_directory = Some dir }
839     | No | Help | Error -> Ask_again
840     | Back -> Prev
841   in
842
843   let ask_username state =
844     let default_username = "root" in
845     match
846     inputbox "Remote username" "Remote username for ssh access to server" 10 50
847       (Option.default default_username state.remote_username)
848     with
849     | Yes ([]|""::_) ->
850         Next { state with remote_username = Some default_username }
851     | Yes (user::_) -> Next { state with remote_username = Some user }
852     | No | Help | Error -> Ask_again
853     | Back -> Prev
854   in
855
856   let ask_network state =
857     match
858     radiolist "Network configuration" "Network configuration" 12 50 4 [
859       "auto", "Automatic configuration", state.network = Some Auto;
860       "ask", "Ask for fixed IP address and gateway",
861         state.network = Some Static;
862       "sh", "Configure from the shell", state.network = Some Shell;
863       "qemu", "QEMU user network (for developers only)",
864         state.network = Some QEMUUserNet
865     ]
866     with
867     | Yes ("auto"::_) -> Next { state with network = Some Auto }
868     | Yes ("ask"::_) -> Next { state with network = Some Static }
869     | Yes ("sh"::_) -> Next { state with network = Some Shell }
870     | Yes ("qemu"::_) -> Next { state with network = Some QEMUUserNet }
871     | Yes _ | No | Help | Error -> Ask_again
872     | Back -> Prev
873   in
874
875   let ask_static_network_config state =
876     let interface, address, netmask, gateway, nameserver =
877       match state.static_network_config with
878       | Some (a,b,c,d,e) -> a,b,c,d,e
879       | None -> "eth0","","","","" in
880     match
881     form "Static network configuration" "Static network configuration"
882       13 50 5 [
883         "Interface",  1, 0, interface,  1, 12, 8,  0;
884         "Address",    2, 0, address,    2, 12, 16, 0;
885         "Netmask",    3, 0, netmask,    3, 12, 16, 0;
886         "Gateway",    4, 0, gateway,    4, 12, 16, 0;
887         "Nameserver", 5, 0, nameserver, 5, 12, 16, 0;
888       ]
889     with
890     | Yes (interface::address::netmask::gateway::nameserver::_) ->
891         Next { state with
892                  static_network_config = Some (interface, address, netmask,
893                                                gateway, nameserver) }
894     | Yes _ | No | Help | Error -> Ask_again
895     | Back -> Prev
896   in
897
898   let ask_devices state =
899     let selected_devices = Option.default [] state.devices_to_send in
900     let devices = List.map (
901       fun (dev, blksize) ->
902         (dev,
903          sprintf "/dev/%s (%.3f GB)" dev
904            ((Int64.to_float blksize) /. (1024.*.1024.*.1024.)),
905          List.mem dev selected_devices)
906     ) all_block_devices in
907     match
908     checklist "Devices" "Pick devices to send" 15 50 8 devices
909     with
910     | Yes [] | No | Help | Error -> Ask_again
911     | Yes devices -> Next { state with devices_to_send = Some devices }
912     | Back -> Prev
913   in
914
915   let ask_root state =
916     let parts = List.mapi (
917       fun i (part, nature) ->
918         let descr =
919           match nature with
920           | LinuxSwap -> " (Linux swap)"
921           | LinuxRoot (_, RHEL (a,b)) -> sprintf " (RHEL %d.%d root)" a b
922           | LinuxRoot (_, Fedora v) -> sprintf " (Fedora %d root)" v
923           | LinuxRoot (_, Debian (a,b)) -> sprintf " (Debian %d.%d root)" a b
924           | LinuxRoot (_, OtherLinux) -> sprintf " (Linux root)"
925           | WindowsRoot -> " (Windows C:)"
926           | LinuxBoot -> " (Linux /boot)"
927           | NotRoot -> " (filesystem)"
928           | UnknownNature -> "" in
929         (string_of_int i,
930          dev_of_partition part ^ descr,
931          Some part = state.root_filesystem)
932     ) all_partitions in
933     match
934     radiolist "Root device"
935       "Pick partition containing the root (/) filesystem" 18 70 9
936       parts
937     with
938     | Yes (i::_) ->
939         let (part, _) = List.nth all_partitions (int_of_string i) in
940         Next { state with root_filesystem = Some part }
941     | Yes [] | No | Help | Error -> Ask_again
942     | Back -> Prev
943   in
944
945   let ask_hypervisor state =
946     match
947     radiolist "Hypervisor"
948       "Choose hypervisor / virtualization system"
949       11 50 4 [
950         "xen", "Xen", state.hypervisor = Some Xen;
951         "qemu", "QEMU", state.hypervisor = Some QEMU;
952         "kvm", "KVM", state.hypervisor = Some KVM;
953         "other", "Other", state.hypervisor = None
954       ]
955     with
956     | Yes ("xen"::_) -> Next { state with hypervisor = Some Xen }
957     | Yes ("qemu"::_) -> Next { state with hypervisor = Some QEMU }
958     | Yes ("kvm"::_) -> Next { state with hypervisor = Some KVM }
959     | Yes _ -> Next { state with hypervisor = None }
960     | No | Help | Error -> Ask_again
961     | Back -> Prev
962   in
963
964   let ask_architecture state =
965     match
966     radiolist "Architecture" "Machine architecture" 16 50 8 [
967       "i386", "i386 and up (32 bit)", state.architecture = Some I386;
968       "x86_64", "x86-64 (64 bit)", state.architecture = Some X86_64;
969       "ia64", "Itanium IA64", state.architecture = Some IA64;
970       "ppc", "PowerPC (32 bit)", state.architecture = Some PPC;
971       "ppc64", "PowerPC (64 bit)", state.architecture = Some PPC64;
972       "sparc", "SPARC (32 bit)", state.architecture = Some SPARC;
973       "sparc64", "SPARC (64 bit)", state.architecture = Some SPARC64;
974       "auto", "Auto-detect",
975         state.architecture = None || state.architecture = Some UnknownArch;
976     ]
977     with
978     | Yes ("i386" :: _) -> Next { state with architecture = Some I386 }
979     | Yes ("x86_64" :: _) -> Next { state with architecture = Some X86_64 }
980     | Yes ("ia64" :: _) -> Next { state with architecture = Some IA64 }
981     | Yes ("ppc" :: _) -> Next { state with architecture = Some PPC }
982     | Yes ("ppc64" :: _) -> Next { state with architecture = Some PPC64 }
983     | Yes ("sparc" :: _) -> Next { state with architecture = Some SPARC }
984     | Yes ("sparc64" :: _) -> Next { state with architecture = Some SPARC64 }
985     | Yes _ -> Next { state with architecture = Some UnknownArch }
986     | No | Help | Error -> Ask_again
987     | Back -> Prev
988   in
989
990   let ask_memory state =
991     match
992     inputbox "Memory" "Memory (MB). Leave blank to use same as physical server."
993       10 50
994       (Option.map_default string_of_int "" state.memory)
995     with
996     | Yes (""::_ | []) -> Next { state with memory = Some 0 }
997     | Yes (mem::_) ->
998         let mem = try int_of_string mem with Failure "int_of_string" -> -1 in
999         if mem < 0 || (mem > 0 && mem < 64) then Ask_again
1000         else Next { state with memory = Some mem }
1001     | No | Help | Error -> Ask_again
1002     | Back -> Prev
1003   in
1004
1005   let ask_vcpus state =
1006     match
1007     inputbox "VCPUs" "Virtual CPUs. Leave blank to use same as physical server."
1008       10 50
1009       (Option.map_default string_of_int "" state.vcpus)
1010     with
1011     | Yes (""::_ | []) -> Next { state with vcpus = Some 0 }
1012     | Yes (vcpus::_) ->
1013         let vcpus =
1014           try int_of_string vcpus with Failure "int_of_string" -> -1 in
1015         if vcpus < 0 then Ask_again
1016         else Next { state with vcpus = Some vcpus }
1017     | No | Help | Error -> Ask_again
1018     | Back -> Prev
1019   in
1020
1021   let ask_mac_address state =
1022     match
1023     inputbox "MAC address"
1024       "Network MAC address. Leave blank to use a random address." 10 50
1025       (Option.default "" state.mac_address)
1026     with
1027     | Yes (""::_ | []) -> Next { state with mac_address = Some "" }
1028     | Yes (mac :: _) -> Next { state with mac_address = Some mac }
1029     | No | Help | Error -> Ask_again
1030     | Back -> Prev
1031   in
1032
1033   let ask_compression state =
1034     match
1035     radiolist "Network compression" "Enable network compression" 10 50 2 [
1036       "yes", "Yes, compress network traffic", state.compression <> Some false;
1037       "no", "No, don't compress", state.compression = Some false
1038     ]
1039     with
1040     | Yes ("no"::_) -> Next { state with compression = Some false }
1041     | Yes _ -> Next { state with compression = Some true }
1042     | No | Help | Error -> Ask_again
1043     | Back -> Prev
1044   in
1045
1046   let ask_verify state =
1047     match
1048     yesno "Verify and proceed"
1049       (sprintf "\nPlease verify the settings below and click [OK] to proceed, or the [Back] button to return to a previous step.
1050
1051 Host:port:    %s : %s
1052 Directory:    %s
1053 Network:      %s
1054 Send devices: %s
1055 Root (/) dev: %s
1056 Hypervisor:   %s
1057 Architecture: %s
1058 Memory:       %s
1059 VCPUs:        %s
1060 MAC address:  %s
1061 Compression:  %b"
1062          (Option.default "" state.remote_host)
1063          (Option.default "" state.remote_port)
1064          (Option.default "" state.remote_directory)
1065          (match state.network with
1066           | Some Auto -> "Auto-configure" | Some Shell -> "Shell"
1067           | Some Static -> "Static" | Some QEMUUserNet -> "QEMU user net"
1068           | None -> "")
1069          (String.concat "," (Option.default [] state.devices_to_send))
1070          (Option.map_default dev_of_partition "" state.root_filesystem)
1071          (match state.hypervisor with
1072           | Some Xen -> "Xen" | Some QEMU -> "QEMU" | Some KVM -> "KVM"
1073           | None -> "Other / not set")
1074          (match state.architecture with
1075           | Some UnknownArch -> "Auto-detect"
1076           | Some arch -> string_of_architecture arch | None -> "")
1077          (match state.memory with
1078           | Some 0 -> "Same as physical"
1079           | Some mem -> string_of_int mem ^ " MB" | None -> "")
1080          (match state.vcpus with
1081           | Some 0 -> "Same as physical"
1082           | Some vcpus -> string_of_int vcpus | None -> "")
1083          (match state.mac_address with
1084           | Some "" -> "Random" | Some mac -> mac | None -> "")
1085          (Option.default true state.compression)
1086       )
1087       21 50
1088     with
1089     | Yes _ -> Next state
1090     | Back -> Prev
1091     | No | Help | Error -> Ask_again
1092   in
1093
1094   (* This is the list of dialogs, in order.  The user can go forwards or
1095    * backwards through them.
1096    *
1097    * The second parameter in each tuple is true if we need to skip
1098    * this dialog statically (info already supplied in 'defaults' above).
1099    *
1100    * The third parameter in each tuple is a function that tests whether
1101    * this dialog should be skipped, given other parts of the current state.
1102    *)
1103   let dlgs =
1104     let dont_skip _ = false in
1105     [|
1106     ask_greeting,      not defaults.greeting,             dont_skip;
1107     ask_hostname,      defaults.remote_host <> None,      dont_skip;
1108     ask_port,          defaults.remote_port <> None,      dont_skip;
1109     ask_directory,     defaults.remote_directory <> None, dont_skip;
1110     ask_username,      defaults.remote_username <> None,  dont_skip;
1111     ask_network,       defaults.network <> None,          dont_skip;
1112     ask_static_network_config,
1113       defaults.static_network_config <> None,
1114       (function { network = Some Static } -> false | _ -> true);
1115     ask_devices,       defaults.devices_to_send <> None,  dont_skip;
1116     ask_root,          defaults.root_filesystem <> None,  dont_skip;
1117     ask_hypervisor,    defaults.hypervisor <> None,       dont_skip;
1118     ask_architecture,  defaults.architecture <> None,     dont_skip;
1119     ask_memory,        defaults.memory <> None,           dont_skip;
1120     ask_vcpus,         defaults.vcpus <> None,            dont_skip;
1121     ask_mac_address,   defaults.mac_address <> None,      dont_skip;
1122     ask_compression,   defaults.compression <> None,      dont_skip;
1123     ask_verify,        not defaults.greeting,             dont_skip;
1124   |] in
1125
1126   (* Loop through the dialogs until we reach the end. *)
1127   let rec loop ?(back=false) posn state =
1128     eprintf "dialog loop: posn = %d, back = %b\n%!" posn back;
1129     if posn >= Array.length dlgs then state (* Finished all dialogs. *)
1130     else if posn < 0 then loop 0 state
1131     else (
1132       let dlg, skip_static, skip_dynamic = dlgs.(posn) in
1133       if skip_static || skip_dynamic state then
1134         (* Skip this dialog. *)
1135         loop ~back (if back then posn-1 else posn+1) state
1136       else (
1137         (* Run dialog. *)
1138         match dlg state with
1139         | Next new_state -> loop (posn+1) new_state (* Forwards. *)
1140         | Ask_again -> loop posn state  (* Repeat the question. *)
1141         | Prev -> loop ~back:true (posn-1) state (* Backwards / back button. *)
1142       )
1143     )
1144   in
1145   let state = loop 0 defaults in
1146
1147   eprintf "finished dialog loop\n%!";
1148
1149   (* Switch LVM config. *)
1150   sh "vgchange -a n";
1151   putenv "LVM_SYSTEM_DIR" "/etc/lvm.new"; (* see lvm(8) *)
1152   sh "rm -f /etc/lvm/cache/.cache";
1153   sh "rm -f /etc/lvm.new/cache/.cache";
1154
1155   (* Snapshot the block devices to send. *)
1156   let devices_to_send = Option.get state.devices_to_send in
1157   let devices_to_send =
1158     List.map (
1159       fun origin_dev ->
1160         let snapshot_dev = snapshot_name origin_dev in
1161         snapshot origin_dev snapshot_dev;
1162         (origin_dev, snapshot_dev)
1163     ) devices_to_send in
1164
1165   (* Run kpartx on the snapshots. *)
1166   List.iter (
1167     fun (origin, snapshot) ->
1168       shfailok ("kpartx -a " ^ quote ("/dev/mapper/" ^ snapshot))
1169   ) devices_to_send;
1170
1171   (* Rescan for LVs. *)
1172   sh "vgscan";
1173   sh "vgchange -a y";
1174
1175   (* Mount the root filesystem under /mnt/root. *)
1176   let root_filesystem = Option.get state.root_filesystem in
1177   (match root_filesystem with
1178    | Part (dev, partnum) ->
1179        let dev = dev ^ partnum in
1180        let snapshot_dev = snapshot_name dev in
1181        sh ("mount " ^ quote ("/dev/mapper/" ^ snapshot_dev) ^ " /mnt/root")
1182
1183    | LV (vg, lv) ->
1184        (* The LV will be backed by a snapshot device, so just mount
1185         * directly.
1186         *)
1187        sh ("mount " ^ quote ("/dev/" ^ vg ^ "/" ^ lv) ^ " /mnt/root")
1188   );
1189
1190   (* See if we can do network configuration. *)
1191   let network = Option.get state.network in
1192   (match network with
1193    | Shell ->
1194        printf "Network configuration.\n\n";
1195        printf "Please configure the network from this shell.\n\n";
1196        printf "When you have finished, exit the shell with ^D or exit.\n\n%!";
1197        shell ()
1198
1199    | Static ->
1200        printf "Trying static network configuration.\n\n%!";
1201        if not (static_network state) then (
1202          printf "\nAuto-configuration failed.  Starting a shell.\n\n";
1203          printf "Please configure the network from this shell.\n\n";
1204          printf "When you have finished, exit the shell with ^D or exit.\n\n";
1205          shell ()
1206        )
1207
1208    | Auto ->
1209        printf
1210          "Trying network auto-configuration from root filesystem ...\n\n%!";
1211        if not (auto_network state) then (
1212          printf "\nAuto-configuration failed.  Starting a shell.\n\n";
1213          printf "Please configure the network from this shell.\n\n";
1214          printf "When you have finished, exit the shell with ^D or exit.\n\n";
1215          shell ()
1216        )
1217    | QEMUUserNet ->
1218        printf "Trying QEMU network configuration.\n\n%!";
1219        qemu_network ()
1220   );
1221
1222   (* Work out what devices will be called at the remote end. *)
1223   let devices_to_send = List.map (
1224     fun (origin_dev, snapshot_dev) ->
1225       let remote_dev = remote_of_origin_dev origin_dev in
1226       (origin_dev, snapshot_dev, remote_dev)
1227   ) devices_to_send in
1228
1229   (* Modify files on the root filesystem. *)
1230   rewrite_fstab state devices_to_send;
1231   (* XXX Other files to rewrite? *)
1232
1233   (* Unmount the root filesystem and sync disks. *)
1234   sh "umount /mnt/root";
1235   sh "sync";                            (* Ugh, should be in stdlib. *)
1236
1237   (* Get architecture of root filesystem, detected previously. *)
1238   let system_architecture =
1239     try
1240       (match List.assoc root_filesystem all_partitions with
1241        | LinuxRoot (arch, _) -> arch
1242        | _ -> raise Not_found
1243       )
1244     with
1245       Not_found ->
1246         (* None was detected before, so assume same as live CD. *)
1247         let arch = shget "uname -m" in
1248         match arch with
1249         | Some (("i386"|"i486"|"i586"|"i686")::_) -> I386
1250         | Some ("x86_64"::_) -> X86_64
1251         | Some ("ia64"::_) -> IA64
1252         | _ -> I386 (* probably wrong XXX *) in
1253
1254   (* Autodetect system memory. *)
1255   let system_memory =
1256     let mem = shget "head -1 /proc/meminfo | awk '{print $2/1024}'" in
1257     match mem with
1258     | Some (mem::_) -> int_of_float (float_of_string mem)
1259     | _ -> 256 in
1260
1261   (* Autodetect system # pCPUs. *)
1262   let system_nr_cpus =
1263     let cpus =
1264       shget "grep ^processor /proc/cpuinfo | tail -1 | awk '{print $3+1}'" in
1265     match cpus with
1266     | Some (cpus::_) -> int_of_string cpus
1267     | _ -> 1 in
1268
1269   let remote_host = Option.get state.remote_host in
1270   let remote_port = Option.get state.remote_port in
1271   let remote_directory = Option.get state.remote_directory in
1272   let remote_username = Option.get state.remote_username in
1273
1274   (* Functions to connect and disconnect from the remote system. *)
1275   let do_connect remote_name _ =
1276     let cmd = sprintf "ssh%s -l %s -p %s %s \"cat > %s/%s\""
1277       (if state.compression = Some false then "" else " -C")
1278       (quote remote_username) (quote remote_port) (quote remote_host)
1279       (quote remote_directory) (quote remote_name) in
1280     eprintf "connect: %s\n%!" cmd;
1281     let chan = open_process_out cmd in
1282     descr_of_out_channel chan, chan
1283   in
1284   let do_disconnect (_, chan) =
1285     match close_process_out chan with
1286     | WEXITED 0 -> ()           (* OK *)
1287     | WEXITED i -> failwith (sprintf "ssh: exited with error code %d" i)
1288     | WSIGNALED i -> failwith (sprintf "ssh: killed by signal %d" i)
1289     | WSTOPPED i -> failwith (sprintf "ssh: stopped by signal %d" i)
1290   in
1291
1292   (* XXX This is using the hostname derived from network configuration
1293    * above.  We might want to ask the user to choose.
1294    *)
1295   let hostname = safe_name (gethostname ()) in
1296   let basename =
1297     let date = sprintf "%04d%02d%02d%02d%02d"
1298       (tm.tm_year+1900) (tm.tm_mon+1) tm.tm_mday tm.tm_hour tm.tm_min in
1299     "p2v-" ^ hostname ^ "-" ^ date in
1300
1301   (* Work out what the image filenames will be at the remote end. *)
1302   let devices_to_send = List.map (
1303     fun (origin_dev, snapshot_dev, remote_dev) ->
1304       let remote_name = basename ^ "-" ^ remote_dev ^ ".img" in
1305       (origin_dev, snapshot_dev, remote_dev, remote_name)
1306   ) devices_to_send in
1307
1308   (* Write a configuration file.  Not sure if this is any better than
1309    * just 'sprintf-ing' bits of XML text together, but at least we will
1310    * always get well-formed XML.
1311    *
1312    * XXX For some of the stuff here we really should do a
1313    * virConnectGetCapabilities call to the remote host first.
1314    *
1315    * XXX There is a case for using virt-install to generate this XML.
1316    * When we start to incorporate libvirt access & storage API this
1317    * needs to be rethought.
1318    *)
1319   let conf_filename = basename ^ ".conf" in
1320
1321   let architecture =
1322     match state.architecture with
1323     | Some UnknownArch | None -> system_architecture
1324     | Some arch -> arch in
1325   let memory =
1326     match state.memory with
1327     | Some 0 | None -> system_memory
1328     | Some memory -> memory in
1329   let vcpus =
1330     match state.vcpus with
1331     | Some 0 | None -> system_nr_cpus
1332     | Some n -> n in
1333   let mac_address =
1334     match state.mac_address with
1335     | Some "" | None -> random_mac_address ()
1336     | Some mac -> mac in
1337
1338   let xml =
1339     (* Shortcut to make "<name>value</name>". *)
1340     let leaf name value = Xml.Element (name, [], [Xml.PCData value]) in
1341     (* ... and the _other_ sort of leaf (god I hate XML). *)
1342     let tleaf name attribs = Xml.Element (name, attribs, []) in
1343
1344     (* Standard stuff for every domain. *)
1345     let name = leaf "name" hostname in
1346     let uuid = leaf "uuid" (random_uuid ()) in
1347     let maxmem = leaf "maxmem" (string_of_int (memory * 1024)) in
1348     let memory = leaf "memory" (string_of_int (memory * 1024)) in
1349     let vcpu = leaf "vcpu" (string_of_int vcpus) in
1350
1351     (* Top-level stuff which differs for each HV type (isn't this supposed
1352      * to be portable ...)
1353      *)
1354     let extras =
1355       match state.hypervisor with
1356       | Some Xen ->
1357           [Xml.Element ("os", [],
1358                         [leaf "type" "hvm";
1359                          leaf "loader" "/usr/lib/xen/boot/hvmloader";
1360                          tleaf "boot" ["dev", "hd"]]);
1361            Xml.Element ("features", [],
1362                         [tleaf "pae" [];
1363                          tleaf "acpi" [];
1364                          tleaf "apic" []]);
1365            tleaf "clock" ["sync", "localtime"]]
1366       | Some KVM ->
1367           [Xml.Element ("os", [], [leaf "type" "hvm"]);
1368            tleaf "clock" ["sync", "localtime"]]
1369       | Some QEMU ->
1370           [Xml.Element ("os", [],
1371                         [Xml.Element ("type",
1372                                       ["arch",
1373                                        string_of_architecture architecture;
1374                                        "machine","pc"],
1375                                       [Xml.PCData "hvm"]);
1376                          tleaf "boot" ["dev", "hd"]])]
1377       | None ->
1378           [] in
1379
1380     (* <devices> section. *)
1381     let devices =
1382       let emulator =
1383         match state.hypervisor with
1384         | Some Xen ->
1385             [leaf "emulator" "/usr/lib64/xen/bin/qemu-dm"] (* XXX lib64? *)
1386         | Some QEMU ->
1387             [leaf "emulator" "/usr/bin/qemu"]
1388         | Some KVM ->
1389             [leaf "emulator" "/usr/bin/qemu-kvm"]
1390         | None ->
1391             [] in
1392       let interface =
1393         Xml.Element ("interface", ["type", "user"],
1394                      [tleaf "mac" ["address", mac_address]]) in
1395       (* XXX should have an option for Xen bridging:
1396         Xml.Element (
1397         "interface", ["type","bridge"],
1398         [tleaf "source" ["bridge","xenbr0"];
1399         tleaf "mac" ["address",mac_address];
1400         tleaf "script" ["path","vif-bridge"]])*)
1401       let graphics = tleaf "graphics" ["type", "vnc"] in
1402
1403       let disks = List.map (
1404         fun (_, _, remote_dev, remote_name) ->
1405           Xml.Element (
1406             "disk", ["type", "file";
1407                      "device", "disk"],
1408             [tleaf "source" ["file", remote_directory ^ "/" ^ remote_name];
1409              tleaf "target" ["dev", remote_dev]]
1410           )
1411       ) devices_to_send in
1412
1413       Xml.Element (
1414         "devices", [],
1415         emulator @ interface :: graphics :: disks
1416       ) in
1417
1418     (* Put it all together in <domain type='foo'>. *)
1419     Xml.Element (
1420       "domain",
1421       (match state.hypervisor with
1422        | Some Xen -> ["type", "xen"]
1423        | Some QEMU -> ["type", "qemu"]
1424        | Some KVM -> ["type", "kvm"]
1425        | None -> []),
1426       name :: uuid :: memory :: maxmem :: vcpu :: extras @ [devices]
1427     ) in
1428
1429   (* Convert XML configuration file to a string, then send it to the
1430    * remote server.
1431    *)
1432   let () =
1433     let xml = Xml.to_string_fmt xml in
1434
1435     let conn_arg =
1436       match state.hypervisor with
1437       | Some Xen | None -> ""
1438       | Some QEMU | Some KVM -> " -c qemu:///system" in
1439     let xml = sprintf "\
1440 <!--
1441   This is a libvirt configuration file.
1442
1443   To start the domain, do:
1444     virsh%s define %s
1445     virsh%s start %s
1446 -->\n\n" conn_arg conf_filename conn_arg hostname ^ xml in
1447
1448     let xml_len = String.length xml in
1449     eprintf "length of configuration file is %d bytes\n%!" xml_len;
1450
1451     let (sock,_) as conn = do_connect conf_filename (Int64.of_int xml_len) in
1452     (* In OCaml this actually loops calling write(2) *)
1453     ignore (write sock xml 0 xml_len);
1454     do_disconnect conn in
1455
1456   (* Send the device snapshots to the remote host. *)
1457   (* XXX This code should be made more robust against both network
1458    * errors and local I/O errors.  Also should allow the user several
1459    * attempts to connect, or let them go back to the dialog stage.
1460    *)
1461   List.iter (
1462     fun (origin_dev, snapshot_dev, remote_dev, remote_name) ->
1463       eprintf "sending %s as %s\n%!" origin_dev remote_name;
1464
1465       let size =
1466         try List.assoc origin_dev all_block_devices
1467         with Not_found -> assert false (* internal error *) in
1468
1469       printf "Sending /dev/%s (%.3f GB) to remote machine\n%!" origin_dev
1470         ((Int64.to_float size) /. (1024.*.1024.*.1024.));
1471
1472       (* Open the snapshot device. *)
1473       let fd = openfile ("/dev/mapper/" ^ snapshot_dev) [O_RDONLY] 0 in
1474
1475       (* Now connect. *)
1476       let (sock,_) as conn = do_connect remote_name size in
1477
1478       (* Copy the data. *)
1479       let bufsize = 1024 * 1024 in
1480       let buffer = String.create bufsize in
1481       let start = gettimeofday () in
1482
1483       let rec copy bytes_sent last_printed_at =
1484         let n = read fd buffer 0 bufsize in
1485         if n > 0 then (
1486           let n' = write sock buffer 0 n in
1487           if n <> n' then assert false; (* never, according to the manual *)
1488
1489           let bytes_sent = Int64.add bytes_sent (Int64.of_int n) in
1490           let last_printed_at =
1491             let now = gettimeofday () in
1492             (* Print progress every few seconds. *)
1493             if now -. last_printed_at > 5. then (
1494               let elapsed = Int64.to_float bytes_sent /. Int64.to_float size in
1495               let secs_elapsed = now -. start in
1496               printf "%.0f%% @ %.1f Mbps"
1497                 (100. *. elapsed)
1498                 (Int64.to_float bytes_sent/.secs_elapsed/.1_000_000. *. 8.);
1499               (* After 60 seconds has elapsed, start printing estimates. *)
1500               if secs_elapsed >= 60. then (
1501                 let remaining = 1. -. elapsed in
1502                 let secs_remaining = (remaining /. elapsed) *. secs_elapsed in
1503                 if secs_remaining > 120. then
1504                   printf " (about %.0f minutes remaining)" (secs_remaining/.60.)
1505                 else
1506                   printf " (about %.0f seconds remaining)"
1507                     secs_remaining
1508               );
1509               printf "          \r%!";
1510               now
1511             )
1512             else last_printed_at in
1513
1514           copy bytes_sent last_printed_at
1515         )
1516       in
1517       copy 0L start;
1518       printf "\n\n%!"; (* because of the messages printed above *)
1519
1520       (* Disconnect. *)
1521       do_disconnect conn
1522   ) devices_to_send;
1523
1524   (*printf "\n\nPress any key ...\n%!"; ignore (read_line ());*)
1525
1526   (* Clean up and reboot. *)
1527   ignore (
1528     msgbox "virt-p2v completed"
1529       (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."
1530          remote_directory conf_filename)
1531       17 50
1532   );
1533
1534   shfailok "eject";
1535   shfailok "reboot";
1536   exit 0
1537
1538 let usage () =
1539   eprintf "usage: virt-p2v [--test] [ttyname]\n%!";
1540   exit 2
1541
1542 (* Make sure that exceptions from 'main' get printed out on stdout
1543  * as well as stderr, since stderr is probably redirected to the
1544  * logfile, and so not visible to the user.
1545  *)
1546 let handle_exn f arg =
1547   try f arg
1548   with exn -> print_endline (Printexc.to_string exn); raise exn
1549
1550 (* Test harness for the Makefile.  The Makefile invokes this script as
1551  * 'virt-p2v --test' just to check it compiles.  When it is running
1552  * from the actual live CD, there is a single parameter which is the
1553  * tty name (so usually 'virt-p2v tty1').
1554  *)
1555 let () =
1556   match Array.to_list Sys.argv with
1557   | [ _; ("--help"|"-help"|"-?"|"-h") ] -> usage ();
1558   | [ _; "--test" ] -> ()               (* Makefile test - do nothing. *)
1559   | [ _; ttyname ] ->                   (* Run main with ttyname. *)
1560       handle_exn main (Some ttyname)
1561   | [ _ ] ->                            (* Interactive - no ttyname. *)
1562       handle_exn main None
1563   | _ -> usage ()
1564
1565 (* This file must end with a newline *)