From 74235f6d085bd9a7bc5068287fb18dfa6dd39d49 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 19 Sep 2022 15:35:17 +0200 Subject: [PATCH 1/4] shared: Add specifier_id128() and specifier_uuid() --- src/shared/specifier.c | 40 ++++++++++++++++++++++++++-------------- src/shared/specifier.h | 2 ++ 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/shared/specifier.c b/src/shared/specifier.c index 0742fae39e..a49b16b736 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -151,9 +151,32 @@ int specifier_real_directory(char specifier, const void *data, const char *root, return path_extract_directory(path, ret); } +int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const sd_id128_t *id = ASSERT_PTR(data); + char *n; + + n = new(char, SD_ID128_STRING_MAX); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(*id, n); + return 0; +} + +int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const sd_id128_t *id = ASSERT_PTR(data); + char *n; + + n = new(char, SD_ID128_UUID_STRING_MAX); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_uuid_string(*id, n); + return 0; +} + int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { sd_id128_t id; - char *n; int r; assert(ret); @@ -172,17 +195,11 @@ int specifier_machine_id(char specifier, const void *data, const char *root, con if (r < 0) return r; - n = new(char, SD_ID128_STRING_MAX); - if (!n) - return -ENOMEM; - - *ret = sd_id128_to_string(id, n); - return 0; + return specifier_id128(specifier, &id, root, userdata, ret); } int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { sd_id128_t id; - char *n; int r; assert(ret); @@ -191,12 +208,7 @@ int specifier_boot_id(char specifier, const void *data, const char *root, const if (r < 0) return r; - n = new(char, SD_ID128_STRING_MAX); - if (!n) - return -ENOMEM; - - *ret = sd_id128_to_string(id, n); - return 0; + return specifier_id128(specifier, &id, root, userdata, ret); } int specifier_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) { diff --git a/src/shared/specifier.h b/src/shared/specifier.h index abde3d9ad2..a245e546e0 100644 --- a/src/shared/specifier.h +++ b/src/shared/specifier.h @@ -16,6 +16,8 @@ int specifier_printf(const char *text, size_t max_length, const Specifier table[ int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret); +int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret); +int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret); From 7b52dc7f4dd16f0dded663821521f29698f78e8d Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 22 Sep 2022 14:39:44 +0200 Subject: [PATCH 2/4] shared: Add specifier_uint64() --- src/shared/specifier.c | 6 ++++++ src/shared/specifier.h | 1 + 2 files changed, 7 insertions(+) diff --git a/src/shared/specifier.c b/src/shared/specifier.c index a49b16b736..d54ab9f5a9 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -175,6 +175,12 @@ int specifier_uuid(char specifier, const void *data, const char *root, const voi return 0; } +int specifier_uint64(char specifier, const void *data, const char *root, const void *userdata, char **ret) { + const uint64_t *n = ASSERT_PTR(data); + + return asprintf(ret, "%" PRIu64, *n) < 0 ? -ENOMEM : 0; +} + int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) { sd_id128_t id; int r; diff --git a/src/shared/specifier.h b/src/shared/specifier.h index a245e546e0..df72bdc39b 100644 --- a/src/shared/specifier.h +++ b/src/shared/specifier.h @@ -18,6 +18,7 @@ int specifier_real_path(char specifier, const void *data, const char *root, cons int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret); +int specifier_uint64(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret); int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret); From 1e58a0a82ca309112016dfa8793f3c37a31d93e9 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 22 Sep 2022 14:54:33 +0200 Subject: [PATCH 3/4] shared: Add GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER() --- src/shared/gpt.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/shared/gpt.h b/src/shared/gpt.h index f6ed2d3eb5..f673194d4a 100644 --- a/src/shared/gpt.h +++ b/src/shared/gpt.h @@ -16,6 +16,9 @@ const char *gpt_partition_type_uuid_to_string_harder( char buffer[static SD_ID128_UUID_STRING_MAX]); int gpt_partition_type_uuid_from_string(const char *s, sd_id128_t *ret); +#define GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(id) \ + gpt_partition_type_uuid_to_string_harder((id), (char[SD_ID128_UUID_STRING_MAX]) {}) + Architecture gpt_partition_type_uuid_to_arch(sd_id128_t id); typedef struct GptPartitionType { From 4cee83331c3071925b1b8d70dea8d365275a2c8e Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 19 Sep 2022 16:58:20 +0200 Subject: [PATCH 4/4] repart: Add --split option to generate split artifacts For use with sysupdate or other systemd tooling, it's useful to be able to generate split artifacts from disk images, where each partition is written to a separate file. Let's support this with a --split switch for repart and a SplitName= configuration option. --split enables split artifacts generation, and SplitName= configures for which partition to generate split artifacts, and which suffix to add to the split artifact name. For SplitName=, we add support for some extra specifiers, more specifically the partition Type UUID and the partition UUID. --- man/repart.d.xml | 53 +++++++++- man/systemd-repart.xml | 15 +++ src/partition/repart.c | 224 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 268 insertions(+), 24 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index df5338c92a..280ce6b8ea 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -669,6 +669,15 @@ all partition types that support it, except if the partition is marked read-only (and thus effectively, defaults to off for Verity partitions). + + + SplitName= + + Configures the suffix to append to split artifacts when the + option of systemd-repart is used. Simple specifier expansion is supported, see + below. Defaults to %t. To disable split artifact generation for a partition, set + SplitName= to -. + @@ -676,8 +685,8 @@ Specifiers Specifiers may be used in the Label=, CopyBlocks=, - CopyFiles=, MakeDirectories= settings. The following expansions are - understood: + CopyFiles=, MakeDirectories=, SplitName= + settings. The following expansions are understood: Specifiers available @@ -710,6 +719,46 @@
+ + Additionally, for the SplitName= setting, the following specifiers are also + understood: + + Specifiers available + + + + + + + Specifier + Meaning + Details + + + + + %T + Partition Type UUID + The partition type UUID, as configured with Type= + + + %t + Partition Type Identifier + The partition type identifier corresponding to the partition type UUID + + + %U + Partition UUID + The partition UUID, as configured with UUID= + + + %n + Partition Number + The partition number assigned to the partition + + + +
diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index 236058b74c..6a7aa21c66 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -332,6 +332,21 @@ for details on these two options. + + BOOL + + Enables generation of split artifacts from partitions configured with + SplitName=. If enabled, for each partition with SplitName= set, + a separate output file containing just the contents of that partition is generated. The output + filename consists of the loopback filename suffixed with the name configured with + SplitName=. If the loopback filename ends with .raw, the suffix + is inserted before the .raw extension instead. + + Note that is independent from . Even if + is enabled, split artifacts will still be generated from an existing image + if is enabled. + + diff --git a/src/partition/repart.c b/src/partition/repart.c index 413d136fe9..5b9d142e40 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -117,6 +117,7 @@ static char *arg_tpm2_device = NULL; static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; static char *arg_tpm2_public_key = NULL; static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX; +static bool arg_split = false; STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); @@ -195,6 +196,9 @@ struct Partition { uint8_t *roothash; size_t roothash_size; + char *split_name_format; + char *split_name_resolved; + Partition *siblings[_VERITY_MODE_MAX]; LIST_FIELDS(Partition, partitions); @@ -315,6 +319,9 @@ static Partition* partition_free(Partition *p) { free(p->roothash); + free(p->split_name_format); + free(p->split_name_resolved); + return mfree(p); } @@ -1449,28 +1456,29 @@ static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, V static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) { ConfigTableItem table[] = { - { "Partition", "Type", config_parse_type, 0, &p->type_uuid }, - { "Partition", "Label", config_parse_label, 0, &p->new_label }, - { "Partition", "UUID", config_parse_uuid, 0, p }, - { "Partition", "Priority", config_parse_int32, 0, &p->priority }, - { "Partition", "Weight", config_parse_weight, 0, &p->weight }, - { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight }, - { "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min }, - { "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max }, - { "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min }, - { "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max }, - { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, - { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, - { "Partition", "Format", config_parse_fstype, 0, &p->format }, - { "Partition", "CopyFiles", config_parse_copy_files, 0, p }, - { "Partition", "MakeDirectories", config_parse_make_dirs, 0, p }, - { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt }, - { "Partition", "Verity", config_parse_verity, 0, &p->verity }, - { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key }, - { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags }, - { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only }, - { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto }, - { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs }, + { "Partition", "Type", config_parse_type, 0, &p->type_uuid }, + { "Partition", "Label", config_parse_label, 0, &p->new_label }, + { "Partition", "UUID", config_parse_uuid, 0, p }, + { "Partition", "Priority", config_parse_int32, 0, &p->priority }, + { "Partition", "Weight", config_parse_weight, 0, &p->weight }, + { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight }, + { "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min }, + { "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max }, + { "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min }, + { "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max }, + { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, + { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, + { "Partition", "Format", config_parse_fstype, 0, &p->format }, + { "Partition", "CopyFiles", config_parse_copy_files, 0, p }, + { "Partition", "MakeDirectories", config_parse_make_dirs, 0, p }, + { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt }, + { "Partition", "Verity", config_parse_verity, 0, &p->verity }, + { "Partition", "VerityMatchKey", config_parse_string, 0, &p->verity_match_key }, + { "Partition", "Flags", config_parse_gpt_flags, 0, &p->gpt_flags }, + { "Partition", "ReadOnly", config_parse_tristate, 0, &p->read_only }, + { "Partition", "NoAuto", config_parse_tristate, 0, &p->no_auto }, + { "Partition", "GrowFileSystem", config_parse_tristate, 0, &p->growfs }, + { "Partition", "SplitName", config_parse_string, 0, &p->split_name_format }, {} }; int r; @@ -1560,6 +1568,15 @@ static int partition_read_definition(Partition *p, const char *path, const char p->read_only <= 0) p->growfs = true; + if (!p->split_name_format) { + char *s = strdup("%t"); + if (!s) + return log_oom(); + + p->split_name_format = s; + } else if (streq(p->split_name_format, "-")) + p->split_name_format = mfree(p->split_name_format); + return 0; } @@ -3984,6 +4001,150 @@ static int context_mangle_partitions(Context *context) { return 0; } +static int split_name_printf(Partition *p) { + assert(p); + + const Specifier table[] = { + { 't', specifier_string, GPT_PARTITION_TYPE_UUID_TO_STRING_HARDER(p->type_uuid) }, + { 'T', specifier_id128, &p->type_uuid }, + { 'U', specifier_id128, &p->new_uuid }, + { 'n', specifier_uint64, &p->partno }, + + COMMON_SYSTEM_SPECIFIERS, + {} + }; + + return specifier_printf(p->split_name_format, NAME_MAX, table, arg_root, p, &p->split_name_resolved); +} + +static int split_name_resolve(Context *context) { + int r; + + LIST_FOREACH(partitions, p, context->partitions) { + if (p->dropped) + continue; + + if (!p->split_name_format) + continue; + + r = split_name_printf(p); + if (r < 0) + return log_error_errno(r, "Failed to resolve specifiers in %s: %m", p->split_name_format); + } + + LIST_FOREACH(partitions, p, context->partitions) { + if (!p->split_name_resolved) + continue; + + LIST_FOREACH(partitions, q, context->partitions) { + if (p == q) + continue; + + if (!q->split_name_resolved) + continue; + + if (!streq(p->split_name_resolved, q->split_name_resolved)) + continue; + + return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "%s and %s have the same resolved split name \"%s\", refusing", + p->definition_path, q->definition_path, p->split_name_resolved); + } + } + + return 0; +} + +static int split_node(const char *node, char **ret_base, char **ret_ext) { + _cleanup_free_ char *base = NULL, *ext = NULL; + char *e; + int r; + + assert(node); + assert(ret_base); + assert(ret_ext); + + r = path_extract_filename(node, &base); + if (r == O_DIRECTORY || r == -EADDRNOTAVAIL) + return log_error_errno(r, "Device node %s cannot be a directory", arg_node); + if (r < 0) + return log_error_errno(r, "Failed to extract filename from %s: %m", arg_node); + + e = endswith(base, ".raw"); + if (e) { + ext = strdup(e); + if (!ext) + return log_oom(); + + *e = 0; + } + + *ret_base = TAKE_PTR(base); + *ret_ext = TAKE_PTR(ext); + + return 0; +} + +static int context_split(Context *context) { + _cleanup_free_ char *base = NULL, *ext = NULL; + _cleanup_close_ int dir_fd = -1; + int fd = -1, r; + + if (!arg_split) + return 0; + + assert(context); + assert(arg_node); + + /* We can't do resolution earlier because the partition UUIDs for verity partitions are only filled + * in after they've been generated. */ + + r = split_name_resolve(context); + if (r < 0) + return r; + + r = split_node(arg_node, &base, &ext); + if (r < 0) + return r; + + dir_fd = r = open_parent(arg_node, O_PATH|O_CLOEXEC, 0); + if (r == -EDESTADDRREQ) + dir_fd = AT_FDCWD; + else if (r < 0) + return log_error_errno(r, "Failed to open parent directory of %s: %m", arg_node); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_free_ char *fname = NULL; + _cleanup_close_ int fdt = -1; + + if (p->dropped) + continue; + + if (!p->split_name_resolved) + continue; + + fname = strjoin(base, ".", p->split_name_resolved, ext); + if (!fname) + return log_oom(); + + fdt = openat(dir_fd, fname, O_WRONLY|O_NOCTTY|O_CLOEXEC|O_NOFOLLOW|O_CREAT|O_EXCL, 0666); + if (fdt < 0) + return log_error_errno(errno, "Failed to open %s: %m", fname); + + if (fd < 0) + assert_se((fd = fdisk_get_devfd(context->fdisk_context)) >= 0); + + if (lseek(fd, p->offset, SEEK_SET) < 0) + return log_error_errno(errno, "Failed to seek to partition offset: %m"); + + r = copy_bytes_full(fd, fdt, p->new_size, COPY_REFLINK|COPY_HOLES, NULL, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to copy to split partition %s: %m", fname); + } + + return 0; +} + static int context_write_partition_table( Context *context, const char *node, @@ -4597,6 +4758,7 @@ static int help(void) { " --size=BYTES Grow loopback file to specified size\n" " --json=pretty|short|off\n" " Generate JSON output\n" + " --split=BOOL Whether to generate split artifacts\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -4629,6 +4791,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_TPM2_PCRS, ARG_TPM2_PUBLIC_KEY, ARG_TPM2_PUBLIC_KEY_PCRS, + ARG_SPLIT, }; static const struct option options[] = { @@ -4653,6 +4816,7 @@ static int parse_argv(int argc, char *argv[]) { { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, + { "split", required_argument, NULL, ARG_SPLIT }, {} }; @@ -4859,6 +5023,14 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_SPLIT: + r = parse_boolean_argument("--split=", optarg, NULL); + if (r < 0) + return r; + + arg_split = r; + break; + case '?': return -EINVAL; @@ -4910,6 +5082,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used."); + if (arg_split && !arg_node) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "A path to a loopback file must be specified when --split is used."); + if (arg_tpm2_pcr_mask == UINT32_MAX) arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT; if (arg_tpm2_public_key_pcr_mask == UINT32_MAX) @@ -5524,6 +5700,10 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_split(context); + if (r < 0) + return r; + (void) context_dump(context, node, /*late=*/ true); return 0;