1
0
mirror of https://github.com/systemd/systemd.git synced 2025-03-19 22:50:17 +03:00

pidfd: cache our own pidfd inode id, and use it at various places (#36060)

This is split out of and preparation for #35224, but makes a ton of
sense on its own
This commit is contained in:
Mike Yuan 2025-01-21 00:16:12 +01:00 committed by GitHub
commit 9bfc13e93a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 184 additions and 60 deletions

6
TODO
View File

@ -218,9 +218,9 @@ Features:
be established on top of dm-crypt or dm-verity devices, or an allowlist of
file systems (which should probably include vfat, for compat with the ESP)
* $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).
* $LISTEN_PID, $SYSTEMD_EXECPID env vars that the service manager sets should
be augmented with $LISTEN_PIDFDID, and $SYSTEMD_EXECPIDFD (and similar for
other env vars we might send).
* port copy.c over to use LabelOps for all labelling.

View File

@ -3886,22 +3886,40 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX
<varlistentry>
<term><varname>$MAINPID</varname></term>
<listitem><para>The PID of the unit's main process if it is
known. This is only set for control processes as invoked by
<varname>ExecReload=</varname> and similar.</para>
<listitem><para>The UNIX process ID (PID) of the unit's main process if it is known. This is only
set for control processes as invoked by <varname>ExecReload=</varname> and similar.</para>
<xi:include href="version-info.xml" xpointer="v209"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>$MAINPIDFDID</varname></term>
<listitem><para>The 64bit inode ID of the file descriptor returned by <citerefentry
project='man-pages'><refentrytitle>pidfd_open</refentrytitle><manvolnum>3</manvolnum></citerefentry>
for the main process (if supported). This is only set for control processes as invoked by
<varname>ExecReload=</varname> and similar.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>$MANAGERPID</varname></term>
<listitem><para>The PID of the user <command>systemd</command>
instance, set for processes spawned by it.</para>
<listitem><para>The PID of the per-user <command>systemd</command> service manager instance, set
for processes spawned by it.</para>
<xi:include href="version-info.xml" xpointer="v208"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>$MANAGERPIDFDID</varname></term>
<listitem><para>The <function>pidfd_open()</function> inode ID (see above) of the per-user
<command>systemd</command> service manager instance, set for processes spawned by it.</para>
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>$LISTEN_FDS</varname></term>
<term><varname>$LISTEN_PID</varname></term>

View File

@ -377,6 +377,7 @@ possible_common_cc_flags = [
'-Warray-bounds=2',
'-Wdate-time',
'-Wendif-labels',
'-Werror=discarded-qualifiers',
'-Werror=format=2',
'-Werror=format-signedness',
'-Werror=implicit-function-declaration',

View File

@ -9,6 +9,7 @@
#include "macro.h"
#include "memory-util.h"
#include "missing_magic.h"
#include "missing_threads.h"
#include "parse-util.h"
#include "path-util.h"
#include "pidfd-util.h"
@ -18,16 +19,14 @@
static int have_pidfs = -1;
static int pidfd_check_pidfs(void) {
static int pidfd_check_pidfs(int pid_fd) {
/* NB: the passed fd *must* be acquired via pidfd_open(), i.e. must be a true pidfd! */
if (have_pidfs >= 0)
return have_pidfs;
_cleanup_close_ int fd = pidfd_open(getpid_cached(), 0);
if (fd < 0)
return -errno;
return (have_pidfs = fd_is_fs_type(fd, PID_FS_MAGIC));
return (have_pidfs = fd_is_fs_type(pid_fd, PID_FS_MAGIC));
}
int pidfd_get_namespace(int fd, unsigned long ns_type_cmd) {
@ -231,7 +230,7 @@ int pidfd_get_inode_id(int fd, uint64_t *ret) {
assert(fd >= 0);
r = pidfd_check_pidfs();
r = pidfd_check_pidfs(fd);
if (r < 0)
return r;
if (r == 0)
@ -245,3 +244,32 @@ int pidfd_get_inode_id(int fd, uint64_t *ret) {
*ret = (uint64_t) st.st_ino;
return 0;
}
int pidfd_get_inode_id_self_cached(uint64_t *ret) {
static thread_local uint64_t cached = 0;
static thread_local pid_t initialized = 0; /* < 0: cached error; == 0: invalid; > 0: valid and pid that was current */
int r;
assert(ret);
if (initialized == getpid_cached()) {
*ret = cached;
return 0;
}
if (initialized < 0)
return initialized;
_cleanup_close_ int fd = pidfd_open(getpid_cached(), 0);
if (fd < 0)
return -errno;
r = pidfd_get_inode_id(fd, &cached);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
return (initialized = -EOPNOTSUPP);
if (r < 0)
return r;
*ret = cached;
initialized = getpid_cached();
return 0;
}

View File

@ -17,3 +17,5 @@ int pidfd_get_uid(int fd, uid_t *ret);
int pidfd_get_cgroupid(int fd, uint64_t *ret);
int pidfd_get_inode_id(int fd, uint64_t *ret);
int pidfd_get_inode_id_self_cached(uint64_t *ret);

View File

@ -83,14 +83,17 @@ bool pidref_equal(PidRef *a, PidRef *b) {
}
int pidref_set_pid(PidRef *pidref, pid_t pid) {
uint64_t pidfdid = 0;
int fd;
assert(pidref);
if (pid < 0)
return -ESRCH;
if (pid == 0)
if (pid == 0) {
pid = getpid_cached();
(void) pidfd_get_inode_id_self_cached(&pidfdid);
}
fd = pidfd_open(pid, 0);
if (fd < 0) {
@ -104,6 +107,7 @@ int pidref_set_pid(PidRef *pidref, pid_t pid) {
*pidref = (PidRef) {
.fd = fd,
.pid = pid,
.fd_id = pidfdid,
};
return 0;
@ -388,17 +392,32 @@ int pidref_verify(const PidRef *pidref) {
return 1; /* We have a pidfd and it still points to the PID we have, hence all is *really* OK → return 1 */
}
bool pidref_is_self(const PidRef *pidref) {
if (!pidref)
bool pidref_is_self(PidRef *pidref) {
if (!pidref_is_set(pidref))
return false;
if (pidref_is_remote(pidref))
return false;
return pidref->pid == getpid_cached();
if (pidref->pid != getpid_cached())
return false;
/* PID1 cannot exit, hence no point in comparing pidfd IDs, they can never change */
if (pidref->pid == 1)
return true;
/* Also compare pidfd ID if we can get it */
if (pidref_acquire_pidfd_id(pidref) < 0)
return true;
uint64_t self_id;
if (pidfd_get_inode_id_self_cached(&self_id) < 0)
return true;
return pidref->fd_id == self_id;
}
int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) {
int pidref_wait(PidRef *pidref, siginfo_t *ret, int options) {
int r;
if (!pidref_is_set(pidref))
@ -424,7 +443,7 @@ int pidref_wait(const PidRef *pidref, siginfo_t *ret, int options) {
return 0;
}
int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret) {
int pidref_wait_for_terminate(PidRef *pidref, siginfo_t *ret) {
int r;
for (;;) {

View File

@ -39,7 +39,7 @@ struct PidRef {
their own pidfs and each process comes with a unique inode number */
};
#define PIDREF_NULL (const PidRef) { .fd = -EBADF }
#define PIDREF_NULL (PidRef) { .fd = -EBADF }
/* A special pidref value that we are using when a PID shall be automatically acquired from some surrounding
* context, for example connection peer. Much like PIDREF_NULL it will be considered unset by
@ -77,7 +77,7 @@ static inline int pidref_set_self(PidRef *pidref) {
return pidref_set_pid(pidref, 0);
}
bool pidref_is_self(const PidRef *pidref);
bool pidref_is_self(PidRef *pidref);
void pidref_done(PidRef *pidref);
PidRef* pidref_free(PidRef *pidref);
@ -92,8 +92,8 @@ int pidref_kill(const PidRef *pidref, int sig);
int pidref_kill_and_sigcont(const PidRef *pidref, int sig);
int pidref_sigqueue(const PidRef *pidref, int sig, int value);
int pidref_wait(const PidRef *pidref, siginfo_t *siginfo, int options);
int pidref_wait_for_terminate(const PidRef *pidref, siginfo_t *ret);
int pidref_wait(PidRef *pidref, siginfo_t *siginfo, int options);
int pidref_wait_for_terminate(PidRef *pidref, siginfo_t *ret);
static inline void pidref_done_sigkill_wait(PidRef *pidref) {
if (!pidref_is_set(pidref))

View File

@ -1120,7 +1120,7 @@ int getenv_for_pid(pid_t pid, const char *field, char **ret) {
return 0;
}
int pidref_is_my_child(const PidRef *pid) {
int pidref_is_my_child(PidRef *pid) {
int r;
if (!pidref_is_set(pid))
@ -1150,7 +1150,7 @@ int pid_is_my_child(pid_t pid) {
return pidref_is_my_child(&PIDREF_MAKE_FROM_PID(pid));
}
int pidref_is_unwaited(const PidRef *pid) {
int pidref_is_unwaited(PidRef *pid) {
int r;
/* Checks whether a PID is still valid at all, including a zombie */

View File

@ -90,9 +90,9 @@ int getenv_for_pid(pid_t pid, const char *field, char **_value);
int pid_is_alive(pid_t pid);
int pidref_is_alive(const PidRef *pidref);
int pid_is_unwaited(pid_t pid);
int pidref_is_unwaited(const PidRef *pidref);
int pidref_is_unwaited(PidRef *pidref);
int pid_is_my_child(pid_t pid);
int pidref_is_my_child(const PidRef *pidref);
int pidref_is_my_child(PidRef *pidref);
int pidref_from_same_root_fs(PidRef *a, PidRef *b);
bool is_main_thread(void);

View File

@ -23,6 +23,7 @@
#include "missing_syscall.h"
#include "missing_threads.h"
#include "parse-util.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "random-util.h"
#include "sha256.h"
@ -39,6 +40,7 @@ static void fallback_random_bytes(void *p, size_t n) {
uint64_t call_id, block_id;
usec_t stamp_mono, stamp_real;
pid_t pid, tid;
uint64_t pidfdid;
uint8_t auxval[16];
} state = {
/* Arbitrary domain separation to prevent other usage of AT_RANDOM from clashing. */
@ -51,6 +53,7 @@ static void fallback_random_bytes(void *p, size_t n) {
memcpy(state.label, "systemd fallback random bytes v1", sizeof(state.label));
memcpy(state.auxval, ULONG_TO_PTR(getauxval(AT_RANDOM)), sizeof(state.auxval));
(void) pidfd_get_inode_id_self_cached(&state.pidfdid);
while (n > 0) {
struct sha256_ctx ctx;

View File

@ -4482,7 +4482,7 @@ Unit *manager_get_unit_by_pidref_watching(Manager *m, const PidRef *pid) {
return NULL;
}
Unit *manager_get_unit_by_pidref(Manager *m, const PidRef *pid) {
Unit* manager_get_unit_by_pidref(Manager *m, PidRef *pid) {
Unit *u;
assert(m);

View File

@ -466,7 +466,7 @@ unsigned manager_dispatch_cgroup_realize_queue(Manager *m);
Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup);
Unit *manager_get_unit_by_pidref_cgroup(Manager *m, const PidRef *pid);
Unit *manager_get_unit_by_pidref_watching(Manager *m, const PidRef *pid);
Unit* manager_get_unit_by_pidref(Manager *m, const PidRef *pid);
Unit* manager_get_unit_by_pidref(Manager *m, PidRef *pid);
Unit* manager_get_unit_by_pid(Manager *m, pid_t pid);
uint64_t unit_get_ancestor_memory_min(Unit *u);

View File

@ -36,6 +36,7 @@
#include "open-file.h"
#include "parse-util.h"
#include "path-util.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "random-util.h"
#include "selinux-util.h"
@ -1769,7 +1770,7 @@ static int service_spawn_internal(
if (r < 0)
return r;
our_env = new0(char*, 13);
our_env = new0(char*, 15);
if (!our_env)
return -ENOMEM;
@ -1781,14 +1782,25 @@ static int service_spawn_internal(
return -ENOMEM;
}
if (pidref_is_set(&s->main_pid))
if (pidref_is_set(&s->main_pid)) {
if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid.pid) < 0)
return -ENOMEM;
if (MANAGER_IS_USER(UNIT(s)->manager))
if (pidref_acquire_pidfd_id(&s->main_pid) >= 0)
if (asprintf(our_env + n_env++, "MAINPIDFDID=%" PRIu64, s->main_pid.fd_id) < 0)
return -ENOMEM;
}
if (MANAGER_IS_USER(UNIT(s)->manager)) {
if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid_cached()) < 0)
return -ENOMEM;
uint64_t pidfdid;
if (pidfd_get_inode_id_self_cached(&pidfdid) >= 0)
if (asprintf(our_env + n_env++, "MANAGERPIDFDID=%" PRIu64, pidfdid) < 0)
return -ENOMEM;
}
if (s->pid_file)
if (asprintf(our_env + n_env++, "PIDFILE=%s", s->pid_file) < 0)
return -ENOMEM;

View File

@ -6007,7 +6007,7 @@ bool unit_needs_console(Unit *u) {
return exec_context_may_touch_console(ec);
}
int unit_pid_attachable(Unit *u, const PidRef *pid, sd_bus_error *error) {
int unit_pid_attachable(Unit *u, PidRef *pid, sd_bus_error *error) {
int r;
assert(u);

View File

@ -1004,7 +1004,7 @@ int unit_warn_leftover_processes(Unit *u, bool start);
bool unit_needs_console(Unit *u);
int unit_pid_attachable(Unit *unit, const PidRef *pid, sd_bus_error *error);
int unit_pid_attachable(Unit *unit, PidRef *pid, sd_bus_error *error);
static inline bool unit_has_job_type(Unit *u, JobType type) {
return u && u->job && u->job->type == type;

View File

@ -80,28 +80,49 @@ static int help(void) {
return 0;
}
static pid_t manager_pid(void) {
const char *e;
pid_t pid;
static int get_manager_pid(PidRef *ret) {
int r;
assert(ret);
/* If we run as a service managed by systemd --user the $MANAGERPID environment variable points to
* the service manager's PID. */
e = getenv("MANAGERPID");
if (!e)
return 0;
r = parse_pid(e, &pid);
if (r < 0) {
log_warning_errno(r, "$MANAGERPID is set to an invalid PID, ignoring: %s", e);
const char *e = getenv("MANAGERPID");
if (!e) {
*ret = PIDREF_NULL;
return 0;
}
return pid;
_cleanup_(pidref_done) PidRef manager = PIDREF_NULL;
r = pidref_set_pidstr(&manager, e);
if (r < 0)
return log_warning_errno(r, "$MANAGERPID is set to an invalid PID, ignoring: %s", e);
e = getenv("MANAGERPIDFDID");
if (e) {
uint64_t manager_pidfd_id;
r = safe_atou64(e, &manager_pidfd_id);
if (r < 0)
return log_warning_errno(r, "$MANAGERPIDFDID is not set to a valid inode number, ignoring: %s", e);
r = pidref_acquire_pidfd_id(&manager);
if (r < 0)
return log_warning_errno(r, "Unable to acquire pidfd ID for manager: %m");
if (manager_pidfd_id != manager.fd_id) {
log_debug("$MANAGERPIDFDID doesn't match process currently referenced by $MANAGERPID, suppressing.");
*ret = PIDREF_NULL;
return 0;
}
}
*ret = TAKE_PIDREF(manager);
return 1;
}
static int pidref_parent_if_applicable(PidRef *ret) {
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL;
_cleanup_(pidref_done) PidRef pidref = PIDREF_NULL, manager = PIDREF_NULL;
int r;
assert(ret);
@ -112,12 +133,18 @@ static int pidref_parent_if_applicable(PidRef *ret) {
/* 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 (pidref.pid <= 1 ||
pidref.pid == manager_pid())
return pidref_set_self(ret);
if (pidref.pid == 1)
goto from_self;
r = get_manager_pid(&manager);
if (r > 0 && pidref_equal(&pidref, &manager))
goto from_self;
*ret = TAKE_PIDREF(pidref);
return 0;
from_self:
return pidref_set_self(ret);
}
static int parse_argv(int argc, char *argv[]) {

View File

@ -7,8 +7,6 @@
#include "stdio-util.h"
#include "tests.h"
#define PIDREF_NULL_NONCONST (PidRef) { .fd = -EBADF }
TEST(pidref_is_set) {
assert_se(!pidref_is_set(NULL));
assert_se(!pidref_is_set(&PIDREF_NULL));
@ -17,14 +15,14 @@ TEST(pidref_is_set) {
TEST(pidref_equal) {
assert_se(pidref_equal(NULL, NULL));
assert_se(pidref_equal(NULL, &PIDREF_NULL_NONCONST));
assert_se(pidref_equal(&PIDREF_NULL_NONCONST, NULL));
assert_se(pidref_equal(&PIDREF_NULL_NONCONST, &PIDREF_NULL_NONCONST));
assert_se(pidref_equal(NULL, &PIDREF_NULL));
assert_se(pidref_equal(&PIDREF_NULL, NULL));
assert_se(pidref_equal(&PIDREF_NULL, &PIDREF_NULL));
assert_se(!pidref_equal(NULL, &PIDREF_MAKE_FROM_PID(1)));
assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), NULL));
assert_se(!pidref_equal(&PIDREF_NULL_NONCONST, &PIDREF_MAKE_FROM_PID(1)));
assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_NULL_NONCONST));
assert_se(!pidref_equal(&PIDREF_NULL, &PIDREF_MAKE_FROM_PID(1)));
assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_NULL));
assert_se(pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_MAKE_FROM_PID(1)));
assert_se(!pidref_equal(&PIDREF_MAKE_FROM_PID(1), &PIDREF_MAKE_FROM_PID(2)));
}
@ -231,7 +229,7 @@ TEST(pidref_is_remote) {
assert_se(!pidref_is_remote(&PIDREF_MAKE_FROM_PID(getpid_cached())));
assert_se(!pidref_is_remote(&PIDREF_AUTOMATIC));
static const PidRef p = {
PidRef p = {
.pid = 1,
.fd = -EREMOTE,
.fd_id = 4711,

View File

@ -28,6 +28,7 @@
#include "missing_syscall.h"
#include "namespace-util.h"
#include "parse-util.h"
#include "pidfd-util.h"
#include "process-util.h"
#include "procfs-util.h"
#include "rlimit-util.h"
@ -1065,6 +1066,21 @@ TEST(pidref_from_same_root_fs) {
ASSERT_OK_ZERO(pidref_from_same_root_fs(&child2, &self));
}
TEST(pidfd_get_inode_id_self_cached) {
int r;
log_info("pid=" PID_FMT, getpid_cached());
uint64_t id;
r = pidfd_get_inode_id_self_cached(&id);
if (ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_info("pidfdid not supported");
else {
assert(r >= 0);
log_info("pidfdid=%" PRIu64, id);
}
}
static int intro(void) {
log_show_color(true);
return EXIT_SUCCESS;