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; 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 { diff --git a/src/shared/specifier.c b/src/shared/specifier.c index 0742fae39e..d54ab9f5a9 100644 --- a/src/shared/specifier.c +++ b/src/shared/specifier.c @@ -151,9 +151,38 @@ 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_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; - char *n; int r; assert(ret); @@ -172,17 +201,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 +214,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..df72bdc39b 100644 --- a/src/shared/specifier.h +++ b/src/shared/specifier.h @@ -16,6 +16,9 @@ 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_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);