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