]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared/gpt: add gpt_probe() for GPT header and partition entry reading
authorDaan De Meyer <daan@amutable.com>
Mon, 30 Mar 2026 19:50:27 +0000 (21:50 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 2 Apr 2026 11:59:55 +0000 (13:59 +0200)
Add gpt_probe() which probes for a GPT partition table at various sector
sizes (512-4096) and optionally returns the header and partition entries.
Returns the detected sector size on success, 0 if no GPT was found, or
negative errno on error.

Refactor probe_sector_size() in dissect-image.c to be a thin wrapper
around gpt_probe().

Co-developed-by: Claude Opus 4.6 <noreply@anthropic.com>
src/shared/dissect-image.c
src/shared/gpt.c
src/shared/gpt.h
src/test/test-gpt.c

index 94d6f0545d4e1e0615e581ececdd340a59463ca1..2bef82dd34b5309ebd5e313d2f819c565255fc69 100644 (file)
@@ -139,54 +139,24 @@ static const char *getenv_fstype(PartitionDesignator d) {
 
 int probe_sector_size(int fd, uint32_t *ret) {
 
-        /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching
-         * for the GPT headers at the relevant byte offsets */
-
-        assert_cc(sizeof(GptHeader) == 92);
-
-        /* We expect a sector size in the range 512…4096. The GPT header is located in the second
-         * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must
-         * read with granularity of the largest sector size we care about. Which means 8K. */
-        uint8_t sectors[2 * 4096];
-        uint32_t found = 0;
-        ssize_t n;
-
         assert(fd >= 0);
         assert(ret);
 
-        n = pread(fd, sectors, sizeof(sectors), 0);
-        if (n < 0)
-                return -errno;
-        if (n != sizeof(sectors)) /* too short? */
-                goto not_found;
-
-        /* Let's see if we find the GPT partition header with various expected sector sizes */
-        for (uint32_t sz = 512; sz <= 4096; sz <<= 1) {
-                const GptHeader *p;
-
-                assert(sizeof(sectors) >= sz * 2);
-                p = (const GptHeader*) (sectors + sz);
-
-                if (!gpt_header_has_signature(p))
-                        continue;
-
-                if (found != 0)
-                        return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
-                                               "Detected valid partition table at offsets matching multiple sector sizes, refusing.");
-
-                found = sz;
-        }
-
-        if (found != 0) {
-                log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", found);
-                *ret = found;
-                return 1; /* indicate we *did* find it */
+        ssize_t ssz = gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL);
+        if (ssz == -ENOTUNIQ)
+                return log_debug_errno(ssz,
+                                       "Detected valid partition table at offsets matching multiple sector sizes, refusing.");
+        if (ssz < 0)
+                return ssz;
+        if (ssz == 0) {
+                log_debug("Couldn't find any partition table to derive sector size of.");
+                *ret = 512; /* pick the traditional default */
+                return 0;   /* indicate we didn't find it */
         }
 
-not_found:
-        log_debug("Couldn't find any partition table to derive sector size of.");
-        *ret = 512; /* pick the traditional default */
-        return 0;   /* indicate we didn't find it */
+        log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", (uint32_t) ssz);
+        *ret = ssz;
+        return 1; /* indicate we *did* find it */
 }
 
 int probe_sector_size_prefer_ioctl(int fd, uint32_t *ret) {
index 9308159ebe9f0a41d4cd2ca61c7a8e56ee307757..d6f264fbe980ba8f8bb9ea9498ad683e808d0c86 100644 (file)
@@ -1,5 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <unistd.h>
+
 #include "alloc-util.h"
 #include "gpt.h"
 #include "string-table.h"
@@ -391,3 +393,86 @@ bool gpt_header_has_signature(const GptHeader *p) {
 
         return true;
 }
+
+ssize_t gpt_probe(
+                int fd,
+                GptHeader *ret_header,
+                void **ret_entries,
+                uint32_t *ret_n_entries,
+                uint32_t *ret_entry_size) {
+
+        assert(fd >= 0);
+
+        /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching
+         * for the GPT headers at the relevant byte offsets. */
+
+        assert_cc(sizeof(GptHeader) == 92);
+
+        /* We expect a sector size in the range 512…4096. The GPT header is located in the second
+         * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must
+         * read with granularity of the largest sector size we care about. Which means 8K. */
+        uint8_t sectors[2 * 4096];
+
+        ssize_t n = pread(fd, sectors, sizeof(sectors), 0);
+        if (n < 0)
+                return -errno;
+        if ((size_t) n < sizeof(sectors))
+                return 0; /* too short */
+
+        /* Let's see if we find the GPT partition header with various expected sector sizes */
+        uint32_t found = 0;
+        for (uint32_t sz = 512; sz <= 4096; sz <<= 1) {
+                const GptHeader *p = (const GptHeader *) (sectors + sz);
+
+                if (!gpt_header_has_signature(p))
+                        continue;
+
+                if (found != 0)
+                        return -ENOTUNIQ;
+
+                found = sz;
+        }
+
+        if (found == 0)
+                return 0;
+
+        const GptHeader *h = (const GptHeader *) (sectors + found);
+
+        uint32_t entry_sz = le32toh(h->size_of_partition_entry);
+        uint32_t entry_count = le32toh(h->number_of_partition_entries);
+
+        if (ret_entries) {
+                uint64_t entry_lba = le64toh(h->partition_entry_lba);
+                if (entry_lba > (uint64_t) INT64_MAX / found)
+                        return -EBADMSG;
+
+                uint64_t entry_offset = entry_lba * found;
+
+                if (entry_sz < sizeof(GptPartitionEntry) || entry_count == 0 || entry_count > 1024)
+                        return -EBADMSG;
+                if (entry_sz > SIZE_MAX / entry_count)
+                        return -EBADMSG;
+
+                size_t entries_size = (size_t) entry_sz * entry_count;
+                _cleanup_free_ void *entries = malloc(entries_size);
+                if (!entries)
+                        return -ENOMEM;
+
+                n = pread(fd, entries, entries_size, entry_offset);
+                if (n < 0)
+                        return -errno;
+                if ((size_t) n < entries_size)
+                        return -EBADMSG;
+
+                *ret_entries = TAKE_PTR(entries);
+        }
+
+        if (ret_header)
+                *ret_header = *h;
+        if (ret_n_entries)
+                *ret_n_entries = entry_count;
+        if (ret_entry_size)
+                *ret_entry_size = entry_sz;
+
+        return found; /* sector size */
+}
index f59f2da29fdcb6aac1baf21c61c8ab0abc925623..18d665441c67935559c251a5763ee754823dc90a 100644 (file)
@@ -113,3 +113,10 @@ typedef struct {
 } _packed_ GptHeader;
 
 bool gpt_header_has_signature(const GptHeader *p) _pure_;
+
+ssize_t gpt_probe(
+                int fd,
+                GptHeader *ret_header,
+                void **ret_entries,
+                uint32_t *ret_n_entries,
+                uint32_t *ret_entry_size);
index 6772d46ef64bd8d3c0e6cb48617aedfe2059c604..430f8e07fdd72b2f9c98061a1d9d2125f9753efe 100644 (file)
@@ -1,8 +1,13 @@
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 
+#include <unistd.h>
+
 #include "architecture.h"
+#include "fd-util.h"
 #include "gpt.h"
 #include "log.h"
+#include "memfd-util.h"
+#include "memory-util.h"
 #include "pretty-print.h"
 #include "strv.h"
 #include "tests.h"
@@ -49,19 +54,19 @@ TEST(verity_mappings) {
                 PartitionDesignator q;
 
                 q = partition_verity_hash_of(p);
-                assert_se(q < 0 || partition_verity_hash_to_data(q) == p);
+                ASSERT_TRUE(q < 0 || partition_verity_hash_to_data(q) == p);
 
                 q = partition_verity_sig_of(p);
-                assert_se(q < 0 || partition_verity_sig_to_data(q) == p);
+                ASSERT_TRUE(q < 0 || partition_verity_sig_to_data(q) == p);
 
                 q = partition_verity_hash_to_data(p);
-                assert_se(q < 0 || partition_verity_hash_of(q) == p);
+                ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p);
 
                 q = partition_verity_sig_to_data(p);
-                assert_se(q < 0 || partition_verity_sig_of(q) == p);
+                ASSERT_TRUE(q < 0 || partition_verity_sig_of(q) == p);
 
                 q = partition_verity_to_data(p);
-                assert_se(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p);
+                ASSERT_TRUE(q < 0 || partition_verity_hash_of(q) == p || partition_verity_sig_of(q) == p);
         }
 }
 
@@ -94,7 +99,7 @@ TEST(override_architecture) {
         x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64);
         ASSERT_EQ(x.arch, y.arch);
         ASSERT_EQ(x.designator, y.designator);
-        assert_se(sd_id128_equal(x.uuid, y.uuid));
+        ASSERT_EQ_ID128(x.uuid, y.uuid);
         ASSERT_STREQ(x.name, y.name);
 
         /* If the partition type does not have an architecture, nothing should change. */
@@ -105,8 +110,148 @@ TEST(override_architecture) {
         x = gpt_partition_type_override_architecture(x, ARCHITECTURE_ARM64);
         ASSERT_EQ(x.arch, y.arch);
         ASSERT_EQ(x.designator, y.designator);
-        assert_se(sd_id128_equal(x.uuid, y.uuid));
+        ASSERT_EQ_ID128(x.uuid, y.uuid);
         ASSERT_STREQ(x.name, y.name);
 }
 
+static void make_gpt(int fd, uint32_t sector_size, const GptPartitionEntry *part_entries, size_t n_entries) {
+        /* Zero-fill enough for header probing (gpt_probe reads 2*4096 = 8KB) */
+        static const uint8_t zeros[2 * 4096] = {};
+        ASSERT_OK_EQ_ERRNO(pwrite(fd, zeros, sizeof(zeros), 0), (ssize_t) sizeof(zeros));
+
+        GptHeader h = {
+                .signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' },
+                .revision = htole32(UINT32_C(0x00010000)),
+                .header_size = htole32(sizeof(GptHeader)),
+                .my_lba = htole64(1),
+                .partition_entry_lba = htole64(2),
+                .number_of_partition_entries = htole32(n_entries),
+                .size_of_partition_entry = htole32(sizeof(GptPartitionEntry)),
+        };
+        ASSERT_OK_EQ_ERRNO(pwrite(fd, &h, sizeof(h), sector_size), (ssize_t) sizeof(h));
+
+        if (n_entries > 0) {
+                size_t entries_size = n_entries * sizeof(GptPartitionEntry);
+                ASSERT_OK_EQ_ERRNO(pwrite(fd, part_entries, entries_size, 2 * sector_size), (ssize_t) entries_size);
+        }
+}
+
+TEST(gpt_probe_empty) {
+        _cleanup_close_ int fd = -EBADF;
+
+        fd = ASSERT_OK(memfd_new("test-gpt-probe"));
+        ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0);
+}
+
+TEST(gpt_probe_too_short) {
+        _cleanup_close_ int fd = -EBADF;
+        static const uint8_t buf[4096] = {};
+
+        fd = ASSERT_OK(memfd_new("test-gpt-probe"));
+        ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf));
+        ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0);
+}
+
+TEST(gpt_probe_no_signature) {
+        _cleanup_close_ int fd = -EBADF;
+        static const uint8_t buf[2 * 4096] = {};
+
+        fd = ASSERT_OK(memfd_new("test-gpt-probe"));
+        ASSERT_OK_EQ_ERRNO(pwrite(fd, buf, sizeof(buf), 0), (ssize_t) sizeof(buf));
+        ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 0);
+}
+
+TEST(gpt_probe_sector_512) {
+        _cleanup_close_ int fd = -EBADF;
+
+        const GptPartitionEntry entries[2] = {
+                {
+                        .unique_partition_guid = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+                                                   0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10 },
+                        .starting_lba = htole64(100),
+                        .ending_lba = htole64(200),
+                },
+                {
+                        .unique_partition_guid = { 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+                                                   0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 },
+                        .starting_lba = htole64(300),
+                        .ending_lba = htole64(400),
+                },
+        };
+
+        fd = ASSERT_OK(memfd_new("test-gpt-probe"));
+        make_gpt(fd, 512, entries, 2);
+
+        /* Sector size detection only */
+        ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512);
+
+        /* Header return */
+        GptHeader h;
+        ASSERT_OK_EQ(gpt_probe(fd, &h, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 512);
+        ASSERT_EQ(le32toh(h.number_of_partition_entries), 2u);
+        ASSERT_EQ(le32toh(h.size_of_partition_entry), (uint32_t) sizeof(GptPartitionEntry));
+
+        /* Full probe with entries */
+        _cleanup_free_ void *ret_entries = NULL;
+        uint32_t n, sz;
+        ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 512);
+        ASSERT_EQ(n, 2u);
+        ASSERT_EQ(sz, (uint32_t) sizeof(GptPartitionEntry));
+        ASSERT_NOT_NULL(ret_entries);
+
+        GptPartitionEntry *e = ret_entries;
+        ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entries[0].unique_partition_guid, sizeof(entries[0].unique_partition_guid)), 0);
+        ASSERT_EQ(memcmp_nn(e[1].unique_partition_guid, sizeof(e[1].unique_partition_guid), entries[1].unique_partition_guid, sizeof(entries[1].unique_partition_guid)), 0);
+        ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(100));
+        ASSERT_EQ(le64toh(e[1].starting_lba), UINT64_C(300));
+}
+
+TEST(gpt_probe_sector_4096) {
+        _cleanup_close_ int fd = -EBADF;
+
+        const GptPartitionEntry entry = {
+                .unique_partition_guid = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11,
+                                           0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 },
+                .starting_lba = htole64(50),
+                .ending_lba = htole64(100),
+        };
+
+        fd = ASSERT_OK(memfd_new("test-gpt-probe"));
+        make_gpt(fd, 4096, &entry, 1);
+
+        ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), (ssize_t) 4096);
+
+        _cleanup_free_ void *ret_entries = NULL;
+        uint32_t n, sz;
+        ASSERT_OK_EQ(gpt_probe(fd, /* ret_header= */ NULL, &ret_entries, &n, &sz), (ssize_t) 4096);
+        ASSERT_EQ(n, 1u);
+
+        GptPartitionEntry *e = ret_entries;
+        ASSERT_EQ(memcmp_nn(e[0].unique_partition_guid, sizeof(e[0].unique_partition_guid), entry.unique_partition_guid, sizeof(entry.unique_partition_guid)), 0);
+        ASSERT_EQ(le64toh(e[0].starting_lba), UINT64_C(50));
+}
+
+TEST(gpt_probe_ambiguous) {
+        _cleanup_close_ int fd = -EBADF;
+
+        const GptPartitionEntry entry = {};
+
+        fd = ASSERT_OK(memfd_new("test-gpt-probe"));
+        make_gpt(fd, 512, &entry, 1);
+
+        /* Place a second valid header at offset 4096 */
+        GptHeader h2 = {
+                .signature = { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' },
+                .revision = htole32(UINT32_C(0x00010000)),
+                .header_size = htole32(sizeof(GptHeader)),
+                .my_lba = htole64(1),
+                .partition_entry_lba = htole64(2),
+                .number_of_partition_entries = htole32(1),
+                .size_of_partition_entry = htole32(sizeof(GptPartitionEntry)),
+        };
+        ASSERT_OK_EQ_ERRNO(pwrite(fd, &h2, sizeof(h2), 4096), (ssize_t) sizeof(h2));
+
+        ASSERT_ERROR(gpt_probe(fd, /* ret_header= */ NULL, /* ret_entries= */ NULL, /* ret_n_entries= */ NULL, /* ret_entry_size= */ NULL), ENOTUNIQ);
+}
+
 DEFINE_TEST_MAIN(LOG_INFO);