1
0
mirror of https://github.com/systemd/systemd synced 2026-03-11 15:44:47 +01:00

Compare commits

...

12 Commits

Author SHA1 Message Date
Lennart Poettering
83b4a5bb3d CODING_STYLE: add a brief log msg style guide 2026-02-12 18:00:37 +01:00
Betacentury
8732896857 Add sensor entry for Toshiba Encore WT10A-108
Added sensor configuration for Toshiba Encore WT10A-108 tablet.
2026-02-12 12:00:53 +00:00
Daan De Meyer
3bba7fee4f
dissect: Various fixes and improvements (#40212) 2026-02-12 11:26:18 +01:00
Daan De Meyer
01fb2d4a9e dissect: Use must_be_root() 2026-02-12 10:38:33 +01:00
Daan De Meyer
f933e959b1 shift-uid: Add debug logging 2026-02-12 09:46:37 +01:00
Daan De Meyer
dbd1a6e34f dissect: Allow --shift for users with CAP_CHOWN 2026-02-12 09:46:37 +01:00
DaanDeMeyer
ba37ed9634 dissect: Introduce --copy-ownership= to configure chown behavior
Currently, if we're copying a file, we won't copy the owner UID/GID
from the source. If we're copying a directory, we will copy the owner
UID/GID from the source. Let's give users a bit more control over this
behavior by introducing --copy-ownership= which will default to the
current behavior but allows users to explicitly enable/disable copying
of ownership.
2026-02-12 09:45:13 +01:00
DaanDeMeyer
ef155c909a dissect: Make --mount/--unmount/--with work unprivileged
Let's check for CAP_SYS_ADMIN instead of root for these, and make
unmounting more graceful if we can't access the backing loop device
because of permission issues. This allows mounting and unmounting images
from an unprvileged mount namespace. The actual files in the image will
end up owned by nobody:nobody because we'll be in an unprivileged user
namespace, but assuming the directory permissions are not too strict, this
still allows interacting with the image in useful ways.
2026-02-12 09:43:07 +01:00
DaanDeMeyer
fe0114db8e dissect: Fix wrong errno passed to log message 2026-02-12 09:43:07 +01:00
DaanDeMeyer
a0a4e9d7d2 dissect: Fix segmentation fault if loop device is not provided 2026-02-12 09:43:07 +01:00
DaanDeMeyer
53d1d6e49c dissect: Fix logging in (with) 2026-02-12 09:43:07 +01:00
DaanDeMeyer
09e38c2709 dissect: Don't use private userns for --copy-to/--copy-from
These actions interact with the host. The former needs privileges to
write into the image, the latter needs privileges to write on the host.
Neither will have the privileges required if the image is attached under
a private userns, hence, don't use one.
2026-02-12 09:43:07 +01:00
12 changed files with 241 additions and 69 deletions

View File

@ -699,6 +699,26 @@ 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,6 +1194,10 @@ 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,6 +551,19 @@
<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,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");

View File

@ -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|

View File

@ -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|

View File

@ -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;

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, 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 */

View File

@ -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;
}

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", 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);

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, 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);

View File

@ -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