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. (see https://uefi.org/sites/default/files/resources/UEFI_Spec_2_8_A_Feb14.pdf 3.5.1.1 Removable Media Boot Behavior) to load. 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 <[email protected]> --- v2v/convert_linux.ml | 15 +++++ v2v/linux_bootloaders.ml | 149 ++++++++++++++++++++++++++++++++++++++++++++-- v2v/linux_bootloaders.mli | 2 + 3 files changed, 162 insertions(+), 4 deletions(-) diff --git a/v2v/convert_linux.ml b/v2v/convert_linux.ml index e91ae12..77a2555 100644 --- a/v2v/convert_linux.ml +++ b/v2v/convert_linux.ml @@ -1122,6 +1122,21 @@ 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 with a vm. If a vm doesn't have uefi + fallback path (/EFI/BOOT/BOOT<arch>.efi), the vm is unbootable + after conversion. The following function will try to make an uefi + fallback path if the vm being converted is an uefi setup. + *) + + let efi_fix_script = bootloader#fix_efi_boot () in + + if efi_fix_script <> "" then + Firstboot.add_firstboot_script g inspect.i_root + "fix uefi boot" efi_fix_script; + (* 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 de3d107..cdab7bf 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 fix_efi_boot : unit -> string end (* Helper function for SUSE: remove (hdX,X) prefix from a path. *) @@ -43,6 +44,115 @@ let remove_hd_prefix = let rex = PCRE.compile "^\\(hd.*\\)" in PCRE.replace rex "" +(* Standard uefi fallback path *) +let uefi_fallback_path = + "/boot/efi/EFI/BOOT/" + +(* 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 + +(* Helper function to get architecture suffixes for uefi files *) +let get_uefi_arch_suffix arch = + let arch_suffix = + if contains arch "x86_64" then "X64" + else if contains arch "x86_32" then "X32" + else "" in + arch_suffix + +(* Function fixes uefi boot. It's used in both cases: legacy grub and grub2 *) +let fix_uefi g distro distro_ver grub_config arch = + let cant_fix_uefi () = ( + info (f_"Can't fix UEFI bootloader. VM may not boot."); + "" ) in + + let file_exists file = + if g#exists file then + true + else ( + info (f_"Can't find file: '%s' needed for UEFI bootloader fixing") file; + false ) in + + let grub_path = String.sub grub_config 0 (String.rindex grub_config '/') in + let uefi_fallback_name = + let arch_suffix = get_uefi_arch_suffix arch in + if arch_suffix <> "" then + String.concat "" [uefi_fallback_path; "BOOT"; arch_suffix; ".EFI"] + else + "" in + + if uefi_fallback_name = "" then ( + info (f_"Can't determine UEFI fallback path."); + cant_fix_uefi () ) + else if g#exists uefi_fallback_name then + (* don't do anything if uefi fallback exists *) + "" + else if uefi_fallback_name = "" then + cant_fix_uefi () + else ( + info (f_"Fixing UEFI bootloader."); + match distro, distro_ver 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 script = sprintf +"#!/bin/bash +efibootmgr -c -L \"CentOS 6\" +rm -rf %s" uefi_fallback_path in + 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 (get_uefi_arch_suffix arch) 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 + sprintf +"#!/bin/bash +sudo efibootmgr -c -L ubuntu -l \\\\EFI\\\\ubuntu\\\\shim%s.efi +rm -rf %s" arch_suffix uefi_fallback_path + else + "") + else + cant_fix_uefi () + | _, _ -> + info (f_"No UEFI fix rule for %s %d") distro distro_ver; + cant_fix_uefi () + ) + (* Grub1 (AKA grub-legacy) representation. *) class bootloader_grub1 (g : G.guestfs) inspect grub_config = let () = @@ -60,6 +170,16 @@ class bootloader_grub1 (g : G.guestfs) inspect grub_config = fun path -> List.mem_assoc path mounts ) [ "/boot/grub"; "/boot" ] with Not_found -> "" in + + let uefi_active = + match inspect.i_firmware with + | I_UEFI _ -> true + | _ -> false in + + let arch = inspect.i_arch in + let distro = inspect.i_distro in + let distro_ver_major = inspect.i_major_version in + object inherit bootloader @@ -184,6 +304,12 @@ object loop paths; g#aug_save () + + method fix_efi_boot () = + if uefi_active then + fix_uefi g distro distro_ver_major grub_config arch + else + "" end (** The method used to get and set the default kernel in Grub2. *) @@ -193,7 +319,7 @@ type default_kernel_method = | MethodNone (** No known way. *) (* Grub2 representation. *) -class bootloader_grub2 (g : G.guestfs) grub_config = +class bootloader_grub2 (g : G.guestfs) inspect grub_config = let grub2_mkconfig_cmd = let elems = [ @@ -221,6 +347,15 @@ class bootloader_grub2 (g : G.guestfs) grub_config = MethodNone ) in + let uefi_active = + match inspect.i_firmware with + | I_UEFI _ -> true + | _ -> false in + + let arch = inspect.i_arch in + let distro = inspect.i_distro in + let distro_ver_major = inspect.i_major_version in + object (self) inherit bootloader @@ -340,8 +475,14 @@ object (self) method remove_console = self#grub2_update_console ~remove:true - method update () = - ignore (g#command [| grub2_mkconfig_cmd; "-o"; grub_config |]) + method update () = ( + ignore (g#command [| grub2_mkconfig_cmd; "-o"; grub_config |])) + + method fix_efi_boot () = + if uefi_active then + fix_uefi g distro distro_ver_major grub_config arch + else + "" end (* Helper type used in detect_bootloader. *) @@ -390,6 +531,6 @@ let detect_bootloader (g : G.guestfs) inspect = let bl = match typ with | Grub1 -> new bootloader_grub1 g inspect grub_config - | Grub2 -> new bootloader_grub2 g grub_config in + | Grub2 -> new bootloader_grub2 g inspect grub_config in debug "detected bootloader %s at %s" bl#name grub_config; bl diff --git a/v2v/linux_bootloaders.mli b/v2v/linux_bootloaders.mli index 30cdfe3..c4e1069 100644 --- a/v2v/linux_bootloaders.mli +++ b/v2v/linux_bootloaders.mli @@ -44,6 +44,8 @@ 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 fix_efi_boot : unit -> string + (** fix UEFI bootloader and return a clean up script. *) end (** Encapsulates a Linux boot loader as object. *) -- 1.8.3.1 _______________________________________________ Libguestfs mailing list [email protected] https://www.redhat.com/mailman/listinfo/libguestfs
