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(a)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