From 047ae265cc3ce339e14022ac63281535da6b6f8f Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 30 Mar 2026 20:11:15 +0000 Subject: [PATCH] udev: probe GPT sector size and trigger loop device on mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- rules.d/99-systemd.rules.in | 6 +++ src/udev/udev-builtin-blkid.c | 87 ++++++++++++++++++++++++++++++----- 2 files changed, 82 insertions(+), 11 deletions(-) diff --git a/rules.d/99-systemd.rules.in b/rules.d/99-systemd.rules.in index bebc4d7d09..da2d311ce4 100644 --- a/rules.d/99-systemd.rules.in +++ b/rules.d/99-systemd.rules.in @@ -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" diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 6ec02674b9..b5fb87437c 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -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)