From 2731560250d38229a677edda546af933f9cb616d Mon Sep 17 00:00:00 2001 From: Vitalii Popov Date: Fri, 2 Oct 2026 00:12:54 +0300 Subject: [PATCH] workaround for waitpid --- ANCIENT-MOD.md | 14 +++++++++++--- src/basic/pidref.c | 2 +- src/include/override/sys/pidfd.h | 3 +++ src/libc/pidfd.c | 28 +++++++++++++++++++++++++--- src/libsystemd/sd-event/sd-event.c | 4 ++-- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/ANCIENT-MOD.md b/ANCIENT-MOD.md index 98394c9132..899603655f 100644 --- a/ANCIENT-MOD.md +++ b/ANCIENT-MOD.md @@ -17,10 +17,11 @@ because upstream code was refactored since the fallback was removed. | `mount_option_supported()` | `fsopen()` (5.2) | **revert** of part of `c2198d0c3f`: return `-EAGAIN` ("don't know") when the new mount API is unavailable. | `src/basic/mountpoint-util.c` | | `close_all_fds*()` (every fork/exec, `systemd-executor`) | `close_range()` (5.9) | **revert** of `c471cca093` and `d58c826c54`: fall back to iterating `/proc/self/fd` (or the frugal loop). | `src/basic/fd-util.c`, `src/basic/fd-util.h`, `src/test/test-fd-util.c` | | `pidref_set_pid()` (every process PID1 tracks, `safe_fork()`, `posix_spawn_wrapper()`) | `pidfd_open()` (5.3) | **revert** (adjusted) of part of `6e14c46bac`: ENOSYS ⇒ PidRef without fd (plain PID). | `src/basic/pidref.c` | +| `pidfd_open()` shim (all pidfd users), `pidfd_waitid()` | `waitid(P_PIDFD)` (5.4) | **new**: vendor kernels (Android 4.19) backport `pidfd_open()` but not `waitid(P_PIDFD)`, so every wait failed with EINVAL (PID1 froze after generators). The shim probes once on a pidfd of self that `pidfd_send_signal()` and `poll()` work (without pidfd poll support a live process reports readable); if not, `pidfd_open()` returns ENOSYS so the plain-PID fallbacks above apply. Waits go through `pidfd_waitid()`, which probes `waitid(P_PIDFD)` (ECHILD = supported, EINVAL = not) and otherwise uses `waitid(P_PID)` with the known PID (safe for children: the PID cannot be reused before reaping). Keeping pidfds is required by logind, which since v258 refuses sessions whose leader has no pidfd (`NoSessionPIDFD`). | `src/libc/pidfd.c`, `src/include/override/sys/pidfd.h`, `src/basic/pidref.c`, `src/libsystemd/sd-event/sd-event.c` | | `pidref_wait_for_terminate_full()` with timeout | pidfd (5.3) | **new**: without pidfd poll `waitid(P_PID, …, WNOHANG)` every 10 ms instead of returning `-ENOMEDIUM`. | `src/basic/pidref.c` | | `pidfd_check_pidfs()`, `pidfd_info_mask_is_supported()`, `pidfd_get_inode_id_self_cached()` | pidfd (5.3) | **revert**/new: ENOSYS ⇒ "not supported" instead of hard error. | `src/basic/pidfd-util.c` | | sd-event child sources (`sd_event_add_child()`, used by udevd, logind, …) | pidfd (5.3), `waitid(P_PIDFD)` (5.4) | **new** (upstream removed the non-pidfd path in `6e14c46bac` and later made the code pidfd-only): if `pidfd_open()` is unsupported the source works on the PID via `waitid(P_PID)` + SIGCHLD signalfd. SIGCHLD is blocked automatically in that case (upstream no longer requires callers to block it). `send_child_signal()` falls back to `kill()`/`rt_sigqueueinfo()`; `set_child_pidfd_own()` returns `-EOPNOTSUPP` without pidfd. | `src/libsystemd/sd-event/sd-event.c` | -| `pam_systemd` session creation | pidfd (5.3) | **revert** of part of `6e14c46bac`: fall back to `CreateSession` (PID based). | `src/login/pam_systemd.c` | +| `pam_systemd` session creation | pidfd (5.3) | **revert** of part of `6e14c46bac`: fall back to `CreateSession` (PID based). Ineffective on its own: logind (Varlink and D-Bus) rejects leaders without pidfd, so sessions need a kernel with `pidfd_open()` + pidfd poll. | `src/login/pam_systemd.c` | | Unit cgroup-empty tracking (`unit_check_cgroup_events()`) | `frozen` key in `cgroup.events` (5.2) | **new**: if the key is missing, read only `populated`. Without this every cgroup event read failed and stopped units were never noticed as empty. | `src/core/cgroup.c` | | Freeze/thaw | cgroup v2 freezer (5.2) | **revert** of `3c86a935ad`: `cg_freezer_supported()` restored; freeze is `-EOPNOTSUPP` / no-op when `cgroup.freeze` is absent. | `src/basic/cgroup-util.[ch]`, `src/core/cgroup.c`, `src/core/unit.c` | | Credentials (`LoadCredential=`, `SetCredential=`, `ImportCredential=` — used by many stock units) | new mount API `fsopen`/`fsmount`/`move_mount` (5.2) | **new**: if the credentials fs cannot be created because the API is missing, use the existing "plain directory" fallback (previously only used on EPERM). | `src/core/exec-credential.c` | @@ -64,7 +65,10 @@ because upstream code was refactored since the fallback was removed. EINVAL workaround (`00c4851af0`), expect warnings when used. - **udev net_id** for IP-over-InfiniBand (`dev_port`, 4.20): `d3d375a686` not reverted; multi-port IPoIB devices may get colliding names. -- `test-event`'s `test_pidfd` inherently requires pidfds and fails without them. +- `test-event`'s `test_pidfd` inherently requires pidfds and fails without them; it and + `test_child_pidfd_wnowait` also call `waitid(P_PIDFD)` directly and fail without it. +- logind sessions require pidfds (see `pam_systemd` row); on kernels without `pidfd_open()` no + login session can be created. - Anything else newer than 5.10 listed in `README` as "recommended baseline" is expected to fall back gracefully upstream, but that has not been verified on a real old kernel. @@ -77,4 +81,8 @@ because upstream code was refactored since the fallback was removed. (with the built generators) under an `LD_PRELOAD` shim that simulates an old kernel: strips `STATX_MNT_ID*`/`STATX_ATTR_MOUNT_ROOT` from `statx()` results and makes `pidfd_open`, `pidfd_send_signal`, `pidfd_spawn`, `close_range` and the new mount API return ENOSYS. -- **Not** booted on a real 4.19 kernel yet. +- `test-pidref`, `test-process-util`, `test-event` (except `test_pidfd`, `test_child_pidfd_wnowait`) + under an `LD_PRELOAD` shim that only makes `waitid(P_PIDFD)` fail with EINVAL (Android 4.19 + vendor kernel case: pidfds stay enabled, waits use the `P_PID` fallback). +- Booted on an Android 4.19 vendor kernel (Xiaomi vince): reaches `graphical.target`; login + sessions not verified yet. diff --git a/src/basic/pidref.c b/src/basic/pidref.c index 28b698745f..c49a380b66 100644 --- a/src/basic/pidref.c +++ b/src/basic/pidref.c @@ -522,7 +522,7 @@ int pidref_wait_for_terminate_full(PidRef *pidref, usec_t timeout, siginfo_t *re siginfo_t si = {}; if (pidref->fd >= 0) - r = RET_NERRNO(waitid(P_PIDFD, pidref->fd, &si, WEXITED)); + r = RET_NERRNO(pidfd_waitid(pidref->fd, pidref->pid, &si, WEXITED)); else r = RET_NERRNO(waitid(P_PID, pidref->pid, &si, WEXITED)); if (r >= 0) { diff --git a/src/include/override/sys/pidfd.h b/src/include/override/sys/pidfd.h index c46b2c6cb7..6dd63454c3 100644 --- a/src/include/override/sys/pidfd.h +++ b/src/include/override/sys/pidfd.h @@ -11,6 +11,9 @@ int pidfd_open_shim(pid_t pid, unsigned flags); int pidfd_open_checked(pid_t pid, unsigned flags); #define pidfd_open pidfd_open_checked +/* ANCIENT-MOD: waitid(P_PIDFD) (kernel v5.4) with waitid(P_PID) fallback. */ +int pidfd_waitid(int fd, pid_t pid, siginfo_t *info, int options); + /* Defined since glibc-2.36. * Supported since kernel v5.1 (3eb39f47934f9d5a3027fe00d906a45fe3a15fad). */ int pidfd_send_signal_shim(int fd, int sig, siginfo_t *info, unsigned flags); diff --git a/src/libc/pidfd.c b/src/libc/pidfd.c index 2d80cf5187..29982cffa0 100644 --- a/src/libc/pidfd.c +++ b/src/libc/pidfd.c @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include #include @@ -10,7 +11,8 @@ DEFINE_SYSCALL_SHIM(pidfd_open, int, unsigned, flags) /* ANCIENT-MOD: vendor kernels < 5.4 (e.g. Android 4.19) may backport pidfd_open() without - * waitid(P_PIDFD); report pidfds as unsupported there so the plain-PID fallbacks are used. */ + * waitid(P_PIDFD). Pidfds are reported usable only if they can be signalled and polled (both needed + * by logind and sd-event); waits go through pidfd_waitid(), which falls back to waitid(P_PID). */ int pidfd_open_checked(pid_t pid, unsigned flags) { static int usable = -1; @@ -22,8 +24,9 @@ int pidfd_open_checked(pid_t pid, unsigned flags) { return fd; } - siginfo_t si = {}; - usable = waitid(P_PIDFD, fd, &si, WEXITED|WNOHANG) >= 0 || errno != EINVAL; + /* Without pidfd poll support the default poll mask reports a live process as readable */ + struct pollfd pfd = { .fd = fd, .events = POLLIN }; + usable = pidfd_send_signal_shim(fd, 0, NULL, 0) >= 0 && poll(&pfd, 1, 0) == 0; close(fd); } @@ -35,6 +38,25 @@ int pidfd_open_checked(pid_t pid, unsigned flags) { return pidfd_open_shim(pid, flags); } +int pidfd_waitid(int fd, pid_t pid, siginfo_t *info, int options) { + static int supported = -1; + + if (supported < 0) { + int self = pidfd_open_shim(getpid(), 0); + if (self < 0) + return -1; + + siginfo_t si = {}; + supported = waitid(P_PIDFD, self, &si, WEXITED|WNOHANG) >= 0 || errno != EINVAL; + close(self); + } + + if (supported) + return waitid(P_PIDFD, fd, info, options); + + return waitid(P_PID, pid, info, options); +} + DEFINE_SYSCALL_SHIM(pidfd_send_signal, int, int, fd, int, sig, diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 87ab9fdc60..6cd586c849 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -61,7 +61,7 @@ static int child_waitid(sd_event_source *s, siginfo_t *si, int options) { assert(s); if (s->child.pidfd >= 0) - return waitid(P_PIDFD, s->child.pidfd, si, options); + return pidfd_waitid(s->child.pidfd, s->child.pid, si, options); return waitid(P_PID, s->child.pid, si, options); } @@ -3860,7 +3860,7 @@ static int process_pidfd(sd_event *e, sd_event_source *s, uint32_t revents) { * be set to pending, and we would have returned early above. */ zero(s->child.siginfo); - if (waitid(P_PIDFD, s->child.pidfd, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0) + if (pidfd_waitid(s->child.pidfd, s->child.pid, &s->child.siginfo, WNOHANG | WNOWAIT | s->child.options) < 0) return -errno; if (s->child.siginfo.si_pid == 0)