mirror of
https://github.com/systemd/systemd.git
synced 2026-08-03 22:50:29 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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
106
src/shared/notify-recv.c
Normal 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
8
src/shared/notify-recv.h
Normal 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);
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
{}
|
||||
|
||||
@@ -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}'
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user