@@ -1127,7 +1127,10 @@ pub(crate) fn exec_in_host_mountns(args: &[std::ffi::OsString]) -> Result<()> {
11271127pub ( crate ) struct RootSetup {
11281128 #[ cfg( feature = "install-to-disk" ) ]
11291129 luks_device : Option < String > ,
1130- pub ( crate ) device_info : bootc_blockdev:: PartitionTable ,
1130+ /// Information about the backing block device partition tables.
1131+ /// Contains all devices that have an ESP partition when the root filesystem
1132+ /// spans multiple backing devices (e.g., LVM across multiple disks).
1133+ pub ( crate ) device_info : Vec < bootc_blockdev:: PartitionTable > ,
11311134 /// Absolute path to the location where we've mounted the physical
11321135 /// root filesystem for the system we're installing.
11331136 pub ( crate ) physical_root_path : Utf8PathBuf ,
@@ -1588,7 +1591,9 @@ async fn install_with_sysroot(
15881591
15891592 if cfg ! ( target_arch = "s390x" ) {
15901593 // TODO: Integrate s390x support into install_via_bootupd
1591- crate :: bootloader:: install_via_zipl ( & rootfs. device_info , boot_uuid) ?;
1594+ // zipl only supports single device
1595+ let device = rootfs. device_info . first ( ) ;
1596+ crate :: bootloader:: install_via_zipl ( device, boot_uuid) ?;
15921597 } else {
15931598 match postfetch. detected_bootloader {
15941599 Bootloader :: Grub => {
@@ -1719,15 +1724,21 @@ async fn install_to_filesystem_impl(
17191724 // Drop exclusive ownership since we're done with mutation
17201725 let rootfs = & * rootfs;
17211726
1722- match & rootfs. device_info . label {
1723- bootc_blockdev:: PartitionType :: Dos => crate :: utils:: medium_visibility_warning (
1724- "Installing to `dos` format partitions is not recommended" ,
1725- ) ,
1726- bootc_blockdev:: PartitionType :: Gpt => {
1727- // The only thing we should be using in general
1728- }
1729- bootc_blockdev:: PartitionType :: Unknown ( o) => {
1730- crate :: utils:: medium_visibility_warning ( & format ! ( "Unknown partition label {o}" ) )
1727+ // Check partition type of all backing devices
1728+ for device_info in & rootfs. device_info {
1729+ match & device_info. label {
1730+ bootc_blockdev:: PartitionType :: Dos => {
1731+ crate :: utils:: medium_visibility_warning ( & format ! (
1732+ "Installing to `dos` format partitions is not recommended: {}" ,
1733+ device_info. path( )
1734+ ) )
1735+ }
1736+ bootc_blockdev:: PartitionType :: Gpt => {
1737+ // The only thing we should be using in general
1738+ }
1739+ bootc_blockdev:: PartitionType :: Unknown ( o) => crate :: utils:: medium_visibility_warning (
1740+ & format ! ( "Unknown partition label {o}: {}" , device_info. path( ) ) ,
1741+ ) ,
17311742 }
17321743 }
17331744
@@ -2277,27 +2288,69 @@ pub(crate) async fn install_to_filesystem(
22772288 } ;
22782289 tracing:: debug!( "boot UUID: {boot_uuid:?}" ) ;
22792290
2280- // Find the real underlying backing device for the root. This is currently just required
2281- // for GRUB (BIOS) and in the future zipl (I think).
2282- let backing_device = {
2291+ // Walk up the block device hierarchy to find physical backing device(s).
2292+ // Examples:
2293+ // /dev/sda3 -> /dev/sda (single disk)
2294+ // /dev/mapper/vg-lv -> /dev/sda2, /dev/sdb2 (LVM across two disks)
2295+ let backing_devices: Vec < String > = {
22832296 let mut dev = inspect. source ;
22842297 loop {
22852298 tracing:: debug!( "Finding parents for {dev}" ) ;
2286- let mut parents = bootc_blockdev:: find_parent_devices ( & dev) ?. into_iter ( ) ;
2287- let Some ( parent) = parents. next ( ) else {
2288- break ;
2289- } ;
2290- if let Some ( next) = parents. next ( ) {
2291- anyhow:: bail!(
2292- "Found multiple parent devices {parent} and {next}; not currently supported"
2299+ let parents = bootc_blockdev:: find_parent_devices ( & dev) ?;
2300+ if parents. is_empty ( ) {
2301+ // Reached a physical disk
2302+ break vec ! [ dev] ;
2303+ }
2304+ if parents. len ( ) > 1 {
2305+ // Multi-device (e.g., LVM across disks) - return all
2306+ tracing:: debug!(
2307+ "Found multiple parent devices: {:?}; will search for ESP" ,
2308+ parents
22932309 ) ;
2310+ break parents;
2311+ }
2312+ // Single parent (e.g. LVM LV -> VG -> PV) - keep walking up
2313+ dev = parents. into_iter ( ) . next ( ) . unwrap ( ) ;
2314+ }
2315+ } ;
2316+ tracing:: debug!( "Backing devices: {backing_devices:?}" ) ;
2317+
2318+ // Determine the device and partition info to use for bootloader installation.
2319+ // If there are multiple backing devices, we search for all that contain an ESP.
2320+ let device_info: Vec < bootc_blockdev:: PartitionTable > = if backing_devices. len ( ) == 1 {
2321+ // Single backing device - use it directly
2322+ let dev = & backing_devices[ 0 ] ;
2323+ vec ! [ bootc_blockdev:: partitions_of( Utf8Path :: new( dev) ) ?]
2324+ } else {
2325+ // Multiple backing devices - find all with ESP
2326+ let mut esp_devices = Vec :: new ( ) ;
2327+ for dev in & backing_devices {
2328+ match bootc_blockdev:: partitions_of ( Utf8Path :: new ( dev) ) {
2329+ Ok ( table) => {
2330+ if table. find_partition_of_esp ( ) ?. is_some ( ) {
2331+ tracing:: info!( "Found ESP on device {dev}" ) ;
2332+ esp_devices. push ( table) ;
2333+ }
2334+ }
2335+ Err ( e) => {
2336+ // Some backing devices may not have partition tables (e.g., raw LVM PVs
2337+ // or whole-disk filesystems). These can't have an ESP, so skip them.
2338+ tracing:: debug!( "Failed to read partition table from {dev}: {e}" ) ;
2339+ }
22942340 }
2295- dev = parent;
22962341 }
2297- dev
2342+ if esp_devices. is_empty ( ) {
2343+ // No ESP found on any backing device. This is not fatal because:
2344+ // - BIOS boot uses MBR, not ESP
2345+ // - bootupd may auto-detect ESP via mounted /boot/efi
2346+ // However, UEFI boot without a detectable ESP will fail.
2347+ tracing:: warn!(
2348+ "No ESP found on any backing device ({:?}); UEFI boot may fail" ,
2349+ backing_devices
2350+ ) ;
2351+ }
2352+ esp_devices
22982353 } ;
2299- tracing:: debug!( "Backing device: {backing_device}" ) ;
2300- let device_info = bootc_blockdev:: partitions_of ( Utf8Path :: new ( & backing_device) ) ?;
23012354
23022355 let rootarg = format ! ( "root={}" , root_info. mount_spec) ;
23032356 let mut boot = if let Some ( spec) = fsopts. boot_mount_spec {
0 commit comments