Set boot order for Linux guests based on grub location

resolves: RHEL-108991
This commit is contained in:
Richard W.M. Jones 2025-08-21 20:48:47 +01:00
parent 8f140b28fb
commit c6d4be1cf0
4 changed files with 367 additions and 2 deletions

View File

@ -0,0 +1,98 @@
From c00234c61eb2a49b2961b24672e41f15d8d734b8 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 21 Aug 2025 09:45:44 +0100
Subject: [PATCH] convert: Model target boot device
SeaBIOS recently changed how it works so it no longer initializes all
disks at boot. To get around this, for some Linux BIOS guests, we
will have to assign a boot order to the disks, with <boot order='1'>
for the disk that contains the GRUB bootloader, and higher boot orders
assigned to the other disks.
As the first step, model the target boot device.
In the current commit this is always unset (set to 'None'), so this
does nothing.
(cherry picked from commit e512d84bc8e18734aac7659e839f32c960f6fb0a)
---
convert/convert.ml | 8 ++++++--
lib/types.ml | 3 ++-
lib/types.mli | 15 ++++++++++++++-
3 files changed, 22 insertions(+), 4 deletions(-)
diff --git a/convert/convert.ml b/convert/convert.ml
index 45d48a95..f8a35506 100644
--- a/convert/convert.ml
+++ b/convert/convert.ml
@@ -136,7 +136,8 @@ let rec convert input_disks options source =
get_target_firmware i_firmware guestcaps source output in
(* Create target metadata file. *)
- let target_meta = { guestcaps; target_buses; target_firmware; target_nics } in
+ let target_meta = { guestcaps; target_buses; target_nics;
+ target_firmware; target_boot_device = None } in
(* This is a good place to dump everything we know about the guest. *)
if verbose () then debug_info source inspect target_meta mpstats;
@@ -366,7 +367,8 @@ and get_target_firmware i_firmware guestcaps source output =
* is enabled.
*)
and debug_info source inspect
- { guestcaps; target_buses; target_firmware; target_nics }
+ { guestcaps; target_buses; target_nics;
+ target_firmware; target_boot_device }
mpstats =
eprintf "info:\n";
eprintf "%s\n" (string_of_source source);
@@ -374,6 +376,8 @@ and debug_info source inspect
eprintf "%s\n" (string_of_guestcaps guestcaps);
eprintf "%s\n" (string_of_target_buses target_buses);
eprintf "target firmware: %s\n" (string_of_target_firmware target_firmware);
+ eprintf "target boot device: %s\n"
+ (match target_boot_device with None -> "" | Some i -> string_of_int i);
eprintf "target NICs:\n";
List.iter (fun nic -> eprintf "%s\n" (string_of_source_nic nic))
target_nics;
diff --git a/lib/types.ml b/lib/types.ml
index 0196a3fd..bd4ab31c 100644
--- a/lib/types.ml
+++ b/lib/types.ml
@@ -485,8 +485,9 @@ let string_of_target_buses buses =
type target_meta = {
guestcaps : guestcaps;
target_buses : target_buses;
+ target_nics : target_nics;
target_firmware : target_firmware;
- target_nics : target_nics
+ target_boot_device : int option;
}
type root_choice = AskRoot | SingleRoot | FirstRoot | RootDev of string
diff --git a/lib/types.mli b/lib/types.mli
index b3815c8e..0c43b149 100644
--- a/lib/types.mli
+++ b/lib/types.mli
@@ -367,8 +367,21 @@ val string_of_target_buses : target_buses -> string
type target_meta = {
guestcaps : guestcaps;
target_buses : target_buses;
+ target_nics : target_nics;
+
target_firmware : target_firmware;
- target_nics : target_nics
+
+ target_boot_device : int option;
+ (** The disk index of the device containing the bootloader (index
+ starting from 0).
+
+ For libvirt guests this should usually be mapped to
+ [<boot order='1'>] for this disk, and [<boot order='N'>]
+ where N > 1 for each other disk (order does not matter
+ for the other disks).
+
+ This is only necessary for SeaBIOS so only collected for
+ a subset of BIOS guests (RHEL-108991). *)
}
(** {2 Command line parameters} *)

View File

@ -0,0 +1,184 @@
From e7abf2e39ed17e324b54c00f2386f56152660522 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 21 Aug 2025 09:53:42 +0100
Subject: [PATCH] output: Add boot order depending on target boot device
If no target boot device was specified, we number them <boot order='1'>
through <boot order='N'> for each disk.
If a target boot device was specified, then that disk has
<boot order='1'>, and the remaining disks are numbered sequentially
starting at 2.
(cherry picked from commit 08e57a392aa5e0720e8787968a562808cb0a31fe)
---
lib/create_ovf.ml | 19 ++++++++-----------
output/create_libvirt_xml.ml | 16 +++++++++++++++-
output/output_qemu.ml | 16 +++++++++++++---
tests/test-cdrom.expected | 1 +
tests/test-floppy.expected | 1 +
tests/test-i-ova.xml | 1 +
6 files changed, 39 insertions(+), 15 deletions(-)
diff --git a/lib/create_ovf.ml b/lib/create_ovf.ml
index 0af62c88..f74ba3a7 100644
--- a/lib/create_ovf.ml
+++ b/lib/create_ovf.ml
@@ -544,7 +544,7 @@ let create_meta_files output_alloc output_format sd_uuid image_uuids sizes =
(* Create the OVF file. *)
let rec create_ovf source inspect
- { guestcaps; target_firmware; target_nics }
+ { guestcaps; target_nics; target_firmware; target_boot_device }
sizes
output_alloc output_format
output_name
@@ -763,7 +763,7 @@ let rec create_ovf source inspect
] in
(* Add disks to the OVF XML. *)
- add_disks sizes guestcaps output_alloc output_format
+ add_disks sizes guestcaps target_boot_device output_alloc output_format
sd_uuid image_uuids vol_uuids need_actual_sizes output_disks
ovf_flavour ovf;
@@ -813,7 +813,7 @@ and get_flavoured_section ovf ovirt_path esd_path esd_path_attr = function
with Not_found -> assert false
(* This modifies the OVF DOM, adding a section for each disk. *)
-and add_disks sizes guestcaps output_alloc output_format
+and add_disks sizes guestcaps target_boot_device output_alloc output_format
sd_uuid image_uuids vol_uuids need_actual_sizes output_disks
ovf_flavour ovf =
let references =
@@ -838,14 +838,11 @@ and add_disks sizes guestcaps output_alloc output_format
(* Iterate over the disks, adding them to the OVF document. *)
List.iteri (
fun i (size, image_uuid, vol_uuid, output_uri) ->
- (* This sets the boot order to boot the first disk first. This
- * isn't generally correct. We should copy over the boot order
- * from the source hypervisor. See long discussion in
- * https://bugzilla.redhat.com/show_bug.cgi?id=1308535 for
- * what we should be doing. (XXX)
- *)
- let is_bootable_drive = i == 0 in
- let boot_order = i+1 in
+ let is_bootable_drive, boot_order =
+ match target_boot_device with
+ | None -> i = 0, i+1
+ | Some disk_index when disk_index = i -> true, 1
+ | Some _ -> false, i+2 in
let fileref =
match ovf_flavour with
diff --git a/output/create_libvirt_xml.ml b/output/create_libvirt_xml.ml
index 7ab8d34c..59f2f1f8 100644
--- a/output/create_libvirt_xml.ml
+++ b/output/create_libvirt_xml.ml
@@ -41,7 +41,8 @@ let get_osinfo_id inspect =
None
let create_libvirt_xml ?pool source inspect
- { guestcaps; target_buses; target_firmware; target_nics }
+ { guestcaps; target_buses; target_nics; target_firmware;
+ target_boot_device }
target_features outdisk_name output_format output_name =
(* The main body of the libvirt XML document. *)
let body = ref [] in
@@ -206,6 +207,18 @@ let create_libvirt_xml ?pool source inspect
| BusSlotDisk d ->
let outdisk = outdisk_name d.s_disk_id in
+ let boot_order =
+ match target_boot_device with
+ | None ->
+ (* No known boot device, just number them sequentially. *)
+ i+1
+ | Some disk_index when disk_index = i ->
+ (* For the boot disk, use order 1. *)
+ 1
+ | Some _ ->
+ (* For the others number them sequentially starting at 2. *)
+ i+2 in
+
e "disk" (
[
"type", if pool = None then "file" else "volume";
@@ -231,6 +244,7 @@ let create_libvirt_xml ?pool source inspect
"dev", drive_prefix ^ drive_name i;
"bus", bus_name;
] [];
+ e "boot" [ "order", string_of_int boot_order ] [];
]
| BusSlotRemovable { s_removable_type = CDROM } ->
diff --git a/output/output_qemu.ml b/output/output_qemu.ml
index 37371aad..dff08b66 100644
--- a/output/output_qemu.ml
+++ b/output/output_qemu.ml
@@ -108,7 +108,8 @@ module QEMU = struct
let _, qemu_boot, output_alloc, output_format,
output_name, output_storage = options in
- let { guestcaps; target_buses; target_firmware } = target_meta in
+ let { guestcaps; target_buses;
+ target_firmware; target_boot_device } = target_meta in
(* Start the shell script. Write it to a temporary file
* which we rename at the end.
@@ -282,8 +283,17 @@ module QEMU = struct
* "disk_id".
*)
let outdisk = disk_path output_storage output_name disk_id in
- arg_list "-drive" [ "file=" ^ outdisk; "format=" ^ output_format;
- "if=none"; "id=" ^ backend_name; "media=disk" ]
+ let bootindex =
+ match target_boot_device with
+ | None -> disk_id+1
+ | Some disk_index when disk_index = disk_id -> 1
+ | Some _ -> disk_id+2 in
+ arg_list "-drive" [ "file=" ^ outdisk;
+ "format=" ^ output_format;
+ "if=none";
+ "id=" ^ backend_name;
+ "media=disk";
+ sprintf "bootindex=%d" bootindex ]
and add_cdrom_backend backend_name =
(* Add a drive (back-end) for an "ide-cd" or "scsi-cd" device (front-end).
diff --git a/tests/test-cdrom.expected b/tests/test-cdrom.expected
index 17bd152d..806461e7 100644
--- a/tests/test-cdrom.expected
+++ b/tests/test-cdrom.expected
@@ -1,6 +1,7 @@
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<target dev='vda' bus='virtio'/>
+ <boot order='1'/>
</disk>
<disk device='cdrom' type='file'>
<driver name='qemu' type='raw'/>
diff --git a/tests/test-floppy.expected b/tests/test-floppy.expected
index a718c21f..c5bd913b 100644
--- a/tests/test-floppy.expected
+++ b/tests/test-floppy.expected
@@ -1,6 +1,7 @@
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<target dev='vda' bus='virtio'/>
+ <boot order='1'/>
</disk>
<disk device='floppy' type='file'>
<driver name='qemu' type='raw'/>
diff --git a/tests/test-i-ova.xml b/tests/test-i-ova.xml
index f1d8f2e3..08b5b9f2 100644
--- a/tests/test-i-ova.xml
+++ b/tests/test-i-ova.xml
@@ -27,6 +27,7 @@
<driver name='qemu' type='raw'/>
<source file='TestOva-sda'/>
<target dev='vda' bus='virtio'/>
+ <boot order='1'/>
</disk>
<disk device='cdrom' type='file'>
<driver name='qemu' type='raw'/>

View File

@ -0,0 +1,78 @@
From 46af95ecd11c9eda5fd4195b62528b1eea214550 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 21 Aug 2025 10:27:41 +0100
Subject: [PATCH] convert: Detect target boot device for Linux guests
If the guest is Linux, try to detect the boot device, so we can set
<boot order='N'> appropriately. We do this for BIOS or UEFI here, but
this only really matters for SeaBIOS.
Suggested-by: Gerd Hoffmann
Fixes: https://issues.redhat.com/browse/RHEL-108991
(cherry picked from commit ca6ec6317e20a633315f783a8ba4ece3c2fc01f2)
---
convert/convert.ml | 37 ++++++++++++++++++++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/convert/convert.ml b/convert/convert.ml
index f8a35506..728ea5a1 100644
--- a/convert/convert.ml
+++ b/convert/convert.ml
@@ -95,6 +95,10 @@ let rec convert input_disks options source =
let root = Choose_root.choose_root options.root_choice g in
let inspect = Mount_filesystems.mount_filesystems g root in
+ (* Detect boot device. *)
+ message (f_"Detecting the boot device");
+ let target_boot_device = get_target_boot_device g inspect in
+
let mpstats = get_mpstats g in
check_guest_free_space inspect mpstats;
@@ -137,7 +141,7 @@ let rec convert input_disks options source =
(* Create target metadata file. *)
let target_meta = { guestcaps; target_buses; target_nics;
- target_firmware; target_boot_device = None } in
+ target_firmware; target_boot_device } in
(* This is a good place to dump everything we know about the guest. *)
if verbose () then debug_info source inspect target_meta mpstats;
@@ -362,6 +366,37 @@ and get_target_firmware i_firmware guestcaps source output =
target_firmware
+and get_target_boot_device g inspect =
+ (* We only do it for Linux, as most likely Windows never(?) boots
+ * from any drive other than C:. We can revisit this decision
+ * if someone reports a bug.
+ *)
+ match inspect.i_type with
+ | "linux" ->
+ (try
+ (* In sane cases, the Grub stage1/boot.img (ie. the boot sector) is
+ * always on the same drive as /boot. So we can just find out
+ * where /boot is mounted and use that.
+ *)
+ let boot_mountpoint = List.assoc "/boot" inspect.i_mountpoints in
+ let boot_device = g#part_to_dev boot_mountpoint in
+ let boot_device = g#device_index boot_device in
+ Some boot_device
+ with
+ | Not_found -> None
+ | G.Error msg
+ (* Returned by part_to_dev if the /boot mountpoint is not
+ * a partition name.
+ *)
+ when String.find msg "device name is not a partition" >= 0 -> None
+ | G.Error msg
+ (* Returned by device_index if the /boot device is not
+ * a normal drive name (eg. /dev/mdX).
+ *)
+ when String.find msg "device not found" >= 0 -> None
+ )
+ | _ -> None
+
(* After conversion we dump as much information about the guest
* as we can in one place. Note this is only called when verbose
* is enabled.

View File

@ -7,7 +7,7 @@
Name: virt-v2v
Epoch: 1
Version: 2.8.1
Release: 8%{?dist}
Release: 9%{?dist}
Summary: Convert a virtual machine to run on KVM
License: GPL-2.0-or-later AND LGPL-2.0-or-later
@ -53,6 +53,9 @@ Patch0023: 0023-RHEL-Add-warning-about-virt-v2v-in-place-not-being-s.patch
Patch0024: 0024-remove-timeout-before-installing-virtio-win-drivers.patch
Patch0025: 0025-v2v-Fix-SELinux-relabelling.patch
Patch0026: 0026-RHEL-10-m4-Depend-on-libguestfs-1.56.1-2.el10-for-gu.patch
Patch0027: 0027-convert-Model-target-boot-device.patch
Patch0028: 0028-output-Add-boot-order-depending-on-target-boot-devic.patch
Patch0029: 0029-convert-Detect-target-boot-device-for-Linux-guests.patch
%if !0%{?rhel}
# libguestfs hasn't been built on i686 for a while since there is no
@ -343,7 +346,7 @@ done
%changelog
* Wed Aug 13 2025 Richard W.M. Jones <rjones@redhat.com> - 1:2.8.1-8
* Aug 21 2025 Richard W.M. Jones <rjones@redhat.com> - 1:2.8.1-9
- Rebase to virt-v2v 2.8.1
related: RHEL-81735
- Fix virt-v2v -v --install dnf5 error
@ -386,6 +389,8 @@ done
resolves: RHEL-104018
- Fix SELinux relabelling in Linux split-/usr
resolves: RHEL-109130
- Set boot order for Linux guests based on grub location
resolves: RHEL-108991
* Tue Feb 11 2025 Richard W.M. Jones <rjones@redhat.com> - 1:2.7.1-4
- Rebase to virt-v2v 2.7.1