From: Daan De Meyer Date: Mon, 30 Mar 2026 19:23:10 +0000 (+0000) Subject: boot: add El Torito CDROM partition UUID discovery X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=05e1ca31447fc75e591edb3bca20c0acf2b3b682;p=thirdparty%2Fsystemd.git boot: add El Torito CDROM partition UUID discovery When booting from a CD-ROM via El Torito, the UEFI device path contains a CDROM_DEVICE_PATH node instead of a HARDDRIVE_DEVICE_PATH node. Unlike the hard drive variant, the CDROM node does not carry a partition UUID, so systemd-boot previously could not determine the boot partition UUID in this scenario. Add disk_get_part_uuid_cdrom() which recovers the partition UUID by reading the GPT from the underlying disk. Since ISO images are commonly mastered with 512-byte GPT sectors on media with 2048-byte blocks, the function probes for the GPT header at multiple sector sizes (512, 1024, 2048, 4096) and matches the partition by comparing byte offsets between the CDROM node's PartitionStart and each GPT entry's StartingLBA. The function reuses read_gpt_entries() for GPT parsing and adds debug logging for each failure path to aid diagnosis on real hardware. Also adds the CDROM_DEVICE_PATH struct and MEDIA_CDROM_DP subtype constant to device-path.h, and fixes disk_get_part_uuid() to preserve the original device path pointer so it can be passed to the CDROM fallback. Co-developed-by: Claude Opus 4.6 --- diff --git a/src/boot/part-discovery.c b/src/boot/part-discovery.c index cafc3aa3e71..9249dc24a18 100644 --- a/src/boot/part-discovery.c +++ b/src/boot/part-discovery.c @@ -6,6 +6,7 @@ #include "proto/block-io.h" #include "proto/device-path.h" #include "proto/disk-io.h" +#include "string-util-fundamental.h" #include "util.h" typedef struct { @@ -319,6 +320,132 @@ 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) { + 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. */ + + 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."); + return NULL; + } + + /* Chop off the CDROM node to get the whole-disk device path */ + _cleanup_free_ EFI_DEVICE_PATH *disk_path = device_path_replace_node(dp, &cdrom->Header, /* new_node= */ NULL); + + EFI_DEVICE_PATH *remaining = disk_path; + 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"); + return NULL; + } + + (void) BS->ConnectController(disk_handle, /* DriverImageHandle= */ NULL, /* RemainingDevicePath= */ NULL, /* Recursive= */ true); + + 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"); + 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 ").", + yes_no(block_io->Media->LogicalPartition), + yes_no(block_io->Media->MediaPresent), + (uint64_t) block_io->Media->LastBlock); + return NULL; + } + + 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); + 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"); + 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 + * 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. */ + uint32_t gpt_sector_size = 0; + GptHeader gpt; + _cleanup_free_ void *entries = NULL; + for (uint32_t ss = 512; ss <= 4096; ss <<= 1) { + EFI_LBA backup_lba = 0; + + err = read_gpt_entries(disk_io, media_id, ss, /* lba= */ 1, &backup_lba, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read primary GPT header at sector size %"PRIu32", ignoring: %m", ss); + + if (backup_lba != 0) { + err = read_gpt_entries(disk_io, media_id, ss, backup_lba, /* reterr_backup_lba= */ NULL, &gpt, &entries); + if (err == EFI_SUCCESS) { + gpt_sector_size = ss; + break; + } + if (err != EFI_NOT_FOUND) + log_debug_status(err, "Failed to read backup GPT header at sector size %"PRIu32", ignoring: %m", ss); + } + } + + if (gpt_sector_size == 0) { + log_debug("No valid GPT found on CDROM at any sector size."); + return NULL; + } + + log_debug("Found GPT on CDROM 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. */ + uint64_t cdrom_start; + if (!MUL_SAFE(&cdrom_start, cdrom->PartitionStart, iso9660_block_size)) { + log_debug("CDROM start offset overflow."); + return NULL; + } + + for (size_t i = 0; i < gpt.NumberOfPartitionEntries; i++) { + const EFI_PARTITION_ENTRY *entry = + (const EFI_PARTITION_ENTRY *) ((const uint8_t *) entries + gpt.SizeOfPartitionEntry * i); + + if (!efi_guid_equal(&entry->PartitionTypeGUID, &(const EFI_GUID) ESP_GUID)) + continue; + + uint64_t entry_start; + if (MUL_SAFE(&entry_start, entry->StartingLBA, gpt_sector_size) && + entry_start == cdrom_start) + return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(entry->UniquePartitionGUID)); + } + + log_debug("No ESP partition matches CDROM start offset %" PRIu64 " (block size %" PRIu32 ").", + cdrom->PartitionStart, iso9660_block_size); + return NULL; +} + char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { EFI_STATUS err; EFI_DEVICE_PATH *dp; @@ -332,16 +459,17 @@ char16_t *disk_get_part_uuid(EFI_HANDLE *handle) { if (err != EFI_SUCCESS) return NULL; - for (; !device_path_is_end(dp); dp = device_path_next_node(dp)) { - if (dp->Type != MEDIA_DEVICE_PATH || dp->SubType != MEDIA_HARDDRIVE_DP) + for (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_HARDDRIVE_DP) continue; - HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) dp; + HARDDRIVE_DEVICE_PATH *hd = (HARDDRIVE_DEVICE_PATH *) node; if (hd->SignatureType != SIGNATURE_TYPE_GUID) continue; return xasprintf(GUID_FORMAT_STR, GUID_FORMAT_VAL(hd->SignatureGuid)); } - return NULL; + /* No GPT partition node found — try CDROM device path as fallback */ + return disk_get_part_uuid_cdrom(dp); } diff --git a/src/boot/proto/device-path.h b/src/boot/proto/device-path.h index d81c0e1f8dd..658c482df50 100644 --- a/src/boot/proto/device-path.h +++ b/src/boot/proto/device-path.h @@ -30,6 +30,7 @@ enum { ACPI_DP = 0x01, MEDIA_HARDDRIVE_DP = 0x01, + MEDIA_CDROM_DP = 0x02, MEDIA_VENDOR_DP = 0x03, MEDIA_FILEPATH_DP = 0x04, MEDIA_PIWG_FW_FILE_DP = 0x06, @@ -84,6 +85,14 @@ typedef struct { uint8_t SignatureType; } _packed_ HARDDRIVE_DEVICE_PATH; +typedef struct { + EFI_DEVICE_PATH Header; + uint32_t BootEntry; + uint64_t PartitionStart; /* In media block size units */ + uint64_t PartitionSize; /* In media block size units */ +} _packed_ CDROM_DEVICE_PATH; +assert_cc(sizeof(CDROM_DEVICE_PATH) == 24); + typedef struct { EFI_DEVICE_PATH Header; char16_t PathName[];