diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 332471a409..ca69b36c22 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -2302,7 +2302,7 @@ static int run(int argc, char *argv[]) { /* 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, UINT64_C(0x10000)); /* allocate 64K users by default */ + 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/export.c b/src/import/export.c index d891ca1816..bbea5b0343 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -207,7 +207,9 @@ static int help(int argc, char *argv[], void *userdata) { " --version Show package version\n" " --format=FORMAT Select format\n" " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n", + " portable)\n" + " --system Operate in per-system mode\n" + " --user Operate in per-user mode\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -223,6 +225,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_VERSION = 0x100, ARG_FORMAT, ARG_CLASS, + ARG_SYSTEM, + ARG_USER, }; static const struct option options[] = { @@ -230,6 +234,8 @@ static int parse_argv(int argc, char *argv[]) { { "version", no_argument, NULL, ARG_VERSION }, { "format", required_argument, NULL, ARG_FORMAT }, { "class", required_argument, NULL, ARG_CLASS }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, {} }; @@ -262,6 +268,14 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case '?': return -EINVAL; diff --git a/src/import/import-common.c b/src/import/import-common.c index 31bc7c95df..f3d70c6f0f 100644 --- a/src/import/import-common.c +++ b/src/import/import-common.c @@ -6,21 +6,26 @@ #include "sd-event.h" #include "capability-util.h" +#include "copy.h" #include "dirent-util.h" +#include "dissect-image.h" #include "fd-util.h" #include "fs-util.h" #include "import-common.h" #include "libarchive-util.h" #include "log.h" +#include "namespace-util.h" +#include "nsresource.h" #include "os-util.h" #include "pidref.h" #include "process-util.h" +#include "rm-rf.h" #include "selinux-util.h" #include "stat-util.h" #include "tar-util.h" #include "tmpfile-util.h" -int import_fork_tar_x(int tree_fd, PidRef *ret_pid) { +int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid) { int r; assert(tree_fd >= 0); @@ -41,7 +46,7 @@ int import_fork_tar_x(int tree_fd, PidRef *ret_pid) { r = pidref_safe_fork_full( "tar-x", /* stdio_fds= */ NULL, - (int[]) { tree_fd, pipefd[0] }, 2, + (int[]) { tree_fd, pipefd[0], userns_fd }, userns_fd >= 0 ? 3 : 2, FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG, ret_pid); if (r < 0) @@ -58,12 +63,20 @@ int import_fork_tar_x(int tree_fd, PidRef *ret_pid) { /* Child */ + if (userns_fd >= 0) { + r = detach_mount_namespace_userns(userns_fd); + if (r < 0) { + log_error_errno(r, "Failed to join user namespace: %m"); + _exit(EXIT_FAILURE); + } + } + if (unshare(CLONE_NEWNET) < 0) - log_warning_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); + log_debug_errno(errno, "Failed to lock tar into network namespace, ignoring: %m"); r = capability_bounding_set_drop(retain, true); if (r < 0) - log_warning_errno(r, "Failed to drop capabilities, ignoring: %m"); + log_debug_errno(r, "Failed to drop capabilities, ignoring: %m"); if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) log_warning_errno(errno, "Failed to enable PR_SET_NO_NEW_PRIVS, ignoring: %m"); @@ -291,3 +304,147 @@ int import_allocate_event_with_signals(sd_event **ret) { *ret = TAKE_PTR(event); return 0; } + +int import_make_foreign_userns(int *userns_fd) { + assert(userns_fd); + + if (*userns_fd >= 0) + return 0; + + *userns_fd = nsresource_allocate_userns(/* name= */ NULL, NSRESOURCE_UIDS_64K); /* allocate 64K users */ + if (*userns_fd < 0) + return log_error_errno(*userns_fd, "Failed to allocate transient user namespace: %m"); + + return 1; +} + +int import_copy_foreign( + int source_fd, + int target_fd, + int *userns_fd) { + + int r; + + assert(source_fd >= 0); + assert(target_fd >= 0); + assert(userns_fd); + + /* Copies dir referenced by source_fd into dir referenced by source_fd, moves to the specified userns + * for that (allocated if needed), which should be foreign UID range */ + + r = import_make_foreign_userns(userns_fd); + if (r < 0) + return r; + + r = safe_fork_full( + "copy-tree", + /* stdio_fds= */ NULL, + (int[]) { *userns_fd, source_fd, target_fd }, 3, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT, + /* ret_pid= */ NULL); + if (r < 0) + return r; + if (r == 0) { + r = namespace_enter( + /* pidns_fd= */ -EBADF, + /* mntns_fd= */ -EBADF, + /* netns_fd= */ -EBADF, + *userns_fd, + /* root_fd= */ -EBADF); + if (r < 0) { + log_error_errno(r, "Failed to join user namespace: %m"); + _exit(EXIT_FAILURE); + } + + r = copy_tree_at( + source_fd, /* from= */ NULL, + target_fd, /* to = */ NULL, + /* override_uid= */ UID_INVALID, + /* override_gid= */ GID_INVALID, + COPY_REFLINK|COPY_HARDLINKS|COPY_MERGE_EMPTY|COPY_MERGE_APPLY_STAT|COPY_SAME_MOUNT|COPY_ALL_XATTRS, + /* denylist= */ NULL, + /* subvolumes= */ NULL); + if (r < 0) { + log_error_errno(r, "Failed to copy tree: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + return 0; +} + +int import_remove_tree_foreign(const char *path, int *userns_fd) { + int r; + + assert(path); + assert(userns_fd); + + r = import_make_foreign_userns(userns_fd); + if (r < 0) + return r; + + _cleanup_close_ int tree_fd = -EBADF; + r = mountfsd_mount_directory( + path, + *userns_fd, + DISSECT_IMAGE_FOREIGN_UID, + &tree_fd); + if (r < 0) + return r; + + r = safe_fork_full( + "rm-tree", + /* stdio_fds= */ NULL, + (int[]) { *userns_fd, tree_fd }, 2, + FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_REOPEN_LOG|FORK_WAIT, + /* ret_pid= */ NULL); + if (r < 0) + return r; + if (r == 0) { + /* child */ + + r = namespace_enter( + /* pidns_fd= */ -EBADF, + /* mntns_fd= */ -EBADF, + /* netns_fd= */ -EBADF, + *userns_fd, + /* root_fd= */ -EBADF); + if (r < 0) { + log_error_errno(r, "Failed to join user namespace: %m"); + _exit(EXIT_FAILURE); + } + + _cleanup_close_ int dfd = fd_reopen(tree_fd, O_DIRECTORY|O_CLOEXEC); + if (dfd < 0) { + log_error_errno(r, "Failed to reopen tree fd: %m"); + _exit(EXIT_FAILURE); + } + + r = rm_rf_children(dfd, REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_CHMOD, /* root_dev= */ NULL); + if (r < 0) + log_warning_errno(r, "Failed to empty '%s' directory in foreign UID mode, ignoring: %m", path); + + _exit(EXIT_SUCCESS); + } + + return 0; +} + +int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags) { + int r; + + assert(path); + assert(userns_fd); + + /* Try the userns dance first, to remove foreign UID range owned trees */ + if (FLAGS_SET(flags, IMPORT_FOREIGN_UID)) + (void) import_remove_tree_foreign(path, userns_fd); + + r = rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME|REMOVE_MISSING_OK|REMOVE_CHMOD); + if (r < 0) + return log_error_errno(r, "Failed to remove '%s': %m", path); + + return 0; +} diff --git a/src/import/import-common.h b/src/import/import-common.h index 55d00ea466..6c6ac655b0 100644 --- a/src/import/import-common.h +++ b/src/import/import-common.h @@ -15,15 +15,16 @@ typedef enum ImportFlags { IMPORT_CONVERT_QCOW2 = 1 << 5, /* raw: if we detect a qcow2 image, unpack it */ IMPORT_DIRECT = 1 << 6, /* import without rename games */ IMPORT_SYNC = 1 << 7, /* fsync() right before we are done */ + IMPORT_FOREIGN_UID = 1 << 8, /* tar: go via nsresourced/mountfsd and make owned by foreign UID */ /* When pulling these flags are defined too */ - IMPORT_PULL_SETTINGS = 1 << 8, /* download .nspawn settings file */ - IMPORT_PULL_ROOTHASH = 1 << 9, /* only for raw: download .roothash file for verity */ - IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 10, /* only for raw: download .roothash.p7s file for verity */ - IMPORT_PULL_VERITY = 1 << 11, /* only for raw: download .verity file for verity */ + IMPORT_PULL_SETTINGS = 1 << 9, /* download .nspawn settings file */ + IMPORT_PULL_ROOTHASH = 1 << 10, /* only for raw: download .roothash file for verity */ + IMPORT_PULL_ROOTHASH_SIGNATURE = 1 << 11, /* only for raw: download .roothash.p7s file for verity */ + IMPORT_PULL_VERITY = 1 << 12, /* only for raw: download .verity file for verity */ /* The supported flags for the tar and the raw importing */ - IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC, + IMPORT_FLAGS_MASK_TAR = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_BTRFS_SUBVOL|IMPORT_BTRFS_QUOTA|IMPORT_DIRECT|IMPORT_SYNC|IMPORT_FOREIGN_UID, IMPORT_FLAGS_MASK_RAW = IMPORT_FORCE|IMPORT_READ_ONLY|IMPORT_CONVERT_QCOW2|IMPORT_DIRECT|IMPORT_SYNC, /* The supported flags for the tar and the raw pulling */ @@ -34,7 +35,7 @@ typedef enum ImportFlags { } ImportFlags; int import_fork_tar_c(const char *path, PidRef *ret); -int import_fork_tar_x(int tree_fd, PidRef *ret_pid); +int import_fork_tar_x(int tree_fd, int userns_fd, PidRef *ret_pid); int import_mangle_os_tree(const char *path); @@ -42,4 +43,11 @@ bool import_validate_local(const char *name, ImportFlags flags); int import_allocate_event_with_signals(sd_event **ret); +int import_make_foreign_userns(int *userns_fd); + +int import_copy_foreign(int source_fd, int target_fd, int *userns_fd); + +int import_remove_tree_foreign(const char *path, int *userns_fd); +int import_remove_tree(const char *path, int *userns_fd, ImportFlags flags); + #define IMPORT_BUFFER_SIZE (128U*1024U) diff --git a/src/import/import-fs.c b/src/import/import-fs.c index 783b0461ef..537fe46853 100644 --- a/src/import/import-fs.c +++ b/src/import/import-fs.c @@ -35,10 +35,12 @@ static bool arg_btrfs_subvol = true; static bool arg_btrfs_quota = true; static bool arg_sync = true; static bool arg_direct = false; -static const char *arg_image_root = NULL; +static char *arg_image_root = NULL; static ImageClass arg_class = IMAGE_MACHINE; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; +STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep); + typedef struct ProgressInfo { RateLimit limit; char *path; @@ -280,7 +282,9 @@ static int help(int argc, char *argv[], void *userdata) { " subvolume\n" " --sync=BOOL Controls whether to sync() before completing\n" " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n", + " portable)\n" + " --system Operate in per-system mode\n" + " --user Operate in per-user mode\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -302,6 +306,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_BTRFS_QUOTA, ARG_SYNC, ARG_CLASS, + ARG_SYSTEM, + ARG_USER, }; static const struct option options[] = { @@ -315,6 +321,8 @@ static int parse_argv(int argc, char *argv[]) { { "btrfs-quota", required_argument, NULL, ARG_BTRFS_QUOTA }, { "sync", required_argument, NULL, ARG_SYNC }, { "class", required_argument, NULL, ARG_CLASS }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, {} }; @@ -338,7 +346,10 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_IMAGE_ROOT: - arg_image_root = optarg; + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + if (r < 0) + return r; + break; case ARG_READ_ONLY: @@ -377,6 +388,14 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case '?': return -EINVAL; @@ -384,8 +403,11 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } - if (!arg_image_root) - arg_image_root = image_root_to_string(arg_class); + if (!arg_image_root) { + r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root); + if (r < 0) + return log_error_errno(r, "Failed to pick image root: %m"); + } return 1; } diff --git a/src/import/import-generator.c b/src/import/import-generator.c index bf5f41a747..4aab1618d3 100644 --- a/src/import/import-generator.c +++ b/src/import/import-generator.c @@ -17,6 +17,7 @@ #include "parse-util.h" #include "path-util.h" #include "proc-cmdline.h" +#include "runtime-scope.h" #include "specifier.h" #include "string-util.h" #include "unit-name.h" @@ -200,7 +201,10 @@ static int parse_pull_expression(const char *v) { if (!GREEDY_REALLOC(arg_transfers, arg_n_transfers + 1)) return log_oom(); - const char *image_root = runtime ? image_root_runtime_to_string(class) : image_root_to_string(class); + _cleanup_free_ char *image_root = NULL; + r = image_root_pick(RUNTIME_SCOPE_SYSTEM, class, runtime, &image_root); + if (r < 0) + return log_error_errno(r, "Failed to pick image root: %m"); _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL; r = sd_json_buildo( @@ -220,7 +224,7 @@ static int parse_pull_expression(const char *v) { .type = type, .local = TAKE_PTR(local), .remote = TAKE_PTR(remote), - .image_root = image_root, + .image_root = TAKE_PTR(image_root), .json = TAKE_PTR(j), .blockdev = blockdev, }; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 7e5499b2b9..85fbf5abd1 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -7,6 +7,7 @@ #include "alloc-util.h" #include "btrfs-util.h" +#include "dissect-image.h" #include "errno-util.h" #include "fd-util.h" #include "format-util.h" @@ -46,6 +47,7 @@ typedef struct TarImport { int input_fd; int tar_fd; int tree_fd; + int userns_fd; ImportCompress compress; @@ -73,7 +75,10 @@ TarImport* tar_import_unref(TarImport *i) { pidref_done_sigkill_wait(&i->tar_pid); - rm_rf_subvolume_and_free(i->temp_path); + if (i->temp_path) { + import_remove_tree(i->temp_path, &i->userns_fd, i->flags); + free(i->temp_path); + } import_compress_free(&i->compress); @@ -81,6 +86,7 @@ TarImport* tar_import_unref(TarImport *i) { safe_close(i->tar_fd); safe_close(i->tree_fd); + safe_close(i->userns_fd); free(i->final_path); free(i->image_root); @@ -114,6 +120,7 @@ int tar_import_new( .input_fd = -EBADF, .tar_fd = -EBADF, .tree_fd = -EBADF, + .userns_fd = -EBADF, .on_finished = on_finished, .userdata = userdata, .last_percent = UINT_MAX, @@ -200,8 +207,8 @@ static int tar_import_finish(TarImport *i) { AT_FDCWD, d, AT_FDCWD, i->final_path, (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) return log_error_errno(r, "Failed to move '%s' into place: %m", i->final_path ?: i->local); @@ -244,26 +251,41 @@ static int tar_import_fork_tar(TarImport *i) { if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE)) (void) rm_rf(d, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (i->flags & IMPORT_BTRFS_SUBVOL) - r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755); - else - r = RET_NERRNO(mkdir(d, 0755)); - if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, - * because in that case our temporary path collided */ - r = 0; - if (r < 0) - return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d); - if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ - if (!(i->flags & IMPORT_DIRECT)) - (void) import_assign_pool_quota_and_warn(root); - (void) import_assign_pool_quota_and_warn(d); + if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) { + r = import_make_foreign_userns(&i->userns_fd); + if (r < 0) + return r; + + _cleanup_close_ int directory_fd = -EBADF; + r = mountfsd_make_directory(d, /* flags= */ 0, &directory_fd); + if (r < 0) + return r; + + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd); + if (r < 0) + return r; + } else { + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_make_fallback(AT_FDCWD, d, 0755); + else + r = RET_NERRNO(mkdir(d, 0755)); + if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, + * because in that case our temporary path collided */ + r = 0; + if (r < 0) + return log_error_errno(r, "Failed to create directory/subvolume %s: %m", d); + if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ + if (!(i->flags & IMPORT_DIRECT)) + (void) import_assign_pool_quota_and_warn(root); + (void) import_assign_pool_quota_and_warn(d); + } + + i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (i->tree_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", d); } - i->tree_fd = open(d, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (i->tree_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", d); - - i->tar_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid); + i->tar_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid); if (i->tar_fd < 0) return i->tar_fd; diff --git a/src/import/import.c b/src/import/import.c index 9b458dff9f..5276f26977 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -26,12 +26,14 @@ #include "string-util.h" #include "verbs.h" -static const char *arg_image_root = NULL; +static char *arg_image_root = NULL; static ImportFlags arg_import_flags = IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC; static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX; static ImageClass arg_class = IMAGE_MACHINE; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; +STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep); + static int normalize_local(const char *local, char **ret) { _cleanup_free_ char *ll = NULL; int r; @@ -290,7 +292,9 @@ static int help(int argc, char *argv[], void *userdata) { " --offset=BYTES Offset to seek to in destination\n" " --size-max=BYTES Maximum number of bytes to write to destination\n" " --class=CLASS Select image class (machine, sysext, confext,\n" - " portable)\n", + " portable)\n" + " --system Operate in per-system mode\n" + " --user Operate in per-user mode\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -315,6 +319,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_OFFSET, ARG_SIZE_MAX, ARG_CLASS, + ARG_SYSTEM, + ARG_USER, }; static const struct option options[] = { @@ -331,6 +337,8 @@ static int parse_argv(int argc, char *argv[]) { { "offset", required_argument, NULL, ARG_OFFSET }, { "size-max", required_argument, NULL, ARG_SIZE_MAX }, { "class", required_argument, NULL, ARG_CLASS }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, {} }; @@ -354,7 +362,10 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_IMAGE_ROOT: - arg_image_root = optarg; + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + if (r < 0) + return r; + break; case ARG_READ_ONLY: @@ -430,6 +441,14 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case '?': return -EINVAL; @@ -446,8 +465,14 @@ static int parse_argv(int argc, char *argv[]) { if (arg_offset != UINT64_MAX && !FLAGS_SET(arg_import_flags, IMPORT_DIRECT)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File offset only supported in --direct mode."); - if (!arg_image_root) - arg_image_root = image_root_to_string(arg_class); + if (!arg_image_root) { + r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root); + if (r < 0) + return log_error_errno(r, "Failed to pick image root: %m"); + } + + if (arg_runtime_scope == RUNTIME_SCOPE_USER) + arg_import_flags |= IMPORT_FOREIGN_UID; return 1; } diff --git a/src/import/importctl.c b/src/import/importctl.c index 94140469c4..d131943413 100644 --- a/src/import/importctl.c +++ b/src/import/importctl.c @@ -45,19 +45,28 @@ static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; static const char* arg_format = NULL; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static ImageClass arg_image_class = _IMAGE_CLASS_INVALID; +static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; #define PROGRESS_PREFIX "Total:" static int settle_image_class(void) { + int r; if (arg_image_class < 0) { _cleanup_free_ char *j = NULL; - for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++) + for (ImageClass class = 0; class < _IMAGE_CLASS_MAX; class++) { + _cleanup_free_ char *root = NULL; + + r = image_root_pick(arg_runtime_scope, class, /* runtime= */ false, &root); + if (r < 0) + return log_error_errno(r, "Failed to pick image root: %m"); + if (strextendf_with_separator(&j, ", ", "%s (downloads to %s/)", image_class_to_string(class), - image_root_to_string(class)) < 0) + root) < 0) return log_oom(); + } return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No image class specified, retry with --class= set to one of: %s.", j); @@ -1014,6 +1023,8 @@ static int help(int argc, char *argv[], void *userdata) { " --no-ask-password Do not ask for system passwords\n" " -H --host=[USER@]HOST Operate on remote host\n" " -M --machine=CONTAINER Operate on local container\n" + " --system Connect to system machine manager\n" + " --user Connect to user machine manager\n" " --read-only Create read-only image\n" " -q --quiet Suppress output\n" " --json=pretty|short|off Generate JSON output\n" @@ -1055,6 +1066,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_FORMAT, ARG_CLASS, ARG_KEEP_DOWNLOAD, + ARG_SYSTEM, + ARG_USER, }; static const struct option options[] = { @@ -1073,6 +1086,8 @@ static int parse_argv(int argc, char *argv[]) { { "format", required_argument, NULL, ARG_FORMAT }, { "class", required_argument, NULL, ARG_CLASS }, { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, {} }; @@ -1198,6 +1213,14 @@ static int parse_argv(int argc, char *argv[]) { arg_import_flags_mask |= IMPORT_PULL_KEEP_DOWNLOAD; break; + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + case '?': return -EINVAL; @@ -1240,9 +1263,9 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; - r = bus_connect_transport(arg_transport, arg_host, RUNTIME_SCOPE_SYSTEM, &bus); + r = bus_connect_transport(arg_transport, arg_host, arg_runtime_scope, &bus); if (r < 0) - return bus_log_connect_error(r, arg_transport, RUNTIME_SCOPE_SYSTEM); + return bus_log_connect_error(r, arg_transport, arg_runtime_scope); (void) sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); diff --git a/src/import/importd.c b/src/import/importd.c index e9923d93ad..99b8af580d 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -32,6 +32,7 @@ #include "notify-recv.h" #include "os-util.h" #include "parse-util.h" +#include "path-lookup.h" #include "percent-util.h" #include "pidref.h" #include "process-util.h" @@ -100,7 +101,8 @@ typedef struct Transfer { typedef struct Manager { sd_event *event; - sd_bus *bus; + sd_bus *api_bus; + sd_bus *system_bus; sd_varlink_server *varlink_server; uint32_t current_transfer_id; @@ -113,7 +115,7 @@ typedef struct Manager { bool use_btrfs_subvol; bool use_btrfs_quota; - RuntimeScope runtime_scope; /* for now: always RUNTIME_SCOPE_SYSTEM */ + RuntimeScope runtime_scope; } Manager; #define TRANSFERS_MAX 64 @@ -223,7 +225,7 @@ static void transfer_send_log_line(Transfer *t, const char *line) { log_full(priority, "(transfer%" PRIu32 ") %s", t->id, line); r = sd_bus_emit_signal( - t->manager->bus, + t->manager->api_bus, t->object_path, "org.freedesktop.import1.Transfer", "LogMessage", @@ -254,7 +256,7 @@ static void transfer_send_progress_update(Transfer *t) { double progress = transfer_percent_as_double(t); r = sd_bus_emit_signal( - t->manager->bus, + t->manager->api_bus, t->object_path, "org.freedesktop.import1.Transfer", "ProgressUpdate", @@ -335,7 +337,7 @@ static int transfer_finalize(Transfer *t, bool success) { transfer_send_logs(t, true); r = sd_bus_emit_signal( - t->manager->bus, + t->manager->api_bus, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", "TransferRemoved", @@ -443,6 +445,7 @@ static int transfer_start(Transfer *t) { const char *cmd[] = { NULL, /* systemd-import, systemd-import-fs, systemd-export or systemd-pull */ NULL, /* tar, raw */ + NULL, /* --system or --user */ NULL, /* --verify= */ NULL, /* verify argument */ NULL, /* --class= */ @@ -524,6 +527,8 @@ static int transfer_start(Transfer *t) { ; } + cmd[k++] = runtime_scope_cmdline_option_to_string(t->manager->runtime_scope); + if (t->verify != _IMPORT_VERIFY_INVALID) { cmd[k++] = "--verify"; cmd[k++] = import_verify_to_string(t->verify); @@ -602,7 +607,7 @@ static int transfer_start(Transfer *t) { return r; r = sd_bus_emit_signal( - t->manager->bus, + t->manager->api_bus, "/org/freedesktop/import1", "org.freedesktop.import1.Manager", "TransferNew", @@ -630,7 +635,8 @@ static Manager *manager_unref(Manager *m) { hashmap_free(m->polkit_registry); - m->bus = sd_bus_flush_close_unref(m->bus); + m->api_bus = sd_bus_flush_close_unref(m->api_bus); + m->system_bus = sd_bus_flush_close_unref(m->system_bus); m->varlink_server = sd_varlink_server_unref(m->varlink_server); sd_event_unref(m->event); @@ -681,7 +687,7 @@ static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void return 0; } -static int manager_new(Manager **ret) { +static int manager_new(RuntimeScope scope, Manager **ret) { _cleanup_(manager_unrefp) Manager *m = NULL; int r; @@ -694,7 +700,7 @@ static int manager_new(Manager **ret) { *m = (Manager) { .use_btrfs_subvol = true, .use_btrfs_quota = true, - .runtime_scope = RUNTIME_SCOPE_SYSTEM, + .runtime_scope = scope, }; r = sd_event_default(&m->event); @@ -757,16 +763,18 @@ static int method_import_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ assert(msg); - r = bus_verify_polkit_async( - msg, - "org.freedesktop.import1.import", - /* details= */ NULL, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ + if (m->runtime_scope != RUNTIME_SCOPE_USER) { + r = bus_verify_polkit_async( + msg, + "org.freedesktop.import1.import", + /* details= */ NULL, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + } if (endswith(sd_bus_message_get_member(msg), "Ex")) { const char *sclass; @@ -858,16 +866,18 @@ static int method_import_fs(sd_bus_message *msg, void *userdata, sd_bus_error *e assert(msg); - r = bus_verify_polkit_async( - msg, - "org.freedesktop.import1.import", - /* details= */ NULL, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ + if (m->runtime_scope != RUNTIME_SCOPE_USER) { + r = bus_verify_polkit_async( + msg, + "org.freedesktop.import1.import", + /* details= */ NULL, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + } if (endswith(sd_bus_message_get_member(msg), "Ex")) { const char *sclass; @@ -956,16 +966,18 @@ static int method_export_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_ assert(msg); - r = bus_verify_polkit_async( - msg, - "org.freedesktop.import1.export", - /* details= */ NULL, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ + if (m->runtime_scope != RUNTIME_SCOPE_USER) { + r = bus_verify_polkit_async( + msg, + "org.freedesktop.import1.export", + /* details= */ NULL, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + } if (endswith(sd_bus_message_get_member(msg), "Ex")) { const char *sclass; @@ -1054,16 +1066,18 @@ static int method_pull_tar_or_raw(sd_bus_message *msg, void *userdata, sd_bus_er assert(msg); - r = bus_verify_polkit_async( - msg, - "org.freedesktop.import1.pull", - /* details= */ NULL, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ + if (m->runtime_scope != RUNTIME_SCOPE_USER) { + r = bus_verify_polkit_async( + msg, + "org.freedesktop.import1.pull", + /* details= */ NULL, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + } if (endswith(sd_bus_message_get_member(msg), "Ex")) { const char *sclass; @@ -1702,28 +1716,43 @@ static int manager_connect_bus(Manager *m) { assert(m); assert(m->event); - assert(!m->bus); + assert(!m->system_bus); + assert(!m->api_bus); - r = bus_open_system_watch_bind(&m->bus); + r = bus_open_system_watch_bind(&m->system_bus); if (r < 0) return log_error_errno(r, "Failed to get system bus connection: %m"); - r = bus_add_implementation(m->bus, &manager_object, m); + r = sd_bus_attach_event(m->system_bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach system bus to event loop: %m"); + + if (m->runtime_scope == RUNTIME_SCOPE_SYSTEM) + m->api_bus = sd_bus_ref(m->system_bus); + else { + assert(m->runtime_scope == RUNTIME_SCOPE_USER); + + r = sd_bus_default_user(&m->api_bus); + if (r < 0) + return log_error_errno(r, "Failed to get user bus connection: %m"); + + r = sd_bus_attach_event(m->api_bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach user bus to event loop: %m"); + } + + r = bus_add_implementation(m->api_bus, &manager_object, m); if (r < 0) return r; - r = bus_log_control_api_register(m->bus); + r = bus_log_control_api_register(m->api_bus); if (r < 0) return r; - r = sd_bus_request_name_async(m->bus, NULL, "org.freedesktop.import1", 0, NULL, NULL); + r = sd_bus_request_name_async(m->api_bus, NULL, "org.freedesktop.import1", 0, NULL, NULL); if (r < 0) return log_error_errno(r, "Failed to request name: %m"); - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - return 0; } @@ -1867,19 +1896,21 @@ static int vl_method_pull(sd_varlink *link, sd_json_variant *parameters, sd_varl if (manager_find(m, tt, p.remote)) return sd_varlink_errorbo(link, "io.systemd.Import.AlreadyInProgress", SD_JSON_BUILD_PAIR_STRING("remote", p.remote)); - r = varlink_verify_polkit_async( - link, - m->bus, - "org.freedesktop.import1.pull", - (const char**) STRV_MAKE( - "remote", p.remote, - "local", p.local, - "class", image_class_to_string(p.class), - "type", import_type_to_string(p.type), - "verify", import_verify_to_string(p.verify)), - &m->polkit_registry); - if (r <= 0) - return r; + if (m->runtime_scope != RUNTIME_SCOPE_USER) { + r = varlink_verify_polkit_async( + link, + m->system_bus, + "org.freedesktop.import1.pull", + (const char**) STRV_MAKE( + "remote", p.remote, + "local", p.local, + "class", image_class_to_string(p.class), + "type", import_type_to_string(p.type), + "verify", import_verify_to_string(p.verify)), + &m->polkit_registry); + if (r <= 0) + return r; + } _cleanup_(transfer_unrefp) Transfer *t = NULL; @@ -1938,9 +1969,11 @@ static int manager_connect_varlink(Manager *m) { assert(m->event); assert(!m->varlink_server); - r = varlink_server_new(&m->varlink_server, - SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA, - m); + r = varlink_server_new( + &m->varlink_server, + (m->runtime_scope != RUNTIME_SCOPE_USER ? SD_VARLINK_SERVER_ACCOUNT_UID : 0)| + SD_VARLINK_SERVER_INHERIT_USERDATA, + m); if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); @@ -1969,7 +2002,12 @@ static int manager_connect_varlink(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to bind to passed Varlink sockets: %m"); if (r == 0) { - r = sd_varlink_server_listen_address(m->varlink_server, "/run/systemd/io.systemd.Import", 0666); + _cleanup_free_ char *socket_path = NULL; + r = runtime_directory_generic(m->runtime_scope, "systemd/io.systemd.Import", &socket_path); + if (r < 0) + return log_error_errno(r, "Failed to determine socket path: %m"); + + r = sd_varlink_server_listen_address(m->varlink_server, socket_path, runtime_scope_to_socket_mode(m->runtime_scope) | SD_VARLINK_SERVER_MODE_MKDIR_0755); if (r < 0) return log_error_errno(r, "Failed to bind to Varlink socket: %m"); } @@ -2009,6 +2047,7 @@ static void manager_parse_env(Manager *m) { static int run(int argc, char *argv[]) { _cleanup_(manager_unrefp) Manager *m = NULL; + RuntimeScope scope = RUNTIME_SCOPE_SYSTEM; int r; log_setup(); @@ -2017,7 +2056,7 @@ static int run(int argc, char *argv[]) { "VM and container image import and export service.", BUS_IMPLEMENTATIONS(&manager_object, &log_control_object), - /* runtime_scope= */ NULL, + &scope, argc, argv); if (r <= 0) return r; @@ -2026,7 +2065,7 @@ static int run(int argc, char *argv[]) { assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD) >= 0); - r = manager_new(&m); + r = manager_new(scope, &m); if (r < 0) return log_error_errno(r, "Failed to allocate manager object: %m"); @@ -2046,7 +2085,7 @@ static int run(int argc, char *argv[]) { r = bus_event_loop_with_idle( m->event, - m->bus, + m->api_bus, "org.freedesktop.import1", DEFAULT_EXIT_USEC, manager_check_idle, diff --git a/src/import/meson.build b/src/import/meson.build index 4aec57373a..3a50d13b0c 100644 --- a/src/import/meson.build +++ b/src/import/meson.build @@ -111,6 +111,9 @@ install_data('org.freedesktop.import1.conf', install_dir : dbuspolicydir) install_data('org.freedesktop.import1.service', install_dir : dbussystemservicedir) +install_data('org.freedesktop.import1.service-for-session', + install_dir : dbussessionservicedir, + rename : 'org.freedesktop.import1.service') install_data('org.freedesktop.import1.policy', install_dir : polkitpolicydir) diff --git a/src/import/org.freedesktop.import1.service-for-session b/src/import/org.freedesktop.import1.service-for-session new file mode 100644 index 0000000000..6e6c4a4c94 --- /dev/null +++ b/src/import/org.freedesktop.import1.service-for-session @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[D-BUS Service] +Name=org.freedesktop.import1 +Exec=/bin/false +SystemdService=dbus-org.freedesktop.import1.service diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index fa44216f8c..cf9e2060da 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -9,11 +9,10 @@ #include "btrfs-util.h" #include "copy.h" #include "curl-util.h" +#include "dissect-image.h" #include "errno-util.h" #include "fd-util.h" #include "fs-util.h" -#include "import-common.h" -#include "import-util.h" #include "install-file.h" #include "log.h" #include "mkdir-label.h" @@ -26,6 +25,7 @@ #include "rm-rf.h" #include "string-util.h" #include "tmpfile-util.h" +#include "uid-classification.h" #include "web-util.h" typedef enum TarProgress { @@ -64,6 +64,7 @@ typedef struct TarPull { char *checksum; int tree_fd; + int userns_fd; } TarPull; TarPull* tar_pull_unref(TarPull *i) { @@ -80,7 +81,10 @@ TarPull* tar_pull_unref(TarPull *i) { curl_glue_unref(i->glue); sd_event_unref(i->event); - rm_rf_subvolume_and_free(i->temp_path); + if (i->temp_path) { + import_remove_tree(i->temp_path, &i->userns_fd, i->flags); + free(i->temp_path); + } unlink_and_free(i->settings_temp_path); free(i->final_path); @@ -90,6 +94,7 @@ TarPull* tar_pull_unref(TarPull *i) { free(i->checksum); safe_close(i->tree_fd); + safe_close(i->userns_fd); return mfree(i); } @@ -138,6 +143,7 @@ int tar_pull_new( .glue = TAKE_PTR(g), .tar_pid = PIDREF_NULL, .tree_fd = -EBADF, + .userns_fd = -EBADF, }; i->glue->on_finished = pull_job_curl_on_finished; @@ -233,6 +239,9 @@ static int tar_pull_make_local_copy(TarPull *i) { if (!i->local) return 0; + /* Creates a copy/clone of the original downloaded version (which is supposed to remain untouched) + * under a local image name (which may then be modified) */ + assert(i->final_path); p = path_join(i->image_root, i->local); @@ -244,18 +253,61 @@ static int tar_pull_make_local_copy(TarPull *i) { if (r < 0) return log_error_errno(r, "Failed to generate temporary filename for %s: %m", p); - if (i->flags & IMPORT_BTRFS_SUBVOL) - r = btrfs_subvol_snapshot_at( - AT_FDCWD, i->final_path, - AT_FDCWD, t, - (i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| - BTRFS_SNAPSHOT_FALLBACK_COPY| - BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| - BTRFS_SNAPSHOT_RECURSIVE); - else - r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to create local image: %m"); + if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) { + /* Copy in userns */ + + r = import_make_foreign_userns(&i->userns_fd); + if (r < 0) + return r; + + /* Usually, tar_pull_job_on_open_disk_tar() would allocate ->tree_fd for us, but if + * already downloaded the image before, and are just making a copy of the original + * download, we need to open ->tree_fd now */ + if (i->tree_fd < 0) { + _cleanup_close_ int directory_fd = open(i->final_path, O_DIRECTORY|O_CLOEXEC); + if (directory_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", i->final_path); + + struct stat st; + if (fstat(directory_fd, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", i->final_path); + + if (uid_is_foreign(st.st_uid)) { + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd); + if (r < 0) + return r; + } else + i->tree_fd = TAKE_FD(directory_fd); + } + + _cleanup_close_ int directory_fd = -EBADF; + r = mountfsd_make_directory(t, /* flags= */ 0, &directory_fd); + if (r < 0) + return r; + + _cleanup_close_ int copy_fd = -EBADF; + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, ©_fd); + if (r < 0) + return r; + + r = import_copy_foreign(i->tree_fd, copy_fd, &i->userns_fd); + if (r < 0) + return r; + } else { + /* Copy locally */ + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_snapshot_at( + AT_FDCWD, i->final_path, + AT_FDCWD, t, + (i->flags & IMPORT_BTRFS_QUOTA ? BTRFS_SNAPSHOT_QUOTA : 0)| + BTRFS_SNAPSHOT_FALLBACK_COPY| + BTRFS_SNAPSHOT_FALLBACK_DIRECTORY| + BTRFS_SNAPSHOT_RECURSIVE); + else + r = copy_tree(i->final_path, t, UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_HARDLINKS, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to create original download image: %m"); + } source = t; } else @@ -264,8 +316,8 @@ static int tar_pull_make_local_copy(TarPull *i) { r = install_file(AT_FDCWD, source, AT_FDCWD, p, (i->flags & IMPORT_FORCE ? INSTALL_REPLACE : 0) | - (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) return log_error_errno(r, "Failed to install local image '%s': %m", p); @@ -427,8 +479,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->local, AT_FDCWD, NULL, - (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_READ_ONLY ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) { log_error_errno(r, "Failed to finalize '%s': %m", i->local); goto finish; @@ -453,8 +505,8 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->temp_path, AT_FDCWD, i->final_path, - (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY : 0) | - (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS : 0)); + (i->flags & IMPORT_PULL_KEEP_DOWNLOAD ? INSTALL_READ_ONLY|INSTALL_GRACEFUL : 0) | + (i->flags & IMPORT_SYNC ? INSTALL_SYNCFS|INSTALL_GRACEFUL : 0)); if (r < 0) { log_error_errno(r, "Failed to rename to final image name to %s: %m", i->final_path); goto finish; @@ -480,7 +532,7 @@ static void tar_pull_job_on_finished(PullJob *j) { r = install_file( AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path, - INSTALL_READ_ONLY| + INSTALL_READ_ONLY|INSTALL_GRACEFUL| (i->flags & IMPORT_SYNC ? INSTALL_FSYNC_FULL : 0)); if (r < 0) { log_error_errno(r, "Failed to rename settings file to %s: %m", i->settings_path); @@ -537,26 +589,42 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { if (FLAGS_SET(i->flags, IMPORT_DIRECT|IMPORT_FORCE)) (void) rm_rf(where, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (i->flags & IMPORT_BTRFS_SUBVOL) - r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755); - else - r = RET_NERRNO(mkdir(where, 0755)); - if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, - * because in that case our temporary path collided */ - r = 0; - if (r < 0) - return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where); - if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ - if (!(i->flags & IMPORT_DIRECT)) - (void) import_assign_pool_quota_and_warn(i->image_root); - (void) import_assign_pool_quota_and_warn(where); + if (FLAGS_SET(i->flags, IMPORT_FOREIGN_UID)) { + r = import_make_foreign_userns(&i->userns_fd); + if (r < 0) + return r; + + _cleanup_close_ int directory_fd = -EBADF; + r = mountfsd_make_directory(where, /* flags= */ 0, &directory_fd); + if (r < 0) + return r; + + r = mountfsd_mount_directory_fd(directory_fd, i->userns_fd, DISSECT_IMAGE_FOREIGN_UID, &i->tree_fd); + if (r < 0) + return r; + } else { + if (i->flags & IMPORT_BTRFS_SUBVOL) + r = btrfs_subvol_make_fallback(AT_FDCWD, where, 0755); + else + r = RET_NERRNO(mkdir(where, 0755)); + if (r == -EEXIST && (i->flags & IMPORT_DIRECT)) /* EEXIST is OK if in direct mode, but not otherwise, + * because in that case our temporary path collided */ + r = 0; + if (r < 0) + return log_error_errno(r, "Failed to create directory/subvolume %s: %m", where); + + if (r > 0 && (i->flags & IMPORT_BTRFS_QUOTA)) { /* actually btrfs subvol */ + if (!(i->flags & IMPORT_DIRECT)) + (void) import_assign_pool_quota_and_warn(i->image_root); + (void) import_assign_pool_quota_and_warn(where); + } + + i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (i->tree_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", where); } - i->tree_fd = open(where, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (i->tree_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", where); - - j->disk_fd = import_fork_tar_x(i->tree_fd, &i->tar_pid); + j->disk_fd = import_fork_tar_x(i->tree_fd, i->userns_fd, &i->tar_pid); if (j->disk_fd < 0) return j->disk_fd; diff --git a/src/import/pull.c b/src/import/pull.c index 41193e4397..a1b9b940fd 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -28,7 +28,7 @@ #include "verbs.h" #include "web-util.h" -static const char *arg_image_root = NULL; +static char *arg_image_root = NULL; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; static ImportFlags arg_import_flags = IMPORT_PULL_SETTINGS | IMPORT_PULL_ROOTHASH | IMPORT_PULL_ROOTHASH_SIGNATURE | IMPORT_PULL_VERITY | IMPORT_BTRFS_SUBVOL | IMPORT_BTRFS_QUOTA | IMPORT_CONVERT_QCOW2 | IMPORT_SYNC; static uint64_t arg_offset = UINT64_MAX, arg_size_max = UINT64_MAX; @@ -37,6 +37,7 @@ static ImageClass arg_class = IMAGE_MACHINE; static RuntimeScope arg_runtime_scope = _RUNTIME_SCOPE_INVALID; STATIC_DESTRUCTOR_REGISTER(arg_checksum, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image_root, freep); static int normalize_local(const char *local, const char *url, char **ret) { _cleanup_free_ char *ll = NULL; @@ -275,7 +276,9 @@ static int help(int argc, char *argv[], void *userdata) { " --class=CLASS Select image class (machine, sysext, confext,\n" " portable)\n" " --keep-download=BOOL Keep a copy pristine copy of the downloaded file\n" - " around\n", + " around\n" + " --system Operate in per-system mode\n" + " --user Operate in per-user mode\n", program_invocation_short_name, ansi_underline(), ansi_normal(), @@ -306,6 +309,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_SIZE_MAX, ARG_CLASS, ARG_KEEP_DOWNLOAD, + ARG_SYSTEM, + ARG_USER, }; static const struct option options[] = { @@ -328,6 +333,8 @@ static int parse_argv(int argc, char *argv[]) { { "size-max", required_argument, NULL, ARG_SIZE_MAX }, { "class", required_argument, NULL, ARG_CLASS }, { "keep-download", required_argument, NULL, ARG_KEEP_DOWNLOAD }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, {} }; @@ -352,7 +359,10 @@ static int parse_argv(int argc, char *argv[]) { break; case ARG_IMAGE_ROOT: - arg_image_root = optarg; + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image_root); + if (r < 0) + return r; + break; case ARG_VERIFY: { @@ -508,6 +518,14 @@ static int parse_argv(int argc, char *argv[]) { auto_keep_download = false; break; + case ARG_SYSTEM: + arg_runtime_scope = RUNTIME_SCOPE_SYSTEM; + break; + + case ARG_USER: + arg_runtime_scope = RUNTIME_SCOPE_USER; + break; + case '?': return -EINVAL; @@ -527,8 +545,11 @@ static int parse_argv(int argc, char *argv[]) { if (arg_checksum && (arg_import_flags & (IMPORT_PULL_SETTINGS|IMPORT_PULL_ROOTHASH|IMPORT_PULL_ROOTHASH_SIGNATURE|IMPORT_PULL_VERITY)) != 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Literal checksum verification only supported if no associated files are downloaded."); - if (!arg_image_root) - arg_image_root = image_root_to_string(arg_class); + if (!arg_image_root) { + r = image_root_pick(arg_runtime_scope < 0 ? RUNTIME_SCOPE_SYSTEM : arg_runtime_scope, arg_class, /* runtime= */ false, &arg_image_root); + if (r < 0) + return log_error_errno(r, "Failed to pick image root: %m"); + } /* .nspawn settings files only really make sense for machine images, not for sysext/confext/portable */ if (auto_settings && arg_class != IMAGE_MACHINE) @@ -539,6 +560,9 @@ static int parse_argv(int argc, char *argv[]) { if (auto_keep_download) SET_FLAG(arg_import_flags, IMPORT_PULL_KEEP_DOWNLOAD, arg_class == IMAGE_MACHINE); + if (arg_runtime_scope == RUNTIME_SCOPE_USER) + arg_import_flags |= IMPORT_FOREIGN_UID; + return 1; } diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 3e4565e4a2..5de826c44a 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -6007,7 +6007,7 @@ static int run(int argc, char *argv[]) { goto finish; } - userns_fd = nsresource_allocate_userns(userns_name, UINT64_C(0x10000)); + userns_fd = nsresource_allocate_userns(userns_name, NSRESOURCE_UIDS_64K); /* allocate 64K UIDs */ if (userns_fd < 0) { r = log_error_errno(userns_fd, "Failed to allocate user namespace with 64K users: %m"); goto finish; @@ -6019,7 +6019,7 @@ static int run(int argc, char *argv[]) { goto finish; } - arg_uid_range = UINT32_C(0x10000); + arg_uid_range = NSRESOURCE_UIDS_64K; } if (arg_directory) { diff --git a/src/shared/copy.c b/src/shared/copy.c index 981427214b..054a01e86b 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -657,7 +657,10 @@ static int hardlink_context_setup( return -errno; } - r = tempfn_random_child(to, "hardlink", &c->subdir); + if (to) + r = tempfn_random_child(to, "hardlink", &c->subdir); + else + r = tempfn_random("hardlink", /* extra= */ NULL, &c->subdir); if (r < 0) return r; @@ -1127,7 +1130,6 @@ static int fd_copy_directory( int r; assert(st); - assert(to); if (depth_left == 0) return -ENAMETOOLONG; @@ -1257,7 +1259,7 @@ static int fd_copy_directory( } finish: - if (!exists) { + if (FLAGS_SET(copy_flags, COPY_MERGE_APPLY_STAT) || !exists) { if (fchown(fdt, uid_is_valid(override_uid) ? override_uid : st->st_uid, gid_is_valid(override_gid) ? override_gid : st->st_gid) < 0) @@ -1345,6 +1347,10 @@ static int fd_copy_tree_generic( override_gid, copy_flags, denylist, subvolumes, hardlink_context, display_path, progress_path, progress_bytes, userdata); + /* Only if we are copying a directory we are fine if the target dir is referenced by fd only */ + if (!to) + return -ENOTDIR; + DenyType t = PTR_TO_INT(hashmap_get(denylist, st)); if (t == DENY_INODE) { log_debug("%s is in the denylist, ignoring", from ?: "file to copy"); @@ -1382,7 +1388,6 @@ int copy_tree_at_full( struct stat st; int r; - assert(to); assert(!FLAGS_SET(copy_flags, COPY_LOCK_BSD)); if (fstatat(fdf, strempty(from), &st, AT_SYMLINK_NOFOLLOW | (isempty(from) ? AT_EMPTY_PATH : 0)) < 0) diff --git a/src/shared/copy.h b/src/shared/copy.h index d4b131d102..8dd3176e8e 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -33,6 +33,7 @@ typedef enum CopyFlags { */ COPY_NOCOW_AFTER = 1 << 20, COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */ + COPY_MERGE_APPLY_STAT = 1 << 22, /* When we reuse an existing directory inode, apply source ownership/mode/xattrs/timestamps */ } CopyFlags; typedef enum DenyType { diff --git a/src/shared/discover-image.c b/src/shared/discover-image.c index 15875968b7..53938a1256 100644 --- a/src/shared/discover-image.c +++ b/src/shared/discover-image.c @@ -110,7 +110,7 @@ static const char *const image_root_table[_IMAGE_CLASS_MAX] = { [IMAGE_CONFEXT] = "/var/lib/confexts", }; -DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root, ImageClass); static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = { [IMAGE_MACHINE] = "/run/machines", @@ -119,7 +119,7 @@ static const char *const image_root_runtime_table[_IMAGE_CLASS_MAX] = { [IMAGE_CONFEXT] = "/run/confexts", }; -DEFINE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(image_root_runtime, ImageClass); static const char *const image_dirname_table[_IMAGE_CLASS_MAX] = { [IMAGE_MACHINE] = "machines", @@ -2022,3 +2022,45 @@ static const char* const image_type_table[_IMAGE_TYPE_MAX] = { }; DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType); + +int image_root_pick( + RuntimeScope scope, + ImageClass c, + bool runtime, + char **ret) { + + int r; + + assert(scope >= 0); + assert(scope < _RUNTIME_SCOPE_MAX); + assert(c >= 0); + assert(c < _IMAGE_CLASS_MAX); + assert(ret); + + /* Picks the primary target directory for downloads, depending on invocation contexts */ + + _cleanup_free_ char *s = NULL; + switch (scope) { + + case RUNTIME_SCOPE_SYSTEM: { + s = strdup(runtime ? image_root_runtime_to_string(c) : image_root_to_string(c)); + if (!s) + return -ENOMEM; + + break; + } + + case RUNTIME_SCOPE_USER: + r = sd_path_lookup(runtime ? SD_PATH_USER_RUNTIME : SD_PATH_USER_STATE_PRIVATE, "machines", &s); + if (r < 0) + return r; + + break; + + default: + return -EOPNOTSUPP; + } + + *ret = TAKE_PTR(s); + return 0; +} diff --git a/src/shared/discover-image.h b/src/shared/discover-image.h index 7ba44bfb79..a7370450a8 100644 --- a/src/shared/discover-image.h +++ b/src/shared/discover-image.h @@ -95,8 +95,7 @@ bool image_is_host(const struct Image *i); int image_to_json(const struct Image *i, sd_json_variant **ret); -const char* image_root_to_string(ImageClass c) _const_; -const char* image_root_runtime_to_string(ImageClass c) _const_; +int image_root_pick(RuntimeScope scope, ImageClass c, bool runtime, char **ret); extern const struct hash_ops image_hash_ops; diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 2166af2e7e..0cbb7d02cf 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -4848,14 +4848,17 @@ int mountfsd_mount_image( #endif } -int mountfsd_mount_directory( - const char *path, +int mountfsd_mount_directory_fd( + int directory_fd, int userns_fd, DissectImageFlags flags, int *ret_mount_fd) { int r; + assert(directory_fd >= 0); + assert(ret_mount_fd); + /* Pick one identity, not both, that makes no sense. */ assert(!FLAGS_SET(flags, DISSECT_IMAGE_FOREIGN_UID|DISSECT_IMAGE_IDENTITY_UID)); @@ -4872,10 +4875,6 @@ int mountfsd_mount_directory( if (r < 0) return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); - _cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH); - if (directory_fd < 0) - return log_error_errno(errno, "Failed to open '%s': %m", path); - r = sd_varlink_push_dup_fd(vl, directory_fd); if (r < 0) return log_error_errno(r, "Failed to push directory fd into varlink connection: %m"); @@ -4919,3 +4918,103 @@ int mountfsd_mount_directory( *ret_mount_fd = TAKE_FD(fsmount_fd); return 0; } + +int mountfsd_mount_directory( + const char *path, + int userns_fd, + DissectImageFlags flags, + int *ret_mount_fd) { + + assert(path); + assert(ret_mount_fd); + + _cleanup_close_ int directory_fd = open(path, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_PATH); + if (directory_fd < 0) + return log_error_errno(errno, "Failed to open '%s': %m", path); + + return mountfsd_mount_directory_fd(directory_fd, userns_fd, flags, ret_mount_fd); +} + +int mountfsd_make_directory_fd( + int parent_fd, + const char *name, + DissectImageFlags flags, + int *ret_directory_fd) { + + int r; + + assert(parent_fd >= 0); + assert(name); + assert(ret_directory_fd); + + _cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL; + r = sd_varlink_connect_address(&vl, "/run/systemd/io.systemd.MountFileSystem"); + if (r < 0) + return log_error_errno(r, "Failed to connect to mountfsd: %m"); + + r = sd_varlink_set_allow_fd_passing_input(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for read: %m"); + + r = sd_varlink_set_allow_fd_passing_output(vl, true); + if (r < 0) + return log_error_errno(r, "Failed to enable varlink fd passing for write: %m"); + + r = sd_varlink_push_dup_fd(vl, parent_fd); + if (r < 0) + return log_error_errno(r, "Failed to push parent fd into varlink connection: %m"); + + sd_json_variant *reply = NULL; + const char *error_id = NULL; + r = varlink_callbo_and_log( + vl, + "io.systemd.MountFileSystem.MakeDirectory", + &reply, + &error_id, + SD_JSON_BUILD_PAIR_UNSIGNED("parentFileDescriptor", 0), + SD_JSON_BUILD_PAIR_STRING("name", name), + SD_JSON_BUILD_PAIR_BOOLEAN("allowInteractiveAuthentication", FLAGS_SET(flags, DISSECT_IMAGE_ALLOW_INTERACTIVE_AUTH))); + if (r < 0) + return r; + + static const sd_json_dispatch_field dispatch_table[] = { + { "directoryFileDescriptor", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint, 0, SD_JSON_MANDATORY }, + {} + }; + + unsigned directory_fd_idx = UINT_MAX; + r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS, &directory_fd_idx); + if (r < 0) + return log_error_errno(r, "Failed to parse MountImage() reply: %m"); + + _cleanup_close_ int directory_fd = sd_varlink_take_fd(vl, directory_fd_idx); + if (directory_fd < 0) + return log_error_errno(directory_fd, "Failed to take directory fd from Varlink connection: %m"); + + *ret_directory_fd = TAKE_FD(directory_fd); + return 0; +} + +int mountfsd_make_directory( + const char *path, + DissectImageFlags flags, + int *ret_directory_fd) { + + int r; + + _cleanup_free_ char *parent = NULL; + r = path_extract_directory(path, &parent); + if (r < 0) + return log_error_errno(r, "Failed to extract parent directory from '%s': %m", path); + + _cleanup_free_ char *dirname = NULL; + r = path_extract_filename(path, &dirname); + if (r < 0) + return log_error_errno(r, "Failed to extract directory name from '%s': %m", path); + + _cleanup_close_ int fd = open(parent, O_DIRECTORY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(r, "Failed to open '%s': %m", parent); + + return mountfsd_make_directory_fd(fd, dirname, flags, ret_directory_fd); +} diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index b4353f9c9e..48d2359960 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -258,4 +258,8 @@ static inline const char* dissected_partition_fstype(const DissectedPartition *m int get_common_dissect_directory(char **ret); int mountfsd_mount_image(const char *path, int userns_fd, const ImagePolicy *image_policy, const VeritySettings *verity, DissectImageFlags flags, DissectedImage **ret); +int mountfsd_mount_directory_fd(int directory_fd, int userns_fd, DissectImageFlags flags, int *ret_mount_fd); int mountfsd_mount_directory(const char *path, int userns_fd, DissectImageFlags flags, int *ret_mount_fd); + +int mountfsd_make_directory_fd(int parent_fd, const char *name, DissectImageFlags flags, int *ret_directory_fd); +int mountfsd_make_directory(const char *path, DissectImageFlags flags, int *ret_directory_fd); diff --git a/src/shared/install-file.c b/src/shared/install-file.c index c3de84f579..b84e0a2986 100644 --- a/src/shared/install-file.c +++ b/src/shared/install-file.c @@ -95,6 +95,11 @@ static int unlinkat_maybe_dir(int dirfd, const char *pathname) { return 0; } +static bool need_opath(InstallFileFlags flags) { + /* Returns true when we need to pin the source file via an O_PATH fd */ + return (flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0; +} + int install_file(int source_atfd, const char *source_name, int target_atfd, const char *target_name, InstallFileFlags flags) { @@ -118,7 +123,7 @@ int install_file(int source_atfd, const char *source_name, * already in place, and only the syncing/read-only marking shall be applied. Note that with * target_name=NULL and flags=0 this call is a NOP */ - if ((flags & (INSTALL_FSYNC|INSTALL_FSYNC_FULL|INSTALL_SYNCFS|INSTALL_READ_ONLY)) != 0) { + if (need_opath(flags)) { _cleanup_close_ int pfd = -EBADF; struct stat st; @@ -137,24 +142,32 @@ int install_file(int source_atfd, const char *source_name, _cleanup_close_ int regfd = -EBADF; regfd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC); - if (regfd < 0) - return regfd; + if (regfd < 0) { + if (!FLAGS_SET(flags, INSTALL_GRACEFUL)) + return log_debug_errno(regfd, "Failed to open referenced inode: %m"); - if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) { - /* If this is just a regular file (as oppose to a fully populated directory) - * let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL, after all this is - * going to be a single inode we install */ - r = fsync_full(regfd); - if (r < 0) - return r; - } else if (flags & INSTALL_FSYNC) { - if (fsync(regfd) < 0) - return -errno; + log_debug_errno(regfd, "Failed to open referenced inode, ignoring: %m"); + } else { + if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) + /* If this is just a regular file (as opposed to a fully populated + * directory) let's downgrade INSTALL_SYNCFS to INSTALL_FSYNC_FULL, + * after all this is going to be a single inode we install */ + r = fsync_full(regfd); + else if (flags & INSTALL_FSYNC) + r = RET_NERRNO(fsync(regfd)); + else + r = 0; + if (r < 0) { + if (!FLAGS_SET(flags, INSTALL_GRACEFUL)) + return log_debug_errno(r, "Failed to sync source inode: %m"); + + log_debug_errno(r, "Failed to sync source inode, ignoring: %m"); + } + + if (flags & INSTALL_READ_ONLY) + rofd = TAKE_FD(regfd); } - if (flags & INSTALL_READ_ONLY) - rofd = TAKE_FD(regfd); - break; } @@ -162,24 +175,31 @@ int install_file(int source_atfd, const char *source_name, _cleanup_close_ int dfd = -EBADF; dfd = fd_reopen(pfd, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (dfd < 0) - return dfd; + if (dfd < 0) { + if (!FLAGS_SET(flags, INSTALL_GRACEFUL)) + return log_debug_errno(dfd, "Failed to open referenced inode: %m"); - if (flags & INSTALL_SYNCFS) { - if (syncfs(dfd) < 0) - return -errno; - } else if (flags & INSTALL_FSYNC_FULL) { - r = fsync_full(dfd); - if (r < 0) - return r; - } else if (flags & INSTALL_FSYNC) { - if (fsync(dfd) < 0) - return -errno; + log_debug_errno(dfd, "Failed to open referenced inode, ignoring: %m"); + } else { + if (flags & INSTALL_SYNCFS) + r = RET_NERRNO(syncfs(dfd)); + else if (flags & INSTALL_FSYNC_FULL) + r = fsync_full(dfd); + else if (flags & INSTALL_FSYNC) + r = RET_NERRNO(fsync(dfd)); + else + r = 0; + if (r < 0) { + if (!FLAGS_SET(flags, INSTALL_GRACEFUL)) + return log_debug_errno(r, "Failed to sync source inode: %m"); + + log_debug_errno(r, "Failed to sync source inode, ignoring: %m"); + } + + if (flags & INSTALL_READ_ONLY) + rofd = TAKE_FD(dfd); } - if (flags & INSTALL_READ_ONLY) - rofd = TAKE_FD(dfd); - break; } @@ -190,8 +210,12 @@ int install_file(int source_atfd, const char *source_name, if (target_name && (flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) { r = fsync_directory_of_file(pfd); - if (r < 0) - return r; + if (r < 0) { + if (!FLAGS_SET(flags, INSTALL_GRACEFUL)) + return log_debug_errno(r, "Failed to sync source inode: %m"); + + log_debug_errno(r, "Failed to sync source inode, ignoring: %m"); + } } break; @@ -254,8 +278,12 @@ int install_file(int source_atfd, const char *source_name, if (rofd >= 0) { r = fs_make_very_read_only(rofd); - if (r < 0) - return r; + if (r < 0) { + if (!FLAGS_SET(flags, INSTALL_GRACEFUL)) + return log_debug_errno(r, "Failed to make inode read-only: %m"); + + log_debug_errno(r, "Failed to make inode read-only, ignoring: %m"); + } } if ((flags & (INSTALL_FSYNC_FULL|INSTALL_SYNCFS)) != 0) { @@ -263,8 +291,12 @@ int install_file(int source_atfd, const char *source_name, r = fsync_parent_at(target_atfd, target_name); else r = fsync_parent_at(source_atfd, source_name); - if (r < 0) - return r; + if (r < 0) { + if (!FLAGS_SET(flags, INSTALL_GRACEFUL)) + return log_debug_errno(r, "Failed to sync inode: %m"); + + log_debug_errno(r, "Failed to sync inode, ignoring: %m"); + } } return 0; diff --git a/src/shared/install-file.h b/src/shared/install-file.h index cb94e32cae..f5e2fa33ca 100644 --- a/src/shared/install-file.h +++ b/src/shared/install-file.h @@ -9,6 +9,7 @@ typedef enum InstallFileFlags { INSTALL_FSYNC = 1 << 2, /* fsync() file contents before moving file in */ INSTALL_FSYNC_FULL = 1 << 3, /* like INSTALL_FSYNC, but also fsync() parent dir before+after moving file in */ INSTALL_SYNCFS = 1 << 4, /* syncfs() before moving file in, fsync() parent dir after moving file in */ + INSTALL_GRACEFUL = 1 << 5, /* don't fail if we cannot sync or mark read-only */ } InstallFileFlags; int install_file(int source_atfd, const char *source_name, int target_atfd, const char *target_name, InstallFileFlags flags); diff --git a/src/shared/nsresource.h b/src/shared/nsresource.h index c06025761a..136b1f85ac 100644 --- a/src/shared/nsresource.h +++ b/src/shared/nsresource.h @@ -3,6 +3,10 @@ #include "shared-forward.h" +/* Helpful constants for the only numbers of UIDs that can currently be allocated */ +#define NSRESOURCE_UIDS_64K 0x10000U +#define NSRESOURCE_UIDS_1 1U + int nsresource_allocate_userns(const char *name, uint64_t size); int nsresource_register_userns(const char *name, int userns_fd); int nsresource_add_mount(int userns_fd, int mount_fd); diff --git a/units/user/meson.build b/units/user/meson.build index 8ad3f0eb76..71d2425038 100644 --- a/units/user/meson.build +++ b/units/user/meson.build @@ -28,6 +28,16 @@ units = [ 'conditions' : ['ENABLE_MACHINED'], 'symlinks' : ['sockets.target.wants/'], }, + { + 'file' : 'systemd-importd.service.in', + 'conditions' : ['ENABLE_IMPORTD'], + 'symlinks' : ['dbus-org.freedesktop.import1.service'], + }, + { + 'file' : 'systemd-importd.socket', + 'conditions' : ['ENABLE_IMPORTD'], + 'symlinks' : ['sockets.target.wants/'], + }, { 'file' : 'paths.target' }, { 'file' : 'printer.target' }, { 'file' : 'session.slice' }, diff --git a/units/user/systemd-importd.service.in b/units/user/systemd-importd.service.in new file mode 100644 index 0000000000..420fb06105 --- /dev/null +++ b/units/user/systemd-importd.service.in @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Disk Image Download Service +Documentation=man:systemd-importd.service(8) +Documentation=man:org.freedesktop.import1(5) + +[Service] +Type=notify +ExecStart={{LIBEXECDIR}}/systemd-importd --user +BusName=org.freedesktop.import1 +KillMode=mixed +NoNewPrivileges=yes +MemoryDenyWriteExecute=yes +RestrictRealtime=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +SystemCallFilter=@system-service @mount +SystemCallErrorNumber=EPERM +SystemCallArchitectures=native +LockPersonality=yes +{{SERVICE_WATCHDOG}} diff --git a/units/user/systemd-importd.socket b/units/user/systemd-importd.socket new file mode 100644 index 0000000000..d266d927f8 --- /dev/null +++ b/units/user/systemd-importd.socket @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=Disk Image Download Service Socket +Documentation=man:systemd-importd.service(8) +Documentation=man:org.freedesktop.import1(5) + +[Socket] +ListenStream=%t/systemd/io.systemd.Import +FileDescriptorName=varlink +SocketMode=0600