udev-builtin-blkid: keep probing the boot disk when it needs a loop device

Since 4e0eabd401 ("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 <noreply@anthropic.com>
This commit is contained in:
Daan De Meyer
2026-06-15 16:00:39 +02:00
committed by Daan De Meyer
parent 240b71fe1f
commit 1dc599ce41
+43 -17
View File
@@ -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,