summaryrefslogtreecommitdiffstats
path: root/src/shared/gpt.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/shared/gpt.c')
-rw-r--r--src/shared/gpt.c361
1 files changed, 361 insertions, 0 deletions
diff --git a/src/shared/gpt.c b/src/shared/gpt.c
new file mode 100644
index 0000000..d639463
--- /dev/null
+++ b/src/shared/gpt.c
@@ -0,0 +1,361 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "gpt.h"
+#include "string-table.h"
+#include "string-util.h"
+#include "utf8.h"
+
+/* Gently push people towards defining GPT type UUIDs for all architectures we know */
+#if !defined(SD_GPT_ROOT_NATIVE) || \
+ !defined(SD_GPT_ROOT_NATIVE_VERITY) || \
+ !defined(SD_GPT_ROOT_NATIVE_VERITY_SIG) || \
+ !defined(SD_GPT_USR_NATIVE) || \
+ !defined(SD_GPT_USR_NATIVE_VERITY) || \
+ !defined(SD_GPT_USR_NATIVE_VERITY_SIG)
+#pragma message "Please define GPT partition types for your architecture."
+#endif
+
+bool partition_designator_is_versioned(PartitionDesignator d) {
+ /* Returns true for all designators where we want to support a concept of "versioning", i.e. which
+ * likely contain software binaries (or hashes thereof) that make sense to be versioned as a
+ * whole. We use this check to automatically pick the newest version of these partitions, by version
+ * comparing the partition labels. */
+
+ return IN_SET(d,
+ PARTITION_ROOT,
+ PARTITION_USR,
+ PARTITION_ROOT_VERITY,
+ PARTITION_USR_VERITY,
+ PARTITION_ROOT_VERITY_SIG,
+ PARTITION_USR_VERITY_SIG);
+}
+
+PartitionDesignator partition_verity_of(PartitionDesignator p) {
+ switch (p) {
+
+ case PARTITION_ROOT:
+ return PARTITION_ROOT_VERITY;
+
+ case PARTITION_USR:
+ return PARTITION_USR_VERITY;
+
+ default:
+ return _PARTITION_DESIGNATOR_INVALID;
+ }
+}
+
+PartitionDesignator partition_verity_sig_of(PartitionDesignator p) {
+ switch (p) {
+
+ case PARTITION_ROOT:
+ return PARTITION_ROOT_VERITY_SIG;
+
+ case PARTITION_USR:
+ return PARTITION_USR_VERITY_SIG;
+
+ default:
+ return _PARTITION_DESIGNATOR_INVALID;
+ }
+}
+
+PartitionDesignator partition_verity_to_data(PartitionDesignator d) {
+ switch (d) {
+
+ case PARTITION_ROOT_VERITY:
+ return PARTITION_ROOT;
+
+ case PARTITION_USR_VERITY:
+ return PARTITION_USR;
+
+ default:
+ return _PARTITION_DESIGNATOR_INVALID;
+ }
+}
+
+PartitionDesignator partition_verity_sig_to_data(PartitionDesignator d) {
+ switch (d) {
+
+ case PARTITION_ROOT_VERITY_SIG:
+ return PARTITION_ROOT;
+
+ case PARTITION_USR_VERITY_SIG:
+ return PARTITION_USR;
+
+ default:
+ return _PARTITION_DESIGNATOR_INVALID;
+ }
+}
+
+static const char *const partition_designator_table[_PARTITION_DESIGNATOR_MAX] = {
+ [PARTITION_ROOT] = "root",
+ [PARTITION_USR] = "usr",
+ [PARTITION_HOME] = "home",
+ [PARTITION_SRV] = "srv",
+ [PARTITION_ESP] = "esp",
+ [PARTITION_XBOOTLDR] = "xbootldr",
+ [PARTITION_SWAP] = "swap",
+ [PARTITION_ROOT_VERITY] = "root-verity",
+ [PARTITION_USR_VERITY] = "usr-verity",
+ [PARTITION_ROOT_VERITY_SIG] = "root-verity-sig",
+ [PARTITION_USR_VERITY_SIG] = "usr-verity-sig",
+ [PARTITION_TMP] = "tmp",
+ [PARTITION_VAR] = "var",
+};
+
+DEFINE_STRING_TABLE_LOOKUP(partition_designator, PartitionDesignator);
+
+static const char *const partition_mountpoint_table[_PARTITION_DESIGNATOR_MAX] = {
+ [PARTITION_ROOT] = "/\0",
+ [PARTITION_USR] = "/usr\0",
+ [PARTITION_HOME] = "/home\0",
+ [PARTITION_SRV] = "/srv\0",
+ [PARTITION_ESP] = "/efi\0/boot\0",
+ [PARTITION_XBOOTLDR] = "/boot\0",
+ [PARTITION_TMP] = "/var/tmp\0",
+ [PARTITION_VAR] = "/var\0",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(partition_mountpoint, PartitionDesignator);
+
+#define _GPT_ARCH_SEXTET(arch, name) \
+ { SD_GPT_ROOT_##arch, "root-" name, ARCHITECTURE_##arch, .designator = PARTITION_ROOT }, \
+ { SD_GPT_ROOT_##arch##_VERITY, "root-" name "-verity", ARCHITECTURE_##arch, .designator = PARTITION_ROOT_VERITY }, \
+ { SD_GPT_ROOT_##arch##_VERITY_SIG, "root-" name "-verity-sig", ARCHITECTURE_##arch, .designator = PARTITION_ROOT_VERITY_SIG }, \
+ { SD_GPT_USR_##arch, "usr-" name, ARCHITECTURE_##arch, .designator = PARTITION_USR }, \
+ { SD_GPT_USR_##arch##_VERITY, "usr-" name "-verity", ARCHITECTURE_##arch, .designator = PARTITION_USR_VERITY }, \
+ { SD_GPT_USR_##arch##_VERITY_SIG, "usr-" name "-verity-sig", ARCHITECTURE_##arch, .designator = PARTITION_USR_VERITY_SIG }
+
+/* Two special cases: alias aarch64 to arm64, and amd64 to x86-64. The DSP mixes debianisms and CPUisms: for
+ * x86, it uses x86 and x86_64, but for aarch64 it uses arm64. This is confusing, and leads to issues for
+ * callers that have to know which -ism to use for which architecture. But we also don't really want to
+ * change the spec and add new partition labels, so add a user-friendly aliasing here, so that both are
+ * accepted but the end result on disk (ie: the partition label).
+ * So always list the canonical name FIRST, and then any aliases later, so that we can match on aliases,
+ * but always return the canonical name. And never return directly a match on the name, always re-resolve
+ * by UUID so that the canonical entry is always found. */
+
+const GptPartitionType gpt_partition_type_table[] = {
+ _GPT_ARCH_SEXTET(ALPHA, "alpha"),
+ _GPT_ARCH_SEXTET(ARC, "arc"),
+ _GPT_ARCH_SEXTET(ARM, "arm"),
+ _GPT_ARCH_SEXTET(ARM, "armv7l"), /* Alias: must be listed after arm */
+ _GPT_ARCH_SEXTET(ARM64, "arm64"),
+ _GPT_ARCH_SEXTET(ARM64, "aarch64"), /* Alias: must be listed after arm64 */
+ _GPT_ARCH_SEXTET(IA64, "ia64"),
+ _GPT_ARCH_SEXTET(LOONGARCH64, "loongarch64"),
+ _GPT_ARCH_SEXTET(MIPS, "mips"),
+ _GPT_ARCH_SEXTET(MIPS64, "mips64"),
+ _GPT_ARCH_SEXTET(MIPS_LE, "mips-le"),
+ _GPT_ARCH_SEXTET(MIPS64_LE, "mips64-le"),
+ _GPT_ARCH_SEXTET(PARISC, "parisc"),
+ _GPT_ARCH_SEXTET(PPC, "ppc"),
+ _GPT_ARCH_SEXTET(PPC64, "ppc64"),
+ _GPT_ARCH_SEXTET(PPC64_LE, "ppc64-le"),
+ _GPT_ARCH_SEXTET(PPC64_LE, "ppc64le"), /* Alias: must be listed after ppc64-le */
+ _GPT_ARCH_SEXTET(RISCV32, "riscv32"),
+ _GPT_ARCH_SEXTET(RISCV64, "riscv64"),
+ _GPT_ARCH_SEXTET(S390, "s390"),
+ _GPT_ARCH_SEXTET(S390X, "s390x"),
+ _GPT_ARCH_SEXTET(TILEGX, "tilegx"),
+ _GPT_ARCH_SEXTET(X86, "x86"),
+ _GPT_ARCH_SEXTET(X86_64, "x86-64"),
+ _GPT_ARCH_SEXTET(X86_64, "x86_64"), /* Alias: must be listed after x86-64 */
+ _GPT_ARCH_SEXTET(X86_64, "amd64"), /* Alias: must be listed after x86-64 */
+#ifdef SD_GPT_ROOT_NATIVE
+ { SD_GPT_ROOT_NATIVE, "root", native_architecture(), .designator = PARTITION_ROOT },
+ { SD_GPT_ROOT_NATIVE_VERITY, "root-verity", native_architecture(), .designator = PARTITION_ROOT_VERITY },
+ { SD_GPT_ROOT_NATIVE_VERITY_SIG, "root-verity-sig", native_architecture(), .designator = PARTITION_ROOT_VERITY_SIG },
+ { SD_GPT_USR_NATIVE, "usr", native_architecture(), .designator = PARTITION_USR },
+ { SD_GPT_USR_NATIVE_VERITY, "usr-verity", native_architecture(), .designator = PARTITION_USR_VERITY },
+ { SD_GPT_USR_NATIVE_VERITY_SIG, "usr-verity-sig", native_architecture(), .designator = PARTITION_USR_VERITY_SIG },
+#endif
+#ifdef SD_GPT_ROOT_SECONDARY
+ { SD_GPT_ROOT_SECONDARY, "root-secondary", ARCHITECTURE_SECONDARY, .designator = PARTITION_ROOT },
+ { SD_GPT_ROOT_SECONDARY_VERITY, "root-secondary-verity", ARCHITECTURE_SECONDARY, .designator = PARTITION_ROOT_VERITY },
+ { SD_GPT_ROOT_SECONDARY_VERITY_SIG, "root-secondary-verity-sig", ARCHITECTURE_SECONDARY, .designator = PARTITION_ROOT_VERITY_SIG },
+ { SD_GPT_USR_SECONDARY, "usr-secondary", ARCHITECTURE_SECONDARY, .designator = PARTITION_USR },
+ { SD_GPT_USR_SECONDARY_VERITY, "usr-secondary-verity", ARCHITECTURE_SECONDARY, .designator = PARTITION_USR_VERITY },
+ { SD_GPT_USR_SECONDARY_VERITY_SIG, "usr-secondary-verity-sig", ARCHITECTURE_SECONDARY, .designator = PARTITION_USR_VERITY_SIG },
+#endif
+
+ { SD_GPT_ESP, "esp", _ARCHITECTURE_INVALID, .designator = PARTITION_ESP },
+ { SD_GPT_XBOOTLDR, "xbootldr", _ARCHITECTURE_INVALID, .designator = PARTITION_XBOOTLDR },
+ { SD_GPT_SWAP, "swap", _ARCHITECTURE_INVALID, .designator = PARTITION_SWAP },
+ { SD_GPT_HOME, "home", _ARCHITECTURE_INVALID, .designator = PARTITION_HOME },
+ { SD_GPT_SRV, "srv", _ARCHITECTURE_INVALID, .designator = PARTITION_SRV },
+ { SD_GPT_VAR, "var", _ARCHITECTURE_INVALID, .designator = PARTITION_VAR },
+ { SD_GPT_TMP, "tmp", _ARCHITECTURE_INVALID, .designator = PARTITION_TMP },
+ { SD_GPT_USER_HOME, "user-home", _ARCHITECTURE_INVALID, .designator = _PARTITION_DESIGNATOR_INVALID },
+ { SD_GPT_LINUX_GENERIC, "linux-generic", _ARCHITECTURE_INVALID, .designator = _PARTITION_DESIGNATOR_INVALID },
+ {}
+};
+
+static const GptPartitionType *gpt_partition_type_find_by_uuid(sd_id128_t id) {
+
+ FOREACH_ARRAY(t, gpt_partition_type_table, ELEMENTSOF(gpt_partition_type_table) - 1)
+ if (sd_id128_equal(id, t->uuid))
+ return t;
+
+ return NULL;
+}
+
+const char *gpt_partition_type_uuid_to_string(sd_id128_t id) {
+ const GptPartitionType *pt;
+
+ pt = gpt_partition_type_find_by_uuid(id);
+ if (!pt)
+ return NULL;
+
+ return pt->name;
+}
+
+const char *gpt_partition_type_uuid_to_string_harder(
+ sd_id128_t id,
+ char buffer[static SD_ID128_UUID_STRING_MAX]) {
+
+ const char *s;
+
+ assert(buffer);
+
+ s = gpt_partition_type_uuid_to_string(id);
+ if (s)
+ return s;
+
+ return sd_id128_to_uuid_string(id, buffer);
+}
+
+int gpt_partition_type_from_string(const char *s, GptPartitionType *ret) {
+ sd_id128_t id = SD_ID128_NULL;
+ int r;
+
+ assert(s);
+
+ FOREACH_ARRAY(t, gpt_partition_type_table, ELEMENTSOF(gpt_partition_type_table) - 1)
+ if (streq(s, t->name)) {
+ /* Don't return immediately, instead re-resolve by UUID so that we can support
+ * aliases like aarch64 -> arm64 transparently. */
+ id = t->uuid;
+ break;
+ }
+
+ if (sd_id128_is_null(id)) {
+ r = sd_id128_from_string(s, &id);
+ if (r < 0)
+ return r;
+ }
+
+ if (ret)
+ *ret = gpt_partition_type_from_uuid(id);
+
+ return 0;
+}
+
+GptPartitionType gpt_partition_type_override_architecture(GptPartitionType type, Architecture arch) {
+ assert(arch >= 0);
+
+ FOREACH_ARRAY(t, gpt_partition_type_table, ELEMENTSOF(gpt_partition_type_table) - 1)
+ if (t->designator == type.designator && t->arch == arch)
+ return *t;
+
+ /* If we can't find an entry with the same designator and the requested architecture, just return the
+ * original partition type. */
+ return type;
+}
+
+Architecture gpt_partition_type_uuid_to_arch(sd_id128_t id) {
+ const GptPartitionType *pt;
+
+ pt = gpt_partition_type_find_by_uuid(id);
+ if (!pt)
+ return _ARCHITECTURE_INVALID;
+
+ return pt->arch;
+}
+
+int gpt_partition_label_valid(const char *s) {
+ _cleanup_free_ char16_t *recoded = NULL;
+
+ recoded = utf8_to_utf16(s, SIZE_MAX);
+ if (!recoded)
+ return -ENOMEM;
+
+ return char16_strlen(recoded) <= GPT_LABEL_MAX;
+}
+
+GptPartitionType gpt_partition_type_from_uuid(sd_id128_t id) {
+ const GptPartitionType *pt;
+
+ pt = gpt_partition_type_find_by_uuid(id);
+ if (pt)
+ return *pt;
+
+ return (GptPartitionType) {
+ .uuid = id,
+ .arch = _ARCHITECTURE_INVALID,
+ .designator = _PARTITION_DESIGNATOR_INVALID,
+ };
+}
+
+const char *gpt_partition_type_mountpoint_nulstr(GptPartitionType type) {
+ return partition_mountpoint_to_string(type.designator);
+}
+
+bool gpt_partition_type_knows_read_only(GptPartitionType type) {
+ return IN_SET(type.designator,
+ PARTITION_ROOT,
+ PARTITION_USR,
+ /* pretty much implied, but let's set the bit to make things really clear */
+ PARTITION_ROOT_VERITY,
+ PARTITION_USR_VERITY,
+ PARTITION_HOME,
+ PARTITION_SRV,
+ PARTITION_VAR,
+ PARTITION_TMP,
+ PARTITION_XBOOTLDR);
+}
+
+bool gpt_partition_type_knows_growfs(GptPartitionType type) {
+ return IN_SET(type.designator,
+ PARTITION_ROOT,
+ PARTITION_USR,
+ PARTITION_HOME,
+ PARTITION_SRV,
+ PARTITION_VAR,
+ PARTITION_TMP,
+ PARTITION_XBOOTLDR);
+}
+
+bool gpt_partition_type_knows_no_auto(GptPartitionType type) {
+ return IN_SET(type.designator,
+ PARTITION_ROOT,
+ PARTITION_ROOT_VERITY,
+ PARTITION_USR,
+ PARTITION_USR_VERITY,
+ PARTITION_HOME,
+ PARTITION_SRV,
+ PARTITION_VAR,
+ PARTITION_TMP,
+ PARTITION_XBOOTLDR,
+ PARTITION_SWAP);
+}
+
+bool gpt_header_has_signature(const GptHeader *p) {
+ assert(p);
+
+ if (memcmp(p->signature, (const char[8]) { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, 8) != 0)
+ return false;
+
+ if (le32toh(p->revision) != UINT32_C(0x00010000)) /* the only known revision of the spec: 1.0 */
+ return false;
+
+ if (le32toh(p->header_size) < sizeof(GptHeader))
+ return false;
+
+ if (le32toh(p->header_size) > 4096) /* larger than a sector? something is off… */
+ return false;
+
+ if (le64toh(p->my_lba) != 1) /* this sector must claim to be at sector offset 1 */
+ return false;
+
+ return true;
+}