1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-19 22:50:17 +03:00

repart: Switch to new mkfs.btrfs subvolume API

In https://github.com/kdave/btrfs-progs/pull/877 the API is changing
to "--subvol <path>:ro,default" so let's adapt our usage to match.

This also adds support for read-only subvolumes.

Fixes #34134
This commit is contained in:
Daan De Meyer 2024-08-27 15:54:12 +02:00
parent 9023266a0e
commit 562881c178
2 changed files with 258 additions and 23 deletions

View File

@ -541,9 +541,31 @@
<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>
that should be a subvolume within the new file system. Each path may optionally be followed by a
colon and a list of comma-separated subvolume flags. The following flags are understood:</para>
<table class='flags'>
<title>Subvolume Flags</title>
<tgroup cols='2' align='left' colsep='1' rowsep='1'>
<colspec colname="spec" />
<colspec colname="purpose" />
<thead>
<row>
<entry>Flag</entry>
<entry>Purpose</entry>
</row>
</thead>
<tbody>
<row id='R'>
<entry><literal>ro</literal></entry>
<entry>Make this subvolume read-only.</entry>
</row>
</tbody>
</tgroup>
</table>
<para>Note that this option 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>

View File

@ -48,6 +48,7 @@
#include "io-util.h"
#include "json-util.h"
#include "list.h"
#include "logarithm.h"
#include "loop-util.h"
#include "main-func.h"
#include "mkdir.h"
@ -259,6 +260,78 @@ static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryp
return mfree(c);
}
typedef enum SubvolumeFlags {
SUBVOLUME_RO = 1 << 0,
_SUBVOLUME_FLAGS_MASK = SUBVOLUME_RO,
_SUBVOLUME_FLAGS_INVALID = -EINVAL,
_SUBVOLUME_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */
} SubvolumeFlags;
static SubvolumeFlags subvolume_flags_from_string_one(const char *s) {
/* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */
assert(s);
if (streq(s, "ro"))
return SUBVOLUME_RO;
return _SUBVOLUME_FLAGS_INVALID;
}
static SubvolumeFlags subvolume_flags_from_string(const char *s) {
SubvolumeFlags flags = 0;
int r;
assert(s);
for (;;) {
_cleanup_free_ char *f = NULL;
SubvolumeFlags ff;
r = extract_first_word(&s, &f, ",", EXTRACT_DONT_COALESCE_SEPARATORS);
if (r < 0)
return r;
if (r == 0)
break;
ff = subvolume_flags_from_string_one(f);
if (ff < 0)
return -EBADRQC; /* recognizable error */
flags |= ff;
}
return flags;
}
static char* subvolume_flags_to_string(SubvolumeFlags flags) {
const char *l[CONST_LOG2U(_SUBVOLUME_FLAGS_MASK + 1) + 1]; /* one string per known flag at most */
size_t m = 0;
if (FLAGS_SET(flags, SUBVOLUME_RO))
l[m++] = "ro";
assert(m < ELEMENTSOF(l));
l[m] = NULL;
return strv_join((char**) l, ",");
}
typedef struct Subvolume {
char *path;
SubvolumeFlags flags;
} Subvolume;
static Subvolume* subvolume_free(Subvolume *s) {
if (!s)
return NULL;
free(s->path);
return mfree(s);
}
DEFINE_PRIVATE_HASH_OPS_WITH_VALUE_DESTRUCTOR(subvolume_hash_ops, char, path_hash_func, path_compare, Subvolume, subvolume_free);
typedef struct Partition {
char *definition_path;
char **drop_in_files;
@ -304,7 +377,7 @@ typedef struct Partition {
char **exclude_files_source;
char **exclude_files_target;
char **make_directories;
char **subvolumes;
OrderedHashmap *subvolumes;
char *default_subvolume;
EncryptMode encrypt;
VerityMode verity;
@ -469,7 +542,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);
ordered_hashmap_free(p->subvolumes);
free(p->default_subvolume);
free(p->verity_match_key);
@ -505,7 +578,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->subvolumes = ordered_hashmap_free(p->subvolumes);
p->default_subvolume = mfree(p->default_subvolume);
p->verity_match_key = mfree(p->verity_match_key);
@ -1679,6 +1752,86 @@ static int config_parse_make_dirs(
}
}
static int config_parse_subvolumes(
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) {
OrderedHashmap **subvolumes = ASSERT_PTR(data);
const char *p = ASSERT_PTR(rvalue);
int r;
for (;;) {
_cleanup_free_ char *word = NULL, *path = NULL, *f = NULL, *d = NULL;
Subvolume *s = NULL;
r = extract_first_word(&p, &word, NULL, EXTRACT_UNQUOTE);
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
return 0;
}
if (r == 0)
return 0;
r = extract_many_words((const char **) &word, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &path, &f);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", word);
continue;
}
r = specifier_printf(path, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &d);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r,
"Failed to expand specifiers in Subvolumes= parameter, ignoring: %s", path);
continue;
}
r = path_simplify_and_warn(d, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
if (r < 0)
continue;
s = ordered_hashmap_get(*subvolumes, d);
if (!s) {
s = new(Subvolume, 1);
if (!s)
return log_oom();
*s = (Subvolume) {
.path = TAKE_PTR(d),
};
r = ordered_hashmap_ensure_put(subvolumes, &subvolume_hash_ops, s->path, s);
if (r < 0) {
subvolume_free(s);
return r;
}
}
if (f) {
SubvolumeFlags flags = subvolume_flags_from_string(f);
if (flags == -EBADRQC) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Unknown subvolume flag in subvolume, ignoring: %s", f);
continue;
}
if (flags < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse subvolume flags, ignoring: %s", f);
continue;
}
s->flags = flags;
}
}
}
static int config_parse_default_subvolume(
const char *unit,
const char *filename,
@ -1961,7 +2114,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 },
{ "Partition", "Subvolumes", config_parse_subvolumes, 0, &p->subvolumes },
{ "Partition", "DefaultSubvolume", config_parse_default_subvolume, 0, &p->default_subvolume },
{ "Partition", "VerityDataBlockSizeBytes", config_parse_block_size, 0, &p->verity_data_block_size },
{ "Partition", "VerityHashBlockSizeBytes", config_parse_block_size, 0, &p->verity_hash_block_size },
@ -1969,9 +2122,9 @@ static int partition_read_definition(Partition *p, const char *path, const char
{ "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p },
{}
};
int r;
_cleanup_free_ char *filename = NULL;
const char* dropin_dirname;
int r;
r = path_extract_filename(path, &filename);
if (r < 0)
@ -2089,7 +2242,7 @@ 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 (p->default_subvolume && !path_strv_contains(p->subvolumes, p->default_subvolume))
if (p->default_subvolume && !ordered_hashmap_contains(p->subvolumes, p->default_subvolume))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"DefaultSubvolume= must be one of the paths in Subvolumes=.");
@ -4819,22 +4972,39 @@ static int add_subvolume_path(const char *path, Set **subvolumes) {
return 0;
}
static int make_subvolumes_strv(const Partition *p, char ***ret) {
_cleanup_strv_free_ char **subvolumes = NULL;
Subvolume *subvolume;
assert(p);
assert(ret);
ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes)
if (strv_extend(&subvolumes, subvolume->path) < 0)
return log_oom();
*ret = TAKE_PTR(subvolumes);
return 0;
}
static int make_subvolumes_set(
const Partition *p,
const char *source,
const char *target,
Set **ret) {
_cleanup_set_free_ Set *subvolumes = NULL;
Subvolume *subvolume;
int r;
assert(p);
assert(target);
assert(ret);
STRV_FOREACH(subvolume, p->subvolumes) {
ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) {
_cleanup_free_ char *path = NULL;
const char *s = path_startswith(*subvolume, target);
const char *s = path_startswith(subvolume->path, target);
if (!s)
continue;
@ -4876,11 +5046,16 @@ static usec_t epoch_or_infinity(void) {
}
static int do_copy_files(Context *context, Partition *p, const char *root) {
_cleanup_strv_free_ char **subvolumes = NULL;
int r;
assert(p);
assert(root);
r = make_subvolumes_strv(p, &subvolumes);
if (r < 0)
return r;
/* copy_tree_at() automatically copies the permissions of source directories to target directories if
* it created them. However, the root directory is created by us, so we have to manually take care
* that it is initialized. We use the first source directory targeting "/" as the metadata source for
@ -4949,7 +5124,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_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes);
r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, subvolumes);
if (r < 0)
return log_error_errno(r, "Failed to create parent directory '%s': %m", dn);
@ -4989,7 +5164,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_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, p->subvolumes);
r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, subvolumes);
if (r < 0)
return log_error_errno(r, "Failed to create parent directory: %m");
@ -5023,13 +5198,18 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
}
static int do_make_directories(Partition *p, const char *root) {
_cleanup_strv_free_ char **subvolumes = NULL;
int r;
assert(p);
assert(root);
r = make_subvolumes_strv(p, &subvolumes);
if (r < 0)
return r;
STRV_FOREACH(d, p->make_directories) {
r = mkdir_p_root_full(root, *d, UID_INVALID, GID_INVALID, 0755, epoch_or_infinity(), p->subvolumes);
r = mkdir_p_root_full(root, *d, UID_INVALID, GID_INVALID, 0755, epoch_or_infinity(), subvolumes);
if (r < 0)
return log_error_errno(r, "Failed to create directory '%s' in file system: %m", *d);
}
@ -5037,6 +5217,27 @@ static int do_make_directories(Partition *p, const char *root) {
return 0;
}
static int make_subvolumes_read_only(Partition *p, const char *root) {
_cleanup_free_ char *path = NULL;
Subvolume *subvolume;
int r;
ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) {
if (!FLAGS_SET(subvolume->flags, SUBVOLUME_RO))
continue;
path = path_join(root, subvolume->path);
if (!path)
return log_oom();
r = btrfs_subvol_set_read_only(path, true);
if (r < 0)
return log_error_errno(r, "Failed to make subvolume '%s' read-only: %m", subvolume->path);
}
return 0;
}
static int set_default_subvolume(Partition *p, const char *root) {
_cleanup_free_ char *path = NULL;
int r;
@ -5132,6 +5333,9 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c
if (do_make_directories(p, fs) < 0)
_exit(EXIT_FAILURE);
if (make_subvolumes_read_only(p, fs) < 0)
_exit(EXIT_FAILURE);
if (set_default_subvolume(p, fs) < 0)
_exit(EXIT_FAILURE);
@ -5149,8 +5353,8 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c
}
static int finalize_extra_mkfs_options(const Partition *p, const char *root, char ***ret) {
int r;
_cleanup_strv_free_ char **sv = NULL;
int r;
assert(p);
assert(ret);
@ -5162,17 +5366,26 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha
p->format);
if (partition_needs_populate(p) && root && streq(p->format, "btrfs")) {
STRV_FOREACH(subvol, p->subvolumes) {
if (streq_ptr(*subvol, p->default_subvolume))
continue;
Subvolume *subvolume;
r = strv_extend_many(&sv, "--subvol", *subvol);
if (r < 0)
ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) {
_cleanup_free_ char *s = NULL, *f = NULL;
s = strdup(subvolume->path);
if (!s)
return log_oom();
}
if (p->default_subvolume) {
r = strv_extend_many(&sv, "--default-subvol", p->default_subvolume);
f = subvolume_flags_to_string(subvolume->flags);
if (!f)
return log_oom();
if (streq_ptr(subvolume->path, p->default_subvolume) && !strextend_with_separator(&f, ",", "default"))
return log_oom();
if (!isempty(f) && !strextend_with_separator(&s, ":", f))
return log_oom();
r = strv_extend_many(&sv, "--subvol", s);
if (r < 0)
return log_oom();
}