udev: probe GPT sector size and trigger loop device on mismatch

When the GPT partition table uses a different sector size than the
device's native block size (e.g. 512-byte GPT on a 2048-byte CD-ROM
booted via El Torito), the kernel cannot parse the partition table.

Probe the GPT sector size upfront and configure blkid with the correct
value so it always finds the partition table. If a sector size mismatch
is detected, trigger a loop device to re-expose the device with the
correct sector size and skip root partition discovery on the original
device — it will happen on the loop device instead.

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daan De Meyer
2026-04-04 20:03:44 +02:00
committed by Daan De Meyer
parent 8f5735ad50
commit 047ae265cc
2 changed files with 82 additions and 11 deletions
+6
View File
@@ -89,4 +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", \
ENV{SYSTEMD_WANTS}+="systemd-loop@.service"
LABEL="systemd_end"
+76 -11
View File
@@ -20,6 +20,7 @@
#include "blockdev-util.h"
#include "device-util.h"
#include "devnum-util.h"
#include "efi-api.h"
#include "efi-loader.h"
#include "errno-util.h"
#include "fd-util.h"
@@ -421,6 +422,64 @@ notloop:
return 0;
}
static int probe_gpt_sector_size_mismatch(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. */
_cleanup_free_ void *entries = NULL;
uint32_t n_entries, entry_size;
ssize_t gpt_ssz = gpt_probe(fd, /* ret_header= */ NULL, &entries, &n_entries, &entry_size);
if (gpt_ssz < 0)
return log_device_debug_errno(dev, gpt_ssz, "Failed to probe GPT: %m");
if (gpt_ssz == 0)
return 0;
uint32_t device_ssz;
r = blockdev_get_sector_size(fd, &device_ssz);
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;
log_device_debug(dev, "GPT sector size %zi does not match device sector size %" PRIu32 ".",
gpt_ssz, device_ssz);
sd_id128_t loader_part_uuid;
r = efi_loader_get_device_part_uuid(&loader_part_uuid);
if (r < 0) {
if (r != -ENOENT && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
return log_device_debug_errno(dev, r, "Failed to get loader partition UUID: %m");
return 0;
}
for (uint32_t i = 0; i < n_entries; i++) {
const GptPartitionEntry *entry = (const GptPartitionEntry *) ((const uint8_t *) entries + (size_t) entry_size * i);
sd_id128_t type = efi_guid_to_id128(entry->partition_type_guid);
if (!sd_id128_in_set(type, SD_GPT_ESP, SD_GPT_XBOOTLDR))
continue;
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");
udev_builtin_add_propertyf(event, "ID_PART_GPT_SECTOR_SIZE", "%zi", gpt_ssz);
return 1; /* mismatch detected and handled */
}
return 0;
}
static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) {
sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev);
const char *devnode, *root_partition = NULL, *data, *name;
@@ -476,17 +535,6 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) {
}
}
sym_blkid_probe_set_superblocks_flags(pr,
BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE |
#ifdef BLKID_SUBLKS_FSINFO /* since util-linux 2.39 */
BLKID_SUBLKS_FSINFO |
#endif
BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION);
if (noraid)
sym_blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID);
r = sd_device_get_devname(dev, &devnode);
if (r < 0)
return log_device_debug_errno(dev, r, "Failed to get device name: %m");
@@ -499,6 +547,23 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) {
return ignore ? 0 : fd;
}
if (offset == 0) {
r = probe_gpt_sector_size_mismatch(event, fd);
if (r > 0)
return 0;
}
sym_blkid_probe_set_superblocks_flags(pr,
BLKID_SUBLKS_LABEL | BLKID_SUBLKS_UUID |
BLKID_SUBLKS_TYPE | BLKID_SUBLKS_SECTYPE |
#ifdef BLKID_SUBLKS_FSINFO /* since util-linux 2.39 */
BLKID_SUBLKS_FSINFO |
#endif
BLKID_SUBLKS_USAGE | BLKID_SUBLKS_VERSION);
if (noraid)
sym_blkid_probe_filter_superblocks_usage(pr, BLKID_FLTR_NOTIN, BLKID_USAGE_RAID);
errno = 0;
r = sym_blkid_probe_set_device(pr, fd, offset, 0);
if (r < 0)