1
0
mirror of https://github.com/systemd/systemd.git synced 2025-01-03 05:18:09 +03:00

core/service: support sd_notify() MAINPIDFD=1 and MAINPIDFDID= (#34932)

This commit is contained in:
Lennart Poettering 2024-10-30 08:45:25 +01:00 committed by GitHub
commit 5c11f6e0a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 183 additions and 79 deletions

3
TODO
View File

@ -131,8 +131,7 @@ Features:
* $LISTEN_PID, $MAINPID and $SYSTEMD_EXECPID env vars that the service manager
sets should be augmented with $LISTEN_PIDFDID, $MAINPIDFDID and
$SYSTEMD_EXECPIDFD (and similar for other env vars we might send). Also,
MAINPID= in sd_notify() should be augmented with MAINPIDFDID=, and so on.
$SYSTEMD_EXECPIDFD (and similar for other env vars we might send).
* port copy.c over to use LabelOps for all labelling.

View File

@ -291,12 +291,35 @@
<varlistentry>
<term>MAINPID=…</term>
<listitem><para>The main process ID (PID) of the service, in case the service manager did not fork
off the process itself. Example: <literal>MAINPID=4711</literal>.</para>
<listitem><para>Change the main process ID (PID) of the service. This is especially useful in the case
where the real main process isn't directly forked off by the service manager.
Example: <literal>MAINPID=4711</literal>.</para>
<xi:include href="version-info.xml" xpointer="v233"/></listitem>
</varlistentry>
<varlistentry>
<term>MAINPIDFDID=…</term>
<listitem><para>The pidfd inode number of the new main process (specified through <varname>MAINPID=</varname>).
This information can be acquired through
<citerefentry project='man-pages'><refentrytitle>fstat</refentrytitle><manvolnum>2</manvolnum></citerefentry>
on the pidfd and is used to identify the process in a race-free fashion. Alternatively,
a pidfd can be sent directly to the service manager (see <varname>MAINPIDFD=1</varname> below).</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term>MAINPIDFD=1</term>
<listitem><para>Similar to <varname>MAINPID=</varname> with <varname>MAINPIDFDID=</varname>, but
the process is referenced directly by the pidfd passed to the service manager. This is useful
if pidfd id is not supported on the system. Exactly one fd is expected for this notification.</para>
<xi:include href="version-info.xml" xpointer="v257"/></listitem>
</varlistentry>
<varlistentry>
<term>WATCHDOG=1</term>

View File

@ -4540,6 +4540,72 @@ static bool service_notify_message_authorized(Service *s, PidRef *pid) {
}
}
static int service_notify_message_parse_new_pid(
Unit *u,
char * const *tags,
FDSet *fds,
PidRef *ret) {
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
const char *e;
int r;
assert(u);
assert(ret);
/* MAINPIDFD=1 always takes precedence */
if (strv_contains(tags, "MAINPIDFD=1")) {
unsigned n_fds = fdset_size(fds);
if (n_fds != 1)
return log_unit_warning_errno(u, SYNTHETIC_ERRNO(EINVAL),
"Got MAINPIDFD=1 with %s fd, ignoring.", n_fds == 0 ? "no" : "more than one");
r = pidref_set_pidfd_consume(&pidref, ASSERT_FD(fdset_steal_first(fds)));
if (r < 0)
return log_unit_warning_errno(u, r, "Failed to create reference to received new main pidfd: %m");
goto finish;
}
e = strv_find_startswith(tags, "MAINPID=");
if (!e) {
*ret = PIDREF_NULL;
return 0;
}
r = pidref_set_pidstr(&pidref, e);
if (r < 0)
return log_unit_warning_errno(u, r, "Failed to parse MAINPID=%s field in notification message, ignoring: %m", e);
e = strv_find_startswith(tags, "MAINPIDFDID=");
if (!e)
goto finish;
uint64_t pidfd_id;
r = safe_atou64(e, &pidfd_id);
if (r < 0)
return log_unit_warning_errno(u, r, "Failed to parse MAINPIDFDID= in notification message, refusing: %s", e);
r = pidref_acquire_pidfd_id(&pidref);
if (r < 0) {
if (!ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_unit_warning_errno(u, r,
"Failed to acquire pidfd id of process " PID_FMT ", not validating MAINPIDFDID=%" PRIu64 ": %m",
pidref.pid, pidfd_id);
goto finish;
}
if (pidref.fd_id != pidfd_id)
return log_unit_warning_errno(u, SYNTHETIC_ERRNO(ESRCH),
"PIDFD ID of process " PID_FMT " (%" PRIu64 ") mismatches with received MAINPIDFDID=%" PRIu64 ", not changing main PID.",
pidref.pid, pidref.fd_id, pidfd_id);
finish:
*ret = TAKE_PIDREF(pidref);
return 1;
}
static void service_notify_message(
Unit *u,
PidRef *pidref,
@ -4565,18 +4631,15 @@ static void service_notify_message(
bool notify_dbus = false;
const char *e;
/* Interpret MAINPID= */
e = strv_find_startswith(tags, "MAINPID=");
if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
SERVICE_STOP, SERVICE_STOP_SIGTERM)) {
/* Interpret MAINPID= (+ MAINPIDFDID=) / MAINPIDFD=1 */
_cleanup_(pidref_done) PidRef new_main_pid = PIDREF_NULL;
r = pidref_set_pidstr(&new_main_pid, e);
if (r < 0)
log_unit_warning_errno(u, r, "Failed to parse MAINPID=%s field in notification message, ignoring: %m", e);
else if (!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid)) {
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY,
SERVICE_STOP, SERVICE_STOP_SIGTERM) &&
(!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) {
r = service_is_suitable_main_pid(s, &new_main_pid, LOG_WARNING);
if (r == 0) {
@ -4598,7 +4661,6 @@ static void service_notify_message(
notify_dbus = true;
}
}
}
/* Parse MONOTONIC_USEC= */
e = strv_find_startswith(tags, "MONOTONIC_USEC=");

View File

@ -28,7 +28,7 @@
static bool arg_ready = false;
static bool arg_reloading = false;
static bool arg_stopping = false;
static pid_t arg_pid = 0;
static PidRef arg_pid = PIDREF_NULL;
static const char *arg_status = NULL;
static bool arg_booted = false;
static uid_t arg_uid = UID_INVALID;
@ -39,6 +39,7 @@ static char **arg_exec = NULL;
static FDSet *arg_fds = NULL;
static char *arg_fdname = NULL;
STATIC_DESTRUCTOR_REGISTER(arg_pid, pidref_done);
STATIC_DESTRUCTOR_REGISTER(arg_env, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_exec, strv_freep);
STATIC_DESTRUCTOR_REGISTER(arg_fds, fdset_freep);
@ -99,16 +100,24 @@ static pid_t manager_pid(void) {
return pid;
}
static pid_t pid_parent_if_possible(void) {
pid_t parent_pid = getppid();
static int pidref_parent_if_applicable(PidRef *ret) {
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
int r;
assert(ret);
r = pidref_set_parent(&pidref);
if (r < 0)
return log_debug_errno(r, "Failed to create reference to our parent process: %m");
/* Don't send from PID 1 or the service manager's PID (which might be distinct from 1, if we are a
* --user service). That'd just be confusing for the service manager. */
if (parent_pid <= 1 ||
parent_pid == manager_pid())
return getpid_cached();
if (pidref.pid <= 1 ||
pidref.pid == manager_pid())
return pidref_set_self(ret);
return parent_pid;
*ret = TAKE_PIDREF(pidref);
return 0;
}
static int parse_argv(int argc, char *argv[]) {
@ -175,17 +184,18 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_PID:
pidref_done(&arg_pid);
if (isempty(optarg) || streq(optarg, "auto"))
arg_pid = pid_parent_if_possible();
r = pidref_parent_if_applicable(&arg_pid);
else if (streq(optarg, "parent"))
arg_pid = getppid();
r = pidref_set_parent(&arg_pid);
else if (streq(optarg, "self"))
arg_pid = getpid_cached();
else {
r = parse_pid(optarg, &arg_pid);
r = pidref_set_self(&arg_pid);
else
r = pidref_set_pidstr(&arg_pid, optarg);
if (r < 0)
return log_error_errno(r, "Failed to parse PID %s.", optarg);
}
return log_error_errno(r, "Failed to refer to --pid='%s': %m", optarg);
break;
@ -276,7 +286,7 @@ 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 || arg_pid > 0 || !fdset_isempty(arg_fds);
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) {
@ -326,9 +336,10 @@ static int parse_argv(int argc, char *argv[]) {
}
static int run(int argc, char* argv[]) {
_cleanup_free_ char *status = NULL, *cpid = NULL, *msg = NULL, *monotonic_usec = NULL, *fdn = NULL;
_cleanup_free_ char *status = NULL, *main_pid = NULL, *main_pidfd_id = NULL, *msg = NULL,
*monotonic_usec = NULL, *fdn = NULL;
_cleanup_strv_free_ char **final_env = NULL;
const char *our_env[9];
const char *our_env[10];
size_t i = 0;
int r;
@ -371,11 +382,22 @@ static int run(int argc, char* argv[]) {
our_env[i++] = status;
}
if (arg_pid > 0) {
if (asprintf(&cpid, "MAINPID="PID_FMT, arg_pid) < 0)
if (pidref_is_set(&arg_pid)) {
if (asprintf(&main_pid, "MAINPID="PID_FMT, arg_pid.pid) < 0)
return log_oom();
our_env[i++] = cpid;
our_env[i++] = main_pid;
r = pidref_acquire_pidfd_id(&arg_pid);
if (r < 0)
log_debug_errno(r, "Unable to acquire pidfd id of new main pid " PID_FMT ", ignoring: %m",
arg_pid.pid);
else {
if (asprintf(&main_pidfd_id, "MAINPIDFDID=%" PRIu64, arg_pid.fd_id) < 0)
return log_oom();
our_env[i++] = main_pidfd_id;
}
}
if (!fdset_isempty(arg_fds)) {
@ -415,11 +437,11 @@ static int run(int argc, char* argv[]) {
/* If --pid= is explicitly specified, use it as source pid. Otherwise, pretend the message originates
* from our parent, i.e. --pid=auto */
if (arg_pid <= 0)
arg_pid = pid_parent_if_possible();
if (!pidref_is_set(&arg_pid))
(void) pidref_parent_if_applicable(&arg_pid);
if (fdset_isempty(arg_fds))
r = sd_pid_notify(arg_pid, /* unset_environment= */ false, msg);
r = sd_pid_notify(arg_pid.pid, /* unset_environment= */ false, msg);
else {
_cleanup_free_ int *a = NULL;
int k;
@ -428,7 +450,7 @@ static int run(int argc, char* argv[]) {
if (k < 0)
return log_error_errno(k, "Failed to convert file descriptor set to array: %m");
r = sd_pid_notify_with_fds(arg_pid, /* unset_environment= */ false, msg, a, k);
r = sd_pid_notify_with_fds(arg_pid.pid, /* unset_environment= */ false, msg, a, k);
}
if (r < 0)
@ -440,7 +462,7 @@ static int run(int argc, char* argv[]) {
arg_fds = fdset_free(arg_fds); /* Close before we execute anything */
if (!arg_no_block) {
r = sd_pid_notify_barrier(arg_pid, /* unset_environment= */ false, 5 * USEC_PER_SEC);
r = sd_pid_notify_barrier(arg_pid.pid, /* unset_environment= */ false, 5 * USEC_PER_SEC);
if (r < 0)
return log_error_errno(r, "Failed to invoke barrier: %m");
if (r == 0)

View File

@ -22,7 +22,7 @@
#define MAKE_SET(s) ((Set*) s)
#define MAKE_FDSET(s) ((FDSet*) s)
FDSet *fdset_new(void) {
FDSet* fdset_new(void) {
return MAKE_FDSET(set_new(NULL));
}
@ -52,12 +52,20 @@ int fdset_new_array(FDSet **ret, const int fds[], size_t n_fds) {
return 0;
}
void fdset_close(FDSet *s, bool async) {
int fdset_steal_first(FDSet *fds) {
void *p;
while ((p = set_steal_first(MAKE_SET(s)))) {
int fd = PTR_TO_FD(p);
p = set_steal_first(MAKE_SET(fds));
if (!p)
return -ENOENT;
return PTR_TO_FD(p);
}
void fdset_close(FDSet *fds, bool async) {
int fd;
while ((fd = fdset_steal_first(fds)) >= 0) {
/* Valgrind's fd might have ended up in this set here, due to fdset_new_fill(). We'll ignore
* all failures here, so that the EBADFD that valgrind will return us on close() doesn't
* influence us */
@ -144,7 +152,7 @@ bool fdset_contains(FDSet *s, int fd) {
return false;
}
return !!set_get(MAKE_SET(s), FD_TO_PTR(fd));
return set_contains(MAKE_SET(s), FD_TO_PTR(fd));
}
int fdset_remove(FDSet *s, int fd) {
@ -230,13 +238,13 @@ int fdset_new_fill(
}
int fdset_cloexec(FDSet *fds, bool b) {
void *p;
int r;
assert(fds);
SET_FOREACH(p, MAKE_SET(fds)) {
r = fd_cloexec(PTR_TO_FD(p), b);
int fd;
FDSET_FOREACH(fd, fds) {
r = fd_cloexec(fd, b);
if (r < 0)
return r;
}
@ -269,7 +277,6 @@ int fdset_new_listen_fds(FDSet **ret, bool unset) {
int fdset_to_array(FDSet *fds, int **ret) {
unsigned j = 0, m;
void *e;
int *a;
assert(ret);
@ -286,8 +293,9 @@ int fdset_to_array(FDSet *fds, int **ret) {
if (!a)
return -ENOMEM;
SET_FOREACH(e, MAKE_SET(fds))
a[j++] = PTR_TO_FD(e);
int fd;
FDSET_FOREACH(fd, fds)
a[j++] = fd;
assert(j == m);
@ -322,13 +330,3 @@ int fdset_iterate(FDSet *s, Iterator *i) {
return PTR_TO_FD(p);
}
int fdset_steal_first(FDSet *fds) {
void *p;
p = set_steal_first(MAKE_SET(fds));
if (!p)
return -ENOENT;
return PTR_TO_FD(p);
}

View File

@ -39,7 +39,7 @@ sync_in b
sync_in d
# Move main process back to toplevel
systemd-notify --pid=parent "MAINPID=$$"
systemd-notify "MAINPID=$$"
# Should be dropped again
systemd-notify --status="BOGUS2" --pid=parent