From 440f805c173f979cb5f7e28becd167467a1c5a2a Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 14 Aug 2023 16:44:30 +0200 Subject: [PATCH] repart: Add Subvolumes= setting This setting indicates which directories in the target partition should be btrfs subvolumes. If set, we'll try to create these directories as subvolumes. Note that this only works when running as root without --offline, as mkfs.btrfs does not support creating subvolumes. --- man/repart.d.xml | 15 +++++++ src/partition/repart.c | 90 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 9 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index a79724a93ea..261c673249c 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -500,6 +500,21 @@ attributes. + + Subvolumes= + + Takes one or more absolute paths, separated by whitespace, each declaring a directory + that should be a subvolume within the new file system. This option may be used more than once to + specify multiple directories. Note that this setting does not create the directories themselves, that + can be configured with MakeDirectories= and CopyFiles=. + + Note that this option only takes effect if the target filesystem supports subvolumes, such as + btrfs. + + Note that due to limitations of mkfs.btrfs, this option is only supported + when running with . + + Encrypt= diff --git a/src/partition/repart.c b/src/partition/repart.c index 0a335b2cc13..decf4e85894 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -243,6 +243,7 @@ typedef struct Partition { char **exclude_files_source; char **exclude_files_target; char **make_directories; + char **subvolumes; EncryptMode encrypt; VerityMode verity; char *verity_match_key; @@ -389,6 +390,7 @@ static Partition* partition_free(Partition *p) { strv_free(p->exclude_files_source); strv_free(p->exclude_files_target); strv_free(p->make_directories); + strv_free(p->subvolumes); free(p->verity_match_key); free(p->roothash); @@ -417,6 +419,7 @@ static void partition_foreignize(Partition *p) { p->exclude_files_source = strv_free(p->exclude_files_source); p->exclude_files_target = strv_free(p->exclude_files_target); p->make_directories = strv_free(p->make_directories); + p->subvolumes = strv_free(p->subvolumes); p->verity_match_key = mfree(p->verity_match_key); p->priority = 0; @@ -1503,7 +1506,7 @@ static int config_parse_make_dirs( void *data, void *userdata) { - Partition *partition = ASSERT_PTR(data); + char ***sv = ASSERT_PTR(data); const char *p = ASSERT_PTR(rvalue); int r; @@ -1531,7 +1534,7 @@ static int config_parse_make_dirs( if (r < 0) continue; - r = strv_consume(&partition->make_directories, TAKE_PTR(d)); + r = strv_consume(sv, TAKE_PTR(d)); if (r < 0) return log_oom(); } @@ -1626,7 +1629,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "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 }, + { "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 }, @@ -1636,6 +1639,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "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 }, {} }; int r; @@ -1749,6 +1753,10 @@ static int partition_read_definition(Partition *p, const char *path, const char "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s", verity_mode_to_string(p->verity)); + if (!strv_isempty(p->subvolumes) && arg_offline != 0) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EOPNOTSUPP), + "Subvolumes= can only be used with --offline=no"); + /* Verity partitions are read only, let's imply the RO flag hence, unless explicitly configured otherwise. */ if ((IN_SET(p->type.designator, PARTITION_ROOT_VERITY, @@ -4304,6 +4312,66 @@ static int make_copy_files_denylist( return 0; } +static int add_subvolume_path(const char *path, Set **subvolumes) { + _cleanup_free_ struct stat *st = NULL; + int r; + + assert(path); + assert(subvolumes); + + st = new(struct stat, 1); + if (!st) + return log_oom(); + + r = chase_and_stat(path, arg_root, CHASE_PREFIX_ROOT, NULL, st); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to stat source file '%s/%s': %m", strempty(arg_root), path); + + r = set_ensure_put(subvolumes, &inode_hash_ops, st); + if (r < 0) + return log_oom(); + if (r > 0) + TAKE_PTR(st); + + return 0; +} + +static int make_subvolumes_set( + Context *context, + const Partition *p, + const char *source, + const char *target, + Set **ret) { + _cleanup_set_free_ Set *subvolumes = NULL; + int r; + + assert(context); + assert(p); + assert(target); + assert(ret); + + STRV_FOREACH(subvolume, p->subvolumes) { + _cleanup_free_ char *path = NULL; + + const char *s = path_startswith(*subvolume, target); + if (!s) + continue; + + path = path_join(source, s); + if (!path) + return log_oom(); + + r = add_subvolume_path(path, &subvolumes); + if (r < 0) + return r; + } + + *ret = TAKE_PTR(subvolumes); + return 0; +} + static int do_copy_files(Context *context, Partition *p, const char *root) { int r; @@ -4337,12 +4405,17 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { STRV_FOREACH_PAIR(source, target, p->copy_files) { _cleanup_hashmap_free_ Hashmap *denylist = NULL; + _cleanup_set_free_ Set *subvolumes_by_source_inode = NULL; _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF; r = make_copy_files_denylist(context, p, *source, *target, &denylist); if (r < 0) return r; + r = make_subvolumes_set(context, p, *source, *target, &subvolumes_by_source_inode); + if (r < 0) + return r; + sfd = chase_and_open(*source, arg_root, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL); if (sfd < 0) return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_root), *source); @@ -4368,7 +4441,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, NULL); + r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory '%s': %m", dn); @@ -4381,14 +4454,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { pfd, fn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, - denylist, NULL); + denylist, subvolumes_by_source_inode); } else r = copy_tree_at( sfd, ".", tfd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE, - denylist, NULL); + denylist, subvolumes_by_source_inode); if (r < 0) return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m", strempty(arg_root), *source, strempty(root), *target); @@ -4408,7 +4481,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); - r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, NULL); + r = mkdir_p_root(root, dn, UID_INVALID, GID_INVALID, 0755, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create parent directory: %m"); @@ -4440,8 +4513,7 @@ static int do_make_directories(Partition *p, const char *root) { assert(root); STRV_FOREACH(d, p->make_directories) { - - r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, NULL); + r = mkdir_p_root(root, *d, UID_INVALID, GID_INVALID, 0755, p->subvolumes); if (r < 0) return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d); }