[Libguestfs] [PATCH v2] v2v: factor out bootloader handling

Pino Toscano ptoscano at redhat.com
Thu Aug 25 16:05:16 UTC 2016


Create an object hierarchy to represent different bootloaders for Linux
guests, moving the separate handling of grub1 and grub2 in different
classes: this isolates the code for each type of bootloader together,
instead of scattering it all around.

This is mostly code refactoring, with no actual behaviour change.
---
 po/POTFILES-ml            |   1 +
 v2v/Makefile.am           |   2 +
 v2v/convert_linux.ml      | 294 ++---------------------------------------
 v2v/linux_bootloaders.ml  | 325 ++++++++++++++++++++++++++++++++++++++++++++++
 v2v/linux_bootloaders.mli |  44 +++++++
 5 files changed, 385 insertions(+), 281 deletions(-)
 create mode 100644 v2v/linux_bootloaders.ml
 create mode 100644 v2v/linux_bootloaders.mli

diff --git a/po/POTFILES-ml b/po/POTFILES-ml
index b7b39fa..6a22f2c 100644
--- a/po/POTFILES-ml
+++ b/po/POTFILES-ml
@@ -120,6 +120,7 @@ v2v/input_libvirtxml.ml
 v2v/input_ova.ml
 v2v/inspect_source.ml
 v2v/linux.ml
+v2v/linux_bootloaders.ml
 v2v/modules_list.ml
 v2v/output_glance.ml
 v2v/output_libvirt.ml
diff --git a/v2v/Makefile.am b/v2v/Makefile.am
index d53e6e6..8a59ab5 100644
--- a/v2v/Makefile.am
+++ b/v2v/Makefile.am
@@ -42,6 +42,7 @@ SOURCES_MLI = \
 	input_ova.mli \
 	inspect_source.mli \
 	linux.mli \
+	linux_bootloaders.mli \
 	modules_list.mli \
 	output_glance.mli \
 	output_libvirt.mli \
@@ -81,6 +82,7 @@ SOURCES_ML = \
 	input_libvirt_xen_ssh.ml \
 	input_libvirt.ml \
 	input_ova.ml \
+	linux_bootloaders.ml \
 	convert_linux.ml \
 	convert_windows.ml \
 	output_null.ml \
diff --git a/v2v/convert_linux.ml b/v2v/convert_linux.ml
index 2a53315..7829612 100644
--- a/v2v/convert_linux.ml
+++ b/v2v/convert_linux.ml
@@ -87,45 +87,9 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
   (* Clean RPM database.  This must be done early to avoid RHBZ#1143866. *)
   Array.iter g#rm_f (g#glob_expand "/var/lib/rpm/__db.00?");
 
-  (* What grub is installed? *)
-  let grub_config, grub =
-    let locations = [
-      "/boot/grub2/grub.cfg", `Grub2;
-      "/boot/grub/menu.lst", `Grub1;
-      "/boot/grub/grub.conf", `Grub1;
-    ] in
-    let locations =
-      match inspect.i_firmware with
-      | I_UEFI _ ->
-        [
-          "/boot/efi/EFI/redhat/grub.cfg", `Grub2;
-          "/boot/efi/EFI/redhat/grub.conf", `Grub1;
-        ] @ locations
-      | I_BIOS -> locations in
-    try
-      List.find (
-        fun (grub_config, _) -> g#is_file ~followsymlinks:true grub_config
-      ) locations
-    with
-      Not_found ->
-        error (f_"no grub1/grub-legacy or grub2 configuration file was found") in
-
-  (* Grub prefix?  Usually "/boot". *)
-  let grub_prefix =
-    match grub with
-    | `Grub2 -> ""
-    | `Grub1 ->
-      if grub_config = "/boot/efi/EFI/redhat/grub.conf" then (
-        g#aug_transform "grub" "/boot/efi/EFI/redhat/grub.conf";
-        Linux.augeas_reload g;
-      );
-
-      let mounts = g#inspect_get_mountpoints inspect.i_root in
-      try
-        List.find (
-          fun path -> List.mem_assoc path mounts
-        ) [ "/boot/grub"; "/boot" ]
-      with Not_found -> "" in
+  (* Detect the installed bootloader. *)
+  let bootloader = Linux_bootloaders.detect_bootloader g inspect in
+  Linux.augeas_reload g;
 
   (* What kernel/kernel-like packages are installed on the current guest? *)
   let installed_kernels : kernel_info list =
@@ -276,88 +240,7 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
    * list is the default booting kernel.
    *)
   let grub_kernels : kernel_info list =
-    (* Helper function for SUSE: remove (hdX,X) prefix from a path. *)
-    let remove_hd_prefix  =
-      let rex = Str.regexp "^(hd.*)\\(.*\\)" in
-      Str.replace_first rex "\\1"
-    in
-
-    let vmlinuzes =
-      match grub with
-      | `Grub1 ->
-        let paths =
-          let expr = sprintf "/files%s/title/kernel" grub_config in
-          let paths = g#aug_match expr in
-          let paths = Array.to_list paths in
-
-          (* Remove duplicates. *)
-          let paths = remove_duplicates paths in
-
-          (* Get the default kernel from grub if it's set. *)
-          let default =
-            let expr = sprintf "/files%s/default" grub_config in
-            try
-              let idx = g#aug_get expr in
-              let idx = int_of_string idx in
-              (* Grub indices are zero-based, augeas is 1-based. *)
-              let expr =
-                sprintf "/files%s/title[%d]/kernel" grub_config (idx+1) in
-              Some expr
-            with G.Error msg
-                 when String.find msg "aug_get: no matching node" >= 0 ->
-              None in
-
-          (* If a default kernel was set, put it at the beginning of the paths
-           * list.  If not set, assume the first kernel always boots (?)
-           *)
-          match default with
-          | None -> paths
-          | Some p -> p :: List.filter ((<>) p) paths in
-
-        (* Resolve the Augeas paths to kernel filenames. *)
-        let vmlinuzes = List.map g#aug_get paths in
-
-        (* Make sure kernel does not begin with (hdX,X). *)
-        let vmlinuzes = List.map remove_hd_prefix vmlinuzes in
-
-        (* Prepend grub filesystem. *)
-        List.map ((^) grub_prefix) vmlinuzes
-
-      | `Grub2 ->
-        let get_default_image () =
-          let cmd =
-            if g#exists "/sbin/grubby" then
-              [| "grubby"; "--default-kernel" |]
-            else
-              [| "/usr/bin/perl"; "-MBootloader::Tools"; "-e"; "
-                    InitLibrary();
-                    my $default = Bootloader::Tools::GetDefaultSection();
-                    print $default->{image};
-                 " |] in
-          match g#command cmd with
-          | "" -> None
-          | k ->
-            let len = String.length k in
-            let k =
-              if len > 0 && k.[len-1] = '\n' then
-                String.sub k 0 (len-1)
-              else k in
-            Some (remove_hd_prefix k)
-        in
-
-        let vmlinuzes =
-          (match get_default_image () with
-          | None -> []
-          | Some k -> [k]) @
-            (* This is how the grub2 config generator enumerates kernels. *)
-            Array.to_list (g#glob_expand "/boot/kernel-*") @
-            Array.to_list (g#glob_expand "/boot/vmlinuz-*") @
-            Array.to_list (g#glob_expand "/vmlinuz-*") in
-        let rex = Str.regexp ".*\\.\\(dpkg-.*|rpmsave|rpmnew\\)$" in
-        let vmlinuzes = List.filter (
-          fun file -> not (Str.string_match rex file 0)
-        ) vmlinuzes in
-        vmlinuzes in
+    let vmlinuzes = bootloader#list_kernels () in
 
     (* Map these to installed kernels. *)
     filter_map (
@@ -396,21 +279,8 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
   (* Conversion step. *)
 
   let rec augeas_grub_configuration () =
-    match grub with
-    | `Grub1 ->
-      (* Ensure Augeas is reading the grub configuration file, and if not
-       * then add it.
-       *)
-      let incls = g#aug_match "/augeas/load/Grub/incl" in
-      let incls = Array.to_list incls in
-      let incls_contains_conf =
-        List.exists (fun incl -> g#aug_get incl = grub_config) incls in
-      if not incls_contains_conf then (
-        g#aug_set "/augeas/load/Grub/incl[last()+1]" grub_config;
-        Linux.augeas_reload g;
-      )
-
-    | `Grub2 -> () (* Not necessary for grub2. *)
+    if bootloader#set_augeas_configuration () then
+      Linux.augeas_reload g
 
   and unconfigure_xen () =
     (* Remove kmod-xenpv-* (RHEL 3). *)
@@ -740,7 +610,7 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
       let kernels = List.rev kernels (* so best is first *) in
       List.hd kernels in
     if best_kernel <> List.hd grub_kernels then
-      grub_set_bootable best_kernel;
+      bootloader#set_default_kernel best_kernel.ki_vmlinuz;
 
     (* Does the best/bootable kernel support virtio? *)
     let virtio = best_kernel.ki_supports_virtio in
@@ -758,46 +628,6 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
 
     best_kernel, virtio
 
-  and grub_set_bootable kernel =
-    match grub with
-    | `Grub1 ->
-      if not (String.is_prefix kernel.ki_vmlinuz grub_prefix) then
-        error (f_"kernel %s is not under grub tree %s")
-          kernel.ki_vmlinuz grub_prefix;
-      let kernel_under_grub_prefix =
-        let prefix_len = String.length grub_prefix in
-        let kernel_len = String.length kernel.ki_vmlinuz in
-        String.sub kernel.ki_vmlinuz prefix_len (kernel_len - prefix_len) in
-
-      (* Find the grub entry for the given kernel. *)
-      let paths = g#aug_match (sprintf "/files%s/title/kernel[. = '%s']"
-                                 grub_config kernel_under_grub_prefix) in
-      let paths = Array.to_list paths in
-      if paths = [] then
-        error (f_"didn't find grub entry for kernel %s") kernel.ki_vmlinuz;
-      let path = List.hd paths in
-      let rex = Str.regexp ".*/title\\[\\([1-9][0-9]*\\)\\]/kernel" in
-      if not (Str.string_match rex path 0) then
-        error (f_"internal error: regular expression did not match '%s'")
-          path;
-      let index = int_of_string (Str.matched_group 1 path) - 1 in
-      g#aug_set (sprintf "/files%s/default" grub_config) (string_of_int index);
-      g#aug_save ()
-
-    | `Grub2 ->
-      let cmd =
-        if g#exists "/sbin/grubby" then
-          [| "grubby"; "--set-default"; kernel.ki_vmlinuz |]
-        else
-          [| "/usr/bin/perl"; "-MBootloader::Tools"; "-e"; sprintf "
-              InitLibrary();
-              my @sections = GetSectionList(type=>image, image=>\"%s\");
-              my $section = GetSection(@sections);
-              my $newdefault = $section->{name};
-              SetGlobals(default, \"$newdefault\");
-            " kernel.ki_vmlinuz |] in
-      ignore (g#command cmd)
-
   (* Even though the kernel was already installed (this version of
    * virt-v2v does not install new kernels), it could have an
    * initrd that does not have support virtio.  Therefore rebuild
@@ -963,28 +793,6 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
 
     g#aug_save ()
 
-  and grub_configure_console () =
-    match grub with
-    | `Grub1 ->
-      let rex = Str.regexp "\\(.*\\)\\b\\([xh]vc0\\)\\b\\(.*\\)" in
-      let expr = sprintf "/files%s/title/kernel/console" grub_config in
-
-      let paths = g#aug_match expr in
-      let paths = Array.to_list paths in
-      List.iter (
-        fun path ->
-          let console = g#aug_get path in
-          if Str.string_match rex console 0 then (
-            let console = Str.global_replace rex "\\1ttyS0\\3" console in
-            g#aug_set path console
-          )
-      ) paths;
-
-      g#aug_save ()
-
-    | `Grub2 ->
-      grub2_update_console ~remove:false
-
   (* If the target doesn't support a serial console, we want to remove
    * all references to it instead.
    *)
@@ -1013,71 +821,6 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
 
     g#aug_save ()
 
-  and grub_remove_console () =
-    match grub with
-    | `Grub1 ->
-      let rex = Str.regexp "\\(.*\\)\\b\\([xh]vc0\\)\\b\\(.*\\)" in
-      let expr = sprintf "/files%s/title/kernel/console" grub_config in
-
-      let rec loop = function
-        | [] -> ()
-        | path :: paths ->
-          let console = g#aug_get path in
-          if Str.string_match rex console 0 then (
-            ignore (g#aug_rm path);
-            (* All the paths are invalid, restart the loop. *)
-            let paths = g#aug_match expr in
-            let paths = Array.to_list paths in
-            loop paths
-          )
-          else
-            loop paths
-      in
-      let paths = g#aug_match expr in
-      let paths = Array.to_list paths in
-      loop paths;
-
-      g#aug_save ()
-
-    | `Grub2 ->
-      grub2_update_console ~remove:true
-
-  and grub2_update_console ~remove =
-    let rex = Str.regexp "\\(.*\\)\\bconsole=[xh]vc0\\b\\(.*\\)" in
-
-    let paths = [
-      "/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX";
-      "/files/etc/default/grub/GRUB_CMDLINE_LINUX";
-      "/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT"
-    ] in
-    let paths = List.map g#aug_match paths in
-    let paths = List.map Array.to_list paths in
-    let paths = List.flatten paths in
-    match paths with
-    | [] ->
-      if not remove then
-        warning (f_"could not add grub2 serial console (ignored)")
-      else
-        warning (f_"could not remove grub2 serial console (ignored)")
-    | path :: _ ->
-      let grub_cmdline = g#aug_get path in
-      if Str.string_match rex grub_cmdline 0 then (
-        let new_grub_cmdline =
-          if not remove then
-            Str.global_replace rex "\\1console=ttyS0\\2" grub_cmdline
-          else
-            Str.global_replace rex "\\1\\2" grub_cmdline in
-        g#aug_set path new_grub_cmdline;
-        g#aug_save ();
-
-        try
-          ignore (g#command [| "grub2-mkconfig"; "-o"; grub_config |])
-        with
-          G.Error msg ->
-            warning (f_"could not rebuild grub2 configuration file (%s).  This may mean that grub output will not be sent to the serial port, but otherwise should be harmless.  Original error message: %s")
-              grub_config msg
-      )
-
   and supports_acpi () =
     (* ACPI known to cause RHEL 3 to fail. *)
     if family = `RHEL_family && inspect.i_major_version == 3 then
@@ -1303,19 +1046,9 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
     let paths = [
       (* /etc/fstab *)
       "/files/etc/fstab/*/spec";
-
-      (* grub-legacy config *)
-      "/files" ^ grub_config ^ "/*/kernel/root";
-      "/files" ^ grub_config ^ "/*/kernel/resume";
-      "/files/boot/grub/device.map/*[label() != \"#comment\"]";
-      "/files/etc/sysconfig/grub/boot";
-
-      (* grub2 config *)
-      "/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX";
-      "/files/etc/default/grub/GRUB_CMDLINE_LINUX";
-      "/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT";
-      "/files/boot/grub2/device.map/*[label() != \"#comment\"]";
     ] in
+    (* Bootloader config *)
+    let paths = paths @ bootloader#augeas_device_patterns in
 
     (* Which of these paths actually exist? *)
     let paths =
@@ -1393,9 +1126,8 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
     if !changed then (
       g#aug_save ();
 
-      (* If it's grub2, we have to regenerate the config files. *)
-      if grub = `Grub2 then
-        ignore (g#command [| "grub2-mkconfig"; "-o"; grub_config |]);
+      (* Make sure the bootloader is up-to-date. *)
+      bootloader#update ();
 
       Linux.augeas_reload g
     );
@@ -1425,10 +1157,10 @@ let rec convert ~keep_serial_console (g : G.guestfs) inspect source rcaps =
 
   if keep_serial_console then (
     configure_console ();
-    grub_configure_console ();
+    bootloader#configure_console ();
   ) else (
     remove_console ();
-    grub_remove_console ();
+    bootloader#remove_console ();
   );
 
   let acpi = supports_acpi () in
diff --git a/v2v/linux_bootloaders.ml b/v2v/linux_bootloaders.ml
new file mode 100644
index 0000000..f6cbd5a
--- /dev/null
+++ b/v2v/linux_bootloaders.ml
@@ -0,0 +1,325 @@
+(* virt-v2v
+ * Copyright (C) 2009-2016 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+open Printf
+
+open Common_gettext.Gettext
+open Common_utils
+
+open Types
+open Utils
+
+module G = Guestfs
+
+class virtual bootloader = object
+  method virtual name : string
+  method virtual augeas_device_patterns : string list
+  method virtual list_kernels : unit -> string list
+  method virtual set_default_kernel : string -> unit
+  method set_augeas_configuration () = false
+  method virtual configure_console : unit -> unit
+  method virtual remove_console : unit -> unit
+  method update () = ()
+end
+
+(* Helper type used in detect_bootloader. *)
+type bootloader_type =
+  | Grub1
+  | Grub2
+
+(* Helper function for SUSE: remove (hdX,X) prefix from a path. *)
+let remove_hd_prefix path =
+  let rex = Str.regexp "^(hd.*)\\(.*\\)" in
+  Str.replace_first rex "\\1" path
+
+(* Grub1 (AKA grub-legacy) representation. *)
+class bootloader_grub1 (g : G.guestfs) inspect grub_config =
+  (* Grub prefix?  Usually "/boot". *)
+  let grub_prefix =
+    let mounts = g#inspect_get_mountpoints inspect.i_root in
+    try
+      List.find (
+        fun path -> List.mem_assoc path mounts
+      ) [ "/boot/grub"; "/boot" ]
+    with Not_found -> "" in
+object
+  inherit bootloader
+
+  method name = "grub1"
+
+  method augeas_device_patterns = [
+    "/files" ^ grub_config ^ "/*/kernel/root";
+    "/files" ^ grub_config ^ "/*/kernel/resume";
+    "/files/boot/grub/device.map/*[label() != \"#comment\"]";
+    "/files/etc/sysconfig/grub/boot";
+  ]
+
+  method list_kernels () =
+    let paths =
+      let expr = sprintf "/files%s/title/kernel" grub_config in
+      let paths = g#aug_match expr in
+      let paths = Array.to_list paths in
+
+      (* Remove duplicates. *)
+      let paths = remove_duplicates paths in
+
+      (* Get the default kernel from grub if it's set. *)
+      let default =
+        let expr = sprintf "/files%s/default" grub_config in
+        try
+          let idx = g#aug_get expr in
+          let idx = int_of_string idx in
+          (* Grub indices are zero-based, augeas is 1-based. *)
+          let expr =
+            sprintf "/files%s/title[%d]/kernel" grub_config (idx+1) in
+          Some expr
+        with G.Error msg
+             when String.find msg "aug_get: no matching node" >= 0 ->
+          None in
+
+      (* If a default kernel was set, put it at the beginning of the paths
+       * list.  If not set, assume the first kernel always boots (?)
+       *)
+      match default with
+      | None -> paths
+      | Some p -> p :: List.filter ((<>) p) paths in
+
+    (* Resolve the Augeas paths to kernel filenames. *)
+    let vmlinuzes = List.map g#aug_get paths in
+
+    (* Make sure kernel does not begin with (hdX,X). *)
+    let vmlinuzes = List.map remove_hd_prefix vmlinuzes in
+
+    (* Prepend grub filesystem. *)
+    List.map ((^) grub_prefix) vmlinuzes
+
+  method set_default_kernel vmlinuz =
+    if not (String.is_prefix vmlinuz grub_prefix) then
+      error (f_"kernel %s is not under grub tree %s")
+        vmlinuz grub_prefix;
+    let kernel_under_grub_prefix =
+      let prefix_len = String.length grub_prefix in
+      let kernel_len = String.length vmlinuz in
+      String.sub vmlinuz prefix_len (kernel_len - prefix_len) in
+
+    (* Find the grub entry for the given kernel. *)
+    let paths = g#aug_match (sprintf "/files%s/title/kernel[. = '%s']"
+                               grub_config kernel_under_grub_prefix) in
+    let paths = Array.to_list paths in
+    if paths = [] then
+      error (f_"didn't find grub entry for kernel %s") vmlinuz;
+    let path = List.hd paths in
+    let rex = Str.regexp ".*/title\\[\\([1-9][0-9]*\\)\\]/kernel" in
+    if not (Str.string_match rex path 0) then
+      error (f_"internal error: regular expression did not match '%s'")
+        path;
+    let index = int_of_string (Str.matched_group 1 path) - 1 in
+    g#aug_set (sprintf "/files%s/default" grub_config) (string_of_int index);
+    g#aug_save ()
+
+  method set_augeas_configuration () =
+    let incls = g#aug_match "/augeas/load/Grub/incl" in
+    let incls = Array.to_list incls in
+    let incls_contains_conf =
+      List.exists (fun incl -> g#aug_get incl = grub_config) incls in
+    if not incls_contains_conf then (
+      g#aug_set "/augeas/load/Grub/incl[last()+1]" grub_config;
+      true;
+    ) else false
+
+  method configure_console () =
+    let rex = Str.regexp "\\(.*\\)\\b\\([xh]vc0\\)\\b\\(.*\\)" in
+    let expr = sprintf "/files%s/title/kernel/console" grub_config in
+
+    let paths = g#aug_match expr in
+    let paths = Array.to_list paths in
+    List.iter (
+      fun path ->
+        let console = g#aug_get path in
+        if Str.string_match rex console 0 then (
+          let console = Str.global_replace rex "\\1ttyS0\\3" console in
+          g#aug_set path console
+        )
+    ) paths;
+
+    g#aug_save ()
+
+  method remove_console () =
+    let rex = Str.regexp "\\(.*\\)\\b\\([xh]vc0\\)\\b\\(.*\\)" in
+    let expr = sprintf "/files%s/title/kernel/console" grub_config in
+
+    let rec loop = function
+      | [] -> ()
+      | path :: paths ->
+        let console = g#aug_get path in
+        if Str.string_match rex console 0 then (
+          ignore (g#aug_rm path);
+          (* All the paths are invalid, restart the loop. *)
+          let paths = g#aug_match expr in
+          let paths = Array.to_list paths in
+          loop paths
+        )
+        else
+          loop paths
+    in
+    let paths = g#aug_match expr in
+    let paths = Array.to_list paths in
+    loop paths;
+
+    g#aug_save ()
+end
+
+(* Grub2 representation. *)
+class bootloader_grub2 (g : G.guestfs) grub_config =
+  let grub2_update_console ~remove =
+    let rex = Str.regexp "\\(.*\\)\\bconsole=[xh]vc0\\b\\(.*\\)" in
+
+    let paths = [
+      "/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX";
+      "/files/etc/default/grub/GRUB_CMDLINE_LINUX";
+      "/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT"
+    ] in
+    let paths = List.map g#aug_match paths in
+    let paths = List.map Array.to_list paths in
+    let paths = List.flatten paths in
+    match paths with
+    | [] ->
+      if not remove then
+        warning (f_"could not add grub2 serial console (ignored)")
+      else
+        warning (f_"could not remove grub2 serial console (ignored)")
+    | path :: _ ->
+      let grub_cmdline = g#aug_get path in
+      if Str.string_match rex grub_cmdline 0 then (
+        let new_grub_cmdline =
+          if not remove then
+            Str.global_replace rex "\\1console=ttyS0\\2" grub_cmdline
+          else
+            Str.global_replace rex "\\1\\2" grub_cmdline in
+        g#aug_set path new_grub_cmdline;
+        g#aug_save ();
+
+        try
+          ignore (g#command [| "grub2-mkconfig"; "-o"; grub_config |])
+        with
+          G.Error msg ->
+            warning (f_"could not rebuild grub2 configuration file (%s).  This may mean that grub output will not be sent to the serial port, but otherwise should be harmless.  Original error message: %s")
+              grub_config msg
+      ) in
+object
+  inherit bootloader
+
+  method name = "grub2"
+
+  method augeas_device_patterns = [
+    "/files/etc/sysconfig/grub/GRUB_CMDLINE_LINUX";
+    "/files/etc/default/grub/GRUB_CMDLINE_LINUX";
+    "/files/etc/default/grub/GRUB_CMDLINE_LINUX_DEFAULT";
+    "/files/boot/grub2/device.map/*[label() != \"#comment\"]";
+  ]
+
+  method list_kernels () =
+    let get_default_image () =
+      let cmd =
+        if g#exists "/sbin/grubby" then
+          [| "grubby"; "--default-kernel" |]
+        else
+          [| "/usr/bin/perl"; "-MBootloader::Tools"; "-e"; "
+                InitLibrary();
+                my $default = Bootloader::Tools::GetDefaultSection();
+                print $default->{image};
+             " |] in
+      match g#command cmd with
+      | "" -> None
+      | k ->
+        let len = String.length k in
+        let k =
+          if len > 0 && k.[len-1] = '\n' then
+            String.sub k 0 (len-1)
+          else k in
+        Some (remove_hd_prefix k)
+    in
+
+    let vmlinuzes =
+      (match get_default_image () with
+      | None -> []
+      | Some k -> [k]) @
+        (* This is how the grub2 config generator enumerates kernels. *)
+        Array.to_list (g#glob_expand "/boot/kernel-*") @
+        Array.to_list (g#glob_expand "/boot/vmlinuz-*") @
+        Array.to_list (g#glob_expand "/vmlinuz-*") in
+    let rex = Str.regexp ".*\\.\\(dpkg-.*|rpmsave|rpmnew\\)$" in
+    let vmlinuzes = List.filter (
+      fun file -> not (Str.string_match rex file 0)
+    ) vmlinuzes in
+    vmlinuzes
+
+  method set_default_kernel vmlinuz =
+    let cmd =
+      if g#exists "/sbin/grubby" then
+        [| "grubby"; "--set-default"; vmlinuz |]
+      else
+        [| "/usr/bin/perl"; "-MBootloader::Tools"; "-e"; sprintf "
+            InitLibrary();
+            my @sections = GetSectionList(type=>image, image=>\"%s\");
+            my $section = GetSection(@sections);
+            my $newdefault = $section->{name};
+            SetGlobals(default, \"$newdefault\");
+          " vmlinuz |] in
+    ignore (g#command cmd)
+
+  method configure_console () =
+    grub2_update_console ~remove:false
+
+  method remove_console () =
+    grub2_update_console ~remove:true
+
+  method update () =
+    ignore (g#command [| "grub2-mkconfig"; "-o"; grub_config |])
+end
+
+let detect_bootloader (g : G.guestfs) inspect =
+  let config_file, typ =
+    let locations = [
+      "/boot/grub2/grub.cfg", Grub2;
+      "/boot/grub/menu.lst", Grub1;
+      "/boot/grub/grub.conf", Grub1;
+    ] in
+    let locations =
+      match inspect.i_firmware with
+      | I_UEFI _ ->
+        [
+          "/boot/efi/EFI/redhat/grub.cfg", Grub2;
+          "/boot/efi/EFI/redhat/grub.conf", Grub1;
+        ] @ locations
+      | I_BIOS -> locations in
+    try
+      List.find (
+        fun (config_file, _) -> g#is_file ~followsymlinks:true config_file
+      ) locations
+    with
+      Not_found ->
+        error (f_"no bootloader detected") in
+
+  match typ with
+  | Grub1 ->
+    if config_file = "/boot/efi/EFI/redhat/grub.conf" then
+      g#aug_transform "grub" "/boot/efi/EFI/redhat/grub.conf";
+
+    new bootloader_grub1 g inspect config_file
+  | Grub2 -> new bootloader_grub2 g config_file
diff --git a/v2v/linux_bootloaders.mli b/v2v/linux_bootloaders.mli
new file mode 100644
index 0000000..cf115e3
--- /dev/null
+++ b/v2v/linux_bootloaders.mli
@@ -0,0 +1,44 @@
+(* virt-v2v
+ * Copyright (C) 2009-2016 Red Hat Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *)
+
+class virtual bootloader : object
+  method virtual name : string
+  (** The name of the bootloader. *)
+  method virtual augeas_device_patterns : string list
+  (** A list of Augeas patterns to search for device names. *)
+  method virtual list_kernels : unit -> string list
+  (** Lists all the kernels configured in the bootloader. *)
+  method virtual set_default_kernel : string -> unit
+  (** Sets the specified vmlinuz path as default bootloader entry. *)
+  method set_augeas_configuration : unit -> bool
+  (** Checks whether Augeas is reading the configuration file
+      of the bootloader, and if not then add it.
+
+      Returns whether Augeas needs to be reloaded. *)
+  method virtual configure_console : unit -> unit
+  (** Sets up the console for the available kernels. *)
+  method virtual remove_console : unit -> unit
+  (** Removes the console in all the available kernels. *)
+  method update : unit -> unit
+  (** Update the bootloader. *)
+end
+(** Encapsulates a Linux boot loader as object. *)
+
+val detect_bootloader : Guestfs.guestfs -> Types.inspect -> bootloader
+(** Detects the bootloader on the guest, and creates the object
+    representing it. *)
-- 
2.7.4




More information about the Libguestfs mailing list