diff --git a/man/systemd-dissect.xml b/man/systemd-dissect.xml index 669cea96f4..9368d90a52 100644 --- a/man/systemd-dissect.xml +++ b/man/systemd-dissect.xml @@ -551,6 +551,19 @@ + + + + Controls whether file ownership (user and group) is preserved when copying files + with or . Takes a boolean. If + yes, ownership is always preserved. If no, ownership is never + preserved and the current user's UID/GID is used instead. If not specified, ownership is preserved + when copying directory trees, but not when copying individual regular files. + + + + + diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 94f796f675..1eb17e67e4 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -14,6 +14,7 @@ #include "argv-util.h" #include "blockdev-util.h" #include "build.h" +#include "capability-util.h" #include "chase.h" #include "copy.h" #include "device-util.h" @@ -108,6 +109,7 @@ static bool arg_all = false; static uid_t arg_uid_base = UID_INVALID; static bool arg_quiet = false; static ImageFilter *arg_image_filter = NULL; +static int arg_copy_ownership = -1; STATIC_DESTRUCTOR_REGISTER(arg_image, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -169,6 +171,8 @@ static int help(void) { " --loop-ref=NAME Set reference string for loopback device\n" " --loop-ref-auto Derive reference string from image file name\n" " --mtree-hash=BOOL Whether to include SHA256 hash in the mtree output\n" + " --copy-ownership=BOOL\n" + " Whether to copy ownership when copying files\n" " --user Discover user images\n" " --system Discover system images\n" " --all Show hidden images too\n" @@ -305,48 +309,50 @@ static int parse_argv(int argc, char *argv[]) { ARG_USER, ARG_ALL, ARG_IMAGE_FILTER, + ARG_COPY_OWNERSHIP, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "mount", no_argument, NULL, 'm' }, - { "umount", no_argument, NULL, 'u' }, - { "attach", no_argument, NULL, ARG_ATTACH }, - { "detach", no_argument, NULL, ARG_DETACH }, - { "with", no_argument, NULL, ARG_WITH }, - { "read-only", no_argument, NULL, 'r' }, - { "discard", required_argument, NULL, ARG_DISCARD }, - { "fsck", required_argument, NULL, ARG_FSCK }, - { "growfs", required_argument, NULL, ARG_GROWFS }, - { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, - { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, - { "usr-hash", required_argument, NULL, ARG_USR_HASH }, - { "usr-hash-sig", required_argument, NULL, ARG_USR_HASH_SIG }, - { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "rmdir", no_argument, NULL, ARG_RMDIR }, - { "in-memory", no_argument, NULL, ARG_IN_MEMORY }, - { "list", no_argument, NULL, 'l' }, - { "mtree", no_argument, NULL, ARG_MTREE }, - { "copy-from", no_argument, NULL, 'x' }, - { "copy-to", no_argument, NULL, 'a' }, - { "json", required_argument, NULL, ARG_JSON }, - { "discover", no_argument, NULL, ARG_DISCOVER }, - { "loop-ref", required_argument, NULL, ARG_LOOP_REF }, - { "loop-ref-auto", no_argument, NULL, ARG_LOOP_REF_AUTO }, - { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, - { "validate", no_argument, NULL, ARG_VALIDATE }, - { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH }, - { "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE }, - { "shift", no_argument, NULL, ARG_SHIFT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "all", no_argument, NULL, ARG_ALL }, - { "quiet", no_argument, NULL, 'q' }, - { "image-filter", required_argument, NULL, ARG_IMAGE_FILTER }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "mount", no_argument, NULL, 'm' }, + { "umount", no_argument, NULL, 'u' }, + { "attach", no_argument, NULL, ARG_ATTACH }, + { "detach", no_argument, NULL, ARG_DETACH }, + { "with", no_argument, NULL, ARG_WITH }, + { "read-only", no_argument, NULL, 'r' }, + { "discard", required_argument, NULL, ARG_DISCARD }, + { "fsck", required_argument, NULL, ARG_FSCK }, + { "growfs", required_argument, NULL, ARG_GROWFS }, + { "root-hash", required_argument, NULL, ARG_ROOT_HASH }, + { "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG }, + { "usr-hash", required_argument, NULL, ARG_USR_HASH }, + { "usr-hash-sig", required_argument, NULL, ARG_USR_HASH_SIG }, + { "verity-data", required_argument, NULL, ARG_VERITY_DATA }, + { "mkdir", no_argument, NULL, ARG_MKDIR }, + { "rmdir", no_argument, NULL, ARG_RMDIR }, + { "in-memory", no_argument, NULL, ARG_IN_MEMORY }, + { "list", no_argument, NULL, 'l' }, + { "mtree", no_argument, NULL, ARG_MTREE }, + { "copy-from", no_argument, NULL, 'x' }, + { "copy-to", no_argument, NULL, 'a' }, + { "json", required_argument, NULL, ARG_JSON }, + { "discover", no_argument, NULL, ARG_DISCOVER }, + { "loop-ref", required_argument, NULL, ARG_LOOP_REF }, + { "loop-ref-auto", no_argument, NULL, ARG_LOOP_REF_AUTO }, + { "image-policy", required_argument, NULL, ARG_IMAGE_POLICY }, + { "validate", no_argument, NULL, ARG_VALIDATE }, + { "mtree-hash", required_argument, NULL, ARG_MTREE_HASH }, + { "make-archive", no_argument, NULL, ARG_MAKE_ARCHIVE }, + { "shift", no_argument, NULL, ARG_SHIFT }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "all", no_argument, NULL, ARG_ALL }, + { "quiet", no_argument, NULL, 'q' }, + { "image-filter", required_argument, NULL, ARG_IMAGE_FILTER }, + { "copy-ownership", required_argument, NULL, ARG_COPY_OWNERSHIP }, {} }; @@ -630,6 +636,12 @@ static int parse_argv(int argc, char *argv[]) { break; } + case ARG_COPY_OWNERSHIP: + r = parse_tristate_argument("--copy-ownership=", optarg, &arg_copy_ownership); + if (r < 0) + return r; + break; + case '?': return -EINVAL; @@ -837,8 +849,25 @@ static int parse_argv(int argc, char *argv[]) { } else arg_via_service = r; - if (!IN_SET(arg_action, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_DISCOVER, ACTION_VALIDATE) && geteuid() != 0) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to be root."); + r = have_effective_cap(CAP_SYS_ADMIN); + if (r < 0) + return log_error_errno(r, "Failed to determine if we have CAP_SYS_ADMIN: %m"); + + if (IN_SET(arg_action, ACTION_MOUNT, ACTION_UMOUNT) && r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to have CAP_SYS_ADMIN to mount/unmount images"); + + r = have_effective_cap(CAP_CHOWN); + if (r < 0) + return log_error_errno(r, "Failed to determine if we have CAP_CHOWN: %m"); + + if (arg_action == ACTION_SHIFT && r == 0) + return log_error_errno(SYNTHETIC_ERRNO(EPERM), "Need to have CAP_CHOWN to shift UID ranges"); + + if (IN_SET(arg_action, ACTION_ATTACH, ACTION_DETACH)) { + r = must_be_root(); + if (r < 0) + return r; + } SET_FLAG(arg_flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH, isatty_safe(STDIN_FILENO)); @@ -1439,6 +1468,13 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD assert(IN_SET(arg_action, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_MAKE_ARCHIVE, ACTION_SHIFT)); + /* Determine whether to copy ownership: + * --copy-ownership=yes: always try to preserve ownership + * --copy-ownership=no: never preserve ownership, use current user + * --copy-ownership=auto (default): preserve ownership for directory trees, + * but not for regular files (since DDI password tables are typically + * distinct from the host ones, individual file ownership is less meaningful) */ + if (arg_image) { assert(m); @@ -1501,7 +1537,12 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD } /* Try to copy as directory? */ - r = copy_directory_at(source_fd, NULL, AT_FDCWD, arg_target, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS); + r = copy_directory_at( + source_fd, /* from= */ NULL, + AT_FDCWD, arg_target, + arg_copy_ownership == 0 ? getuid() : UID_INVALID, + arg_copy_ownership == 0 ? getgid() : GID_INVALID, + COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS); if (r >= 0) return 0; if (r != -ENOTDIR) @@ -1524,9 +1565,10 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0); (void) copy_access(source_fd, target_fd); + if (arg_copy_ownership > 0) + (void) copy_owner(source_fd, target_fd); (void) copy_times(source_fd, target_fd, 0); - /* When this is a regular file we don't copy ownership! */ return 0; } @@ -1580,9 +1622,23 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD if (errno != ENOENT) return log_error_errno(errno, "Failed to open destination '%s': %m", arg_target); - r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL); + r = copy_tree_at( + source_fd, ".", + dfd, bn, + arg_copy_ownership == 0 ? getuid() : UID_INVALID, + arg_copy_ownership == 0 ? getgid() : GID_INVALID, + COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, + /* denylist= */ NULL, + /* subvolumes= */ NULL); } else - r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL); + r = copy_tree_at( + source_fd, ".", + target_fd, ".", + arg_copy_ownership == 0 ? getuid() : UID_INVALID, + arg_copy_ownership == 0 ? getgid() : GID_INVALID, + COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, + /* denylist= */ NULL, + /* subvolumes= */ NULL); if (r < 0) return log_error_errno(r, "Failed to copy '%s' to '%s' in image '%s': %m", arg_source, arg_target, arg_image); @@ -1603,9 +1659,10 @@ static int action_list_or_mtree_or_copy_or_make_archive(DissectedImage *m, LoopD (void) copy_xattr(source_fd, NULL, target_fd, NULL, 0); (void) copy_access(source_fd, target_fd); + if (arg_copy_ownership > 0) + (void) copy_owner(source_fd, target_fd); (void) copy_times(source_fd, target_fd, 0); - /* When this is a regular file we don't copy ownership! */ return 0; } @@ -1721,7 +1778,7 @@ static int action_umount(const char *path) { return log_error_errno(r, "Failed to find backing block device for '%s': %m", canonical); r = loop_device_open(dev, 0, LOCK_EX, &d); - if (r < 0) + if (r < 0 && !ERRNO_IS_PRIVILEGE(r)) return log_device_error_errno(dev, r, "Failed to open loopback block device: %m"); /* We've locked the loop device, now we're ready to unmount. To allow the unmount to succeed, we have @@ -1733,7 +1790,8 @@ static int action_umount(const char *path) { return log_error_errno(r, "Failed to unmount '%s': %m", canonical); /* We managed to lock and unmount successfully? That means we can try to remove the loop device. */ - loop_device_unrelinquish(d); + if (d) + loop_device_unrelinquish(d); if (arg_rmdir) { r = RET_NERRNO(rmdir(canonical)); @@ -1785,7 +1843,7 @@ static int action_with(DissectedImage *m, LoopDevice *d) { return log_error_errno(r, "Failed to unlock loopback block device: %m"); } - rcode = pidref_safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL); + rcode = pidref_safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT, /* ret= */ NULL); if (rcode == 0) { /* Child */ @@ -1799,7 +1857,7 @@ static int action_with(DissectedImage *m, LoopDevice *d) { _exit(EXIT_FAILURE); } - if (setenv("SYSTEMD_DISSECT_DEVICE", d->node, /* overwrite= */ true) < 0) { + if (d && setenv("SYSTEMD_DISSECT_DEVICE", d->node, /* overwrite= */ true) < 0) { log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_DEVICE: %m"); _exit(EXIT_FAILURE); } @@ -1839,7 +1897,7 @@ static int action_with(DissectedImage *m, LoopDevice *d) { created_dir = TAKE_PTR(mounted_dir); if (rmdir(created_dir) < 0) - log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", created_dir); + log_warning_errno(errno, "Failed to remove directory '%s', ignoring: %m", created_dir); temp = TAKE_PTR(created_dir); @@ -2114,7 +2172,7 @@ static int run(int argc, char *argv[]) { else r = loop_device_make_by_path(arg_image, open_flags, /* sector_size= */ UINT32_MAX, loop_flags, LOCK_SH, &d); if (r < 0) { - if (!ERRNO_IS_PRIVILEGE(r) || !IN_SET(arg_action, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_SHIFT)) + if (!ERRNO_IS_PRIVILEGE(r) || !IN_SET(arg_action, ACTION_MOUNT, ACTION_UMOUNT, ACTION_WITH, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_SHIFT)) return log_error_errno(r, "Failed to set up loopback device for %s: %m", arg_image); log_debug_errno(r, "Lacking permissions to set up loopback block device for %s, using service: %m", arg_image); @@ -2180,8 +2238,9 @@ static int run(int argc, char *argv[]) { if (arg_loop_ref || arg_loop_ref_auto) /* yes, the 2nd check is strictly speaking redundant, given the normalization we did above, but let's be explicit here */ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--loop-ref=/--loop-ref-auto not supported when operating via systemd-mountfsd."); - /* Don't run things in private userns, if the mount shall be attached to the host */ - if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH)) { + /* Don't run things in private userns, if the mount shall be attached to the host + * or if we're copying from/to the host. */ + if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH, ACTION_COPY_FROM, ACTION_COPY_TO)) { userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K); /* allocate 64K users by default */ if (userns_fd < 0) return log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m"); diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 947bc7c9b5..46daf3acc0 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -223,6 +223,8 @@ static int import_fs(int argc, char *argv[], void *userdata) { r = copy_directory_at_full( fd, NULL, AT_FDCWD, dest, + /* override_uid= */ UID_INVALID, + /* override_gid= */ GID_INVALID, COPY_REFLINK| COPY_SAME_MOUNT| COPY_HARDLINKS| diff --git a/src/shared/btrfs-util.c b/src/shared/btrfs-util.c index fffe9f2b34..9bd0a74c5f 100644 --- a/src/shared/btrfs-util.c +++ b/src/shared/btrfs-util.c @@ -1480,6 +1480,8 @@ int btrfs_subvol_snapshot_at_full( r = copy_directory_at_full( dir_fdf, from, new_fd, subvolume, + /* override_uid= */ UID_INVALID, + /* override_gid= */ GID_INVALID, COPY_MERGE_EMPTY| COPY_REFLINK| COPY_SAME_MOUNT| diff --git a/src/shared/copy.c b/src/shared/copy.c index 0598759313..445c246359 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -1442,6 +1442,8 @@ int copy_directory_at_full( const char *from, int dir_fdt, const char *to, + uid_t override_uid, + gid_t override_gid, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, @@ -1468,9 +1470,13 @@ int copy_directory_at_full( dir_fdt, to, st.st_dev, COPY_DEPTH_MAX, - UID_INVALID, GID_INVALID, + override_uid, + override_gid, copy_flags, - NULL, NULL, NULL, NULL, + /* denylist= */ NULL, + /* subvolumes= */ NULL, + /* progress_path= */ NULL, + /* progress_bytes= */ NULL, progress_path, progress_bytes, userdata); @@ -1750,6 +1756,18 @@ int copy_access(int fdf, int fdt) { return RET_NERRNO(fchmod(fdt, st.st_mode & 07777)); } +int copy_owner(int fdf, int fdt) { + struct stat st; + + assert(fdf >= 0); + assert(fdt >= 0); + + if (fstat(fdf, &st) < 0) + return -errno; + + return RET_NERRNO(fchown(fdt, st.st_uid, st.st_gid)); +} + int copy_rights_with_fallback(int fdf, int fdt, const char *patht) { struct stat st; diff --git a/src/shared/copy.h b/src/shared/copy.h index 704abdb84b..6e4a3b177b 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -88,9 +88,9 @@ static inline int copy_tree(const char *from, const char *to, uid_t override_uid return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, denylist, subvolumes, NULL, NULL, NULL); } -int copy_directory_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); -static inline int copy_directory_at(int dir_fdf, const char *from, int dir_fdt, const char *to, CopyFlags copy_flags) { - return copy_directory_at_full(dir_fdf, from, dir_fdt, to, copy_flags, NULL, NULL, NULL); +int copy_directory_at_full(int dir_fdf, const char *from, int dir_fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); +static inline int copy_directory_at(int dir_fdf, const char *from, int dir_fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags) { + return copy_directory_at_full(dir_fdf, from, dir_fdt, to, override_uid, override_gid, copy_flags, NULL, NULL, NULL); } int copy_bytes_full(int fdf, int fdt, uint64_t max_bytes, CopyFlags copy_flags, void **ret_remains, size_t *ret_remains_size, copy_progress_bytes_t progress, void *userdata); @@ -100,6 +100,7 @@ static inline int copy_bytes(int fdf, int fdt, uint64_t max_bytes, CopyFlags cop int copy_times(int fdf, int fdt, CopyFlags flags); int copy_access(int fdf, int fdt); +int copy_owner(int fdf, int fdt); int copy_rights_with_fallback(int fdf, int fdt, const char *patht); static inline int copy_rights(int fdf, int fdt) { return copy_rights_with_fallback(fdf, fdt, NULL); /* no fallback */ diff --git a/src/shared/shift-uid.c b/src/shared/shift-uid.c index f0353f2391..2dcd5f2359 100644 --- a/src/shared/shift-uid.c +++ b/src/shared/shift-uid.c @@ -387,12 +387,13 @@ read_only: int path_patch_uid(const char *path, uid_t shift, uid_t range) { _cleanup_close_ int fd = -EBADF; struct stat st; + int r; assert(path); fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (fd < 0) - return -errno; + return log_debug_errno(errno, "Failed to open '%s': %m", path); /* Recursively adjusts the UID/GIDs of all files of a directory tree. This is used to automatically fix up an * OS tree to the used user namespace UID range. Note that this automatic adjustment only works for UID ranges @@ -401,21 +402,30 @@ int path_patch_uid(const char *path, uid_t shift, uid_t range) { /* We only support containers where the shift starts at a 2^16 boundary */ if ((shift & 0xFFFF) != 0) - return -EOPNOTSUPP; + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "UID shift 0x%"PRIx32" is not at a 2^16 boundary.", + (uint32_t) shift); if (shift == UID_BUSY_BASE) - return -EINVAL; + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "UID shift 0x%"PRIx32" conflicts with busy base.", + (uint32_t) shift); /* We only support containers with 16-bit UID ranges for the patching logic */ if (range != 0x10000) - return -EOPNOTSUPP; + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "UID range 0x%"PRIx32" is not supported, must be 0x10000.", + (uint32_t) range); if (fstat(fd, &st) < 0) - return -errno; + return log_debug_errno(errno, "Failed to stat '%s': %m", path); /* We only support containers where the uid/gid container ID match */ if ((uint32_t) st.st_uid >> 16 != (uint32_t) st.st_gid >> 16) - return -EBADE; + return log_debug_errno(SYNTHETIC_ERRNO(EBADE), + "UID container ID 0x%"PRIx32" does not match GID container ID 0x%"PRIx32".", + (uint32_t) st.st_uid >> 16, + (uint32_t) st.st_gid >> 16); /* Try to detect if the range is already right. Of course, this a pretty drastic optimization, as we assume * that if the top-level dir has the right upper 16-bit assigned, then everything below will have too... */ @@ -430,7 +440,11 @@ int path_patch_uid(const char *path, uid_t shift, uid_t range) { if (fchown(fd, UID_BUSY_BASE | (st.st_uid & ~UID_BUSY_MASK), (gid_t) UID_BUSY_BASE | (st.st_gid & ~(gid_t) UID_BUSY_MASK)) < 0) - return -errno; + log_debug_errno(errno, "Failed to mark '%s' as busy, ignoring: %m", path); - return recurse_fd(TAKE_FD(fd), &st, shift, true); + r = recurse_fd(TAKE_FD(fd), &st, shift, /* is_toplevel= */ true); + if (r < 0) + return log_debug_errno(r, "Failed to recursively patch UID/GID of '%s': %m", path); + + return r; } diff --git a/src/test/test-copy.c b/src/test/test-copy.c index e1bb7ae049..758a597fc5 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -560,7 +560,7 @@ TEST(copy_lock) { assert_se(mkdirat(tfd, "abc", 0755) >= 0); assert_se(write_string_file_at(tfd, "abc/def", "abc", WRITE_STRING_FILE_CREATE) >= 0); - assert_se((fd = copy_directory_at(tfd, "abc", tfd, "qed", COPY_LOCK_BSD)) >= 0); + assert_se((fd = copy_directory_at(tfd, "abc", tfd, "qed", UID_INVALID, GID_INVALID, COPY_LOCK_BSD)) >= 0); assert_se(faccessat(tfd, "qed", F_OK, 0) >= 0); assert_se(faccessat(tfd, "qed/def", F_OK, 0) >= 0); assert_se(xopenat_lock(tfd, "qed", 0, LOCK_BSD, LOCK_EX|LOCK_NB) == -EAGAIN); diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 55b7c08924..ebd0260892 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -1497,7 +1497,7 @@ static int prepare_ns(const char *process_name) { /* Copy unit files to make them accessible even when unprivileged. */ ASSERT_OK(get_testdata_dir("test-execute/", &unit_dir)); - ASSERT_OK(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, COPY_MERGE_EMPTY)); + ASSERT_OK(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, UID_INVALID, GID_INVALID, COPY_MERGE_EMPTY)); /* Mount tmpfs on the following directories to make not StateDirectory= or friends disturb the host. */ ASSERT_OK_OR(get_build_exec_dir(&build_dir), -ENOEXEC); diff --git a/test/units/TEST-50-DISSECT.dissect.sh b/test/units/TEST-50-DISSECT.dissect.sh index ad8b14139c..07e0d1a8a0 100755 --- a/test/units/TEST-50-DISSECT.dissect.sh +++ b/test/units/TEST-50-DISSECT.dissect.sh @@ -1050,6 +1050,45 @@ echo abc >abc systemd-dissect --copy-to /tmp/img abc /abc test -f /tmp/img/abc +# Test --copy-ownership= option +rm -rf /tmp/copychown-test +mkdir -p /tmp/copychown-test/srcdir +echo "test file" >/tmp/copychown-test/srcdir/testfile +chown 1234:5678 /tmp/copychown-test/srcdir/testfile +chown 1234:5678 /tmp/copychown-test/srcdir + +# Test --copy-ownership=yes preserves ownership for regular files +systemd-dissect --copy-from /tmp/img etc/os-release /tmp/copychown-test/os-release-chown-yes --copy-ownership=yes +test "$(stat -c %u:%g /tmp/copychown-test/os-release-chown-yes)" = "0:0" + +# Test --copy-ownership=no uses current user for regular files +systemd-dissect --copy-from /tmp/img etc/os-release /tmp/copychown-test/os-release-chown-no --copy-ownership=no +test "$(stat -c %u:%g /tmp/copychown-test/os-release-chown-no)" = "0:0" + +# Test --copy-ownership=auto (default) does not preserve ownership for regular files +systemd-dissect --copy-from /tmp/img etc/os-release /tmp/copychown-test/os-release-chown-auto +test "$(stat -c %u:%g /tmp/copychown-test/os-release-chown-auto)" = "0:0" + +# Test --copy-ownership=yes preserves ownership for directories +systemd-dissect --copy-to /tmp/img /tmp/copychown-test/srcdir /copychown-dir-yes --copy-ownership=yes +test "$(stat -c %u:%g /tmp/img/copychown-dir-yes)" = "1234:5678" +test "$(stat -c %u:%g /tmp/img/copychown-dir-yes/testfile)" = "1234:5678" +rm -rf /tmp/img/copychown-dir-yes + +# Test --copy-ownership=no overrides ownership for directories +systemd-dissect --copy-to /tmp/img /tmp/copychown-test/srcdir /copychown-dir-no --copy-ownership=no +test "$(stat -c %u:%g /tmp/img/copychown-dir-no)" = "0:0" +test "$(stat -c %u:%g /tmp/img/copychown-dir-no/testfile)" = "0:0" +rm -rf /tmp/img/copychown-dir-no + +# Test --copy-ownership=auto (default) preserves ownership for directories +systemd-dissect --copy-to /tmp/img /tmp/copychown-test/srcdir /copychown-dir-auto +test "$(stat -c %u:%g /tmp/img/copychown-dir-auto)" = "1234:5678" +test "$(stat -c %u:%g /tmp/img/copychown-dir-auto/testfile)" = "1234:5678" +rm -rf /tmp/img/copychown-dir-auto + +rm -rf /tmp/copychown-test + # Test for dissect tool support with systemd-sysext mkdir -p /run/extensions/ testkit/usr/lib/extension-release.d/ echo "ID=_any" >testkit/usr/lib/extension-release.d/extension-release.testkit