[Libguestfs] [PATCH v2] v2v: fix UEFI bootloader for linux guests

Denis Plotnikov dplotnikov at virtuozzo.com
Fri Jul 24 12:36:58 UTC 2020


v2:
  Rich, I hope I've done all modifications according to your comments, namely:
    * moved the code from linux_bootloaders to convert_linux
    * made minor code modifications 
---

Not all UEFI guests can survive conversion, because of lost bootloader
information in UEFI NVRAM. But some guest can cope with this because they
have a fallback bootloader and use UEFI Removable Media Boot Behavior to load
(see https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf
3.5.1.1 Removable Media Boot Behavior). If UEFI firmware can't find
a bootloader in its settings it uses the removable media boot behavior to
find a bootloader.

We can fix the guests which don't have such a fallback loader by providing
a temporary one. This bootloader is used for the first boot only, then the
conversion script restores the initial bootloader settings and removes the
temporary loader.

Signed-off-by: Denis Plotnikov <dplotnikov at virtuozzo.com>
---
 v2v/convert_linux.ml      | 123 ++++++++++++++++++++++++++++++++++++++
 v2v/linux_bootloaders.ml  |   7 +++
 v2v/linux_bootloaders.mli |   4 ++
 3 files changed, 134 insertions(+)

diff --git a/v2v/convert_linux.ml b/v2v/convert_linux.ml
index e91ae120f..045afa98b 100644
--- a/v2v/convert_linux.ml
+++ b/v2v/convert_linux.ml
@@ -1122,6 +1122,129 @@ shutdown -h -P +0
       Linux.augeas_reload g
     );
 
+    (* Some linux uefi setups can't boot after conversion because of
+       lost uefi boot entries. The uefi boot entries are stored in uefi
+       NVRAM. The NVRAM content isn't a part of vm disk content and
+       usualy can't be converted alongside the vm.
+       If a vm doesn't have uefi fallback path (/EFI/BOOT/BOOT<arch>.efi)
+       the vm is unbootable after conversion.
+       The following code tries to make an uefi fallback path for
+       a uefi linux vm.
+    *)
+    match inspect.i_firmware with
+    | I_UEFI _ -> (
+      (* Standard uefi fallback path *)
+      let uefi_fallback_path = "/boot/efi/EFI/BOOT/" in
+
+      (* Helper function checks if 'source' contains 's' *)
+      let contains source s = (
+        let re = Str.regexp_string s in
+        try
+          ignore (Str.search_forward re source 0);
+          true
+        with Not_found -> false
+      ) in
+
+      let cant_fix_uefi () =
+        info (f_"Can't fix UEFI bootloader. VM may not boot.")
+      in
+
+      let get_uefi_arch_suffix = function
+        | "x86_64" -> Some "X64"
+        | "x86_32" -> Some "X32"
+        | _ -> None
+      in
+
+      match get_uefi_arch_suffix inspect.i_arch with
+      | None -> cant_fix_uefi ()
+      | Some suffix -> (
+        let uefi_fallback_name =
+          sprintf "%sBOOT%s.EFI" uefi_fallback_path suffix in
+
+        let file_exists file =
+          if g#exists file then
+            true
+          else (
+            info (f_"Can't find file: '%s' needed for UEFI fixing")
+                 file;
+            false )
+        in
+
+        let grub_config = bootloader#get_config_file () in
+
+        let grub_path =
+          String.sub grub_config 0 (String.rindex grub_config '/') in
+
+        if g#exists uefi_fallback_name then
+          (* don't do anything if uefi fallback exists *)
+          ()
+        else (
+          info (f_"Fixing UEFI bootloader.");
+          match inspect.i_distro, inspect.i_major_version with
+            | "centos", 6 ->
+              (* to make a bootable uefi centos 6 we need to
+               * copy grub.efi and grub.conf to UEFI fallback path
+               * and rename them to BOOT<arch>.efi and BOOT<arch>.conf
+               * correspondingly *)
+              let uefi_grub_name = String.concat "" [grub_path; "/grub.efi"] in
+              let uefi_grub_conf = String.concat "" [
+                                     String.sub uefi_fallback_name 0
+                                     (String.rindex uefi_fallback_name '.');
+                                     ".conf" ] in
+              if file_exists uefi_grub_name && file_exists grub_config then (
+                g#mkdir_p uefi_fallback_path;
+                g#cp uefi_grub_name uefi_fallback_name;
+                g#cp grub_config uefi_grub_conf;
+                let fix_script = sprintf
+"#!/bin/bash
+efibootmgr -c -L \"CentOS 6\"
+rm -rf %s" uefi_fallback_path in
+                Firstboot.add_firstboot_script
+                  g inspect.i_root "fix uefi boot" fix_script)
+              else
+                cant_fix_uefi ()
+            | "ubuntu", 14 ->
+              (* to make a bootable uefi ubuntu 14 we need to
+               * copy shim<arch>.efi to UEFI fallback path
+               * and rename it to BOOT<arch>.efi, also we copy
+               * grub.efi and grub.cfg to UEFI fallback path without renaming *)
+              let arch_suffix = String.lowercase_ascii suffix in
+
+              let shim =
+                String.concat "" [grub_path; "/shim"; arch_suffix; ".efi"] in
+              let uefi_grub_name =
+                String.concat "" [grub_path; "/grub"; arch_suffix; ".efi"] in
+
+              if file_exists shim && file_exists uefi_grub_name
+                                  && file_exists grub_config then (
+                g#mkdir_p uefi_fallback_path;
+                g#cp shim uefi_fallback_name;
+                g#cp uefi_grub_name uefi_fallback_path;
+                g#cp grub_config uefi_fallback_path;
+                (* if the shim is at the standard path, clean up uefi fixing
+                 * if not, then just don't clean up and leave the temp loader
+                 * at UEFI fallback path for simplicity
+                 *)
+                if contains shim "/boot/efi/EFI/ubuntu/shim" then
+                  let fix_script = sprintf
+"#!/bin/bash
+sudo efibootmgr -c -L ubuntu -l \\\\EFI\\\\ubuntu\\\\shim%s.efi
+rm -rf %s" arch_suffix uefi_fallback_path in
+                  Firstboot.add_firstboot_script
+                    g inspect.i_root "fix uefi boot" fix_script
+                else
+                  ())
+              else
+                cant_fix_uefi ()
+          | _, _ ->
+            info (f_"No UEFI fix rule for %s %d")
+                 inspect.i_distro inspect.i_major_version;
+            cant_fix_uefi ()
+        )
+      )
+    )
+    | I_BIOS -> ();
+
     (* Delete blkid caches if they exist, since they will refer to the old
      * device names.  blkid will rebuild these on demand.
      *
diff --git a/v2v/linux_bootloaders.ml b/v2v/linux_bootloaders.ml
index de3d107e9..4ca28782b 100644
--- a/v2v/linux_bootloaders.ml
+++ b/v2v/linux_bootloaders.ml
@@ -36,6 +36,7 @@ class virtual bootloader = object
   method virtual configure_console : unit -> unit
   method virtual remove_console : unit -> unit
   method update () = ()
+  method virtual get_config_file : unit -> string
 end
 
 (* Helper function for SUSE: remove (hdX,X) prefix from a path. *)
@@ -184,6 +185,9 @@ object
     loop paths;
 
     g#aug_save ()
+
+  method get_config_file () =
+    grub_config
 end
 
 (** The method used to get and set the default kernel in Grub2. *)
@@ -342,6 +346,9 @@ object (self)
 
   method update () =
     ignore (g#command [| grub2_mkconfig_cmd; "-o"; grub_config |])
+
+  method get_config_file () =
+    grub_config
 end
 
 (* Helper type used in detect_bootloader. *)
diff --git a/v2v/linux_bootloaders.mli b/v2v/linux_bootloaders.mli
index 30cdfe3c7..d9f0be8e1 100644
--- a/v2v/linux_bootloaders.mli
+++ b/v2v/linux_bootloaders.mli
@@ -44,6 +44,10 @@ class virtual bootloader : object
   (** Update the bootloader: For grub2 only this runs the
       [grub2-mkconfig] command to rebuild the configuration.  This
       is not necessary for grub-legacy. *)
+
+  method virtual get_config_file : unit -> string
+  (** Returns the path to the bootloader config file,
+      e.g /boot/grub/grub.cfg *)
 end
 (** Encapsulates a Linux boot loader as object. *)
 
-- 
2.17.0




More information about the Libguestfs mailing list