Commit f795d54591 ("libc,shared: detect newer library symbols at
runtime") replaced the build-time HAVE_EPOLL_PWAIT2 gate with a runtime
shim that resolves the libc symbol via dlsym(RTLD_DEFAULT, "epoll_pwait2").
That breaks on 32-bit architectures built with _TIME_BITS=64 (e.g. Debian
armhf since the time64 transition).
When _TIME_BITS=64 is in effect on a 32-bit target, glibc's <sys/epoll.h>
asm-renames the C identifier `epoll_pwait2` to the matching 64-bit-time_t
ABI symbol `__epoll_pwait2_time64`, so a normal C call resolves to the
variant whose `struct timespec` layout (16 bytes: s64 tv_sec, long tv_nsec,
long pad) matches what the compiler is using:
/* /usr/include/sys/epoll.h */
#ifndef __USE_TIME64_REDIRECTS
extern int epoll_pwait2 (int __epfd, struct epoll_event *__events,
int __maxevents,
const struct timespec *__timeout,
const __sigset_t *__ss) ...;
#else
# ifdef __REDIRECT
extern int __REDIRECT (epoll_pwait2, (int __epfd, ...,
const struct timespec *__timeout,
const __sigset_t *__ss),
__epoll_pwait2_time64) ...;
# else
# define epoll_pwait2 __epoll_pwait2_time64
# endif
#endif
dlsym() doesn't see that header-level rename and returns the legacy
32-bit-time_t `epoll_pwait2` symbol, which interprets only the first 8
bytes of the 16-byte timespec we hand it. With a little-endian layout
that means the legacy callee reads tv_sec = (low 32 bits of real tv_sec)
and tv_nsec = (high 32 bits of real tv_sec, normally 0). Sub-second
timeouts therefore become {0,0} and return immediately, while multi-
second timeouts get truncated to whole seconds.
That matches the test failure reported on armv7:
/* test_simple_timeout */
src/libsystemd/sd-event/test-event.c:724: Assertion failed:
Expected "t >= usec_add(f, some_time)",
but 571153514285 < 571154403965
(the 889680 us shortfall is consistent with a sub-second `some_time` that
got rounded down to zero by the truncated wait).
Fix it by adding a DEFINE_LIBC_ERRNO_SHIM_NAMED() variant of the macro
that takes an explicit dlsym symbol name string. Both macros keep their
own copy of the body so every `func` reference stays behind `#` or `##`,
otherwise the override-header `#define epoll_pwait2 epoll_pwait2_shim`
would rewrite the token before the inner macro could paste it (yielding
`epoll_pwait2_shim_shim`). src/libc/epoll.c then picks
"__epoll_pwait2_time64" under __USE_TIME_BITS64 so the cached function
pointer matches the timespec ABI the rest of the code is built with.
Follow-up for f795d54591
Co-developed-by: Claude Opus 4.7 <noreply@anthropic.com>