diff options
Diffstat (limited to 'src/shared/gpt.c')
-rw-r--r-- | src/shared/gpt.c | 361 |
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; +} |