diff --git a/rules.d/99-systemd.rules.in b/rules.d/99-systemd.rules.in index da2d311ce4..98f483503e 100644 --- a/rules.d/99-systemd.rules.in +++ b/rules.d/99-systemd.rules.in @@ -89,10 +89,10 @@ SUBSYSTEM=="module", KERNEL=="configfs", TAG+="systemd", ENV{SYSTEMD_WANTS}+="sy SUBSYSTEM=="tpmrm", KERNEL=="tpmrm[0-9]*", TAG+="systemd", ENV{SYSTEMD_WANTS}+="tpm2.target" SUBSYSTEM=="tpm", KERNEL=="tpm[0-9]*", TAG+="systemd" -# If the GPT sector size doesn't match the device's native sector size (e.g. 512-byte GPT on a -# 2048-byte CD-ROM booted via El Torito), trigger a loop device to re-expose it with the correct -# sector size so the kernel can parse the partition table. -SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH}=="1", \ +# If the kernel cannot parse the GPT partition table on the boot disk (e.g. due to a sector size +# mismatch on a CD-ROM booted via El Torito, or because the device does not support partition +# scanning), trigger a loop device to expose the partitions. +SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP}=="1", \ ENV{SYSTEMD_WANTS}+="systemd-loop@.service" LABEL="systemd_end" diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index 9249dc24a1..8f5fefad47 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -320,21 +320,23 @@ EFI_STATUS partition_open(const EFI_GUID *type, EFI_HANDLE *device, EFI_HANDLE * return EFI_SUCCESS; } -static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { +static char16_t* disk_get_part_uuid_eltorito(const EFI_DEVICE_PATH *dp) { EFI_STATUS err; assert(dp); - /* When booting from a CD-ROM via El Torito, the device path contains a CDROM node instead of - * a HARDDRIVE node. The CDROM node doesn't carry a partition UUID, so we need to read the GPT - * from the underlying disk to find it. */ + /* When booting via El Torito, the device path contains a CDROM node instead of a HARDDRIVE + * node (UEFI specification §10.3.5.2). The CDROM node doesn't carry a partition UUID, so we + * need to read the GPT from the underlying disk to find it. Per §13.3.2, El Torito partition + * discovery applies to any block device, not just optical media (e.g. an ISO image dd'd to a + * USB stick). */ const CDROM_DEVICE_PATH *cdrom = NULL; for (const EFI_DEVICE_PATH *node = dp; !device_path_is_end(node); node = device_path_next_node(node)) if (node->Type == MEDIA_DEVICE_PATH && node->SubType == MEDIA_CDROM_DP) cdrom = (const CDROM_DEVICE_PATH *) node; if (!cdrom) { - log_debug("No CDROM device path node found."); + log_debug("No El Torito device path node found."); return NULL; } @@ -345,7 +347,7 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { EFI_HANDLE disk_handle; err = BS->LocateDevicePath(MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), &remaining, &disk_handle); if (err != EFI_SUCCESS) { - log_debug_status(err, "Failed to locate disk device for CDROM: %m"); + log_debug_status(err, "Failed to locate disk device for El Torito boot: %m"); return NULL; } @@ -354,13 +356,13 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { EFI_BLOCK_IO_PROTOCOL *block_io; err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_BLOCK_IO_PROTOCOL), (void **) &block_io); if (err != EFI_SUCCESS) { - log_debug_status(err, "Failed to get block I/O protocol for CDROM disk: %m"); + log_debug_status(err, "Failed to get block I/O protocol for El Torito disk: %m"); return NULL; } if (block_io->Media->LogicalPartition || !block_io->Media->MediaPresent || block_io->Media->LastBlock <= 1) { - log_debug("CDROM disk has unsuitable media (partition=%ls, present=%ls, lastblock=%" PRIu64 ").", + log_debug("El Torito disk has unsuitable media (partition=%ls, present=%ls, lastblock=%" PRIu64 ").", yes_no(block_io->Media->LogicalPartition), yes_no(block_io->Media->MediaPresent), (uint64_t) block_io->Media->LastBlock); @@ -369,24 +371,24 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { uint32_t iso9660_block_size = block_io->Media->BlockSize; if (iso9660_block_size < 512 || iso9660_block_size > 4096 || !ISPOWEROF2(iso9660_block_size)) { - log_debug("Unexpected CDROM block size %" PRIu32 ", skipping.", iso9660_block_size); + log_debug("Unexpected El Torito block size %" PRIu32 ", skipping.", iso9660_block_size); return NULL; } EFI_DISK_IO_PROTOCOL *disk_io; err = BS->HandleProtocol(disk_handle, MAKE_GUID_PTR(EFI_DISK_IO_PROTOCOL), (void **) &disk_io); if (err != EFI_SUCCESS) { - log_debug_status(err, "Failed to get disk I/O protocol for CDROM disk: %m"); + log_debug_status(err, "Failed to get disk I/O protocol for El Torito disk: %m"); return NULL; } uint32_t media_id = block_io->Media->MediaId; /* Probe for the GPT header at multiple possible sector sizes (512, 1024, 2048, 4096). - * The GPT header is at LBA 1, i.e. byte offset == sector_size. On CD-ROMs, the GPT + * The GPT header is at LBA 1, i.e. byte offset == sector_size. On El Torito media, the GPT * may use a different sector size than the media's block size (e.g. 512-byte GPT sectors - * on 2048-byte CD-ROM blocks), so we try all possibilities. If the primary GPT header is - * corrupt but contains a valid backup LBA, fall back to the backup header. */ + * on 2048-byte blocks), so we try all possibilities. If the primary GPT header is corrupt + * but contains a valid backup LBA, fall back to the backup header. */ uint32_t gpt_sector_size = 0; GptHeader gpt; _cleanup_free_ void *entries = NULL; @@ -413,18 +415,18 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { } if (gpt_sector_size == 0) { - log_debug("No valid GPT found on CDROM at any sector size."); + log_debug("No valid GPT found on El Torito disk at any sector size."); return NULL; } - log_debug("Found GPT on CDROM with sector size %" PRIu32 ", %" PRIu32 " partition entries.", + log_debug("Found GPT on El Torito disk with sector size %" PRIu32 ", %" PRIu32 " partition entries.", gpt_sector_size, gpt.NumberOfPartitionEntries); - /* Find the partition whose byte offset matches the CDROM's PartitionStart. - * CDROM PartitionStart is in media iso9660_block_size units, GPT StartingLBA is in gpt_sector_size units. */ + /* Find the partition whose byte offset matches the El Torito PartitionStart. + * El Torito PartitionStart is in media iso9660_block_size units, GPT StartingLBA is in gpt_sector_size units. */ uint64_t cdrom_start; if (!MUL_SAFE(&cdrom_start, cdrom->PartitionStart, iso9660_block_size)) { - log_debug("CDROM start offset overflow."); + log_debug("El Torito start offset overflow."); return NULL; } @@ -441,7 +443,7 @@ static char16_t* disk_get_part_uuid_cdrom(const EFI_DEVICE_PATH *dp) { return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(entry->UniquePartitionGUID)); } - log_debug("No ESP partition matches CDROM start offset %" PRIu64 " (block size %" PRIu32 ").", + log_debug("No ESP partition matches El Torito start offset %" PRIu64 " (block size %" PRIu32 ").", cdrom->PartitionStart, iso9660_block_size); return NULL; } @@ -470,6 +472,6 @@ char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(hd->SignatureGuid)); } - /* No GPT partition node found — try CDROM device path as fallback */ - return disk_get_part_uuid_cdrom(dp); + /* No GPT partition node found — try El Torito device path as fallback */ + return disk_get_part_uuid_eltorito(dp); } diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index b5fb87437c..edadb71c7a 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -422,16 +422,20 @@ notloop: return 0; } -static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { +static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); int r; - /* Probe the GPT sector size. For CD-ROMs booted via El Torito, the GPT may use a different - * sector size than the device (e.g. 512-byte GPT on a 2048-byte CD-ROM). If there's a mismatch, - * check if this is the boot disk by comparing GPT partition UUIDs with the ESP/XBOOTLDR UUID - * exported by the boot loader. If it matches, set a property so that udev rules can set up a - * loop device with the correct sector size — the kernel can't parse the partition table itself - * in this case. */ + /* Probe the GPT sector size. For devices booted via El Torito, the GPT may use a different + * sector size than the device (e.g. a 512-byte GPT on a device with 2048-byte blocks). If + * there's a mismatch, check if this is the boot disk by comparing GPT partition UUIDs with the + * ESP/XBOOTLDR UUID exported by the boot loader. If it matches, set a property so that udev + * rules can set up a loop device with the correct sector size — the kernel can't parse the + * partition table itself in this case. + * + * Even if the sector sizes match, if the device does not support partition scanning (e.g. some + * CD-ROM drives), the kernel still can't parse the partition table. In that case, if the disk + * contains the ESP we booted from, we still need a loop device to expose the partitions. */ _cleanup_free_ void *entries = NULL; uint32_t n_entries, entry_size; @@ -446,11 +450,21 @@ static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { if (r < 0) return log_device_debug_errno(dev, r, "Failed to get device sector size: %m"); - if ((uint32_t) gpt_ssz == device_ssz) - return 0; + bool sector_size_mismatch = (uint32_t) gpt_ssz != device_ssz; - log_device_debug(dev, "GPT sector size %zi does not match device sector size %" PRIu32 ".", - gpt_ssz, device_ssz); + if (!sector_size_mismatch) { + r = blockdev_partscan_enabled(dev); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to check if partition scanning is enabled: %m"); + if (r > 0) + return 0; + } + + if (sector_size_mismatch) + log_device_debug(dev, "GPT sector size %zi does not match device sector size %" PRIu32 ".", + gpt_ssz, device_ssz); + else + log_device_debug(dev, "Device does not support partition scanning."); sd_id128_t loader_part_uuid; r = efi_loader_get_device_part_uuid(&loader_part_uuid); @@ -471,10 +485,10 @@ static int probe_gpt_sector_size_mismatch(UdevEvent *event, int fd) { if (!sd_id128_equal(efi_guid_to_id128(entry->unique_partition_guid), loader_part_uuid)) continue; - log_device_debug(dev, "Found boot partition (ESP/XBOOTLDR) on disk with sector size mismatch."); - udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH", "1"); + log_device_debug(dev, "Found boot partition (ESP/XBOOTLDR) on disk where kernel cannot scan partitions."); + udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP", "1"); udev_builtin_add_propertyf(event, "ID_PART_GPT_SECTOR_SIZE", "%zi", gpt_ssz); - return 1; /* mismatch detected and handled */ + return 1; /* boot disk needs loop device */ } return 0; @@ -548,7 +562,7 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { } if (offset == 0) { - r = probe_gpt_sector_size_mismatch(event, fd); + r = probe_gpt_boot_disk_needs_loop(event, fd); if (r > 0) return 0; }