From b63bd125d4fb8ad1905d6c7dc4d87033ecf8148c Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 20 Feb 2023 20:30:44 +0100 Subject: [PATCH] copy: Support both inode exclusion and contents exclusion 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 | 4 ++++ src/partition/repart.c | 27 +++++++++++++++------------ src/shared/copy.c | 25 +++++++++++++++++++------ src/shared/copy.h | 14 +++++++++++--- src/test/test-copy.c | 4 ++-- test/units/testsuite-58.sh | 8 ++++---- 6 files changed, 55 insertions(+), 27 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index fb5d34baea..4c13ccfb58 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -453,6 +453,10 @@ exclude multiple files or directories from host from being copied into the newly formatted file system. + If the path is a directory and ends with /, only the directory's + contents are excluded but not the directory itself. If the path is a directory and does not end with + /, both the directory and its contents are excluded. + When systemd-repart8 is invoked with the or command line switches the diff --git a/src/partition/repart.c b/src/partition/repart.c index 9d5c60977f..786f7d8da5 100644 --- a/src/partition/repart.c +++ b/src/partition/repart.c @@ -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; diff --git a/src/shared/copy.c b/src/shared/copy.c index a1df3048ba..9963e10f4d 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -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) { diff --git a/src/shared/copy.h b/src/shared/copy.h index cb40a10f09..ab2e256915 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -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); } diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 5a4af174fe..a2b2b34d98 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -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); diff --git a/test/units/testsuite-58.sh b/test/units/testsuite-58.sh index 7aef2d723e..99f6223d40 100755 --- a/test/units/testsuite-58.sh +++ b/test/units/testsuite-58.sh @@ -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" -- 2.25.1