]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Switch to new mkfs.btrfs subvolume API 34149/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 27 Aug 2024 13:54:12 +0000 (15:54 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Fri, 30 Aug 2024 10:18:10 +0000 (12:18 +0200)
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

man/repart.d.xml
src/partition/repart.c

index 4ad27649335c2b251bcc76f659e622c539829eee..2e3aa68f446f4597fc438e798ba309c9d83abc3f 100644 (file)
         <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>
index 07029cb14291f2d6047d6f1e82785c3b97f34914..27e6702390c310dba4aadbe9a026b60794694655 100644 (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();
+
+                        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();
-                }
 
-                if (p->default_subvolume) {
-                        r = strv_extend_many(&sv, "--default-subvol", p->default_subvolume);
+                        r = strv_extend_many(&sv, "--subvol", s);
                         if (r < 0)
                                 return log_oom();
                 }