]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
copy: Support both inode exclusion and contents exclusion
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Mon, 20 Feb 2023 19:30:44 +0000 (20:30 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Wed, 22 Feb 2023 11:44:36 +0000 (12:44 +0100)
In some cases, we want to exclude a directory's contents but not
the directory itself. In other cases, we want to exclude a directory
and its contents. Let's extend the denylist logic in copy.h to support
both by changing the denylist from a set to hashmap so we can store the
deny type as the value.

We also modify the repart ExcludeFiles= option to make use of this. If
a directory to exclude ends with a "/", we'll only exclude its contents.
Otherwise, we'll exclude the full directory.

man/repart.d.xml
src/partition/repart.c
src/shared/copy.c
src/shared/copy.h
src/test/test-copy.c
test/units/testsuite-58.sh

index fb5d34baea00d27f96cd645c88daec6a098a02dd..4c13ccfb58028b461e21196ea38c53908d6167c0 100644 (file)
         exclude multiple files or directories from host from being copied into the newly formatted file
         system.</para>
 
+        <para>If the path is a directory and ends with <literal>/</literal>, only the directory's
+        contents are excluded but not the directory itself. If the path is a directory and does not end with
+        <literal>/</literal>, both the directory and its contents are excluded.</para>
+
         <para>When
         <citerefentry><refentrytitle>systemd-repart</refentrytitle><manvolnum>8</manvolnum></citerefentry>
         is invoked with the <option>--image=</option> or <option>--root=</option> command line switches the
index 9d5c60977fd1524366d15a75396b86b624fdece6..786f7d8da56fe82b67a3e2ea8d8d41d2a67a1475 100644 (file)
@@ -3787,7 +3787,7 @@ static int context_copy_blocks(Context *context) {
         return 0;
 }
 
-static int do_copy_files(Partition *p, const char *root, const Set *denylist) {
+static int do_copy_files(Partition *p, const char *root, Hashmap *denylist) {
         int r;
 
         assert(p);
@@ -3932,7 +3932,7 @@ static bool partition_needs_populate(Partition *p) {
         return !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories);
 }
 
-static int partition_populate_directory(Partition *p, const Set *denylist, char **ret) {
+static int partition_populate_directory(Partition *p, Hashmap *denylist, char **ret) {
         _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
         const char *vt;
         int r;
@@ -3963,7 +3963,7 @@ static int partition_populate_directory(Partition *p, const Set *denylist, char
         return 0;
 }
 
-static int partition_populate_filesystem(Partition *p, const char *node, const Set *denylist) {
+static int partition_populate_filesystem(Partition *p, const char *node, Hashmap *denylist) {
         int r;
 
         assert(p);
@@ -4010,7 +4010,7 @@ static int partition_populate_filesystem(Partition *p, const char *node, const S
         return 0;
 }
 
-static int add_exclude_path(const char *path, Set **denylist) {
+static int add_exclude_path(const char *path, Hashmap **denylist, DenyType type) {
         _cleanup_free_ struct stat *st = NULL;
         int r;
 
@@ -4028,10 +4028,10 @@ static int add_exclude_path(const char *path, Set **denylist) {
                 return log_error_errno(r, "Failed to stat source file '%s%s': %m",
                                         strempty(arg_root), path);
 
-        if (set_contains(*denylist, st))
+        if (hashmap_contains(*denylist, st))
                 return 0;
 
-        if (set_ensure_put(denylist, &inode_hash_ops, st) < 0)
+        if (hashmap_ensure_put(denylist, &inode_hash_ops, st, INT_TO_PTR(type)) < 0)
                 return log_oom();
 
         TAKE_PTR(st);
@@ -4039,8 +4039,8 @@ static int add_exclude_path(const char *path, Set **denylist) {
         return 0;
 }
 
-static int make_copy_files_denylist(Context *context, const Partition *p, Set **ret) {
-        _cleanup_set_free_ Set *denylist = NULL;
+static int make_copy_files_denylist(Context *context, const Partition *p, Hashmap **ret) {
+        _cleanup_hashmap_free_ Hashmap *denylist = NULL;
         int r;
 
         assert(context);
@@ -4048,6 +4048,9 @@ static int make_copy_files_denylist(Context *context, const Partition *p, Set **
         assert(ret);
 
         LIST_FOREACH(partitions, q, context->partitions) {
+                if (p == q)
+                        continue;
+
                 const char *sources = gpt_partition_type_mountpoint_nulstr(q->type);
                 if (!sources)
                         continue;
@@ -4056,14 +4059,14 @@ static int make_copy_files_denylist(Context *context, const Partition *p, Set **
                         /* Exclude the children of partition mount points so that the nested partition mount
                          * point itself still ends up in the upper partition. */
 
-                        r = add_exclude_path(s, &denylist);
+                        r = add_exclude_path(s, &denylist, DENY_CONTENTS);
                         if (r < 0)
                                 return r;
                 }
         }
 
         STRV_FOREACH(e, p->exclude_files) {
-                r = add_exclude_path(*e, &denylist);
+                r = add_exclude_path(*e, &denylist, endswith(*e, "/") ? DENY_CONTENTS : DENY_INODE);
                 if (r < 0)
                         return r;
         }
@@ -4080,7 +4083,7 @@ static int context_mkfs(Context *context) {
         /* Make a file system */
 
         LIST_FOREACH(partitions, p, context->partitions) {
-                _cleanup_set_free_ Set *denylist = NULL;
+                _cleanup_hashmap_free_ Hashmap *denylist = NULL;
                 _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
                 _cleanup_(partition_target_freep) PartitionTarget *t = NULL;
 
@@ -5400,7 +5403,7 @@ static int context_minimize(Context *context) {
                 return log_error_errno(r, "Could not determine temporary directory: %m");
 
         LIST_FOREACH(partitions, p, context->partitions) {
-                _cleanup_set_free_ Set *denylist = NULL;
+                _cleanup_hashmap_free_ Hashmap *denylist = NULL;
                 _cleanup_(rm_rf_physical_and_freep) char *root = NULL;
                 _cleanup_(unlink_and_freep) char *temp = NULL;
                 _cleanup_(loop_device_unrefp) LoopDevice *d = NULL;
index a1df3048baeed36e304475565cba22cce3e5d231..9963e10f4dba91874c116ed1cbb6af2d8448f753 100644 (file)
@@ -694,7 +694,7 @@ static int fd_copy_tree_generic(
                 uid_t override_uid,
                 gid_t override_gid,
                 CopyFlags copy_flags,
-                const Set *denylist,
+                Hashmap *denylist,
                 HardlinkContext *hardlink_context,
                 const char *display_path,
                 copy_progress_path_t progress_path,
@@ -897,7 +897,7 @@ static int fd_copy_directory(
                 uid_t override_uid,
                 gid_t override_gid,
                 CopyFlags copy_flags,
-                const Set *denylist,
+                Hashmap *denylist,
                 HardlinkContext *hardlink_context,
                 const char *display_path,
                 copy_progress_path_t progress_path,
@@ -971,6 +971,11 @@ static int fd_copy_directory(
 
         r = 0;
 
+        if (PTR_TO_INT(hashmap_get(denylist, st)) == DENY_CONTENTS) {
+                log_debug("%s is in the denylist, not recursing", from);
+                goto finish;
+        }
+
         FOREACH_DIRENT_ALL(de, d, return -errno) {
                 const char *child_display_path = NULL;
                 _cleanup_free_ char *dp = NULL;
@@ -1000,8 +1005,8 @@ static int fd_copy_directory(
                                 return r;
                 }
 
-                if (set_contains(denylist, &buf)) {
-                        log_debug("%s/%s is in the denylist, skipping", from, de->d_name);
+                if (PTR_TO_INT(hashmap_get(denylist, &buf)) == DENY_INODE) {
+                        log_debug("%s/%s is in the denylist, ignoring", from, de->d_name);
                         continue;
                 }
 
@@ -1050,6 +1055,7 @@ static int fd_copy_directory(
                         r = q;
         }
 
+finish:
         if (created) {
                 if (fchown(fdt,
                            uid_is_valid(override_uid) ? override_uid : st->st_uid,
@@ -1111,7 +1117,7 @@ static int fd_copy_tree_generic(
                 uid_t override_uid,
                 gid_t override_gid,
                 CopyFlags copy_flags,
-                const Set *denylist,
+                Hashmap *denylist,
                 HardlinkContext *hardlink_context,
                 const char *display_path,
                 copy_progress_path_t progress_path,
@@ -1124,6 +1130,13 @@ static int fd_copy_tree_generic(
                                          override_gid, copy_flags, denylist, hardlink_context, display_path,
                                          progress_path, progress_bytes, userdata);
 
+        DenyType t = PTR_TO_INT(hashmap_get(denylist, st));
+        if (t == DENY_INODE) {
+                log_debug("%s is in the denylist, ignoring", from);
+                return 0;
+        } else if (t == DENY_CONTENTS)
+                log_debug("%s is configured to have its contents excluded, but is not a directory", from);
+
         r = fd_copy_leaf(df, from, st, dt, to, override_uid, override_gid, copy_flags, hardlink_context, display_path, progress_bytes, userdata);
         /* We just tried to copy a leaf node of the tree. If it failed because the node already exists *and* the COPY_REPLACE flag has been provided, we should unlink the node and re-copy. */
         if (r == -EEXIST && (copy_flags & COPY_REPLACE)) {
@@ -1145,7 +1158,7 @@ int copy_tree_at_full(
                 uid_t override_uid,
                 gid_t override_gid,
                 CopyFlags copy_flags,
-                const Set *denylist,
+                Hashmap *denylist,
                 copy_progress_path_t progress_path,
                 copy_progress_bytes_t progress_bytes,
                 void *userdata) {
index cb40a10f091fbe761dc289225c20c8e00aa419e5..ab2e256915c9c9c29efdd8c4f4c80287d114c957 100644 (file)
@@ -30,6 +30,14 @@ typedef enum CopyFlags {
         COPY_GRACEFUL_WARN = 1 << 15, /* Skip copying file types that aren't supported by the target filesystem */
 } CopyFlags;
 
+typedef enum DenyType {
+        DENY_DONT = 0, /* we want INT_TO_PTR(DENY_DONT) to map to NULL */
+        DENY_INODE,
+        DENY_CONTENTS,
+        _DENY_TYPE_MAX,
+        _DENY_TYPE_INVALID = -EINVAL,
+} DenyType;
+
 typedef int (*copy_progress_bytes_t)(uint64_t n_bytes, void *userdata);
 typedef int (*copy_progress_path_t)(const char *path, const struct stat *st, void *userdata);
 
@@ -54,11 +62,11 @@ static inline int copy_file_atomic(const char *from, const char *to, mode_t mode
         return copy_file_atomic_full(from, to, mode, chattr_flags, chattr_mask, copy_flags, NULL, NULL);
 }
 
-int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, const Set *denylist, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata);
-static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, const Set *denylist) {
+int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata);
+static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist) {
         return copy_tree_at_full(fdf, from, fdt, to, override_uid, override_gid, copy_flags, denylist, NULL, NULL, NULL);
 }
-static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, const Set *denylist) {
+static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist) {
         return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, denylist, NULL, NULL, NULL);
 }
 
index 5a4af174fe1c9854a058a232ee6c328f20a2da90..a2b2b34d9845b21a81b5c505afe8f34a9523c3b7 100644 (file)
@@ -132,7 +132,7 @@ TEST(copy_file_fd) {
 }
 
 TEST(copy_tree) {
-        _cleanup_set_free_ Set *denylist = NULL;
+        _cleanup_hashmap_free_ Hashmap *denylist = NULL;
         _cleanup_free_ char *cp = NULL;
         char original_dir[] = "/tmp/test-copy_tree/";
         char copy_dir[] = "/tmp/test-copy_tree-copy/";
@@ -191,7 +191,7 @@ TEST(copy_tree) {
         assert_se(write_string_file(ignorep, "ignore", WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_MKDIR_0755) == 0);
         assert_se(RET_NERRNO(stat(ignorep, &st)) >= 0);
         assert_se(cp = memdup(&st, sizeof(st)));
-        assert_se(set_ensure_put(&denylist, &inode_hash_ops, cp) >= 0);
+        assert_se(hashmap_ensure_put(&denylist, &inode_hash_ops, cp, INT_TO_PTR(DENY_INODE)) >= 0);
         TAKE_PTR(cp);
 
         assert_se(copy_tree(original_dir, copy_dir, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_HARDLINKS, denylist) == 0);
index 7aef2d723ee2a7d87e81b85fdefd7daeea9a7e22..99f6223d402a7e7e1790a13aa645b48cef90a8ef 100755 (executable)
@@ -883,14 +883,14 @@ EOF
     loop=$(losetup -P --show -f "$imgs/zzz")
     udevadm wait --timeout 60 --settle "${loop:?}"
 
-    # Test that the /usr directory did not end up in the root partition but other files did.
+    # Test that /usr/def did not end up in the root partition but other files did.
     mkdir "$imgs/mnt"
     mount -t ext4 "${loop}p1" "$imgs/mnt"
     assert_rc 0 ls "$imgs/mnt/abc"
-    assert_rc 2 ls "$imgs/mnt/usr"
+    assert_rc 0 ls "$imgs/mnt/usr"
+    assert_rc 2 ls "$imgs/mnt/usr/def"
 
-    # Test that the qed file did not end up in the usr partition but other files did.
-    mkdir "$imgs/mnt/usr"
+    # Test that /usr/qed did not end up in the usr partition but /usr/def did.
     mount -t ext4 "${loop}p2" "$imgs/mnt/usr"
     assert_rc 0 ls "$imgs/mnt/usr/def"
     assert_rc 2 ls "$imgs/mnt/usr/qed"