Auto-detect filesystems and architectures.
authorRichard W.M. Jones <rjones@redhat.com>
Sat, 2 Feb 2008 12:57:49 +0000 (12:57 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Sat, 2 Feb 2008 12:57:49 +0000 (12:57 +0000)
virt-p2v.ml

index 3906518..4b590af 100755 (executable)
@@ -40,7 +40,7 @@ type state = { greeting : bool;
               devices_to_send : string list option;
               root_filesystem : partition option;
               hypervisor : hypervisor option;
-              architecture : string option;
+              architecture : architecture option;
               memory : int option; vcpus : int option;
               mac_address : string option;
             }
@@ -48,6 +48,8 @@ and network = Auto | Shell
 and partition = Part of string * string (* eg. "hda", "1" *)
               | LV of string * string  (* eg. "VolGroup00", "LogVol00" *)
 and hypervisor = Xen | QEMU | KVM
+and architecture = I386 | X86_64 | IA64 | PPC | PPC64 | SPARC | SPARC64
+                | OtherArch of string | UnknownArch
 
 (*----------------------------------------------------------------------*)
 (* TO MAKE A CUSTOM virt-p2v SCRIPT, adjust the defaults in this section.
@@ -96,8 +98,9 @@ let defaults = {
   (* Hypervisor: Set to 'Some Xen', 'Some QEMU' or 'Some KVM'. *)
   hypervisor = None;
 
-  (* Architecture: Set to 'Some "x86_64"' (or another architecture).
-   * If set to 'Some ""' then we try to autodetect the right architecture.
+  (* Architecture: Set to 'Some X86_64' (or another architecture).
+   * If set to 'Some UnknownArch' then we try to autodetect the
+   * right architecture.
    *)
   architecture = None;
 
@@ -143,10 +146,46 @@ let dev_of_partition = function
   | Part (dev, partnum) -> sprintf "/dev/%s%s" dev partnum
   | LV (vg, lv) -> sprintf "/dev/%s/%s" vg lv
 
+let string_of_architecture = function
+  | I386 -> "i386"
+  | X86_64 -> "x86_64"
+  | IA64 -> "ia64"
+  | PPC -> "ppc"
+  | PPC64 -> "ppc64"
+  | SPARC -> "sparc"
+  | SPARC64 -> "sparc64"
+  | OtherArch arch -> arch
+  | UnknownArch -> ""
+
 type dialog_status = Yes of string list | No | Help | Back | Error
 
 type ask_result = Next of state | Prev | Ask_again
 
+type nature = LinuxSwap
+           | LinuxRoot of architecture * linux_distro
+           | WindowsRoot               (* Windows C: *)
+           | LinuxBoot                 (* Linux /boot *)
+           | NotRoot                   (* mountable, but not / or /boot *)
+           | UnknownNature
+and linux_distro = RHEL of int * int
+                | Fedora of int
+                | Debian of int * int
+                | OtherLinux
+
+let rec string_of_nature = function
+  | LinuxSwap -> "Linux swap"
+  | LinuxRoot (architecture, distro) ->
+      string_of_linux_distro distro ^ " " ^ string_of_architecture architecture
+  | WindowsRoot -> "Windows root"
+  | LinuxBoot -> "Linux /boot"
+  | NotRoot -> "Mountable non-root"
+  | UnknownNature -> "Unknown"
+and string_of_linux_distro = function
+  | RHEL (a,b) -> sprintf "RHEL %d.%d" a b
+  | Fedora v -> sprintf "Fedora %d" v
+  | Debian (a,b) -> sprintf "Debian %d.%d" a b
+  | OtherLinux -> "Linux"
+
 (* Dialog functions.
  *
  * Each function takes some common parameters (eg. ~title) and some
@@ -524,6 +563,13 @@ let rec main ttyname =
        close fd);
   printf "virt-p2v.ml starting up ...\n%!";
 
+  (* Check that the environment is a sane-looking live CD.  If not, bail. *)
+  if is_dir "/mnt/root" <> Some true then
+    fail_dialog
+      "You should only run this script from the live CD or a USB key.";
+
+  printf "virt-p2v.ml detecting hard drives (this may take some time) ...\n%!";
+
   (* Search for all non-removable block devices.  Do this early and bail
    * if we can't find anything.  This is a list of strings, like "hda".
    *)
@@ -590,6 +636,133 @@ let rec main ttyname =
     (* Concatenate LVs & Parts *)
     lvs @ parts in
 
+  (* Try to determine the nature of each partition.
+   * Root? Swap? Architecture? etc.
+   *)
+  let all_partitions : (partition * nature) list =
+    (* Output of 'file' command for Linux swap file. *)
+    let swap = Pcre.regexp "Linux.*swap.*file" in
+    (* Contents of /etc/redhat-release. *)
+    let rhel = Pcre.regexp "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)\\.(\\d+)" in
+    let fedora = Pcre.regexp "Fedora.*release (\\d+)\\.(\\d+)" in
+    (* Contents of /etc/debian_version. *)
+    let debian = Pcre.regexp "^(\\d+)\\.(\\d+)" in
+    (* Output of 'file' on certain executables. *)
+    let i386 = Pcre.regexp ", Intel 80386," in
+    let x86_64 = Pcre.regexp ", x86-64," in
+    let itanic = Pcre.regexp ", IA-64," in
+
+    (* Examine the filesystem mounted on 'mnt' to determine the
+     * operating system, and, if Linux, the distro.
+     *)
+    let detect_os mnt =
+      if is_dir (mnt ^ "/Windows") = Some true &&
+       is_file (mnt ^ "/autoexec.bat") = Some true then
+         WindowsRoot
+      else if is_dir (mnt ^ "/etc") = Some true &&
+       is_dir (mnt ^ "/sbin") = Some true &&
+       is_dir (mnt ^ "/var") = Some true then (
+         if is_file (mnt ^ "/etc/redhat-release") = Some true then (
+           let chan = open_in (mnt ^ "/etc/redhat-release") in
+           let lines = input_all_lines chan in
+           close_in chan;
+
+           match lines with
+           | [] -> (* empty /etc/redhat-release ...? *)
+               LinuxRoot (UnknownArch, OtherLinux)
+           | line::_ ->
+               try
+                 let subs = Pcre.exec ~rex:rhel line in
+                 let major = int_of_string (Pcre.get_substring subs 1) in
+                 let minor = int_of_string (Pcre.get_substring subs 2) in
+                 LinuxRoot (UnknownArch, RHEL (major, minor))
+               with
+                 Not_found | Failure "int_of_string" ->
+                   try
+                     let subs = Pcre.exec ~rex:fedora line in
+                     let version = int_of_string (Pcre.get_substring subs 1) in
+                     LinuxRoot (UnknownArch, Fedora version)
+                   with
+                     Not_found | Failure "int_of_string" ->
+                       LinuxRoot (UnknownArch, OtherLinux)
+         )
+         else if is_file (mnt ^ "/etc/debian_version") = Some true then (
+           let chan = open_in (mnt ^ "/etc/debian_version") in
+           let lines = input_all_lines chan in
+           close_in chan;
+
+           match lines with
+           | [] -> (* empty /etc/debian_version ...? *)
+               LinuxRoot (UnknownArch, OtherLinux)
+           | line::_ ->
+               try
+                 let subs = Pcre.exec ~rex:debian line in
+                 let major = int_of_string (Pcre.get_substring subs 1) in
+                 let minor = int_of_string (Pcre.get_substring subs 2) in
+                 LinuxRoot (UnknownArch, Debian (major, minor))
+               with
+                 Not_found | Failure "int_of_string" ->
+                   LinuxRoot (UnknownArch, OtherLinux)
+         )
+         else
+           LinuxRoot (UnknownArch, OtherLinux)
+       ) else if is_dir (mnt ^ "/grub") = Some true &&
+         is_file (mnt ^ "/grub/stage1") = Some true then (
+           LinuxBoot
+       ) else
+         NotRoot (* mountable, but not a root filesystem *)
+    in
+
+    (* Examine the Linux root filesystem mounted on 'mnt' to
+     * determine the architecture. We do this by looking at some
+     * well-known binaries that we expect to be there.
+     *)
+    let detect_architecture mnt =
+      let cmd = "file -bL " ^ quote (mnt ^ "/sbin/init") in
+      match shget cmd with
+      | Some (str::_) when Pcre.pmatch ~rex:i386 str -> I386
+      | Some (str::_) when Pcre.pmatch ~rex:x86_64 str -> X86_64
+      | Some (str::_) when Pcre.pmatch ~rex:itanic str -> IA64
+      | _ -> UnknownArch
+    in
+
+    List.map (
+      fun part ->
+       let dev = dev_of_partition part in (* Get /dev device. *)
+
+       let nature =
+         (* Use 'file' command to detect if it is swap. *)
+         let cmd = "file -sbL " ^ quote dev in
+         match shget cmd with
+         | Some (str::_) when Pcre.pmatch ~rex:swap str -> LinuxSwap
+         | _ ->
+             (* Blindly try to mount the device. *)
+             let cmd = "mount -o ro " ^ quote dev ^ " /mnt/root" in
+             match shwithstatus cmd with
+             | 0 ->
+                 let os = detect_os "/mnt/root" in
+                 let nature =
+                   match os with
+                   | LinuxRoot (UnknownArch, distro) ->
+                       let architecture = detect_architecture "/mnt/root" in
+                       LinuxRoot (architecture, distro)
+                   | os -> os in
+                 sh "umount /mnt/root";
+                 nature
+
+             | _ -> UnknownNature (* not mountable *)
+
+       in
+
+       eprintf "partition detection: %s is %s\n%!"
+         dev (string_of_nature nature);
+
+       (part, nature)
+    ) all_partitions
+  in
+
+  printf "virt-p2v.ml finished detecting hard drives\n%!";
+
   (* Dialogs. *)
   let ask_greeting state =
     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);
@@ -662,17 +835,29 @@ let rec main ttyname =
 
   let ask_root state =
     let parts = List.mapi (
-      fun i part ->
-       (string_of_int i, dev_of_partition part,
+      fun i (part, nature) ->
+       let descr =
+         match nature with
+         | LinuxSwap -> " (Linux swap)"
+         | LinuxRoot (_, RHEL (a,b)) -> sprintf " (RHEL %d.%d root)" a b
+         | LinuxRoot (_, Fedora v) -> sprintf " (Fedora %d root)" v
+         | LinuxRoot (_, Debian (a,b)) -> sprintf " (Debian %d.%d root)" a b
+         | LinuxRoot (_, OtherLinux) -> sprintf " (Linux root)"
+         | WindowsRoot -> " (Windows C:)"
+         | LinuxBoot -> " (Linux /boot)"
+         | NotRoot -> " (filesystem)"
+         | UnknownNature -> "" in
+       (string_of_int i,
+        dev_of_partition part ^ descr,
         Some part = state.root_filesystem)
     ) all_partitions in
     match
     radiolist "Root device"
-      "Pick partition containing the root (/) filesystem" 15 50 6
+      "Pick partition containing the root (/) filesystem" 18 70 9
       parts
     with
     | Yes (i::_) ->
-       let part = List.nth all_partitions (int_of_string i) in
+       let (part, _) = List.nth all_partitions (int_of_string i) in
        Next { state with root_filesystem = Some part }
     | Yes [] | No | Help | Error -> Ask_again
     | Back -> Prev
@@ -700,19 +885,25 @@ let rec main ttyname =
   let ask_architecture state =
     match
     radiolist "Architecture" "Machine architecture" 16 50 8 [
-      "i386", "i386 and up (32 bit)", state.architecture = Some "i386";
-      "x86_64", "x86-64 (64 bit)", state.architecture = Some "x86_64";
-      "ia64", "Itanium IA64", state.architecture = Some "ia64";
-      "ppc", "PowerPC (32 bit)", state.architecture = Some "ppc";
-      "ppc64", "PowerPC (64 bit)", state.architecture = Some "ppc64";
-      "sparc", "SPARC (32 bit)", state.architecture = Some "sparc";
-      "sparc64", "SPARC (64 bit)", state.architecture = Some "sparc64";
-(*      "auto", "Other or auto-detect",
-        state.architecture = None || state.architecture = Some "";*)
+      "i386", "i386 and up (32 bit)", state.architecture = Some I386;
+      "x86_64", "x86-64 (64 bit)", state.architecture = Some X86_64;
+      "ia64", "Itanium IA64", state.architecture = Some IA64;
+      "ppc", "PowerPC (32 bit)", state.architecture = Some PPC;
+      "ppc64", "PowerPC (64 bit)", state.architecture = Some PPC64;
+      "sparc", "SPARC (32 bit)", state.architecture = Some SPARC;
+      "sparc64", "SPARC (64 bit)", state.architecture = Some SPARC64;
+      "auto", "Auto-detect",
+        state.architecture = None || state.architecture = Some UnknownArch;
     ]
     with
-    | Yes (("auto"|"")::_ | []) -> Next { state with architecture = Some "" }
-    | Yes (arch :: _) -> Next { state with architecture = Some arch }
+    | Yes ("i386" :: _) -> Next { state with architecture = Some I386 }
+    | Yes ("x86_64" :: _) -> Next { state with architecture = Some X86_64 }
+    | Yes ("ia64" :: _) -> Next { state with architecture = Some IA64 }
+    | Yes ("ppc" :: _) -> Next { state with architecture = Some PPC }
+    | Yes ("ppc64" :: _) -> Next { state with architecture = Some PPC64 }
+    | Yes ("sparc" :: _) -> Next { state with architecture = Some SPARC }
+    | Yes ("sparc64" :: _) -> Next { state with architecture = Some SPARC64 }
+    | Yes _ -> Next { state with architecture = Some UnknownArch }
     | No | Help | Error -> Ask_again
     | Back -> Prev
   in
@@ -787,7 +978,8 @@ MAC address:  %s"
          | Some Xen -> "Xen" | Some QEMU -> "QEMU" | Some KVM -> "KVM"
          | None -> "Other / not set")
          (match state.architecture with
-         | Some "" -> "Guess" | Some arch -> arch | None -> "")
+         | Some UnknownArch -> "Auto-detect"
+         | Some arch -> string_of_architecture arch | None -> "")
          (match state.memory with
          | Some 0 -> "Same as physical"
          | Some mem -> string_of_int mem ^ " MB" | None -> "")
@@ -861,10 +1053,6 @@ MAC address:  %s"
 
   eprintf "finished dialog loop\n%!";
 
-  (* Check that the environment is a sane-looking live CD.  If not, bail. *)
-  if is_dir "/mnt/root" <> Some true then
-    fail_dialog "You should only run this script from the live CD or a USB key.";
-
   (* Switch LVM config. *)
   sh "vgchange -a n";
   putenv "LVM_SYSTEM_DIR" "/etc/lvm.new"; (* see lvm(8) *)
@@ -938,7 +1126,7 @@ MAC address:  %s"
   (* XXX Other files to rewrite? *)
 
   (* XXX Autodetect architecture of root filesystem by looking for /bin/ls. *)
-  let system_architecture = "x86_64" in
+  let system_architecture = X86_64 in
 
   (* XXX Autodetect system memory. *)
   let system_memory = 256 in
@@ -1005,7 +1193,7 @@ MAC address:  %s"
 
   let architecture =
     match state.architecture with
-    | Some "" | None -> system_architecture
+    | Some UnknownArch | None -> system_architecture
     | Some arch -> arch in
   let memory =
     match state.memory with
@@ -1056,8 +1244,10 @@ MAC address:  %s"
           tleaf "clock" ["sync", "localtime"]]
       | Some QEMU ->
          [Xml.Element ("os", [],
-                       [Xml.Element ("type", ["arch",architecture;
-                                              "machine","pc"],
+                       [Xml.Element ("type",
+                                     ["arch",
+                                      string_of_architecture architecture;
+                                      "machine","pc"],
                                      [Xml.PCData "hvm"]);
                         tleaf "boot" ["dev", "hd"]])]
       | None ->