diff --git a/man/repart.d.xml b/man/repart.d.xml
index 4ad27649335..2e3aa68f446 100644
--- a/man/repart.d.xml
+++ b/man/repart.d.xml
@@ -541,9 +541,31 @@
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=.
+ 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:
+
+
+ Subvolume Flags
+
+
+
+
+
+ Flag
+ Purpose
+
+
+
+
+ ro
+ Make this subvolume read-only.
+
+
+
+
+
+ Note that this option 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.
diff --git a/src/partition/repart.c b/src/partition/repart.c
index 07029cb1429..27e6702390c 100644
--- a/src/partition/repart.c
+++ b/src/partition/repart.c
@@ -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();
}