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.
This commit is contained in:
Daan De Meyer 2023-08-14 16:44:30 +02:00
parent c55a97f1fd
commit 440f805c17
2 changed files with 96 additions and 9 deletions

View file

@ -500,6 +500,21 @@
attributes.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>Subvolumes=</varname></term>
<listitem><para>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 <varname>MakeDirectories=</varname> and <varname>CopyFiles=</varname>.</para>
<para>Note that this option only takes effect if the target filesystem supports subvolumes, such as
<literal>btrfs</literal>.</para>
<para>Note that due to limitations of <literal>mkfs.btrfs</literal>, this option is only supported
when running with <option>--offline=no</option>.</para></listitem>
</varlistentry>
<varlistentry>
<term><varname>Encrypt=</varname></term>

View file

@ -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);
}