]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Add MakeSymlinks= 34377/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 11 Sep 2024 15:11:45 +0000 (17:11 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 11 Sep 2024 16:45:05 +0000 (18:45 +0200)
Similar to MakeDirectories=, but creates symlinks in the filesystem.

man/repart.d.xml
src/partition/repart.c
test/units/TEST-58-REPART.sh

index a3c42e15e0143b9c63af3e67fe47e7849db846b5..3ffb6aba026e6c93ebeb414f27f2e6a418b84cc9 100644 (file)
         <xi:include href="version-info.xml" xpointer="v249"/></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>MakeSymlinks=</varname></term>
+
+        <listitem><para>Takes one or more arguments, separated by whitespace, each declaring a symlink to
+        create within the new file system. Each argument is a pair of symlink source and target paths,
+        separated by a colon. This option may be used more than once to create multiple symlinks. When
+        <varname>CopyFiles=</varname> and <varname>MakeSymlinks=</varname> are used together the former is
+        applied first.</para>
+
+        <para>The primary use case for this option is to create symlinks that need to exist before
+        <citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        is executed. For example, when using
+        <citerefentry><refentrytitle>systemd-confext</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+        this setting can be used to create symlinks in <filename>/var/lib/extensions.mutable</filename> to
+        redirect writes to mutable confexts to a custom location.</para>
+
+        <para>Consider using
+        <citerefentry><refentrytitle>systemd-tmpfiles</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        with its <option>--image=</option> option to pre-create other symlinks (as well as other inodes) with
+        fine-grained control of ownership, access modes and other file attributes.</para>
+
+        <xi:include href="version-info.xml" xpointer="v257"/></listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>Subvolumes=</varname></term>
 
index 566b67da6f4e231982552151541b4b8e546dc549..e24656f451bf7920849dc56bbdb1138a07b7bb16 100644 (file)
@@ -377,6 +377,7 @@ typedef struct Partition {
         char **exclude_files_source;
         char **exclude_files_target;
         char **make_directories;
+        char **make_symlinks;
         OrderedHashmap *subvolumes;
         char *default_subvolume;
         EncryptMode encrypt;
@@ -544,6 +545,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->make_symlinks);
         ordered_hashmap_free(p->subvolumes);
         free(p->default_subvolume);
         free(p->verity_match_key);
@@ -582,6 +584,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->make_symlinks = strv_free(p->make_symlinks);
         p->subvolumes = ordered_hashmap_free(p->subvolumes);
         p->default_subvolume = mfree(p->default_subvolume);
         p->verity_match_key = mfree(p->verity_match_key);
@@ -1758,6 +1761,64 @@ static int config_parse_make_dirs(
         }
 }
 
+static int config_parse_make_symlinks(
+                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) {
+
+        char ***sv = ASSERT_PTR(data);
+        const char *p = ASSERT_PTR(rvalue);
+        int r;
+
+        for (;;) {
+                _cleanup_free_ char *word = NULL, *path = NULL, *target = NULL, *d = 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;
+
+                const char *q = word;
+                r = extract_many_words(&q, ":", EXTRACT_UNQUOTE|EXTRACT_DONT_COALESCE_SEPARATORS, &path, &target);
+                if (r < 0) {
+                        log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", q);
+                        continue;
+                }
+                if (r != 2) {
+                        log_syntax(unit, LOG_WARNING, filename, line, SYNTHETIC_ERRNO(EINVAL),
+                                   "Missing source or target in %s, ignoring", rvalue);
+                        continue;
+                }
+
+                r = specifier_printf(path, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, /*userdata=*/ 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;
+
+                r = strv_consume_pair(sv, TAKE_PTR(d), TAKE_PTR(target));
+                if (r < 0)
+                        return log_error_errno(r, "Failed to add symlink to list: %m");
+        }
+}
+
 static int config_parse_subvolumes(
                 const char *unit,
                 const char *filename,
@@ -2094,7 +2155,7 @@ static int partition_finalize_fstype(Partition *p, const char *path) {
 
 static bool partition_needs_populate(const Partition *p) {
         assert(p);
-        return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories);
+        return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || !strv_isempty(p->make_symlinks);
 }
 
 static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
@@ -2117,6 +2178,7 @@ static int partition_read_definition(Partition *p, const char *path, const char
                 { "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->make_directories        },
+                { "Partition", "MakeSymlinks",             config_parse_make_symlinks,     0,                                  &p->make_symlinks           },
                 { "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        },
@@ -2177,11 +2239,11 @@ static int partition_read_definition(Partition *p, const char *path, const char
 
         if ((p->copy_blocks_path || p->copy_blocks_auto) && (p->format || partition_needs_populate(p)))
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
-                                  "Format=/CopyFiles=/MakeDirectories= and CopyBlocks= cannot be combined, refusing.");
+                                  "Format=/CopyFiles=/MakeDirectories=/MakeSymlinks= and CopyBlocks= cannot be combined, refusing.");
 
         if (partition_needs_populate(p) && streq_ptr(p->format, "swap"))
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
-                                  "Format=swap and CopyFiles=/MakeDirectories= cannot be combined, refusing.");
+                                  "Format=swap and CopyFiles=/MakeDirectories=/MakeSymlinks= cannot be combined, refusing.");
 
         if (!p->format) {
                 const char *format = NULL;
@@ -2209,7 +2271,7 @@ static int partition_read_definition(Partition *p, const char *path, const char
 
         if (partition_needs_populate(p) && !mkfs_supports_root_option(p->format) && geteuid() != 0)
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EPERM),
-                                  "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=.",
+                                  "Need to be root to populate %s filesystems with CopyFiles=/MakeDirectories=/MakeSymlinks=.",
                                   p->format);
 
         if (p->format && fstype_is_ro(p->format) && !partition_needs_populate(p))
@@ -2234,7 +2296,7 @@ static int partition_read_definition(Partition *p, const char *path, const char
 
         if (IN_SET(p->verity, VERITY_HASH, VERITY_SIG) && (p->copy_blocks_path || p->copy_blocks_auto || p->format || partition_needs_populate(p)))
                 return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
-                                  "CopyBlocks=/CopyFiles=/Format=/MakeDirectories= cannot be used with Verity=%s.",
+                                  "CopyBlocks=/CopyFiles=/Format=/MakeDirectories=/MakeSymlinks= cannot be used with Verity=%s.",
                                   verity_mode_to_string(p->verity));
 
         if (p->verity != VERITY_OFF && p->encrypt != ENCRYPT_OFF)
@@ -5276,6 +5338,27 @@ static int do_make_directories(Partition *p, const char *root) {
         return 0;
 }
 
+static int do_make_symlinks(Partition *p, const char *root) {
+        int r;
+
+        assert(p);
+        assert(root);
+
+        STRV_FOREACH_PAIR(path, target, p->make_symlinks) {
+                _cleanup_close_ int parent_fd = -EBADF;
+                _cleanup_free_ char *f = NULL;
+
+                r = chase(*path, root, CHASE_PREFIX_ROOT|CHASE_PARENT|CHASE_EXTRACT_FILENAME, &f, &parent_fd);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to resolve %s in %s", *path, root);
+
+                if (symlinkat(*target, parent_fd, f) < 0)
+                        return log_error_errno(errno, "Failed to create symlink at %s to %s: %m", *path, *target);
+        }
+
+        return 0;
+}
+
 static int make_subvolumes_read_only(Partition *p, const char *root) {
         _cleanup_free_ char *path = NULL;
         Subvolume *subvolume;
@@ -5347,6 +5430,10 @@ static int partition_populate_directory(Context *context, Partition *p, char **r
         if (r < 0)
                 return r;
 
+        r = do_make_symlinks(p, root);
+        if (r < 0)
+                return r;
+
         log_info("Successfully populated %s filesystem.", p->format);
 
         *ret = TAKE_PTR(root);
@@ -5387,6 +5474,9 @@ static int partition_populate_filesystem(Context *context, Partition *p, const c
                 if (do_make_directories(p, fs) < 0)
                         _exit(EXIT_FAILURE);
 
+                if (do_make_symlinks(p, fs) < 0)
+                        _exit(EXIT_FAILURE);
+
                 if (make_subvolumes_read_only(p, fs) < 0)
                         _exit(EXIT_FAILURE);
 
index bbef52ea515c51aef2709f6ae7c8c98143357adb..05fc017d231e04e3cd16fe511b91212cf3dd9752 100755 (executable)
@@ -1390,6 +1390,45 @@ EOF
     [[ "$(sfdisk -d "$imgs/zzz" | grep -F 'uuid=' | awk '{ print $8 }' | sort -u | wc -l)" == "3" ]]
 }
 
+testcase_make_symlinks() {
+    local defs imgs output
+
+    if systemd-detect-virt --quiet --container; then
+        echo "Skipping MakeSymlinks= test in container."
+        return
+    fi
+
+    # For issue #34257
+
+    defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
+    imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
+    # shellcheck disable=SC2064
+    trap "rm -rf '$defs' '$imgs'" RETURN
+    chmod 0755 "$defs"
+
+    tee "$defs/root.conf" <<EOF
+[Partition]
+Type=root
+MakeDirectories=/dir
+MakeSymlinks=/foo:/bar
+MakeSymlinks=/dir/foo:/bar
+EOF
+
+    systemd-repart --offline="$OFFLINE" \
+                   --definitions="$defs" \
+                   --empty=create \
+                   --size=1G \
+                   --dry-run=no \
+                   --offline="$OFFLINE" \
+                   --json=pretty \
+                   "$imgs/zzz"
+
+    systemd-dissect "$imgs/zzz" -M "$imgs/mnt"
+    assert_eq "$(readlink "$imgs/mnt/foo")" "/bar"
+    assert_eq "$(readlink "$imgs/mnt/dir/foo")" "/bar"
+    systemd-dissect -U "$imgs/mnt"
+}
+
 OFFLINE="yes"
 run_testcases