udev: also trigger loop device for boot disk when partition scanning is unsupported (#41509)
Previously, probe_gpt_sector_size_mismatch() would bail out early when the GPT sector size matched the device sector size. However, some devices (e.g. certain CD-ROM drives) do not support kernel partition scanning even when sector sizes match. In that case, the kernel still cannot parse the partition table, and we need to set up a loop device to expose the partitions — just as we do for the sector size mismatch case. Check blockdev_partscan_enabled() when sector sizes match, and only skip the boot partition check if partition scanning is actually supported. Also rename the function, udev property, and log messages to reflect the broader scope: - probe_gpt_sector_size_mismatch() -> probe_gpt_boot_disk_needs_loop() - ID_PART_GPT_AUTO_ROOT_DISK_SECTOR_SIZE_MISMATCH -> ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP
This commit is contained 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"
|
||||
|
||||
+23
-21
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user