Skip to content

Commit 62c7d90

Browse files
gburdnyh
authored andcommitted
core/epoll: implement epoll_pwait2(2)
Add epoll_pwait2(2) (Linux syscall 441), the epoll_pwait variant that takes a struct timespec* timeout instead of an int millisecond timeout, plus an optional signal mask. The timespec is validated up front: NULL means block indefinitely; a malformed timespec (negative tv_sec/tv_nsec, or tv_nsec >= 1e9) returns EINVAL like Linux; {0,0} returns immediately; otherwise the value is converted to milliseconds (rounding up) and saturated to INT_MAX before any multiply, avoiding signed overflow UB. The result is forwarded to the existing epoll_pwait(), so the sigmask handling and event delivery are shared. Wired into the syscall table (x86-64 and aarch64 __NR_epoll_pwait2 = 441), exported from libc and the musl loader, and covered by a new runnable test (tst-epoll-pwait2) that also builds and passes on Linux. Closes #1427
1 parent 1603209 commit 62c7d90

10 files changed

Lines changed: 124 additions & 0 deletions

File tree

core/epoll.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
#include <memory>
1313
#include <stdio.h>
1414
#include <errno.h>
15+
#include <limits.h>
16+
#include <time.h>
1517
#include <signal.h>
1618

1719
#include <osv/file.h>
@@ -355,6 +357,43 @@ int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout
355357
return ret;
356358
}
357359

360+
// epoll_pwait2(2): like epoll_pwait but the timeout is a struct timespec (ns
361+
// resolution) instead of an int millisecond count. A NULL timeout means block
362+
// indefinitely. OSv's epoll_wait takes a millisecond timeout, so we round the
363+
// timespec up to whole milliseconds (a sub-ms nonzero timeout becomes 1 ms so
364+
// we do not busy-spin as if it were 0).
365+
//
366+
// Declared extern "C" here (musl's <sys/epoll.h> does not declare it) so the
367+
// syscall dispatch and callers get an unmangled symbol.
368+
extern "C" OSV_LIBC_API
369+
int epoll_pwait2(int epfd, struct epoll_event *events, int maxevents,
370+
const struct timespec *timeout, const sigset_t *sigmask);
371+
372+
int epoll_pwait2(int epfd, struct epoll_event *events, int maxevents,
373+
const struct timespec *timeout, const sigset_t *sigmask)
374+
{
375+
int timeout_ms;
376+
if (!timeout) {
377+
timeout_ms = -1; // block indefinitely
378+
} else if (timeout->tv_sec < 0 || timeout->tv_nsec < 0 ||
379+
timeout->tv_nsec >= 1000000000) {
380+
// Malformed timespec -> EINVAL, matching Linux, before any arithmetic.
381+
errno = EINVAL;
382+
return -1;
383+
} else if (timeout->tv_sec == 0 && timeout->tv_nsec == 0) {
384+
timeout_ms = 0; // return immediately
385+
} else if (timeout->tv_sec >= (long long)INT_MAX / 1000) {
386+
// Would overflow the millisecond count; saturate to the max wait rather
387+
// than risk signed-overflow UB in the multiply below.
388+
timeout_ms = INT_MAX;
389+
} else {
390+
long long ms = (long long)timeout->tv_sec * 1000 +
391+
(timeout->tv_nsec + 999999) / 1000000; // round up
392+
timeout_ms = ms > INT_MAX ? INT_MAX : (int)ms;
393+
}
394+
return epoll_pwait(epfd, events, maxevents, timeout_ms, sigmask);
395+
}
396+
358397
void epoll_file_closed(epoll_ptr ptr)
359398
{
360399
ptr.epoll->del(ptr.key);

exported_symbols/osv_ld-musl.so.1.symbols

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ epoll_create
146146
epoll_create1
147147
epoll_ctl
148148
epoll_pwait
149+
epoll_pwait2
149150
epoll_wait
150151
erand48
151152
erf

exported_symbols/osv_libc.so.6.symbols

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ epoll_create
110110
epoll_create1
111111
epoll_ctl
112112
epoll_pwait
113+
epoll_pwait2
113114
epoll_wait
114115
erand48
115116
__errno_location

include/api/aarch64/bits/syscall.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@
281281
#define __NR_io_uring_setup 425
282282
#define __NR_io_uring_enter 426
283283
#define __NR_io_uring_register 427
284+
#define __NR_epoll_pwait2 441
284285
#define __NR_sys_io_uring_setup __NR_io_uring_setup
285286
#define __NR_sys_io_uring_enter __NR_io_uring_enter
286287
#define __NR_sys_io_uring_register __NR_io_uring_register
@@ -584,6 +585,7 @@
584585
#define SYS_io_uring_setup 425
585586
#define SYS_io_uring_enter 426
586587
#define SYS_io_uring_register 427
588+
#define SYS_epoll_pwait2 441
587589
#define SYS_open_tree 428
588590
#define SYS_move_mount 429
589591
#define SYS_fsopen 430

include/api/x64/bits/syscall.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,7 @@
647647
#define SYS_io_uring_setup 425
648648
#define SYS_io_uring_enter 426
649649
#define SYS_io_uring_register 427
650+
#define SYS_epoll_pwait2 441
650651

651652
#undef SYS_fstatat
652653
#undef SYS_pread
@@ -661,6 +662,7 @@
661662
#define __NR_io_uring_setup 425
662663
#define __NR_io_uring_enter 426
663664
#define __NR_io_uring_register 427
665+
#define __NR_epoll_pwait2 441
664666
#define __NR_sys_io_uring_setup __NR_io_uring_setup
665667
#define __NR_sys_io_uring_enter __NR_io_uring_enter
666668
#define __NR_sys_io_uring_register __NR_io_uring_register

linux.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868

6969
extern "C" int eventfd2(unsigned int, int);
7070
extern "C" int osv_sigtimedwait(const sigset_t *, siginfo_t *, const struct timespec *);
71+
extern "C" int epoll_pwait2(int epfd, struct epoll_event *events, int maxevents,
72+
const struct timespec *timeout, const sigset_t *sigmask);
7173

7274
extern "C" OSV_LIBC_API long gettid()
7375
{

modules/tests/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ tests := tst-pthread.so misc-ramdisk.so tst-vblk.so tst-bsd-evh.so \
122122
tst-mmap-file.so misc-mmap-big-file.so tst-mmap.so tst-huge.so \
123123
tst-pthread-timedlock.so \
124124
tst-signal-fills.so \
125+
tst-epoll-pwait2.so \
125126
tst-prctl.so \
126127
tst-mmap-file-cow.so \
127128
tst-elf-permissions.so misc-mutex.so misc-sockets.so tst-condvar.so \

syscalls/syscall_tracepoints.cc.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ TRACEPOINT(trace_syscall_ftruncate, "%d <= %d %ld", int, int, off_t);
6969
TRACEPOINT(trace_syscall_fsync, "%d <= %d", int, int);
7070
#if CONF_core_epoll
7171
TRACEPOINT(trace_syscall_epoll_pwait, "%d <= %d %p %d %d %p", int, int, struct epoll_event *, int, int, const sigset_t*);
72+
TRACEPOINT(trace_syscall_epoll_pwait2, "%d <= %d %p %d %p %p", int, int, struct epoll_event *, int, const struct timespec *, const sigset_t*);
7273
#endif
7374
TRACEPOINT(trace_syscall_getrandom, "%lu <= 0x%x %lu %u", ssize_t, char *, size_t, unsigned int);
7475
TRACEPOINT(trace_syscall_nanosleep, "%d <= %p %p", int, const struct timespec*, struct timespec *);

syscalls/syscalls.cc.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
SYSCALL1(fsync, int);
7070
#if CONF_core_epoll
7171
SYSCALL5(epoll_pwait, int, struct epoll_event *, int, int, const sigset_t*);
72+
SYSCALL5(epoll_pwait2, int, struct epoll_event *, int, const struct timespec *, const sigset_t*);
7273
#endif
7374
SYSCALL3(getrandom, char *, size_t, unsigned int);
7475
SYSCALL2(nanosleep, const struct timespec*, struct timespec *);

tests/tst-epoll-pwait2.cc

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright (C) 2026 Greg Burd
3+
*
4+
* This work is open source software, licensed under the terms of the
5+
* BSD license as described in the LICENSE file in the top-level directory.
6+
*/
7+
8+
// Exercises epoll_pwait2(2) via the raw syscall. Built and run on the OSv
9+
// test image.
10+
11+
#include <sys/epoll.h>
12+
#include <sys/eventfd.h>
13+
#include <sys/syscall.h>
14+
#include <unistd.h>
15+
#include <time.h>
16+
#include <errno.h>
17+
#include <stdint.h>
18+
19+
#include <cassert>
20+
#include <iostream>
21+
22+
static int epoll_pwait2_(int epfd, struct epoll_event *ev, int maxev,
23+
const struct timespec *to, const sigset_t *sig)
24+
{
25+
return syscall(SYS_epoll_pwait2, epfd, ev, maxev, to, sig);
26+
}
27+
28+
int main()
29+
{
30+
std::cerr << "Running epoll_pwait2 tests\n";
31+
32+
int ep = epoll_create1(0);
33+
assert(ep >= 0);
34+
int efd = eventfd(0, EFD_NONBLOCK);
35+
assert(efd >= 0);
36+
37+
struct epoll_event ev {};
38+
ev.events = EPOLLIN;
39+
ev.data.fd = efd;
40+
assert(epoll_ctl(ep, EPOLL_CTL_ADD, efd, &ev) == 0);
41+
42+
struct epoll_event out[4];
43+
44+
// Nothing ready: a zero timespec returns immediately with 0 events.
45+
struct timespec zero { 0, 0 };
46+
assert(epoll_pwait2_(ep, out, 4, &zero, nullptr) == 0);
47+
48+
// Nothing ready: a short timeout returns 0 after roughly that long.
49+
struct timespec ts { 0, 100 * 1000 * 1000 }; // 100 ms
50+
struct timespec before, after;
51+
clock_gettime(CLOCK_MONOTONIC, &before);
52+
assert(epoll_pwait2_(ep, out, 4, &ts, nullptr) == 0);
53+
clock_gettime(CLOCK_MONOTONIC, &after);
54+
long long elapsed_ms = (after.tv_sec - before.tv_sec) * 1000 +
55+
(after.tv_nsec - before.tv_nsec) / 1000000;
56+
assert(elapsed_ms >= 90); // waited about 100 ms, not returned instantly
57+
58+
// Make the eventfd readable: pwait2 reports it.
59+
uint64_t one = 1;
60+
assert(write(efd, &one, sizeof(one)) == sizeof(one));
61+
int n = epoll_pwait2_(ep, out, 4, &ts, nullptr);
62+
assert(n == 1);
63+
assert(out[0].data.fd == efd);
64+
assert(out[0].events & EPOLLIN);
65+
66+
// NULL timeout with a ready fd returns immediately (does not block forever).
67+
n = epoll_pwait2_(ep, out, 4, nullptr, nullptr);
68+
assert(n == 1);
69+
70+
close(efd);
71+
close(ep);
72+
std::cerr << "epoll_pwait2 tests PASSED\n";
73+
return 0;
74+
}

0 commit comments

Comments
 (0)