diff --git a/man/systemd-notify.xml b/man/systemd-notify.xml
index 9a66721a613..25b5e2d041f 100644
--- a/man/systemd-notify.xml
+++ b/man/systemd-notify.xml
@@ -26,7 +26,10 @@
systemd-notify OPTIONS VARIABLE=VALUE
- systemd-notify --exec OPTIONS VARIABLE=VALUE ; CMDLINE
+ systemd-notify --exec OPTIONS VARIABLE=VALUE ; -- CMDLINE
+
+
+ systemd-notify --fork OPTIONS -- CMDLINE
@@ -237,6 +240,54 @@
+
+
+
+ Instead of sending a notification message, fork off a command line and wait until a
+ READY=1 message is received from it. In other words: this makes
+ systemd-notify the receiver of notification messages instead of the sender,
+ swapping roles. This is useful to quickly fork off a process that implements the
+ sd_notify() protocol from a shell script. The invoked command line will have
+ standard input and standard output connected to /dev/null, but standard error
+ will be inherited from the invoking process. The numeric process ID is written to standard output by
+ systemd-notify (unless is specified), which may be used
+ to later terminate the forked off process.
+
+ Note that processes forked off like this will likely remain running after
+ systemd-notify already returned, which hence will result in them being reparented
+ to the closest process reaper process, i.e. typically the per-user or system service manager.
+
+ Note that this option should not be used to invoke full services ad-hoc, use
+ systemd-run for that.
+
+ Also note that when invoked with this switch systemd-notify will exit
+ successfully under two distinction conditions:
+
+
+ systemd-notify received a READY=1
+ notification from the child it just forked off.
+
+ The child process exited cleanly (with exit status zero) before sending
+ READY=1.
+
+
+ Example use:# PID=$(systemd-notify --fork -- mycommand)
+…
+kill "$PID"
+unset PID
+
+
+
+
+
+
+
+
+ Turn off output of the numeric process ID when is used.
+
+
+
+
diff --git a/man/varlinkctl.xml b/man/varlinkctl.xml
index 41d911c5115..37d8b6731a1 100644
--- a/man/varlinkctl.xml
+++ b/man/varlinkctl.xml
@@ -61,6 +61,18 @@
ARGUMENTS
+
+ varlinkctl
+ OPTIONS
+ --exec call
+ call
+ ADDRESS
+ METHOD
+ ARGUMENTS
+ --
+ CMDLINE
+
+
varlinkctl
OPTIONS
@@ -307,6 +319,25 @@
+
+
+
+
+ Once the method call issued via call completed successfully, chainload the
+ specified command line, with the method call output parameters serialized to JSON passed into
+ standard input (and standard output and standard error inherited from the invoking
+ process). Moreover any file descriptors passed back on the underlying communication socket are
+ passed to the invoked process via the usual $LISTEN_FDS protocol. This
+ functionality may be used to consume replies that come with associated file descriptors in a
+ reasonable way.
+
+ Now that if is specified the the third parameter to
+ call is not optional (i.e. the method call parameters).
+
+
+
+
+
@@ -349,7 +380,9 @@ type ResolvedAddress(
Invoking a Method
- The following command resolves a hostname via systemd-resolved.service8's ResolveHostname method call.
+ The following command resolves a hostname via
+ systemd-resolved.service8's
+ ResolveHostname method call.
$ varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name":"systemd.io","family":2}' -j
{
diff --git a/src/import/importd.c b/src/import/importd.c
index 414994ee300..8e1fe51bf8d 100644
--- a/src/import/importd.c
+++ b/src/import/importd.c
@@ -28,6 +28,7 @@
#include "main-func.h"
#include "missing_capability.h"
#include "mkdir-label.h"
+#include "notify-recv.h"
#include "os-util.h"
#include "parse-util.h"
#include "path-util.h"
@@ -637,56 +638,25 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_unref);
static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
Manager *m = ASSERT_PTR(userdata);
- char buf[NOTIFY_BUFFER_MAX+1];
- struct iovec iovec = {
- .iov_base = buf,
- .iov_len = sizeof(buf)-1,
- };
- CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) +
- CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)) control;
- struct msghdr msghdr = {
- .msg_iov = &iovec,
- .msg_iovlen = 1,
- .msg_control = &control,
- .msg_controllen = sizeof(control),
- };
- ssize_t n;
int r;
- n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
- if (ERRNO_IS_NEG_TRANSIENT(n))
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &pidref);
+ if (r == -EAGAIN)
return 0;
- if (n == -ECHRNG) {
- log_warning_errno(n, "Got message with truncated control data (unexpected fds sent?), ignoring.");
- return 0;
- }
- if (n == -EXFULL) {
- log_warning_errno(n, "Got message with truncated payload data, ignoring.");
- return 0;
- }
- if (n < 0)
- return (int) n;
-
- cmsg_close_all(&msghdr);
-
- struct ucred *ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
- if (!ucred || ucred->pid <= 0) {
- log_warning("Got notification datagram lacking credential information, ignoring.");
- return 0;
- }
+ if (r < 0)
+ return log_warning_errno(r, "Failed to receive notification message: %m");
Transfer *t;
HASHMAP_FOREACH(t, m->transfers)
- if (ucred->pid == t->pidref.pid)
+ if (pidref_equal(&pidref, &t->pidref))
break;
-
if (!t) {
log_warning("Got notification datagram from unexpected peer, ignoring.");
return 0;
}
- buf[n] = 0;
-
char *p = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
if (!p)
return 0;
@@ -761,6 +731,10 @@ static int manager_new(Manager **ret) {
if (r < 0)
return r;
+ r = setsockopt_int(m->notify_fd, SOL_SOCKET, SO_PASSPIDFD, true);
+ if (r < 0)
+ log_debug_errno(r, "Failed to enable SO_PASSPIDFD on notification socket, ignoring. %m");
+
r = sd_event_add_io(m->event, &m->notify_event_source,
m->notify_fd, EPOLLIN, manager_on_notify, m);
if (r < 0)
diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym
index 726a6bce12a..07553359df9 100644
--- a/src/libsystemd/libsystemd.sym
+++ b/src/libsystemd/libsystemd.sym
@@ -1069,6 +1069,7 @@ global:
sd_varlink_get_current_method;
sd_varlink_get_description;
sd_varlink_get_input_fd;
+ sd_varlink_get_n_fds;
sd_varlink_get_output_fd;
sd_varlink_reset_fds;
sd_varlink_server_listen_name;
diff --git a/src/libsystemd/sd-event/event-util.c b/src/libsystemd/sd-event/event-util.c
index 42e973564e3..482df37225c 100644
--- a/src/libsystemd/sd-event/event-util.c
+++ b/src/libsystemd/sd-event/event-util.c
@@ -2,6 +2,7 @@
#include
+#include "errno-util.h"
#include "event-source.h"
#include "event-util.h"
#include "fd-util.h"
@@ -172,6 +173,30 @@ int event_add_child_pidref(
return sd_event_add_child(e, s, pid->pid, options, callback, userdata);
}
+int event_source_get_child_pidref(sd_event_source *s, PidRef *ret) {
+ int r;
+
+ assert(s);
+ assert(ret);
+
+ pid_t pid;
+ r = sd_event_source_get_child_pid(s, &pid);
+ if (r < 0)
+ return r;
+
+ int pidfd = sd_event_source_get_child_pidfd(s);
+ if (pidfd < 0)
+ return pidfd;
+
+ /* Note, we don't actually duplicate the fd here, i.e. we do not pass ownership of this PidRef to the caller */
+ *ret = (PidRef) {
+ .pid = pid,
+ .fd = pidfd,
+ };
+
+ return 0;
+}
+
dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts) {
assert(e);
assert(ts);
diff --git a/src/libsystemd/sd-event/event-util.h b/src/libsystemd/sd-event/event-util.h
index 383b4c82b7c..2c6fcccb908 100644
--- a/src/libsystemd/sd-event/event-util.h
+++ b/src/libsystemd/sd-event/event-util.h
@@ -37,6 +37,8 @@ int event_add_time_change(sd_event *e, sd_event_source **ret, sd_event_io_handle
int event_add_child_pidref(sd_event *e, sd_event_source **s, const PidRef *pid, int options, sd_event_child_handler_t callback, void *userdata);
+int event_source_get_child_pidref(sd_event_source *s, PidRef *ret);
+
dual_timestamp* event_dual_timestamp_now(sd_event *e, dual_timestamp *ts);
void event_source_unref_many(sd_event_source **array, size_t n);
diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c
index d2ece7b2e6d..cdfcb9935e8 100644
--- a/src/libsystemd/sd-varlink/sd-varlink.c
+++ b/src/libsystemd/sd-varlink/sd-varlink.c
@@ -3183,6 +3183,15 @@ _public_ int sd_varlink_take_fd(sd_varlink *v, size_t i) {
return TAKE_FD(v->input_fds[i]);
}
+_public_ int sd_varlink_get_n_fds(sd_varlink *v) {
+ assert_return(v, -EINVAL);
+
+ if (!v->allow_fd_passing_input)
+ return -EPERM;
+
+ return (int) v->n_input_fds;
+}
+
static int verify_unix_socket(sd_varlink *v) {
assert(v);
diff --git a/src/notify/notify.c b/src/notify/notify.c
index 1c5b5e7be3d..a2c53005aa9 100644
--- a/src/notify/notify.c
+++ b/src/notify/notify.c
@@ -7,30 +7,40 @@
#include
#include "sd-daemon.h"
+#include "sd-event.h"
#include "alloc-util.h"
#include "build.h"
#include "env-util.h"
+#include "escape.h"
+#include "event-util.h"
+#include "exit-status.h"
#include "fd-util.h"
#include "fdset.h"
#include "format-util.h"
#include "log.h"
#include "main-func.h"
+#include "notify-recv.h"
#include "parse-util.h"
#include "pretty-print.h"
#include "process-util.h"
+#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "time-util.h"
#include "user-util.h"
+static enum {
+ ACTION_NOTIFY,
+ ACTION_BOOTED,
+ ACTION_FORK,
+} arg_action = ACTION_NOTIFY;
static bool arg_ready = false;
static bool arg_reloading = false;
static bool arg_stopping = false;
static PidRef arg_pid = PIDREF_NULL;
static const char *arg_status = NULL;
-static bool arg_booted = false;
static uid_t arg_uid = UID_INVALID;
static gid_t arg_gid = GID_INVALID;
static bool arg_no_block = false;
@@ -38,6 +48,7 @@ static char **arg_env = NULL;
static char **arg_exec = NULL;
static FDSet *arg_fds = NULL;
static char *arg_fdname = NULL;
+static bool arg_quiet = false;
STATIC_DESTRUCTOR_REGISTER(arg_pid, pidref_done);
STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
@@ -54,7 +65,8 @@ static int help(void) {
return log_oom();
printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n"
- "%s [OPTIONS...] --exec [VARIABLE=VALUE...] ; CMDLINE...\n"
+ "%s [OPTIONS...] --exec [VARIABLE=VALUE...] ; -- CMDLINE...\n"
+ "%s [OPTIONS...] --fork -- CMDLINE...\n"
"\n%sNotify the init system about service status updates.%s\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
@@ -70,9 +82,12 @@ static int help(void) {
" --exec Execute command line separated by ';' once done\n"
" --fd=FD Pass specified file descriptor with along with message\n"
" --fdname=NAME Name to assign to passed file descriptor(s)\n"
+ " --fork Receive notifications from child rather than sending them\n"
+ " -q --quiet Do not show PID of child when forking\n"
"\nSee the %s for details.\n",
program_invocation_short_name,
program_invocation_short_name,
+ program_invocation_short_name,
ansi_highlight(),
ansi_normal(),
link);
@@ -162,6 +177,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_EXEC,
ARG_FD,
ARG_FDNAME,
+ ARG_FORK,
};
static const struct option options[] = {
@@ -178,6 +194,8 @@ static int parse_argv(int argc, char *argv[]) {
{ "exec", no_argument, NULL, ARG_EXEC },
{ "fd", required_argument, NULL, ARG_FD },
{ "fdname", required_argument, NULL, ARG_FDNAME },
+ { "fork", no_argument, NULL, ARG_FORK },
+ { "quiet", no_argument, NULL, 'q' },
{}
};
@@ -188,7 +206,7 @@ static int parse_argv(int argc, char *argv[]) {
assert(argc >= 0);
assert(argv);
- while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+ while ((c = getopt_long(argc, argv, "hq", options, NULL)) >= 0) {
switch (c) {
@@ -231,7 +249,7 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_BOOTED:
- arg_booted = true;
+ arg_action = ACTION_BOOTED;
break;
case ARG_UID: {
@@ -302,6 +320,14 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_FORK:
+ arg_action = ACTION_FORK;
+ break;
+
+ case 'q':
+ arg_quiet = true;
+ break;
+
case '?':
return -EINVAL;
@@ -310,58 +336,271 @@ static int parse_argv(int argc, char *argv[]) {
}
}
- if (arg_fdname && fdset_isempty(arg_fds))
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing.");
-
bool have_env = arg_ready || arg_stopping || arg_reloading || arg_status || pidref_is_set(&arg_pid) || !fdset_isempty(arg_fds);
- size_t n_arg_env;
- if (do_exec) {
- int i;
+ switch (arg_action) {
- for (i = optind; i < argc; i++)
- if (streq(argv[i], ";"))
- break;
+ case ACTION_NOTIFY: {
+ if (arg_fdname && fdset_isempty(arg_fds))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No file descriptors passed, but --fdname= set, refusing.");
- if (i >= argc)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used argument list must contain ';' separator, refusing.");
- if (i+1 == argc)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing.");
+ size_t n_arg_env;
- arg_exec = strv_copy_n(argv + i + 1, argc - i - 1);
- if (!arg_exec)
- return log_oom();
+ if (do_exec) {
+ int i;
- n_arg_env = i - optind;
- } else
- n_arg_env = argc - optind;
+ for (i = optind; i < argc; i++)
+ if (streq(argv[i], ";"))
+ break;
- have_env = have_env || n_arg_env > 0;
+ if (i >= argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "If --exec is used argument list must contain ';' separator, refusing.");
+ if (i+1 == argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty command line specified after ';' separator, refusing.");
- if (!have_env && !arg_booted) {
- if (do_exec)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No notify message specified while --exec, refusing.");
+ arg_exec = strv_copy_n(argv + i + 1, argc - i - 1);
+ if (!arg_exec)
+ return log_oom();
- /* No argument at all? */
- help();
- return -EINVAL;
+ n_arg_env = i - optind;
+ } else
+ n_arg_env = argc - optind;
+
+ have_env = have_env || n_arg_env > 0;
+ if (!have_env) {
+ if (do_exec)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No notify message specified while --exec, refusing.");
+
+ /* No argument at all? */
+ help();
+ return -EINVAL;
+ }
+
+ if (n_arg_env > 0) {
+ arg_env = strv_copy_n(argv + optind, n_arg_env);
+ if (!arg_env)
+ return log_oom();
+ }
+
+ if (!fdset_isempty(passed))
+ log_warning("Warning: %u more file descriptors passed than referenced with --fd=.", fdset_size(passed));
+
+ break;
}
- if (have_env && arg_booted)
- log_warning("Notify message specified along with --booted, ignoring.");
+ case ACTION_BOOTED:
+ if (argc > optind)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--booted takes no parameters, refusing.");
- if (n_arg_env > 0) {
- arg_env = strv_copy_n(argv + optind, n_arg_env);
- if (!arg_env)
- return log_oom();
+ break;
+
+ case ACTION_FORK:
+ if (optind >= argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--fork requires a command to be specified, refusing.");
+
+ break;
+
+ default:
+ assert_not_reached();
}
- if (!fdset_isempty(passed))
- log_warning("Warning: %u more file descriptors passed than referenced with --fd=.", fdset_size(passed));
+ if (have_env && arg_action != ACTION_NOTIFY)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--ready, --reloading, --stopping, --pid=, --status=, --fd= may not be combined with --fork or --booted, refusing.");
return 1;
}
+static int on_notify_socket(sd_event_source *s, int fd, unsigned event, void *userdata) {
+ PidRef *child = ASSERT_PTR(userdata);
+ int r;
+
+ assert(s);
+ assert(fd >= 0);
+
+ _cleanup_free_ char *text = NULL;
+ _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
+ r = notify_recv(fd, &text, /* ret_ucred= */ NULL, &pidref);
+ if (r == -EAGAIN)
+ return 0;
+ if (r < 0)
+ return log_error_errno(r, "Failed to receive notification message: %m");
+
+ if (!pidref_equal(child, &pidref)) {
+ log_warning("Received notification message from unexpected process " PID_FMT " (expected " PID_FMT "), ignoring.",
+ pidref.pid, child->pid);
+ return 0;
+ }
+
+ const char *p = find_line_startswith(text, "READY=1");
+ if (!p || !IN_SET(*p, '\n', 0)) {
+ if (!DEBUG_LOGGING)
+ return 0;
+
+ _cleanup_free_ char *escaped = cescape(text);
+ log_debug("Received notification message without READY=1, ignoring: %s", strna(escaped));
+ return 0;
+ }
+
+ log_debug("Received READY=1, exiting.");
+ return sd_event_exit(sd_event_source_get_event(s), EXIT_SUCCESS);
+}
+
+static int on_child(sd_event_source *s, const siginfo_t *si, void *userdata) {
+ assert(s);
+ assert(si);
+
+ int ret;
+ if (si->si_code == CLD_EXITED) {
+ if (si->si_status != EXIT_SUCCESS)
+ log_debug("Child failed with exit status %i.", si->si_status);
+ else
+ log_debug("Child exited successfully. (But no READY=1 message was sent!)");
+
+ /* NB: we propagate success here if the child exited cleanly but never sent us READY=1. We
+ * are not a service manager after all, where this would be a protocol violation. We are just
+ * a shell tool to fork off stuff in the background, where I think it makes sense to allow
+ * clean early exit of forked off processes. */
+ ret = si->si_status;
+
+ } else if (IN_SET(si->si_code, CLD_KILLED, CLD_DUMPED))
+ ret = log_debug_errno(SYNTHETIC_ERRNO(EPROTO),
+ "Child terminated by signal %s.", signal_to_string(si->si_status));
+ else
+ ret = log_debug_errno(SYNTHETIC_ERRNO(EPROTO),
+ "Child terminated due to unknown reason.");
+
+ return sd_event_exit(sd_event_source_get_event(s), ret);
+}
+
+static int action_fork(char *const *_command) {
+
+ static const int forward_signals[] = {
+ SIGHUP,
+ SIGTERM,
+ SIGINT,
+ SIGQUIT,
+ SIGTSTP,
+ SIGCONT,
+ SIGUSR1,
+ SIGUSR2,
+ };
+
+ int r;
+
+ assert(!strv_isempty(_command));
+
+ /* Make a copy, since pidref_safe_fork_full() will change argv[] further down. */
+ _cleanup_strv_free_ char **command = strv_copy(_command);
+ if (!command)
+ return log_oom();
+
+ _cleanup_close_ int socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0);
+ if (socket_fd < 0)
+ return log_error_errno(errno, "Failed to allocate AF_UNIX socket for notifications: %m");
+
+ r = setsockopt_int(socket_fd, SOL_SOCKET, SO_PASSCRED, true);
+ if (r < 0)
+ return log_error_errno(r, "Failed to enable SO_PASSCRED: %m");
+
+ r = setsockopt_int(socket_fd, SOL_SOCKET, SO_PASSPIDFD, true);
+ if (r < 0)
+ log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
+
+ /* Pick an address via auto-bind */
+ union sockaddr_union sa = {
+ .sa.sa_family = AF_UNIX,
+ };
+ if (bind(socket_fd, &sa.sa, offsetof(union sockaddr_union, un.sun_path)) < 0)
+ return log_error_errno(errno, "Failed to bind AF_UNIX socket: %m");
+
+ _cleanup_free_ char *addr_string = NULL;
+ r = getsockname_pretty(socket_fd, &addr_string);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get socket name: %m");
+
+ _cleanup_free_ char *c = strv_join(command, " ");
+ if (!c)
+ return log_oom();
+
+ _cleanup_(pidref_done) PidRef child = PIDREF_NULL;
+ r = pidref_safe_fork_full(
+ "(notify)",
+ /* stdio_fds= */ (const int[]) { -EBADF, -EBADF, STDERR_FILENO },
+ /* except_fds= */ NULL,
+ /* n_except_fds= */ 0,
+ /* flags= */ FORK_REARRANGE_STDIO,
+ &child);
+ if (r < 0)
+ return log_error_errno(r, "Failed to fork child in order to execute '%s': %m", c);
+ if (r == 0) {
+ /* Let's explicitly close the fds we just opened. Not because it was necessary (we should be
+ * setting O_CLOEXEC after all on all of them), but mostly to make debugging nice */
+ socket_fd = safe_close(socket_fd);
+ pidref_done(&child);
+
+ if (setenv("NOTIFY_SOCKET", addr_string, /* overwrite= */ true) < 0) {
+ log_error_errno(errno, "Failed to set $NOTIFY_SOCKET: %m");
+ _exit(EXIT_MEMORY);
+ }
+
+ log_debug("Executing: %s", c);
+ execvp(command[0], command);
+ log_error_errno(errno, "Failed to execute '%s': %m", c);
+ _exit(EXIT_EXEC);
+ }
+
+ if (!arg_quiet) {
+ printf(PID_FMT "\n", child.pid);
+ fflush(stdout);
+ }
+
+ BLOCK_SIGNALS(SIGCHLD);
+
+ _cleanup_(sd_event_unrefp) sd_event *event = NULL;
+ r = sd_event_new(&event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate event loop: %m");
+
+ _cleanup_(sd_event_source_disable_unrefp) sd_event_source *socket_event_source = NULL;
+ r = sd_event_add_io(event, &socket_event_source, socket_fd, EPOLLIN, on_notify_socket, &child);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate IO source: %m");
+
+ /* If we receive both the sd_notify() message and a SIGCHLD always process sd_notify() first, it's
+ * the more interesting, "positive" information. */
+ r = sd_event_source_set_priority(socket_event_source, SD_EVENT_PRIORITY_NORMAL - 10);
+ if (r < 0)
+ return log_error_errno(r, "Failed to change child event source priority: %m");
+
+ _cleanup_(sd_event_source_disable_unrefp) sd_event_source *child_event_source = NULL;
+ r = event_add_child_pidref(event, &child_event_source, &child, WEXITED, on_child, /* userdata= */ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to allocate child source: %m");
+
+ /* Handle SIGCHLD before propagating the other signals below */
+ r = sd_event_source_set_priority(child_event_source, SD_EVENT_PRIORITY_NORMAL - 5);
+ if (r < 0)
+ return log_error_errno(r, "Failed to change child event source priority: %m");
+
+ sd_event_source **forward_signal_sources = NULL;
+ size_t n_forward_signal_sources = 0;
+ CLEANUP_ARRAY(forward_signal_sources, n_forward_signal_sources, event_source_unref_many);
+
+ r = event_forward_signals(
+ event,
+ child_event_source,
+ forward_signals, ELEMENTSOF(forward_signals),
+ &forward_signal_sources, &n_forward_signal_sources);
+ if (r < 0)
+ return log_error_errno(r, "Failed to set up signal forwarding: %m");
+
+ r = sd_event_loop(event);
+ if (r < 0)
+ return log_error_errno(r, "Failed to run event loop: %m");
+
+ return r;
+}
+
static int run(int argc, char* argv[]) {
_cleanup_free_ char *status = NULL, *main_pid = NULL, *main_pidfd_id = NULL, *msg = NULL,
*monotonic_usec = NULL, *fdn = NULL;
@@ -376,7 +615,10 @@ static int run(int argc, char* argv[]) {
if (r <= 0)
return r;
- if (arg_booted) {
+ if (arg_action == ACTION_FORK)
+ return action_fork(argv + optind);
+
+ if (arg_action == ACTION_BOOTED) {
r = sd_booted();
if (r < 0)
log_debug_errno(r, "Failed to determine whether we are booted with systemd, assuming we aren't: %m");
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
index bcf0baaf7fb..4ba3694280b 100644
--- a/src/nspawn/nspawn.c
+++ b/src/nspawn/nspawn.c
@@ -68,6 +68,7 @@
#include "mount-util.h"
#include "mountpoint-util.h"
#include "namespace-util.h"
+#include "notify-recv.h"
#include "nspawn-bind-user.h"
#include "nspawn-cgroup.h"
#include "nspawn-expose-ports.h"
@@ -3751,7 +3752,11 @@ static int setup_notify_child(const void *directory) {
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
- return log_error_errno(r, "SO_PASSCRED failed: %m");
+ return log_error_errno(r, "Failed to enable SO_PASSCRED: %m");
+
+ r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
+ if (r < 0)
+ log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
return TAKE_FD(fd);
}
@@ -4610,59 +4615,25 @@ static int setup_uid_map(
}
static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
- char buf[NOTIFY_BUFFER_MAX+1];
- char *p = NULL;
- struct iovec iovec = {
- .iov_base = buf,
- .iov_len = sizeof(buf)-1,
- };
- CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) +
- CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)) control;
- struct msghdr msghdr = {
- .msg_iov = &iovec,
- .msg_iovlen = 1,
- .msg_control = &control,
- .msg_controllen = sizeof(control),
- };
- struct ucred *ucred;
- ssize_t n;
- pid_t inner_child_pid;
- _cleanup_strv_free_ char **tags = NULL;
+ pid_t inner_child_pid = PTR_TO_PID(userdata);
int r;
assert(userdata);
- inner_child_pid = PTR_TO_PID(userdata);
-
- if (revents != EPOLLIN) {
- log_warning("Got unexpected poll event for notify fd.");
+ _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
+ _cleanup_free_ char *buf = NULL;
+ r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
+ if (r == -EAGAIN)
return 0;
- }
+ if (r < 0)
+ return log_error_errno(r, "Failed to receive notification message: %m");
- n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
- if (ERRNO_IS_NEG_TRANSIENT(n))
- return 0;
- if (n == -ECHRNG) {
- log_warning_errno(n, "Got message with truncated control data (too many fds sent?), ignoring.");
- return 0;
- }
- if (n == -EXFULL) {
- log_warning_errno(n, "Got message with truncated payload data, ignoring.");
- return 0;
- }
- if (n < 0)
- return log_warning_errno(n, "Couldn't read notification socket: %m");
-
- cmsg_close_all(&msghdr);
-
- ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
- if (!ucred || ucred->pid != inner_child_pid) {
+ if (sender_pid.pid != inner_child_pid) {
log_debug("Received notify message from process that is not the payload's PID 1. Ignoring.");
return 0;
}
- buf[n] = 0;
- tags = strv_split(buf, "\n\r");
+ _cleanup_strv_free_ char **tags = strv_split(buf, "\n\r");
if (!tags)
return log_oom();
@@ -4683,7 +4654,7 @@ static int nspawn_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t r
log_warning_errno(r, "Failed to send readiness notification, ignoring: %m");
}
- p = strv_find_startswith(tags, "STATUS=");
+ char *p = strv_find_startswith(tags, "STATUS=");
if (p)
(void) sd_notifyf(false, "STATUS=Container running: %s", p);
diff --git a/src/ptyfwd/ptyfwd-tool.c b/src/ptyfwd/ptyfwd-tool.c
index c6ae362e3d3..f62a4dc767b 100644
--- a/src/ptyfwd/ptyfwd-tool.c
+++ b/src/ptyfwd/ptyfwd-tool.c
@@ -108,6 +108,9 @@ static int parse_argv(int argc, char *argv[]) {
assert_not_reached();
}
+ if (optind >= argc)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected command line, refusing.");
+
return 1;
}
@@ -156,6 +159,8 @@ static int run(int argc, char *argv[]) {
if (!l)
return log_oom();
+ assert_se(!strv_isempty(l));
+
r = sd_event_default(&event);
if (r < 0)
return log_error_errno(r, "Failed to get event loop: %m");
diff --git a/src/shared/meson.build b/src/shared/meson.build
index ed7cad7c883..0459975005e 100644
--- a/src/shared/meson.build
+++ b/src/shared/meson.build
@@ -126,6 +126,7 @@ shared_sources = files(
'netif-naming-scheme.c',
'netif-sriov.c',
'netif-util.c',
+ 'notify-recv.c',
'nsflags.c',
'nsresource.c',
'numa-util.c',
diff --git a/src/shared/notify-recv.c b/src/shared/notify-recv.c
new file mode 100644
index 00000000000..c3596d0258a
--- /dev/null
+++ b/src/shared/notify-recv.c
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "fd-util.h"
+#include "notify-recv.h"
+#include "socket-util.h"
+
+int notify_recv(int fd, char **ret_text, struct ucred *ret_ucred, PidRef *ret_pidref) {
+ CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred)) +
+ CMSG_SPACE(sizeof(int)) + /* SCM_PIDFD */
+ CMSG_SPACE(sizeof(int) * NOTIFY_FD_MAX)) control;
+ struct iovec iovec;
+ struct msghdr msghdr = {
+ .msg_iov = &iovec,
+ .msg_iovlen = 1,
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ ssize_t n;
+
+ assert(fd >= 0);
+
+ /* Receives a $NOTIFY_SOCKET message (aka sd_notify()). Does various validations. Returns -EAGAIN in
+ * case an invalid message is received (following the logic that an invalid message shall be ignored,
+ * and treated like no message at all). */
+
+ _cleanup_free_ char *buf = new(char, NOTIFY_BUFFER_MAX+1);
+ if (!buf)
+ return log_oom_debug();
+
+ iovec = (struct iovec) {
+ .iov_base = buf,
+ .iov_len = NOTIFY_BUFFER_MAX,
+ };
+
+ n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
+ if (ERRNO_IS_NEG_TRANSIENT(n))
+ return -EAGAIN;
+ if (n == -ECHRNG) {
+ log_debug_errno(n, "Got message with truncated control data (unexpected fds sent?), ignoring.");
+ return -EAGAIN;
+ }
+ if (n == -EXFULL) {
+ log_debug_errno(n, "Got message with truncated payload data, ignoring.");
+ return -EAGAIN;
+ }
+ if (n < 0)
+ return (int) n;
+
+ const struct ucred *ucred = NULL;
+ _cleanup_close_ int pidfd = -EBADF;
+ struct cmsghdr *cmsg;
+ CMSG_FOREACH(cmsg, &msghdr) {
+ if (cmsg->cmsg_level != SOL_SOCKET)
+ continue;
+
+ switch (cmsg->cmsg_type) {
+
+ case SCM_RIGHTS:
+ /* For now, just close every fd */
+ close_many(CMSG_TYPED_DATA(cmsg, int),
+ (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int));
+ break;
+
+ case SCM_PIDFD:
+ assert(cmsg->cmsg_len == CMSG_LEN(sizeof(int)));
+ pidfd = *CMSG_TYPED_DATA(cmsg, int);
+ break;
+
+ case SCM_CREDENTIALS:
+ assert(cmsg->cmsg_len == CMSG_LEN(sizeof(struct ucred)));
+ ucred = CMSG_TYPED_DATA(cmsg, struct ucred);
+ break;
+ }
+ }
+
+ if ((ret_ucred || ret_pidref) && (!ucred || ucred->pid <= 0))
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got notification datagram lacking valid credential information, ignoring.");
+
+ if (n == 0)
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got empty notification message, ignoring.");
+ if (memchr(buf, 0, n - 1))
+ return log_debug_errno(SYNTHETIC_ERRNO(EAGAIN), "Got notification message with embedded NUL, ignoring.");
+
+ if (ret_pidref) {
+ assert(ucred);
+ assert(ucred->pid > 0);
+
+ if (pidfd >= 0)
+ *ret_pidref = (PidRef) {
+ .pid = ucred->pid,
+ .fd = TAKE_FD(pidfd),
+ };
+ else
+ *ret_pidref = PIDREF_MAKE_FROM_PID(ucred->pid);
+ }
+
+ if (ret_text) {
+ buf[n] = 0;
+ *ret_text = TAKE_PTR(buf);
+ }
+
+ if (ret_ucred)
+ *ret_ucred = *ucred;
+
+ return 0;
+}
diff --git a/src/shared/notify-recv.h b/src/shared/notify-recv.h
new file mode 100644
index 00000000000..5cd5062b35d
--- /dev/null
+++ b/src/shared/notify-recv.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+#pragma once
+
+#include
+
+#include "pidref.h"
+
+int notify_recv(int fd, char **ret_text, struct ucred *ret_ucred, PidRef *ret_pidref);
diff --git a/src/systemd/sd-varlink.h b/src/systemd/sd-varlink.h
index 528c20e8295..27e63a7fba1 100644
--- a/src/systemd/sd-varlink.h
+++ b/src/systemd/sd-varlink.h
@@ -195,6 +195,7 @@ int sd_varlink_reset_fds(sd_varlink *v);
int sd_varlink_peek_fd(sd_varlink *v, size_t i);
int sd_varlink_peek_dup_fd(sd_varlink *v, size_t i);
int sd_varlink_take_fd(sd_varlink *v, size_t i);
+int sd_varlink_get_n_fds(sd_varlink *v);
int sd_varlink_set_allow_fd_passing_input(sd_varlink *v, int b);
int sd_varlink_set_allow_fd_passing_output(sd_varlink *v, int b);
diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c
index f0e8ad3b36c..f3dea04314b 100644
--- a/src/sysupdate/sysupdate-transfer.c
+++ b/src/sysupdate/sysupdate-transfer.c
@@ -15,6 +15,7 @@
#include "hexdecoct.h"
#include "install-file.h"
#include "mkdir.h"
+#include "notify-recv.h"
#include "parse-helpers.h"
#include "parse-util.h"
#include "percent-util.h"
@@ -28,11 +29,11 @@
#include "stdio-util.h"
#include "strv.h"
#include "sync-util.h"
+#include "sysupdate.h"
#include "sysupdate-feature.h"
#include "sysupdate-pattern.h"
#include "sysupdate-resource.h"
#include "sysupdate-transfer.h"
-#include "sysupdate.h"
#include "tmpfile-util.h"
#include "web-util.h"
@@ -977,56 +978,25 @@ static int helper_on_exit(sd_event_source *s, const siginfo_t *si, void *userdat
}
static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- char buf[NOTIFY_BUFFER_MAX+1];
- struct iovec iovec = {
- .iov_base = buf,
- .iov_len = sizeof(buf)-1,
- };
- CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control;
- struct msghdr msghdr = {
- .msg_iov = &iovec,
- .msg_iovlen = 1,
- .msg_control = &control,
- .msg_controllen = sizeof(control),
- };
- struct ucred *ucred;
CalloutContext *ctx = ASSERT_PTR(userdata);
- char *progress_str, *errno_str;
- int progress;
- ssize_t n;
int r;
- n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
- if (ERRNO_IS_NEG_TRANSIENT(n))
- return 0;
- if (n == -ECHRNG) {
- log_warning_errno(n, "Got message with truncated control data (unexpected fds sent?), ignoring.");
- return 0;
- }
- if (n == -EXFULL) {
- log_warning_errno(n, "Got message with truncated payload data, ignoring.");
- return 0;
- }
- if (n < 0)
- return (int) n;
+ assert(fd >= 0);
- cmsg_close_all(&msghdr);
-
- ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
- if (!ucred || ucred->pid <= 0) {
- log_warning("Got notification datagram lacking credential information, ignoring.");
+ _cleanup_free_ char *buf = NULL;
+ _cleanup_(pidref_done) PidRef sender_pid = PIDREF_NULL;
+ r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pid);
+ if (r == -EAGAIN)
return 0;
- }
- if (ucred->pid != ctx->pid.pid) {
+ if (r < 0)
+ return log_warning_errno(r, "Failed to receive notification message: %m");
+
+ if (!pidref_equal(&ctx->pid, &sender_pid)) {
log_warning("Got notification datagram from unexpected peer, ignoring.");
return 0;
}
- buf[n] = 0;
-
- progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
- errno_str = find_line_startswith(buf, "ERRNO=");
-
+ char *errno_str = find_line_startswith(buf, "ERRNO=");
if (errno_str) {
truncate_nl(errno_str);
r = parse_errno(errno_str);
@@ -1038,9 +1008,11 @@ static int helper_on_notify(sd_event_source *s, int fd, uint32_t revents, void *
}
}
+ char *progress_str = find_line_startswith(buf, "X_IMPORT_PROGRESS=");
if (progress_str) {
truncate_nl(progress_str);
- progress = parse_percent(progress_str);
+
+ int progress = parse_percent(progress_str);
if (progress < 0)
log_warning("Got invalid percent value '%s', ignoring.", progress_str);
else {
@@ -1105,7 +1077,11 @@ static int run_callout(
r = setsockopt_int(fd, SOL_SOCKET, SO_PASSCRED, true);
if (r < 0)
- return log_error_errno(r, "Failed to set socket options: %m");
+ return log_error_errno(r, "Failed to enable SO_PASSCRED: %m");
+
+ r = setsockopt_int(fd, SOL_SOCKET, SO_PASSPIDFD, true);
+ if (r < 0)
+ log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
r = pidref_safe_fork(ctx->name, FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_LOG, &ctx->pid);
if (r < 0)
diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c
index 58967930f77..1083345374c 100644
--- a/src/sysupdate/sysupdated.c
+++ b/src/sysupdate/sysupdated.c
@@ -24,6 +24,7 @@
#include "main-func.h"
#include "memfd-util.h"
#include "mkdir-label.h"
+#include "notify-recv.h"
#include "os-util.h"
#include "process-util.h"
#include "service-util.h"
@@ -1645,65 +1646,39 @@ static Manager *manager_free(Manager *m) {
DEFINE_TRIVIAL_CLEANUP_FUNC(Manager *, manager_free);
static int manager_on_notify(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
- char buf[NOTIFY_BUFFER_MAX+1];
- struct iovec iovec = {
- .iov_base = buf,
- .iov_len = sizeof(buf)-1,
- };
- CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(struct ucred))) control;
- struct msghdr msghdr = {
- .msg_iov = &iovec,
- .msg_iovlen = 1,
- .msg_control = &control,
- .msg_controllen = sizeof(control),
- };
- struct ucred *ucred;
Manager *m = ASSERT_PTR(userdata);
+ int r;
+
+ assert(fd >= 0);
+
+ _cleanup_(pidref_done) PidRef sender_pidref = PIDREF_NULL;
+ _cleanup_free_ char *buf = NULL;
+ r = notify_recv(fd, &buf, /* ret_ucred= */ NULL, &sender_pidref);
+ if (r == -EAGAIN)
+ return 0;
+ if (r < 0)
+ return log_warning_errno(r, "Failed to receive notification message: %m");
+
Job *j;
- ssize_t n;
- char *version, *progress, *errno_str, *ready;
-
- n = recvmsg_safe(fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC);
- if (ERRNO_IS_NEG_TRANSIENT(n))
- return 0;
- if (n == -ECHRNG) {
- log_warning_errno(n, "Got message with truncated control data (unexpected fds sent?), ignoring.");
- return 0;
- }
- if (n == -EXFULL) {
- log_warning_errno(n, "Got message with truncated payload data, ignoring.");
- return 0;
- }
- if (n < 0)
- return (int) n;
-
- cmsg_close_all(&msghdr);
-
- ucred = CMSG_FIND_DATA(&msghdr, SOL_SOCKET, SCM_CREDENTIALS, struct ucred);
- if (!ucred || ucred->pid <= 0) {
- log_warning("Got notification datagram lacking credential information, ignoring.");
- return 0;
- }
-
HASHMAP_FOREACH(j, m->jobs) {
- pid_t pid;
- assert_se(sd_event_source_get_child_pid(j->child, &pid) >= 0);
+ PidRef child_pidref = PIDREF_NULL;
- if (ucred->pid == pid)
+ r = event_source_get_child_pidref(j->child, &child_pidref);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to get child pidref: %m");
+
+ if (pidref_equal(&sender_pidref, &child_pidref))
break;
}
-
if (!j) {
log_warning("Got notification datagram from unexpected peer, ignoring.");
return 0;
}
- buf[n] = 0;
-
- version = find_line_startswith(buf, "X_SYSUPDATE_VERSION=");
- progress = find_line_startswith(buf, "X_SYSUPDATE_PROGRESS=");
- errno_str = find_line_startswith(buf, "ERRNO=");
- ready = find_line_startswith(buf, "READY=1");
+ char *version = find_line_startswith(buf, "X_SYSUPDATE_VERSION=");
+ char *progress = find_line_startswith(buf, "X_SYSUPDATE_PROGRESS=");
+ char *errno_str = find_line_startswith(buf, "ERRNO=");
+ const char *ready = find_line_startswith(buf, "READY=1");
if (version)
job_on_version(j, truncate_nl(version));
@@ -1777,6 +1752,10 @@ static int manager_new(Manager **ret) {
if (r < 0)
return r;
+ r = setsockopt_int(notify_fd, SOL_SOCKET, SO_PASSPIDFD, true);
+ if (r < 0)
+ log_debug_errno(r, "Failed to enable SO_PASSPIDFD, ignoring: %m");
+
r = sd_event_add_io(m->event, &m->notify_event, notify_fd, EPOLLIN, manager_on_notify, m);
if (r < 0)
return r;
diff --git a/src/varlinkctl/varlinkctl.c b/src/varlinkctl/varlinkctl.c
index cecd6c90df8..978bed5b7f0 100644
--- a/src/varlinkctl/varlinkctl.c
+++ b/src/varlinkctl/varlinkctl.c
@@ -5,11 +5,13 @@
#include "sd-varlink.h"
#include "build.h"
+#include "env-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "format-table.h"
#include "io-util.h"
#include "main-func.h"
+#include "memfd-util.h"
#include "pager.h"
#include "parse-argument.h"
#include "path-util.h"
@@ -27,6 +29,7 @@ static bool arg_collect = false;
static bool arg_quiet = false;
static char **arg_graceful = NULL;
static usec_t arg_timeout = 0;
+static bool arg_exec = false;
STATIC_DESTRUCTOR_REGISTER(arg_graceful, strv_freep);
@@ -53,6 +56,8 @@ static int help(void) {
" Show interface definition\n"
" call ADDRESS METHOD [PARAMS]\n"
" Invoke method\n"
+ " --exec call ADDRESS METHOD PARAMS -- CMDLINE…\n"
+ " Invoke method and pass response and fds to command\n"
" validate-idl [FILE] Validate interface description\n"
" help Show this help\n"
"\n%3$sOptions:%4$s\n"
@@ -94,6 +99,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_COLLECT,
ARG_GRACEFUL,
ARG_TIMEOUT,
+ ARG_EXEC,
};
static const struct option options[] = {
@@ -107,6 +113,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "quiet", no_argument, NULL, 'q' },
{ "graceful", required_argument, NULL, ARG_GRACEFUL },
{ "timeout", required_argument, NULL, ARG_TIMEOUT },
+ { "exec", no_argument, NULL, ARG_EXEC },
{},
};
@@ -187,6 +194,10 @@ static int parse_argv(int argc, char *argv[]) {
break;
+ case ARG_EXEC:
+ arg_exec = true;
+ break;
+
case '?':
return -EINVAL;
@@ -529,18 +540,34 @@ static int verb_call(int argc, char *argv[], void *userdata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *jp = NULL;
_cleanup_(sd_varlink_unrefp) sd_varlink *vl = NULL;
const char *url, *method, *parameter, *source;
+ char **cmdline;
int r;
assert(argc >= 3);
- assert(argc <= 4);
+
+ if (argc > 4 && !arg_exec)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");
+ if (arg_exec && argc < 5)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected command line to execute.");
+
+ if (arg_exec && (arg_collect || (arg_method_flags & (SD_VARLINK_METHOD_ONEWAY|SD_VARLINK_METHOD_MORE))) != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--exec and --collect/--more/--oneway may not be combined.");
+
url = argv[1];
method = argv[2];
parameter = argc > 3 && !streq(argv[3], "-") ? argv[3] : NULL;
+ cmdline = strv_skip(argv, 4);
- /* No JSON mode explicitly configured? Then default to the same as -j */
+ /* No JSON mode explicitly configured? Then default to the same as -j (except if --exec is used, in
+ * which case generate shortest possible JSON since we are going to pass it to a program rather than
+ * a user anyway) */
if (!sd_json_format_enabled(arg_json_format_flags)) {
arg_json_format_flags &= ~SD_JSON_FORMAT_OFF;
- arg_json_format_flags |= SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
+
+ if (arg_exec)
+ arg_json_format_flags |= SD_JSON_FORMAT_NEWLINE;
+ else
+ arg_json_format_flags |= SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
}
/* For pipeable text tools it's kinda customary to finish output off in a newline character, and not
@@ -648,6 +675,15 @@ static int verb_call(int argc, char *argv[], void *userdata) {
} else {
sd_json_variant *reply = NULL;
const char *error = NULL;
+ bool process_fds = false;
+
+ if (arg_exec) {
+ r = sd_varlink_set_allow_fd_passing_input(vl, true);
+ if (r < 0)
+ log_debug_errno(r, "Unable to enable file descriptor receiving, ignoring: %m");
+ else
+ process_fds = true;
+ }
r = sd_varlink_call(vl, method, jp, &reply, &error);
if (r < 0)
@@ -668,6 +704,100 @@ static int verb_call(int argc, char *argv[], void *userdata) {
} else
r = 0;
+ if (arg_exec && r == 0) {
+ (void) sd_notify(/* unset_environment= */ false, "READY=1");
+
+ _cleanup_free_ char *formatted = NULL;
+ r = sd_json_variant_format(reply, arg_json_format_flags, &formatted);
+ if (r < 0)
+ return log_error_errno(r, "Failed to format reply: %m");
+
+ _cleanup_close_ int mfd = memfd_new_and_seal_string("varlink-reply", formatted);
+ if (mfd < 0)
+ return log_error_errno(mfd, "Failed to allocate memfd for reply: %m");
+
+ _cleanup_free_ char *j = strv_join(cmdline, " ");
+ if (!j)
+ return log_oom();
+
+ int *fd_array = NULL, n = 0;
+ size_t m = 0;
+ CLEANUP_ARRAY(fd_array, m, close_many_and_free);
+
+ if (process_fds) {
+ n = sd_varlink_get_n_fds(vl);
+ if (n < 0)
+ return log_error_errno(n, "Failed to determine how many file descriptors we received: %m");
+
+ fd_array = new(int, n);
+ if (!fd_array)
+ return log_oom();
+
+ for (int i = 0; i < n; i++) {
+ fd_array[m] = sd_varlink_take_fd(vl, i);
+ if (fd_array[m] < 0)
+ return log_error_errno(fd_array[m], "Failed to acquire fd we received: %m");
+
+ m++;
+ }
+ }
+
+ /* We'll now close all remaining fds. This means we are stealing other code that
+ * lives in our process their fds. Hence we will now no longer bubble up any
+ * errors. */
+
+ log_close();
+ log_set_open_when_needed(true);
+
+ r = move_fd(mfd, STDIN_FILENO, /* cloexec= */ false);
+ if (r < 0) {
+ log_error_errno(r, "Failed to move reply to STDIN_FILENO: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ r = close_all_fds(fd_array, m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to close all remaining file descriptors: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ r = pack_fds(fd_array, m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to rearrange file descriptors: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ r = fd_cloexec_many(fd_array, m, false);
+ if (r < 0) {
+ log_error_errno(r, "Failed to disable O_CLOEXEC for file descriptors: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ if (m > 0) {
+ r = setenvf("LISTEN_FDS", /* overwrite= */ true, "%zu", m);
+ if (r < 0) {
+ log_error_errno(r, "Failed to set $LISTEN_FDS environment variable: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ r = setenvf("LISTEN_PID", /* overwrite= */ true, PID_FMT, getpid_cached());
+ if (r < 0) {
+ log_error_errno(r, "Failed to set $LISTEN_PID environment variable: %m");
+ _exit(EXIT_FAILURE);
+ }
+ } else {
+ (void) unsetenv("LISTEN_FDS");
+ (void) unsetenv("LISTEN_PID");
+ }
+ (void) unsetenv("LISTEN_FDNAMES");
+
+ log_debug("Executing: %s", j);
+
+ execvp(cmdline[0], cmdline);
+ log_error_errno(errno, "Failed to execute '%s': %m", j);
+ _exit(EXIT_FAILURE);
+ }
+
if (arg_quiet)
return r;
@@ -735,7 +865,7 @@ static int varlinkctl_main(int argc, char *argv[]) {
{ "list-interfaces", 2, 2, 0, verb_info },
{ "introspect", 2, VERB_ANY, 0, verb_introspect },
{ "list-methods", 2, VERB_ANY, 0, verb_introspect },
- { "call", 3, 4, 0, verb_call },
+ { "call", 3, VERB_ANY, 0, verb_call },
{ "validate-idl", 1, 2, 0, verb_validate_idl },
{ "help", VERB_ANY, VERB_ANY, 0, verb_help },
{}
diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh
index 5bc24236747..4875e48f809 100755
--- a/test/units/TEST-13-NSPAWN.machined.sh
+++ b/test/units/TEST-13-NSPAWN.machined.sh
@@ -388,9 +388,18 @@ varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell"}'
rm -f /tmp/none-existent-file
-varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/sh", "args": ["/bin/sh", "-c", "echo $FOO > /tmp/none-existent-file"], "environment": ["FOO=BAR"]}'
+
+# We need to make sure the acquired pty fd stays open if we invoke a command
+# server side, to not generate early SIGHUP. Hence, let's just invoke "sleep
+# infinity" client side, once we acquired the fd (passing it to it), and kill
+# it once we verified everything worked.
+PID=$(systemd-notify --fork -- varlinkctl --exec call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/bash", "args": ["/bin/bash", "-c", "echo $FOO > /tmp/none-existent-file"], "environment": ["FOO=BAR"]}' -- sleep infinity)
timeout 30 bash -c "until test -e /tmp/none-existent-file; do sleep .5; done"
grep -q "BAR" /tmp/none-existent-file
+kill "$PID"
+
+# Test varlinkctl's --exec fd passing logic properly
+assert_eq "$(varlinkctl --exec call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/bash", "args": ["/bin/bash", "-c", "echo $((7 + 8))"]}' -- bash -c 'read -r -N 2 x <&3 ; echo "$x"')" 15
# test io.systemd.Machine.MapFrom
varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "uid":0, "gid": 0}'
diff --git a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh
index a0797469a46..921fadf998c 100755
--- a/test/units/TEST-74-AUX-UTILS.varlinkctl.sh
+++ b/test/units/TEST-74-AUX-UTILS.varlinkctl.sh
@@ -160,3 +160,9 @@ done
varlinkctl info /run/systemd/io.systemd.Hostname
varlinkctl introspect /run/systemd/io.systemd.Hostname io.systemd.Hostname
varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}'
+
+# Validate that --exec results in the very same values
+varlinkctl call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}' | jq > /tmp/describe1.json
+varlinkctl --exec call /run/systemd/io.systemd.Hostname io.systemd.Hostname.Describe '{}' -- jq > /tmp/describe2.json
+cmp /tmp/describe1.json /tmp/describe2.json
+rm /tmp/describe1.json /tmp/describe2.json