From 1dc599ce411d26ba10e245d3cf3018abe0e743a5 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 15 Jun 2026 07:45:46 +0000 Subject: [PATCH] udev-builtin-blkid: keep probing the boot disk when it needs a loop device Since 4e0eabd40118 ("udev: also trigger loop device for boot disk when partition scanning is unsupported"), builtin_blkid() bails out entirely as soon as probe_gpt_boot_disk_needs_loop() reports that a loop device is needed, skipping all superblock probing. As a result whole-disk properties such as ID_PART_TABLE_UUID and ID_FS_* are no longer set. This regresses any whole disk whose partitions the kernel cannot expose itself but which is otherwise perfectly probeable, most notably device-mapper multipath disks: kernel partition scanning is disabled on them (their partitions are managed in userspace by kpartx), so they are now flagged as needing a loop device and lose their ID_PART_TABLE_UUID. The early return was never necessary. The original intent was only to skip root partition discovery on the device, and that already happens on the loop device instead: find_gpt_root() bails when the kernel can't scan partitions, blkid probes at the device's own logical sector size so a GPT written for a different sector size is simply not detected, and PART_ENTRY_* is only emitted for partitions the kernel actually registered, of which a loop-needing whole disk has none. So keep probing the device for its whole-disk properties unconditionally and let partition and root discovery happen on the loop device. Co-developed-by: Claude Fable 5 --- src/udev/udev-builtin-blkid.c | 60 +++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index 57fa7f9585..0a75ee8dcf 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -422,28 +422,37 @@ notloop: return 0; } -static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { - sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); +static int gpt_boot_disk_needs_loop(sd_device *dev, int fd, ssize_t *ret_sector_size) { int r; - /* 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. + assert(dev); + assert(fd >= 0); + assert(ret_sector_size); + + /* Determine whether the boot disk needs a loop device to expose its partitions. 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 whether this is the boot + * disk by comparing GPT partition UUIDs with the ESP/XBOOTLDR UUID exported by the boot loader. + * The kernel can't parse the partition table itself in this case, so the caller sets up a loop + * device with the returned sector size. * * 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. */ + * CD-ROM drives), the kernel still can't parse the partition table. In that case too, if the + * disk contains the ESP we booted from, a loop device is needed to expose the partitions. + * + * Returns 1 if the boot disk needs a loop device, 0 if not. On success the GPT sector size is + * returned in *ret_sector_size (0 if the disk has no GPT), regardless of whether a loop device + * is needed. */ _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) + if (gpt_ssz == 0) { + *ret_sector_size = 0; return 0; + } uint32_t device_ssz; r = blockdev_get_sector_size(fd, &device_ssz); @@ -456,8 +465,11 @@ static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { 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) + if (r > 0) { + /* The kernel can parse the partition table itself; no loop device needed. */ + *ret_sector_size = gpt_ssz; return 0; + } } if (sector_size_mismatch) @@ -472,6 +484,8 @@ static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { if (r != -ENOENT && !ERRNO_IS_NEG_NOT_SUPPORTED(r)) return log_device_debug_errno(dev, r, "Failed to get loader partition UUID: %m"); + /* No loader partition UUID set, can't tell whether this is the boot disk. */ + *ret_sector_size = gpt_ssz; return 0; } @@ -486,11 +500,11 @@ static int probe_gpt_boot_disk_needs_loop(UdevEvent *event, int fd) { continue; 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; /* boot disk needs loop device */ + *ret_sector_size = gpt_ssz; + return 1; /* boot disk needs a loop device */ } + *ret_sector_size = gpt_ssz; return 0; } @@ -554,10 +568,22 @@ static int builtin_blkid(UdevEvent *event, int argc, char *argv[]) { return ignore ? 0 : fd; } + /* If the kernel can't parse the boot disk's partition table, set properties so a udev rule sets + * up a loop device (with the correct sector size) to expose the partitions. We still probe the + * device itself below for its whole-disk properties (filesystem type, partition table UUID, and + * so on); partition and root discovery happen on the loop device instead. We don't need to + * suppress the latter here: blkid probes at the device's own logical sector size, so a GPT + * written for a different sector size is simply not detected, and both PART_ENTRY_* and + * find_gpt_root() only ever act on partitions the kernel actually registered — which, for a disk + * that needs a loop device, is none. */ if (offset == 0) { - r = probe_gpt_boot_disk_needs_loop(event, fd); + ssize_t gpt_ssz = 0; + + r = gpt_boot_disk_needs_loop(dev, fd, &gpt_ssz); + if (gpt_ssz > 0) + udev_builtin_add_propertyf(event, "ID_PART_GPT_SECTOR_SIZE", "%zi", gpt_ssz); if (r > 0) - return 0; + udev_builtin_add_property(event, "ID_PART_GPT_AUTO_ROOT_DISK_NEEDS_LOOP", "1"); } sym_blkid_probe_set_superblocks_flags(pr,