From a2de89f622a17a47a69722e9fc26724841c5b77d Mon Sep 17 00:00:00 2001 From: Yu Watanabe Date: Mon, 29 Jun 2026 17:08:50 +0900 Subject: [PATCH] libc: call epoll_pwait2 syscall directly instead of resolving glibc symbols As mentioned in a05fe6684ae0af8128c20e2054d52d8393c2ac61, glibc provides two wrappers for epoll_pwait2() on 32-bit architectures, and sys/epoll.h, together with features-time64.h, selects the appropriate one. To avoid depending on symbols introduced in newer glibc releases, that commit relied on the glibc internal macro __USE_TIME_BITS64 to determine which wrapper to use. However, that is incorrect on some architectures. See: https://sourceware.org/git/?p=glibc.git;a=commit;h=a4ed0471d71739928a0d0fa3258b3ff3b158e9b9 The above glibc commit introduces a new internal macro, __USE_TIME64_REDIRECTS, which correctly describes whether the time64 wrapper should be used. However, we cannot rely on it because it was introduced in glibc 2.40, while our baseline remains glibc 2.34. Moreover, even after raising the baseline, relying on glibc internal implementation details is undesirable. Instead, call the epoll_pwait2 syscall directly. This avoids any dependency on glibc wrapper selection logic while preserving the original goal of avoiding newer glibc symbols. As a side effect, this also enables epoll_pwait2() on musl, which does not currently provide a libc wrapper for it. Currently, epoll_pwait2() is only used by sd-event, so this is also the simplest solution. --- src/include/override/sys/epoll.h | 13 +++++--- src/libc/epoll.c | 51 ++++++++++++++++++------------ src/libsystemd/sd-event/sd-event.c | 14 ++++---- 3 files changed, 46 insertions(+), 32 deletions(-) diff --git a/src/include/override/sys/epoll.h b/src/include/override/sys/epoll.h index 0ed9c27a4b..123283cf09 100644 --- a/src/include/override/sys/epoll.h +++ b/src/include/override/sys/epoll.h @@ -3,8 +3,11 @@ #include_next /* IWYU pragma: export */ -/* epoll_pwait2() was added to glibc 2.35. Redirect to a shim that sets errno=ENOSYS at runtime when - * the libc symbol isn't available, so callers don't need to worry about the libc version. */ -int epoll_pwait2_shim(int fd, struct epoll_event *events, int maxevents, - const struct timespec *timeout, const sigset_t *sigmask); -#define epoll_pwait2 epoll_pwait2_shim +/* epoll_pwait2() was added in glibc 2.35 and is currently unavailable in musl. + * Call the syscall directly instead of relying on the libc wrapper. */ +int missing_epoll_pwait2( + int fd, + struct epoll_event *events, + int maxevents, + const struct timespec *timeout, + const sigset_t *sigmask); diff --git a/src/libc/epoll.c b/src/libc/epoll.c index a7d4075f37..9bd16450c2 100644 --- a/src/libc/epoll.c +++ b/src/libc/epoll.c @@ -1,26 +1,35 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include /* IWYU pragma: keep */ +#include #include +#include +#include -#include "libc-shim.h" +int missing_epoll_pwait2( + int fd, + struct epoll_event *events, + int maxevents, + const struct timespec *timeout, + const sigset_t *sigmask) { -/* On 32-bit architectures built with _TIME_BITS=64, glibc renames epoll_pwait2() to - * __epoll_pwait2_time64 via __asm__() in so the linker picks the variant whose - * struct timespec ABI matches our 64-bit time_t. dlsym() can't see that header-level rename, so - * we have to spell out the right name here, otherwise we'd silently dispatch a 64-bit timespec to - * the legacy 32-bit-time_t entry point. */ -#ifdef __USE_TIME_BITS64 -DEFINE_LIBC_ERRNO_SHIM_NAMED(epoll_pwait2, "__epoll_pwait2_time64", int, - int, fd, - struct epoll_event *, events, - int, maxevents, - const struct timespec *, timeout, - const sigset_t *, sigmask) -#else -DEFINE_LIBC_ERRNO_SHIM(epoll_pwait2, int, - int, fd, - struct epoll_event *, events, - int, maxevents, - const struct timespec *, timeout, - const sigset_t *, sigmask) -#endif + /* We intentionally reject non-NULL sigmask to avoid depending on the architecture-specific kernel + * sigset_t size. If sigmask support is ever added, the correct sigsetsize must be passed to the + * syscall. */ + if (sigmask) { + errno = EINVAL; + return -1; + } + + /* Convert struct timespec to struct __kernel_timespec, as they may differ. */ + struct __kernel_timespec t, *p = NULL; + if (timeout) { + t = (struct __kernel_timespec) { + .tv_sec = timeout->tv_sec, + .tv_nsec = timeout->tv_nsec, + }; + p = &t; + } + + return syscall(__NR_epoll_pwait2, fd, events, maxevents, p, /* sigmask= */ NULL, /* sigsetsize= */ 0); +} diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c index 590ac06b8e..dd293a89fd 100644 --- a/src/libsystemd/sd-event/sd-event.c +++ b/src/libsystemd/sd-event/sd-event.c @@ -4631,14 +4631,16 @@ static int epoll_wait_usec( static bool epoll_pwait2_absent = false; int r, msec; - /* A wrapper that uses epoll_pwait2() if available, and falls back to epoll_wait() if not. */ + + /* A wrapper that uses missing_epoll_pwait2(), which directly calls epoll_pwait2 syscall. */ if (!epoll_pwait2_absent && timeout != USEC_INFINITY) { - r = epoll_pwait2(fd, - events, - maxevents, - TIMESPEC_STORE(timeout), - NULL); + r = missing_epoll_pwait2( + fd, + events, + maxevents, + TIMESPEC_STORE(timeout), + /* sigmask= */ NULL); if (r >= 0) return r; if (!ERRNO_IS_NOT_SUPPORTED(errno) && !ERRNO_IS_PRIVILEGE(errno))