Skip to content

Commit 65ad12f

Browse files
authored
poll, pselect: handle POLLPRI / errorfds gracefully (WebAssembly#754)
pselect() returned ENOSYS when errorfds had any bits set. This breaks programs that use the standard POSIX pattern of passing exceptfds to select() for EOF/error monitoring (e.g., vim, ncurses applications). Since WASI has no out-of-band data mechanism, errorfds can never have any exceptional conditions to report. Clear it instead of rejecting the call, matching the behavior callers expect from a POSIX-compatible select(). Signed-off-by: Christian Stewart <christian@aperture.us>
1 parent 288f165 commit 65ad12f

9 files changed

Lines changed: 172 additions & 16 deletions

File tree

expected/wasm32-wasip1-threads/predefined-macros.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,7 @@
13591359
#define POLLIN POLLRDNORM
13601360
#define POLLNVAL 0x4000
13611361
#define POLLOUT POLLWRNORM
1362+
#define POLLPRI 0x0200
13621363
#define POLLRDNORM 0x1
13631364
#define POLLWRNORM 0x2
13641365
#define POSIX_CLOSE_RESTART 0

expected/wasm32-wasip1/predefined-macros.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,6 +1359,7 @@
13591359
#define POLLIN POLLRDNORM
13601360
#define POLLNVAL 0x4000
13611361
#define POLLOUT POLLWRNORM
1362+
#define POLLPRI 0x0200
13621363
#define POLLRDNORM 0x1
13631364
#define POLLWRNORM 0x2
13641365
#define POSIX_CLOSE_RESTART 0

expected/wasm32-wasip2/predefined-macros.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,6 +1490,7 @@
14901490
#define POLLIN POLLRDNORM
14911491
#define POLLNVAL 0x4000
14921492
#define POLLOUT POLLWRNORM
1493+
#define POLLPRI 0x0200
14931494
#define POLLRDNORM 0x1
14941495
#define POLLWRNORM 0x2
14951496
#define POSIX_CLOSE_RESTART 0

expected/wasm32-wasip3/predefined-macros.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,7 @@
14681468
#define POLLIN POLLRDNORM
14691469
#define POLLNVAL 0x4000
14701470
#define POLLOUT POLLWRNORM
1471+
#define POLLPRI 0x0200
14711472
#define POLLRDNORM 0x1
14721473
#define POLLWRNORM 0x2
14731474
#define POSIX_CLOSE_RESTART 0

libc-bottom-half/cloudlibc/src/libc/poll/poll.c

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,35 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
1515
size_t maxevents = 2 * nfds + 1;
1616
__wasi_subscription_t subscriptions[maxevents];
1717
size_t nsubscriptions = 0;
18+
19+
// POLLPRI (exceptional/out-of-band data) is not supported in WASI.
20+
// If all requested events across all fds are POLLPRI, return ENOSYS.
21+
// Otherwise, remove POLLPRI so it never fires (same as no OOB data).
22+
{
23+
bool has_pri_only = false;
24+
bool has_non_pri = false;
25+
for (size_t i = 0; i < nfds; ++i) {
26+
if (fds[i].fd < 0 || fds[i].events == 0)
27+
continue;
28+
if (fds[i].events & ~POLLPRI)
29+
has_non_pri = true;
30+
else
31+
has_pri_only = true;
32+
}
33+
if (has_pri_only && !has_non_pri) {
34+
errno = ENOSYS;
35+
return -1;
36+
}
37+
}
38+
1839
for (size_t i = 0; i < nfds; ++i) {
1940
struct pollfd *pollfd = &fds[i];
2041
if (pollfd->fd < 0)
2142
continue;
43+
// Strip POLLPRI as it is never reported in WASI.
44+
short events = pollfd->events & ~POLLPRI;
2245
bool created_events = false;
23-
if ((pollfd->events & POLLRDNORM) != 0) {
46+
if ((events & POLLRDNORM) != 0) {
2447
__wasi_subscription_t *subscription = &subscriptions[nsubscriptions++];
2548
*subscription = (__wasi_subscription_t){
2649
.userdata = (uintptr_t)pollfd,
@@ -29,7 +52,7 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
2952
};
3053
created_events = true;
3154
}
32-
if ((pollfd->events & POLLWRNORM) != 0) {
55+
if ((events & POLLWRNORM) != 0) {
3356
__wasi_subscription_t *subscription = &subscriptions[nsubscriptions++];
3457
*subscription = (__wasi_subscription_t){
3558
.userdata = (uintptr_t)pollfd,
@@ -42,8 +65,9 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
4265
// As entries are decomposed into separate read/write subscriptions,
4366
// we cannot detect POLLERR, POLLHUP and POLLNVAL if POLLRDNORM and
4467
// POLLWRNORM are not specified. Disallow this for now.
45-
// Ignore fd entries that have no events requested.
46-
if (!created_events && pollfd->events != 0) {
68+
// Ignore fd entries that have no events requested (including
69+
// entries that only had POLLPRI which was stripped above).
70+
if (!created_events && events != 0) {
4771
errno = ENOSYS;
4872
return -1;
4973
}
@@ -176,6 +200,26 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
176200
fds[i].revents = 0;
177201
}
178202

203+
// POLLPRI (exceptional/out-of-band data) is not supported in WASI.
204+
// If all requested events across all fds are exclusively POLLPRI,
205+
// return ENOSYS. Otherwise, strip POLLPRI and proceed.
206+
{
207+
bool has_pri_only = false;
208+
bool has_non_pri = false;
209+
for (size_t i = 0; i < nfds; ++i) {
210+
if (fds[i].fd < 0 || fds[i].events == 0)
211+
continue;
212+
if (fds[i].events & ~POLLPRI)
213+
has_non_pri = true;
214+
else
215+
has_pri_only = true;
216+
}
217+
if (has_pri_only && !has_non_pri) {
218+
errno = ENOSYS;
219+
return -1;
220+
}
221+
}
222+
179223
size_t max_pollables = (2 * nfds) + 1;
180224
state_t states[max_pollables];
181225
poll_borrow_pollable_t pollables[max_pollables];
@@ -207,14 +251,17 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
207251
continue;
208252
}
209253

254+
// Strip POLLPRI, it is never reported in WASI (no OOB data).
255+
short events = pollfd->events & ~POLLPRI;
256+
210257
// Without a custom registration handle read/write readiness
211258
// below, but everything else is unsupported.
212-
if (pollfd->events & ~(POLLRDNORM | POLLWRNORM)) {
259+
if (events & ~(POLLRDNORM | POLLWRNORM)) {
213260
errno = EOPNOTSUPP;
214261
return -1;
215262
}
216263

217-
if (pollfd->events & POLLRDNORM) {
264+
if (events & POLLRDNORM) {
218265
if (entry->vtable->get_read_stream) {
219266
wasi_read_t read;
220267
if (entry->vtable->get_read_stream(entry->data, &read) < 0)
@@ -227,7 +274,7 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
227274
}
228275
}
229276

230-
if (pollfd->events & POLLWRNORM) {
277+
if (events & POLLWRNORM) {
231278
if (entry->vtable->get_write_stream) {
232279
wasi_write_t write;
233280
if (entry->vtable->get_write_stream(entry->data, &write) < 0)

libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,17 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
2020
return -1;
2121
}
2222

23-
// This implementation does not support polling for exceptional
24-
// conditions, such as out-of-band data on TCP sockets.
25-
if (errorfds != NULL && errorfds->__nfds > 0) {
26-
errno = ENOSYS;
27-
return -1;
28-
}
29-
3023
// Replace NULL pointers by the empty set.
3124
fd_set empty;
3225
FD_ZERO(&empty);
3326
if (readfds == NULL)
3427
readfds = &empty;
3528
if (writefds == NULL)
3629
writefds = &empty;
30+
if (errorfds == NULL)
31+
errorfds = &empty;
3732

38-
struct pollfd poll_fds[readfds->__nfds + writefds->__nfds];
33+
struct pollfd poll_fds[readfds->__nfds + writefds->__nfds + errorfds->__nfds];
3934
size_t poll_nfds = 0;
4035

4136
for (size_t i = 0; i < readfds->__nfds; ++i) {
@@ -60,6 +55,20 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
6055
}
6156
}
6257

58+
// Thread exceptional conditions through as POLLPRI. WASI has no out-of-band
59+
// data, so POLLPRI will never fire, but poll() will allow the call to proceed
60+
// as long as there are other non-POLLPRI events to wait on.
61+
for (size_t i = 0; i < errorfds->__nfds; ++i) {
62+
int fd = errorfds->__fds[i];
63+
if (fd < nfds) {
64+
poll_fds[poll_nfds++] = (struct pollfd){
65+
.fd = fd,
66+
.events = POLLPRI,
67+
.revents = 0
68+
};
69+
}
70+
}
71+
6372
int poll_timeout;
6473
if (timeout) {
6574
uint64_t timeout_u64;
@@ -92,6 +101,7 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
92101

93102
FD_ZERO(readfds);
94103
FD_ZERO(writefds);
104+
FD_ZERO(errorfds);
95105
for (size_t i = 0; i < poll_nfds; ++i) {
96106
struct pollfd* pollfd = poll_fds + i;
97107
if ((pollfd->revents & POLLRDNORM) != 0) {
@@ -100,7 +110,10 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
100110
if ((pollfd->revents & POLLWRNORM) != 0) {
101111
writefds->__fds[writefds->__nfds++] = pollfd->fd;
102112
}
113+
if ((pollfd->revents & POLLPRI) != 0) {
114+
errorfds->__fds[errorfds->__nfds++] = pollfd->fd;
115+
}
103116
}
104117

105-
return readfds->__nfds + writefds->__nfds;
118+
return readfds->__nfds + writefds->__nfds + errorfds->__nfds;
106119
}

libc-bottom-half/headers/public/__header_poll.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@
1010
#define POLLIN POLLRDNORM
1111
#define POLLOUT POLLWRNORM
1212

13+
// POLLPRI is defined for source compatibility. WASI has no out-of-band
14+
// data, so POLLPRI is never reported in revents. poll() silently strips
15+
// it when other events are present and returns ENOSYS when it is the
16+
// only requested event.
17+
#define POLLPRI 0x0200
18+
1319
#define POLLERR 0x1000
1420
#define POLLHUP 0x2000
1521
#define POLLNVAL 0x4000

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ add_wasilibc_test(memcmp.c LDFLAGS -Wl,--stack-first -Wl,--initial-memory=327680
290290
add_wasilibc_test(opendir.c FS ARGV /)
291291
add_wasilibc_test(open_relative_path.c FS ARGV /)
292292
add_wasilibc_test(poll.c FS FAILP3)
293+
add_wasilibc_test(pselect.c FS FAILP3)
293294
add_wasilibc_test(preadvwritev.c FS)
294295
add_wasilibc_test(preadwrite.c FS)
295296
add_wasilibc_test(readlink.c FS)

test/src/pselect.c

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Test pselect() with errorfds and poll() POLLPRI handling.
2+
//
3+
// WASI has no out-of-band data, so POLLPRI / exceptfds should never
4+
// report ready. However, pselect() should not fail when errorfds is
5+
// provided alongside other fd sets. poll() should only fail when
6+
// POLLPRI is the exclusive set of events being requested.
7+
8+
#include "test.h"
9+
#include <errno.h>
10+
#include <fcntl.h>
11+
#include <poll.h>
12+
#include <sys/select.h>
13+
#include <unistd.h>
14+
15+
#define TEST(c, ...) ((c) ? 1 : (t_error(#c " failed: " __VA_ARGS__), 0))
16+
17+
int main(void) {
18+
char tmp[] = "testsuite-XXXXXX";
19+
int fd;
20+
21+
TEST((fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0600)) > 2);
22+
TEST(write(fd, "hello", 5) == 5);
23+
TEST(lseek(fd, 0, SEEK_SET) == 0);
24+
25+
// ---- pselect: readfds + errorfds should succeed ----
26+
{
27+
fd_set rfds, efds;
28+
FD_ZERO(&rfds);
29+
FD_ZERO(&efds);
30+
FD_SET(fd, &rfds);
31+
FD_SET(fd, &efds);
32+
33+
// Use blocking wait (no timeout) like the poll test does.
34+
// A zero timeout may race with event delivery on some runtimes.
35+
int r = pselect(fd + 1, &rfds, NULL, &efds, NULL, NULL);
36+
TEST(r >= 0, "pselect with readfds+errorfds returned %d, errno=%d\n", r,
37+
errno);
38+
// errorfds should be empty (POLLPRI never fires in WASI).
39+
TEST(!FD_ISSET(fd, &efds),
40+
"errorfds should be empty (no OOB data in WASI)\n");
41+
// readfds should have the fd set (file has data).
42+
TEST(FD_ISSET(fd, &rfds), "readfds should have fd set\n");
43+
}
44+
45+
// ---- pselect: only errorfds, no readfds/writefds should fail ----
46+
{
47+
fd_set efds;
48+
FD_ZERO(&efds);
49+
FD_SET(fd, &efds);
50+
51+
struct timespec tv = {0, 0};
52+
errno = 0;
53+
int r = pselect(fd + 1, NULL, NULL, &efds, &tv, NULL);
54+
TEST(r == -1 && errno == ENOSYS,
55+
"pselect with only errorfds should fail with ENOSYS, got r=%d "
56+
"errno=%d\n",
57+
r, errno);
58+
}
59+
60+
// ---- poll: POLLPRI | POLLRDNORM should succeed ----
61+
{
62+
struct pollfd pfd = {
63+
.fd = fd, .events = POLLPRI | POLLRDNORM, .revents = 0};
64+
int r = poll(&pfd, 1, -1);
65+
TEST(r >= 0, "poll with POLLPRI|POLLRDNORM returned %d, errno=%d\n", r,
66+
errno);
67+
// POLLPRI should not be set in revents.
68+
TEST(!(pfd.revents & POLLPRI), "POLLPRI should not be reported in WASI\n");
69+
}
70+
71+
// ---- poll: exclusively POLLPRI should fail ----
72+
{
73+
struct pollfd pfd = {.fd = fd, .events = POLLPRI, .revents = 0};
74+
errno = 0;
75+
int r = poll(&pfd, 1, 0);
76+
TEST(r == -1 && errno == ENOSYS,
77+
"poll with only POLLPRI should fail with ENOSYS, got r=%d errno=%d\n",
78+
r, errno);
79+
}
80+
81+
close(fd);
82+
TEST(unlink(tmp) != -1);
83+
84+
return t_status;
85+
}

0 commit comments

Comments
 (0)