From 1a0541d44c78ced78a566051ec8f63417370aeaa Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 25 Dec 2023 23:11:22 +0100 Subject: [PATCH] repart: Add --generate-fstab= and --generate-crypttab= options These can be used along with two new settings MountPoint= and EncryptedVolume= to write fstab and crypttab entries to the given paths respectively in the root directory that repart is operating on. This is useful to cover scenarios that aren't covered by the Discoverable Partitions Spec. For example when one wants to mount /home as a separate btrfs subvolume. Because multiple btrfs subvolumes can be mounted from the same partition, we allow specifying MountPoint= multiple times to add multiple entries for the same partition. --- man/repart.d.xml | 36 ++++ man/systemd-repart.xml | 22 +++ src/partition/repart.c | 433 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 462 insertions(+), 29 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index f297933ccec..50a2de7c33d 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -734,6 +734,42 @@ + + + MountPoint= + + Specifies where and how the partition should be mounted. Takes at least one and at + most two fields separated with a colon (:). The first field specifies where the + partition should be mounted. The second field specifies extra mount options to append to the default + mount options. These fields correspond to the second and fourth column of the + fstab5 + format. This setting may be specified multiple times to mount the partition multiple times. This can + be used to add mounts for different btrfs subvolumes located on the same btrfs partition. + + Note that this setting is only taken into account when is + specified on the systemd-repart command line. + + + + + + EncryptedVolume= + + Specify how the encrypted partition should be set up. Takes at least one and at most + three fields separated with a colon (:). The first field specifies the encrypted + volume name under /dev/mapper/. If not specified, luks-UUID + will be used where UUID is the LUKS UUID. The second field specifies the keyfile + to use following the same format as specified in crypttab. The third field specifies a + comma-delimited list of crypttab options. These fields correspond to the first, third and fourth + column of the + crypttab5 format. + + + Note that this setting is only taken into account when + is specified on the systemd-repart command line. + + + diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index da5d5858459..7dfcfc90b31 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -588,6 +588,28 @@ + + PATH + + Specifies a path where to write fstab entries for the mountpoints configured with + in the root directory specified with or + or in the host's root directory if neither is specified. Disabled by + default. + + + + + + PATH + + Specifies a path where to write crypttab entries for the encrypted volumes configured + with in the root directory specified with + or or in the host's root directory if + neither is specified. Disabled by default. + + + + diff --git a/src/partition/repart.c b/src/partition/repart.c index 6f347a6e1fa..63e9dcfbd16 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -168,6 +168,8 @@ static int arg_offline = -1; static char **arg_copy_from = NULL; static char *arg_copy_source = NULL; static char *arg_make_ddi = NULL; +static char *arg_generate_fstab = NULL; +static char *arg_generate_crypttab = NULL; STATIC_DESTRUCTOR_REGISTER(arg_node, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -186,6 +188,8 @@ STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep); STATIC_DESTRUCTOR_REGISTER(arg_copy_from, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_copy_source, freep); STATIC_DESTRUCTOR_REGISTER(arg_make_ddi, freep); +STATIC_DESTRUCTOR_REGISTER(arg_generate_fstab, freep); +STATIC_DESTRUCTOR_REGISTER(arg_generate_crypttab, freep); typedef struct FreeArea FreeArea; @@ -215,6 +219,39 @@ typedef enum MinimizeMode { _MINIMIZE_MODE_INVALID = -EINVAL, } MinimizeMode; +typedef struct PartitionMountPoint { + char *where; + char *options; +} PartitionMountPoint; + +static void partition_mountpoint_free_many(PartitionMountPoint *f, size_t n) { + assert(f || n == 0); + + FOREACH_ARRAY(i, f, n) { + free(i->where); + free(i->options); + } + + free(f); +} + +typedef struct PartitionEncryptedVolume { + char *name; + char *keyfile; + char *options; +} PartitionEncryptedVolume; + +static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryptedVolume *c) { + if (!c) + return NULL; + + free(c->name); + free(c->keyfile); + free(c->options); + + return mfree(c); +} + typedef struct Partition { char *definition_path; char **drop_in_files; @@ -277,6 +314,11 @@ typedef struct Partition { char *split_name_format; char *split_path; + PartitionMountPoint *mountpoints; + size_t n_mountpoints; + + PartitionEncryptedVolume *encrypted_volume; + struct Partition *siblings[_VERITY_MODE_MAX]; LIST_FIELDS(struct Partition, partitions); @@ -426,6 +468,12 @@ static Partition* partition_free(Partition *p) { free(p->split_name_format); unlink_and_free(p->split_path); + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + p->mountpoints = NULL; + p->n_mountpoints = 0; + + partition_encrypted_volume_free(p->encrypted_volume); + return mfree(p); } @@ -461,6 +509,12 @@ static void partition_foreignize(Partition *p) { p->read_only = -1; p->growfs = -1; p->verity = VERITY_OFF; + + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + p->mountpoints = NULL; + p->n_mountpoints = 0; + + p->encrypted_volume = partition_encrypted_volume_free(p->encrypted_volume); } static bool partition_type_exclude(const GptPartitionType *type) { @@ -1678,41 +1732,163 @@ static int config_parse_uuid( return 0; } +static int config_parse_mountpoint( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *where = NULL, *options = NULL; + Partition *p = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); + return 0; + } + + const char *q = rvalue; + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE, + &where, &options, NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (r < 1) { + log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL), + "Too few arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (!isempty(q)) { + log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL), + "Too many arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + r = path_simplify_and_warn(where, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue); + if (r < 0) + return 0; + + if (!GREEDY_REALLOC(p->mountpoints, p->n_mountpoints + 1)) + return log_oom(); + + p->mountpoints[p->n_mountpoints++] = (PartitionMountPoint) { + .where = TAKE_PTR(where), + .options = TAKE_PTR(options), + }; + + return 0; +} + +static int config_parse_encrypted_volume( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + _cleanup_free_ char *volume = NULL, *keyfile = NULL, *options = NULL; + Partition *p = ASSERT_PTR(data); + int r; + + if (isempty(rvalue)) { + p->encrypted_volume = mfree(p->encrypted_volume); + return 0; + } + + const char *q = rvalue; + r = extract_many_words(&q, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS|EXTRACT_UNQUOTE, + &volume, &keyfile, &options, NULL); + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Invalid syntax in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (r < 1) { + log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL), + "Too few arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + if (!isempty(q)) { + log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL), + "Too many arguments in %s=, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (!filename_is_valid(volume)) { + log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL), + "Volume name %s is not valid, ignoring", volume); + return 0; + } + + partition_encrypted_volume_free(p->encrypted_volume); + + p->encrypted_volume = new(PartitionEncryptedVolume, 1); + if (!p->encrypted_volume) + return log_oom(); + + *p->encrypted_volume = (PartitionEncryptedVolume) { + .name = TAKE_PTR(volume), + .keyfile = TAKE_PTR(keyfile), + .options = TAKE_PTR(options), + }; + + return 0; +} + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF, "Invalid verity mode"); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF, "Invalid minimize mode"); 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 }, - { "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->copy_files }, - { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, - { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, - { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, - { "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 }, - { "Partition", "Minimize", config_parse_minimize, 0, &p->minimize }, - { "Partition", "Subvolumes", config_parse_make_dirs, 0, &p->subvolumes }, - { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size, 0, &p->verity_data_block_size }, - { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, + { "Partition", "Type", config_parse_type, 0, &p->type }, + { "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->copy_files }, + { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, + { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, + { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, + { "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 }, + { "Partition", "Minimize", config_parse_minimize, 0, &p->minimize }, + { "Partition", "Subvolumes", config_parse_make_dirs, 0, &p->subvolumes }, + { "Partition", "VerityDataBlockSizeBytes", config_parse_block_size, 0, &p->verity_data_block_size }, + { "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size }, + { "Partition", "MountPoint", config_parse_mountpoint, 0, p }, + { "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p }, {} }; int r; @@ -6130,6 +6306,177 @@ static int fd_apparent_size(int fd, uint64_t *ret) { return 0; } +static bool need_fstab_one(const Partition *p) { + assert(p); + + if (p->dropped) + return false; + + if (!p->format) + return false; + + if (p->n_mountpoints == 0) + return false; + + return true; +} + +static bool need_fstab(const Context *context) { + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) + if (need_fstab_one(p)) + return true; + + return false; +} + +static int context_fstab(Context *context) { + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(context); + + if (!arg_generate_fstab) + return false; + + if (!need_fstab(context)) { + log_notice("MountPoint= is not specified for any elligible partitions, not generating %s", + arg_generate_fstab); + return 0; + } + + path = path_join(arg_copy_source, arg_generate_fstab); + if (!path) + return log_oom(); + + r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open temporary file for %s: %m", path); + + fprintf(f, "# Automatically generated by systemd-repart\n\n"); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_free_ char *what = NULL, *options = NULL; + + if (!need_fstab_one(p)) + continue; + + what = strjoin("UUID=", SD_ID128_TO_UUID_STRING(p->fs_uuid)); + if (!what) + return log_oom(); + + FOREACH_ARRAY(mountpoint, p->mountpoints, p->n_mountpoints) { + r = partition_pick_mount_options( + p->type.designator, + p->format, + /* rw= */ true, + /* discard= */ !IN_SET(p->type.designator, PARTITION_ESP, PARTITION_XBOOTLDR), + &options, + NULL); + if (r < 0) + return r; + + if (!strextend_with_separator(&options, ",", mountpoint->options)) + return log_oom(); + + fprintf(f, "%s %s %s %s 0 %i\n", + what, + mountpoint->where, + p->format, + options, + p->type.designator == PARTITION_ROOT ? 1 : 2); + } + } + + r = flink_tmpfile(f, t, path, 0); + if (r < 0) + return log_error_errno(r, "Failed to link temporary file to %s: %m", path); + + log_info("%s written.", path); + + return 0; +} + +static bool need_crypttab_one(const Partition *p) { + assert(p); + + if (p->dropped) + return false; + + if (p->encrypt == ENCRYPT_OFF) + return false; + + if (!p->encrypted_volume) + return false; + + return true; +} + +static bool need_crypttab(Context *context) { + assert(context); + + LIST_FOREACH(partitions, p, context->partitions) + if (need_crypttab_one(p)) + return true; + + return false; +} + +static int context_crypttab(Context *context) { + _cleanup_(unlink_and_freep) char *t = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(context); + + if (!arg_generate_crypttab) + return false; + + if (!need_crypttab(context)) { + log_notice("EncryptedVolume= is not specified for any elligible partitions, not generating %s", + arg_generate_crypttab); + return 0; + } + + path = path_join(arg_copy_source, arg_generate_crypttab); + if (!path) + return log_oom(); + + r = fopen_tmpfile_linkable(path, O_WRONLY|O_CLOEXEC, &t, &f); + if (r < 0) + return log_error_errno(r, "Failed to open temporary file for %s: %m", path); + + fprintf(f, "# Automatically generated by systemd-repart\n\n"); + + LIST_FOREACH(partitions, p, context->partitions) { + _cleanup_free_ char *volume = NULL; + + if (!need_crypttab_one(p)) + continue; + + if (!p->encrypted_volume->name && asprintf(&volume, "luks-%s", SD_ID128_TO_UUID_STRING(p->luks_uuid)) < 0) + return log_oom(); + + fprintf(f, "%s UUID=%s %s %s\n", + p->encrypted_volume->name ?: volume, + SD_ID128_TO_UUID_STRING(p->luks_uuid), + isempty(p->encrypted_volume->keyfile) ? "-" : p->encrypted_volume->keyfile, + strempty(p->encrypted_volume->options)); + } + + r = flink_tmpfile(f, t, path, 0); + if (r < 0) + return log_error_errno(r, "Failed to link temporary file to %s: %m", path); + + log_info("%s written.", path); + + return 0; +} + static int context_minimize(Context *context) { const char *vt = NULL; int r; @@ -6496,6 +6843,10 @@ static int help(void) { " -S --make-ddi=sysext Make a system extension DDI\n" " -C --make-ddi=confext Make a configuration extension DDI\n" " -P --make-ddi=portable Make a portable service DDI\n" + " --generate-fstab=PATH\n" + " Write fstab configuration to the given path\n" + " --generate-crypttab=PATH\n" + " Write crypttab configuration to the given path\n" "\nSee the %s for details.\n", program_invocation_short_name, ansi_highlight(), @@ -6546,6 +6897,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_OFFLINE, ARG_COPY_FROM, ARG_MAKE_DDI, + ARG_GENERATE_FSTAB, + ARG_GENERATE_CRYPTTAB, }; static const struct option options[] = { @@ -6587,6 +6940,8 @@ static int parse_argv(int argc, char *argv[]) { { "copy-from", required_argument, NULL, ARG_COPY_FROM }, { "copy-source", required_argument, NULL, 's' }, { "make-ddi", required_argument, NULL, ARG_MAKE_DDI }, + { "generate-fstab", required_argument, NULL, ARG_GENERATE_FSTAB }, + { "generate-crypttab", required_argument, NULL, ARG_GENERATE_CRYPTTAB }, {} }; @@ -6964,6 +7319,18 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_GENERATE_FSTAB: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_fstab); + if (r < 0) + return r; + break; + + case ARG_GENERATE_CRYPTTAB: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_generate_crypttab); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -7713,6 +8080,14 @@ static int run(int argc, char *argv[]) { if (r < 0) return r; + r = context_fstab(context); + if (r < 0) + return r; + + r = context_crypttab(context); + if (r < 0) + return r; + r = context_minimize(context); if (r < 0) return r; -- 2.39.5