diff --git a/man/repart.d.xml b/man/repart.d.xml index a79724a93e..261c673249 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 0a335b2cc1..decf4e8589 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); }