Fix race in io.systemd.Machine.Open() test case (#36410)

This started out as a simple attempt to fix a race in an existing test
case for io.systemd.Machine.Open(). To address it nicely I added some
machinery to varlinkctl and systemd-notify though, and because of that I
refacting reception of sd_notify() messages in various places of our
codebase. So it became much much bigger.

This ports all receivers of sd_notify() messages over to a new common
implementation, except for one: the one in PID1. It's more powerful than
the others, since it accepts fds too. I think we should generalize
notify_recv() to cover that too, it's not that much more work, but for
now I didn't want to add even more refactorings on top, and this can
easily happen later separately, hence I left it out for now.
This commit is contained in:
Daan De Meyer
2025-02-20 19:53:00 +01:00
committed by GitHub
19 changed files with 750 additions and 221 deletions

View File

@@ -26,7 +26,10 @@
<command>systemd-notify</command> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">VARIABLE=VALUE</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-notify</command> <arg>--exec</arg> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">VARIABLE=VALUE</arg> <arg>;</arg> <arg rep="repeat">CMDLINE</arg>
<command>systemd-notify</command> <arg choice="plain">--exec</arg> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="opt" rep="repeat">VARIABLE=VALUE</arg> <arg choice="plain">; --</arg> <arg choice="req" rep="repeat">CMDLINE</arg>
</cmdsynopsis>
<cmdsynopsis>
<command>systemd-notify</command> <arg choice="plain">--fork</arg> <arg choice="opt" rep="repeat">OPTIONS</arg> <arg choice="plain">--</arg> <arg choice="req" rep="repeat">CMDLINE</arg>
</cmdsynopsis>
</refsynopsisdiv>
@@ -237,6 +240,54 @@
<xi:include href="version-info.xml" xpointer="v254"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--fork</option></term>
<listitem><para>Instead of sending a notification message, fork off a command line and wait until a
<literal>READY=1</literal> message is received from it. In other words: this makes
<command>systemd-notify</command> the receiver of notification messages instead of the sender,
swapping roles. This is useful to quickly fork off a process that implements the
<function>sd_notify()</function> protocol from a shell script. The invoked command line will have
standard input and standard output connected to <filename>/dev/null</filename>, but standard error
will be inherited from the invoking process. The numeric process ID is written to standard output by
<command>systemd-notify</command> (unless <option>--quiet</option> is specified), which may be used
to later terminate the forked off process.</para>
<para>Note that processes forked off like this will likely remain running after
<command>systemd-notify</command> 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.</para>
<para>Note that this option should not be used to invoke full services ad-hoc, use
<command>systemd-run</command> for that.</para>
<para>Also note that when invoked with this switch <command>systemd-notify</command> will exit
successfully under two distinction conditions:
<orderedlist>
<listitem><para><command>systemd-notify</command> received a <literal>READY=1</literal>
notification from the child it just forked off.</para></listitem>
<listitem><para>The child process exited cleanly (with exit status zero) before sending
<literal>READY=1</literal>.</para></listitem>
</orderedlist></para>
<para>Example use:<programlisting># PID=$(systemd-notify --fork -- mycommand)
kill "$PID"
unset PID</programlisting></para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><option>--quiet</option></term>
<term><option>-q</option></term>
<listitem><para>Turn off output of the numeric process ID when <option>--fork</option> is used.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>

View File

@@ -61,6 +61,18 @@
<arg choice="opt"><replaceable>ARGUMENTS</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="plain">--exec call</arg>
<arg choice="plain">call</arg>
<arg choice="plain"><replaceable>ADDRESS</replaceable></arg>
<arg choice="plain"><replaceable>METHOD</replaceable></arg>
<arg choice="plain"><replaceable>ARGUMENTS</replaceable></arg>
<arg choice="plain">--</arg>
<arg choice="plain"><replaceable>CMDLINE</replaceable></arg>
</cmdsynopsis>
<cmdsynopsis>
<command>varlinkctl</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
@@ -307,6 +319,25 @@
</listitem>
</varlistentry>
<varlistentry>
<term><option>--exec</option></term>
<listitem>
<para>Once the method call issued via <command>call</command> 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 <varname>$LISTEN_FDS</varname> protocol. This
functionality may be used to consume replies that come with associated file descriptors in a
reasonable way.</para>
<para>Now that if <option>--exec</option> is specified the the third parameter to
<command>call</command> is not optional (i.e. the method call parameters).</para>
<xi:include href="version-info.xml" xpointer="v258"/>
</listitem>
</varlistentry>
<xi:include href="standard-options.xml" xpointer="no-pager" />
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
@@ -349,7 +380,9 @@ type ResolvedAddress(
<example>
<title>Invoking a Method</title>
<para>The following command resolves a hostname via <citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s <function>ResolveHostname</function> method call.</para>
<para>The following command resolves a hostname via
<citerefentry><refentrytitle>systemd-resolved.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>'s
<function>ResolveHostname</function> method call.</para>
<programlisting>$ varlinkctl call /run/systemd/resolve/io.systemd.Resolve io.systemd.Resolve.ResolveHostname '{"name":"systemd.io","family":2}' -j
{

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
#include <errno.h>
#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);

View File

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

View File

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

View File

@@ -7,30 +7,40 @@
#include <unistd.h>
#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");

View File

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

View File

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

View File

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

106
src/shared/notify-recv.c Normal file
View File

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

8
src/shared/notify-recv.h Normal file
View File

@@ -0,0 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <sys/socket.h>
#include "pidref.h"
int notify_recv(int fd, char **ret_text, struct ucred *ret_ucred, PidRef *ret_pidref);

View File

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

View File

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

View File

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

View File

@@ -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 },
{}

View File

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

View File

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