libc: call epoll_pwait2 syscall directly instead of resolving glibc symbols

As mentioned in a05fe6684a, 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.
This commit is contained in:
Yu Watanabe
2026-08-28 06:19:13 +09:00
parent 82fb7a0297
commit a2de89f622
3 changed files with 46 additions and 32 deletions
+8 -5
View File
@@ -3,8 +3,11 @@
#include_next <sys/epoll.h> /* 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);
+30 -21
View File
@@ -1,26 +1,35 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <errno.h> /* IWYU pragma: keep */
#include <linux/time_types.h>
#include <sys/epoll.h>
#include <sys/syscall.h>
#include <unistd.h>
#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 <sys/epoll.h> 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);
}
+8 -6
View File
@@ -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))