1
0
mirror of https://github.com/systemd/systemd synced 2026-03-12 08:04:46 +01:00

Compare commits

..

No commits in common. "83b4a5bb3d6a0f565aebcba975efad8dac73abea" and "5bc11cd4e69f75fd72492cb213db5b4f0534462f" have entirely different histories.

12 changed files with 69 additions and 241 deletions

View File

@ -699,26 +699,6 @@ SPDX-License-Identifier: LGPL-2.1-or-later
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read ...");
```
- When generating log messages that contain filenames, user controlled strings,
or similar, please enclose them in single ticks.
- Think about the log level you choose: for functions that are of the "logging"
kind (see above), please ensure that failures we propagate should be logged
about at `LOG_ERR` level. Failures that are noteworthy, but we proceed anyway,
should be loged at `LOG_WARN` level. Important informational messages should
use `LOG_NOTICE` and regular informational messages should use
`LOG_INFO`. Note that the latter is the default maximum log level, i.e. only
`LOG_DEBUG` messages are hidden by default.
- All log messages that show some failure which is not fatal for the immediate
operation (i.e. generally those you'd log at `LOG_WARN` level, as described
above) should be suffixed with a `…, ignoring: %m"` or similar. Or in other
words, they should make clear not only in log level but also in English
language that the issue is not fatal, but ignored. Depending on context you
can also use `…, proceeding anyway: %m"`, `…, skipping: %m` or other language
that makes clear that the failure is not actionable and doesn't strictly
require immediate administrator attention.
## Memory Allocation
- Always check OOM. There is no excuse. In program code, you can use

View File

@ -1194,10 +1194,6 @@ sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAWT10-A-103:*
sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAWT10-A-102:*
ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
# Toshiba Encore WT10A-108 tablet
sensor:modalias:acpi:INVN6500*:dmi:*:svnTOSHIBA:pnTOSHIBAWT10-A-108:*
ACCEL_MOUNT_MATRIX=0, -1, 0; -1, 0, 0; 0, 0, 1
#########################################
# Trekstor
#########################################

View File

@ -551,19 +551,6 @@
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--copy-ownership=</option></term>
<listitem><para>Controls whether file ownership (user and group) is preserved when copying files
with <option>--copy-from</option> or <option>--copy-to</option>. Takes a boolean. If
<literal>yes</literal>, ownership is always preserved. If <literal>no</literal>, 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.
</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--system</option></term>
<term><option>--user</option></term>

View File

@ -14,7 +14,6 @@
#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"
@ -109,7 +108,6 @@ 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);
@ -171,8 +169,6 @@ 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"
@ -309,7 +305,6 @@ static int parse_argv(int argc, char *argv[]) {
ARG_USER,
ARG_ALL,
ARG_IMAGE_FILTER,
ARG_COPY_OWNERSHIP,
};
static const struct option options[] = {
@ -352,7 +347,6 @@ static int parse_argv(int argc, char *argv[]) {
{ "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 },
{}
};
@ -636,12 +630,6 @@ 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;
@ -849,25 +837,8 @@ static int parse_argv(int argc, char *argv[]) {
} else
arg_via_service = r;
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;
}
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.");
SET_FLAG(arg_flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH, isatty_safe(STDIN_FILENO));
@ -1468,13 +1439,6 @@ 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);
@ -1537,12 +1501,7 @@ 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, /* 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);
r = copy_directory_at(source_fd, NULL, AT_FDCWD, arg_target, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS);
if (r >= 0)
return 0;
if (r != -ENOTDIR)
@ -1565,10 +1524,9 @@ 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;
}
@ -1622,23 +1580,9 @@ 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,
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);
r = copy_tree_at(source_fd, ".", dfd, bn, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, NULL);
} else
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);
r = copy_tree_at(source_fd, ".", target_fd, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS, NULL, 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);
@ -1659,10 +1603,9 @@ 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;
}
@ -1778,7 +1721,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 && !ERRNO_IS_PRIVILEGE(r))
if (r < 0)
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
@ -1790,7 +1733,6 @@ 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. */
if (d)
loop_device_unrelinquish(d);
if (arg_rmdir) {
@ -1843,7 +1785,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_REOPEN_LOG|FORK_WAIT, /* ret= */ NULL);
rcode = pidref_safe_fork("(with)", FORK_CLOSE_ALL_FDS|FORK_LOG|FORK_WAIT, /* ret= */ NULL);
if (rcode == 0) {
/* Child */
@ -1857,7 +1799,7 @@ static int action_with(DissectedImage *m, LoopDevice *d) {
_exit(EXIT_FAILURE);
}
if (d && setenv("SYSTEMD_DISSECT_DEVICE", d->node, /* overwrite= */ true) < 0) {
if (setenv("SYSTEMD_DISSECT_DEVICE", d->node, /* overwrite= */ true) < 0) {
log_error_errno(errno, "Failed to set $SYSTEMD_DISSECT_DEVICE: %m");
_exit(EXIT_FAILURE);
}
@ -1897,7 +1839,7 @@ static int action_with(DissectedImage *m, LoopDevice *d) {
created_dir = TAKE_PTR(mounted_dir);
if (rmdir(created_dir) < 0)
log_warning_errno(errno, "Failed to remove directory '%s', ignoring: %m", created_dir);
log_warning_errno(r, "Failed to remove directory '%s', ignoring: %m", created_dir);
temp = TAKE_PTR(created_dir);
@ -2172,7 +2114,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_MOUNT, ACTION_UMOUNT, ACTION_WITH, ACTION_DISSECT, ACTION_LIST, ACTION_MTREE, ACTION_COPY_FROM, ACTION_COPY_TO, ACTION_SHIFT))
if (!ERRNO_IS_PRIVILEGE(r) || !IN_SET(arg_action, 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);
@ -2238,9 +2180,8 @@ 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
* or if we're copying from/to the host. */
if (!IN_SET(arg_action, ACTION_MOUNT, ACTION_WITH, ACTION_COPY_FROM, ACTION_COPY_TO)) {
/* 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)) {
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");

View File

@ -223,8 +223,6 @@ 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|

View File

@ -1480,8 +1480,6 @@ 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|

View File

@ -1442,8 +1442,6 @@ 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,
@ -1470,13 +1468,9 @@ int copy_directory_at_full(
dir_fdt, to,
st.st_dev,
COPY_DEPTH_MAX,
override_uid,
override_gid,
UID_INVALID, GID_INVALID,
copy_flags,
/* denylist= */ NULL,
/* subvolumes= */ NULL,
/* progress_path= */ NULL,
/* progress_bytes= */ NULL,
NULL, NULL, NULL, NULL,
progress_path,
progress_bytes,
userdata);
@ -1756,18 +1750,6 @@ 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;

View File

@ -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, 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_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_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,7 +100,6 @@ 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 */

View File

@ -387,13 +387,12 @@ 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 log_debug_errno(errno, "Failed to open '%s': %m", path);
return -errno;
/* 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
@ -402,30 +401,21 @@ 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 log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"UID shift 0x%"PRIx32" is not at a 2^16 boundary.",
(uint32_t) shift);
return -EOPNOTSUPP;
if (shift == UID_BUSY_BASE)
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
"UID shift 0x%"PRIx32" conflicts with busy base.",
(uint32_t) shift);
return -EINVAL;
/* We only support containers with 16-bit UID ranges for the patching logic */
if (range != 0x10000)
return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"UID range 0x%"PRIx32" is not supported, must be 0x10000.",
(uint32_t) range);
return -EOPNOTSUPP;
if (fstat(fd, &st) < 0)
return log_debug_errno(errno, "Failed to stat '%s': %m", path);
return -errno;
/* 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 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);
return -EBADE;
/* 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... */
@ -440,11 +430,7 @@ 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)
log_debug_errno(errno, "Failed to mark '%s' as busy, ignoring: %m", path);
return -errno;
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;
return recurse_fd(TAKE_FD(fd), &st, shift, true);
}

View File

@ -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", UID_INVALID, GID_INVALID, COPY_LOCK_BSD)) >= 0);
assert_se((fd = copy_directory_at(tfd, "abc", tfd, "qed", 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);

View File

@ -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, UID_INVALID, GID_INVALID, COPY_MERGE_EMPTY));
ASSERT_OK(copy_directory_at(AT_FDCWD, unit_dir, AT_FDCWD, PRIVATE_UNIT_DIR, 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);

View File

@ -1050,45 +1050,6 @@ 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