<xi:include href="version-info.xml" xpointer="v240"/></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--root-directory=</option></term>
+
+ <listitem><para>Runs the service process with the specified root directory. Also see
+ <varname>RootDirectory=</varname> in
+ <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
+
+ <para>Note that the path is looked up inside the file system namespace that systemd-run is running
+ in, which might be different that the file system namespace the manager process is running in. Use
+ the <varname>RootDirectory=</varname> property directly if you want the path to be looked up in the
+ manager process's file system namespace.</para>
+
+ <xi:include href="version-info.xml" xpointer="v259"/>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--same-root-dir</option></term>
+ <term><option>-R</option></term>
+
+ <listitem><para>Similar to <option>--root-directory=</option>, but uses the root directory of the
+ systemd-run process as the root directory to execute the service in.</para>
+
+ <xi:include href="version-info.xml" xpointer="v259"/></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-E <replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
<term><option>--setenv=<replaceable>NAME</replaceable>[=<replaceable>VALUE</replaceable>]</option></term>
return 1;
}
+ if (streq(name, "RootDirectoryFileDescriptor"))
+ return bus_set_transient_exec_context_fd(u, &s->root_directory_fd, &s->exec_context.root_directory_as_fd, message, flags, error);
+
return 0;
}
if (context->n_temporary_filesystems > 0)
return true;
- if (root_dir || root_image)
+ if (root_dir || root_image || context->root_directory_as_fd)
return true;
if (context->n_mount_images > 0)
int r;
assert(context);
+ assert(!context->root_directory_as_fd);
assert(runtime);
assert(root_image);
assert(root_directory);
int r;
assert(context);
+ assert(!context->root_directory_as_fd);
assert(params);
assert(ret_root_image);
assert(ret_root_directory);
CLEANUP_ARRAY(bind_mounts, n_bind_mounts, bind_mount_free_many);
- if (params->flags & EXEC_APPLY_CHROOT) {
+ if (params->flags & EXEC_APPLY_CHROOT && !context->root_directory_as_fd) {
r = pick_versions(
context,
params,
.root_directory = root_dir,
.root_image = root_image,
+ .root_directory_fd = params->flags & EXEC_APPLY_CHROOT ? params->root_directory_fd : -EBADF,
.root_image_options = context->root_image_options,
.root_image_policy = context->root_image_policy ?: &image_policy_service,
context->n_bind_mounts > 0 ||
context->n_temporary_filesystems > 0 ||
context->root_directory ||
+ context->root_directory_as_fd ||
!strv_isempty(context->extension_directories) ||
context->root_image ||
context->n_mount_images > 0 ||
}
#endif
+ r = add_shifted_fd(&keep_fds, &n_keep_fds, ¶ms->root_directory_fd);
+ if (r < 0) {
+ *exit_status = EXIT_FDS;
+ return log_error_errno(r, "Failed to collect shifted fd: %m");
+ }
+
r = close_remaining_fds(params, runtime, socket_fd, keep_fds, n_keep_fds);
if (r < 0) {
*exit_status = EXIT_FDS;
if (r < 0)
return r;
+ r = serialize_fd(f, fds, "exec-parameters-root-directory-fd", p->root_directory_fd);
+ if (r < 0)
+ return r;
+
r = serialize_fd(f, fds, "exec-parameters-exec-fd", p->exec_fd);
if (r < 0)
return r;
continue;
close_and_replace(p->stderr_fd, fd);
+
+ } else if ((val = startswith(l, "exec-parameters-root-directory-fd="))) {
+ int fd;
+
+ fd = deserialize_fd(fds, val);
+ if (fd < 0)
+ continue;
+
+ close_and_replace(p->root_directory_fd, fd);
+
} else if ((val = startswith(l, "exec-parameters-exec-fd="))) {
int fd;
if (r < 0)
return r;
+ r = serialize_bool_elide(f, "exec-context-root-directory-as-fd", c->root_directory_as_fd);
+ if (r < 0)
+ return r;
+
switch (c->std_input) {
case EXEC_INPUT_NAMED_FD:
r = serialize_item(f, "exec-context-std-input-fd-name", c->stdio_fdname[STDIN_FILENO]);
if (r < 0)
return r;
c->stdio_as_fds = r;
+ } else if ((val = startswith(l, "exec-context-root-directory-as-fd="))) {
+ r = parse_boolean(val);
+ if (r < 0)
+ return r;
+ c->root_directory_as_fd = r;
} else if ((val = startswith(l, "exec-context-std-input-fd-name="))) {
r = free_and_strdup(&c->stdio_fdname[STDIN_FILENO], val);
if (r < 0)
if (context->root_image)
return true;
+ if (context->root_directory_as_fd)
+ return true;
+
if (!strv_isempty(context->read_write_paths) ||
!strv_isempty(context->read_only_paths) ||
!strv_isempty(context->inaccessible_paths) ||
if (!needs_sandboxing)
return NULL;
- if (!context->root_directory && !context->root_image)
+ if (!context->root_directory && !context->root_image && !context->root_directory_as_fd)
return NULL;
if (!exec_context_get_effective_mount_apivfs(context))
bool exec_context_with_rootfs(const ExecContext *c) {
assert(c);
- /* Checks if RootDirectory= or RootImage= are used */
+ /* Checks if RootDirectory=, RootImage= or RootDirectoryFileDescriptor= are used */
- return !empty_or_root(c->root_directory) || c->root_image;
+ return !empty_or_root(c->root_directory) || c->root_image || c->root_directory_as_fd;
}
int exec_context_has_vpicked_extensions(const ExecContext *context) {
p->stdin_fd = safe_close(p->stdin_fd);
p->stdout_fd = safe_close(p->stdout_fd);
p->stderr_fd = safe_close(p->stderr_fd);
+ p->root_directory_fd = safe_close(p->root_directory_fd);
p->notify_socket = mfree(p->notify_socket);
/* At least one of stdin/stdout/stderr was initialized from an fd passed in. This boolean survives
* the fds being closed. This only makes sense for transient units. */
bool stdio_as_fds;
+ bool root_directory_as_fd;
char *stdio_fdname[3];
char *stdio_file[3];
int stdin_fd;
int stdout_fd;
int stderr_fd;
+ int root_directory_fd;
/* An fd that is closed by the execve(), and thus will result in EOF when the execve() is done. */
int exec_fd;
.stdin_fd = -EBADF, \
.stdout_fd = -EBADF, \
.stderr_fd = -EBADF, \
+ .root_directory_fd = -EBADF, \
.exec_fd = -EBADF, \
.bpf_restrict_fs_map_fd = -EBADF, \
.user_lookup_fd = -EBADF, \
params.stdin_fd = -EBADF;
params.stdout_fd = -EBADF;
params.stderr_fd = -EBADF;
+ params.root_directory_fd = -EBADF;
params.exec_fd = -EBADF;
params.user_lookup_fd = -EBADF;
params.bpf_restrict_fs_map_fd = -EBADF;
/* We assume /run/systemd/journal/ is available if not changing root, which isn't entirely accurate
* but shouldn't matter, as either way the user would get ENOENT when accessing /dev/log */
- if ((!p->root_image && !p->root_directory) || p->bind_log_sockets) {
+ if ((!p->root_image && !p->root_directory && p->root_directory_fd < 0) || p->bind_log_sockets) {
const char *devlog = strjoina(temporary_mount, "/dev/log");
if (symlink("/run/systemd/journal/dev-log", devlog) < 0)
log_debug_errno(errno,
if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0)
return log_debug_errno(errno, "Failed to remount '/' as SLAVE: %m");
- if (p->root_image) {
+ if (p->root_directory_fd >= 0) {
+
+ if (move_mount(p->root_directory_fd, "", AT_FDCWD, root, MOVE_MOUNT_F_EMPTY_PATH) < 0)
+ return log_debug_errno(errno, "Failed to move detached mount to '%s': %m", root);
+
+ /* We just remounted / as slave, but that didn't affect the detached mount that we just
+ * mounted, so remount that one as slave recursive as well now. */
+
+ if (mount(NULL, root, NULL, MS_SLAVE|MS_REC, NULL) < 0)
+ return log_debug_errno(errno, "Failed to remount '%s' as SLAVE: %m", root);
+
+ } else if (p->root_image) {
/* A root image is specified, mount it to the right place */
r = dissected_image_mount(
dissected_image,
}
/* Try to set up the new root directory before mounting anything else there. */
- if (p->root_image || p->root_directory)
+ if (p->root_image || p->root_directory || p->root_directory_fd >= 0)
(void) base_filesystem_create(root, UID_INVALID, GID_INVALID);
/* Now make the magic happen */
typedef struct NamespaceParameters {
RuntimeScope runtime_scope;
+ int root_directory_fd;
const char *root_directory;
const char *root_image;
const MountOptions *root_image_options;
s->type = _SERVICE_TYPE_INVALID;
s->socket_fd = -EBADF;
s->stdin_fd = s->stdout_fd = s->stderr_fd = -EBADF;
+ s->root_directory_fd = -EBADF;
s->guess_main_pid = true;
s->main_pid = PIDREF_NULL;
s->control_pid = PIDREF_NULL;
service_release_stdio_fd(s);
service_release_fd_store(s);
service_release_extra_fds(s);
+ s->root_directory_fd = asynchronous_close(s->root_directory_fd);
s->mount_request = sd_bus_message_unref(s->mount_request);
}
f,
prefix);
+ if (s->root_directory_fd >= 0)
+ (void) service_dump_fd(s->root_directory_fd, "Root Directory File Descriptor", "", f, prefix);
+
if (s->open_files)
LIST_FOREACH(open_files, of, s->open_files) {
_cleanup_free_ char *ofs = NULL;
exec_params.stdin_fd = s->stdin_fd;
exec_params.stdout_fd = s->stdout_fd;
exec_params.stderr_fd = s->stderr_fd;
+ exec_params.root_directory_fd = s->root_directory_fd;
r = exec_spawn(UNIT(s),
c,
.n_extension_images = s->exec_context.n_extension_images,
.extension_directories = s->exec_context.extension_directories,
.extension_image_policy = s->exec_context.extension_image_policy,
+ .root_directory_fd = -EBADF,
};
/* Only reload confext, and not sysext as they also typically contain the executable(s) used
r = serialize_fd(f, fds, "stdin-fd", s->stdin_fd);
if (r < 0)
return r;
+
r = serialize_fd(f, fds, "stdout-fd", s->stdout_fd);
if (r < 0)
return r;
+
r = serialize_fd(f, fds, "stderr-fd", s->stderr_fd);
if (r < 0)
return r;
+ r = serialize_fd(f, fds, "root-directory-fd", s->root_directory_fd);
+ if (r < 0)
+ return r;
+
if (s->exec_fd_event_source) {
r = serialize_fd(f, fds, "exec-fd", sd_event_source_get_io_fd(s->exec_fd_event_source));
if (r < 0)
if (s->stderr_fd >= 0)
s->exec_context.stdio_as_fds = true;
+ } else if (streq(key, "root-directory-fd")) {
+
+ asynchronous_close(s->root_directory_fd);
+ s->root_directory_fd = deserialize_fd(fds, value);
+ if (s->root_directory_fd >= 0)
+ s->exec_context.root_directory_as_fd = true;
+
} else if (streq(key, "exec-fd")) {
_cleanup_close_ int fd = -EBADF;
service_release_socket_fd(s);
service_release_stdio_fd(s);
service_release_extra_fds(s);
+ s->root_directory_fd = asynchronous_close(s->root_directory_fd);
if (s->fd_store_preserve_mode != EXEC_PRESERVE_YES)
service_release_fd_store(s);
return -ENODATA;
_cleanup_free_ char *path = NULL;
- r = chase(c->path, s->exec_context.root_directory, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL);
+ if (s->exec_context.root_directory_as_fd)
+ r = chaseat(s->root_directory_fd, c->path, CHASE_AT_RESOLVE_IN_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL);
+ else
+ r = chase(c->path, s->exec_context.root_directory, CHASE_PREFIX_ROOT|CHASE_TRIGGER_AUTOFS, &path, NULL);
if (r < 0) {
log_unit_debug_errno(UNIT(s), r, "Failed to resolve service binary '%s', ignoring.", c->path);
return -ENODATA;
int stdout_fd;
int stderr_fd;
+ /* File descriptor received from RootDirectoryFileDescriptor= */
+ int root_directory_fd;
+
/* If service spawned from transient unit, extra file descriptors can be passed via dbus API */
ServiceExtraFD *extra_fds;
size_t n_extra_fds;
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/mount.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <unistd.h>
static bool arg_verbose = false;
static bool arg_aggressive_gc = false;
static char *arg_working_directory = NULL;
+static char *arg_root_directory = NULL;
static bool arg_shell = false;
static JobMode arg_job_mode = JOB_FAIL;
static char **arg_cmdline = NULL;
" --nice=NICE Nice level\n"
" --working-directory=PATH Set working directory\n"
" -d --same-dir Inherit working directory from caller\n"
+ " --root-directory=PATH Set root directory\n"
+ " -R --same-root-dir Inherit root directory from caller\n"
" -E --setenv=NAME[=VALUE] Set environment variable\n"
" -t --pty Run service on pseudo TTY as STDIN/STDOUT/\n"
" STDERR\n"
ARG_NO_ASK_PASSWORD,
ARG_WAIT,
ARG_WORKING_DIRECTORY,
+ ARG_ROOT_DIRECTORY,
ARG_SHELL,
ARG_JOB_MODE,
ARG_IGNORE_FAILURE,
{ "collect", no_argument, NULL, 'G' },
{ "working-directory", required_argument, NULL, ARG_WORKING_DIRECTORY },
{ "same-dir", no_argument, NULL, 'd' },
+ { "root-directory", required_argument, NULL, ARG_ROOT_DIRECTORY },
+ { "same-root-dir", no_argument, NULL, 'R' },
{ "shell", no_argument, NULL, 'S' },
{ "job-mode", required_argument, NULL, ARG_JOB_MODE },
{ "ignore-failure", no_argument, NULL, ARG_IGNORE_FAILURE },
{},
};
- bool with_trigger = false;
+ bool with_trigger = false, same_dir = false;
int r, c;
assert(argc >= 0);
if (r < 0)
return r;
+ same_dir = false;
break;
case 'd': {
arg_working_directory = mfree(arg_working_directory);
else
free_and_replace(arg_working_directory, p);
+
+ same_dir = true;
break;
}
+ case ARG_ROOT_DIRECTORY:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root_directory);
+ if (r < 0)
+ return r;
+
+ break;
+
+ case 'R':
+ r = free_and_strdup_warn(&arg_root_directory, "/");
+ if (r < 0)
+ return r;
+
+ break;
+
case 'G':
arg_aggressive_gc = true;
break;
"--wait may not be combined with --scope.");
}
+ if (same_dir && arg_root_directory && !path_equal(arg_root_directory, "/"))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "--same-dir cannot be used with a root directory other than '/'");
+
return 1;
}
return bus_log_create_error(r);
}
+ if (arg_root_directory) {
+ _cleanup_close_ int fd = open_tree(AT_FDCWD, arg_root_directory, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_RECURSIVE);
+ if (fd < 0)
+ return log_error_errno(errno, "Failed to clone mount tree at '%s': %m", arg_root_directory);
+
+ r = sd_bus_message_append(m, "(sv)", "RootDirectoryFileDescriptor", "h", fd);
+ if (r < 0)
+ return bus_log_create_error(r);
+ }
+
if (pty_path) {
r = sd_bus_message_append(m, "(sv)(sv)(sv)(sv)",
"TTYPath", "s", pty_path,
static const NamespaceParameters p = {
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
.protect_kernel_logs = true,
+ .root_directory_fd = -EBADF,
};
pid_t pid;
.runtime_scope = RUNTIME_SCOPE_SYSTEM,
.root_directory = root_directory,
+ .root_directory_fd = -EBADF,
.read_write_paths = (char**) writable,
.read_only_paths = (char**) readonly,
timeout 10 bash -c 'until test -z "$(ls -A /var/lib/systemd/ephemeral-trees)"; do sleep .5; done'
test ! -f /tmp/img/abc
+# Test RootDirectoryFileDescriptor=
+systemd-run --wait --pipe --root-directory=/tmp/img -- grep -q 'MARKER=1' /usr/lib/os-release
+
systemd-dissect --mtree /tmp/img >/dev/null
systemd-dissect --list /tmp/img >/dev/null