Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions expected/wasm32-wasip1-threads/predefined-macros.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,7 @@
#define POLLIN POLLRDNORM
#define POLLNVAL 0x4000
#define POLLOUT POLLWRNORM
#define POLLPRI 0x0200
#define POLLRDNORM 0x1
#define POLLWRNORM 0x2
#define POSIX_CLOSE_RESTART 0
Expand Down
1 change: 1 addition & 0 deletions expected/wasm32-wasip1/predefined-macros.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,7 @@
#define POLLIN POLLRDNORM
#define POLLNVAL 0x4000
#define POLLOUT POLLWRNORM
#define POLLPRI 0x0200
#define POLLRDNORM 0x1
#define POLLWRNORM 0x2
#define POSIX_CLOSE_RESTART 0
Expand Down
1 change: 1 addition & 0 deletions expected/wasm32-wasip2/predefined-macros.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,7 @@
#define POLLIN POLLRDNORM
#define POLLNVAL 0x4000
#define POLLOUT POLLWRNORM
#define POLLPRI 0x0200
#define POLLRDNORM 0x1
#define POLLWRNORM 0x2
#define POSIX_CLOSE_RESTART 0
Expand Down
1 change: 1 addition & 0 deletions expected/wasm32-wasip3/predefined-macros.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,7 @@
#define POLLIN POLLRDNORM
#define POLLNVAL 0x4000
#define POLLOUT POLLWRNORM
#define POLLPRI 0x0200
#define POLLRDNORM 0x1
#define POLLWRNORM 0x2
#define POSIX_CLOSE_RESTART 0
Expand Down
61 changes: 54 additions & 7 deletions libc-bottom-half/cloudlibc/src/libc/poll/poll.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,35 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
size_t maxevents = 2 * nfds + 1;
__wasi_subscription_t subscriptions[maxevents];
size_t nsubscriptions = 0;

// POLLPRI (exceptional/out-of-band data) is not supported in WASI.
// If all requested events across all fds are POLLPRI, return ENOSYS.
// Otherwise, remove POLLPRI so it never fires (same as no OOB data).
{
bool has_pri_only = false;
bool has_non_pri = false;
for (size_t i = 0; i < nfds; ++i) {
if (fds[i].fd < 0 || fds[i].events == 0)
continue;
if (fds[i].events & ~POLLPRI)
has_non_pri = true;
else
has_pri_only = true;
}
if (has_pri_only && !has_non_pri) {
errno = ENOSYS;
return -1;
}
}

for (size_t i = 0; i < nfds; ++i) {
struct pollfd *pollfd = &fds[i];
if (pollfd->fd < 0)
continue;
// Strip POLLPRI as it is never reported in WASI.
short events = pollfd->events & ~POLLPRI;
bool created_events = false;
if ((pollfd->events & POLLRDNORM) != 0) {
if ((events & POLLRDNORM) != 0) {
__wasi_subscription_t *subscription = &subscriptions[nsubscriptions++];
*subscription = (__wasi_subscription_t){
.userdata = (uintptr_t)pollfd,
Expand All @@ -29,7 +52,7 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
};
created_events = true;
}
if ((pollfd->events & POLLWRNORM) != 0) {
if ((events & POLLWRNORM) != 0) {
__wasi_subscription_t *subscription = &subscriptions[nsubscriptions++];
*subscription = (__wasi_subscription_t){
.userdata = (uintptr_t)pollfd,
Expand All @@ -42,8 +65,9 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
// As entries are decomposed into separate read/write subscriptions,
// we cannot detect POLLERR, POLLHUP and POLLNVAL if POLLRDNORM and
// POLLWRNORM are not specified. Disallow this for now.
// Ignore fd entries that have no events requested.
if (!created_events && pollfd->events != 0) {
// Ignore fd entries that have no events requested (including
// entries that only had POLLPRI which was stripped above).
if (!created_events && events != 0) {
errno = ENOSYS;
return -1;
}
Expand Down Expand Up @@ -176,6 +200,26 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
fds[i].revents = 0;
}

// POLLPRI (exceptional/out-of-band data) is not supported in WASI.
// If all requested events across all fds are exclusively POLLPRI,
// return ENOSYS. Otherwise, strip POLLPRI and proceed.
{
bool has_pri_only = false;
bool has_non_pri = false;
for (size_t i = 0; i < nfds; ++i) {
if (fds[i].fd < 0 || fds[i].events == 0)
continue;
if (fds[i].events & ~POLLPRI)
has_non_pri = true;
else
has_pri_only = true;
}
if (has_pri_only && !has_non_pri) {
errno = ENOSYS;
return -1;
}
}

size_t max_pollables = (2 * nfds) + 1;
state_t states[max_pollables];
poll_borrow_pollable_t pollables[max_pollables];
Expand Down Expand Up @@ -207,14 +251,17 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
continue;
}

// Strip POLLPRI, it is never reported in WASI (no OOB data).
short events = pollfd->events & ~POLLPRI;

// Without a custom registration handle read/write readiness
// below, but everything else is unsupported.
if (pollfd->events & ~(POLLRDNORM | POLLWRNORM)) {
if (events & ~(POLLRDNORM | POLLWRNORM)) {
errno = EOPNOTSUPP;
return -1;
}

if (pollfd->events & POLLRDNORM) {
if (events & POLLRDNORM) {
if (entry->vtable->get_read_stream) {
wasi_read_t read;
if (entry->vtable->get_read_stream(entry->data, &read) < 0)
Expand All @@ -227,7 +274,7 @@ static int poll_impl(struct pollfd *fds, size_t nfds, int timeout) {
}
}

if (pollfd->events & POLLWRNORM) {
if (events & POLLWRNORM) {
if (entry->vtable->get_write_stream) {
wasi_write_t write;
if (entry->vtable->get_write_stream(entry->data, &write) < 0)
Expand Down
31 changes: 22 additions & 9 deletions libc-bottom-half/cloudlibc/src/libc/sys/select/pselect.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,17 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
return -1;
}

// This implementation does not support polling for exceptional
// conditions, such as out-of-band data on TCP sockets.
if (errorfds != NULL && errorfds->__nfds > 0) {
errno = ENOSYS;
return -1;
}

// Replace NULL pointers by the empty set.
fd_set empty;
FD_ZERO(&empty);
if (readfds == NULL)
readfds = &empty;
if (writefds == NULL)
writefds = &empty;
if (errorfds == NULL)
errorfds = &empty;

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

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

// Thread exceptional conditions through as POLLPRI. WASI has no out-of-band
// data, so POLLPRI will never fire, but poll() will allow the call to proceed
// as long as there are other non-POLLPRI events to wait on.
for (size_t i = 0; i < errorfds->__nfds; ++i) {
int fd = errorfds->__fds[i];
if (fd < nfds) {
poll_fds[poll_nfds++] = (struct pollfd){
.fd = fd,
.events = POLLPRI,
.revents = 0
};
}
}

int poll_timeout;
if (timeout) {
uint64_t timeout_u64;
Expand Down Expand Up @@ -92,6 +101,7 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,

FD_ZERO(readfds);
FD_ZERO(writefds);
FD_ZERO(errorfds);
for (size_t i = 0; i < poll_nfds; ++i) {
struct pollfd* pollfd = poll_fds + i;
if ((pollfd->revents & POLLRDNORM) != 0) {
Expand All @@ -100,7 +110,10 @@ int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
if ((pollfd->revents & POLLWRNORM) != 0) {
writefds->__fds[writefds->__nfds++] = pollfd->fd;
}
if ((pollfd->revents & POLLPRI) != 0) {
errorfds->__fds[errorfds->__nfds++] = pollfd->fd;
}
}

return readfds->__nfds + writefds->__nfds;
return readfds->__nfds + writefds->__nfds + errorfds->__nfds;
}
6 changes: 6 additions & 0 deletions libc-bottom-half/headers/public/__header_poll.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
#define POLLIN POLLRDNORM
#define POLLOUT POLLWRNORM

// POLLPRI is defined for source compatibility. WASI has no out-of-band
// data, so POLLPRI is never reported in revents. poll() silently strips
// it when other events are present and returns ENOSYS when it is the
// only requested event.
#define POLLPRI 0x0200

#define POLLERR 0x1000
#define POLLHUP 0x2000
#define POLLNVAL 0x4000
Expand Down
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ add_wasilibc_test(memcmp.c LDFLAGS -Wl,--stack-first -Wl,--initial-memory=327680
add_wasilibc_test(opendir.c FS ARGV /)
add_wasilibc_test(open_relative_path.c FS ARGV /)
add_wasilibc_test(poll.c FS FAILP3)
add_wasilibc_test(pselect.c FS FAILP3)
add_wasilibc_test(preadvwritev.c FS)
add_wasilibc_test(preadwrite.c FS)
add_wasilibc_test(readlink.c FS)
Expand Down
85 changes: 85 additions & 0 deletions test/src/pselect.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Test pselect() with errorfds and poll() POLLPRI handling.
//
// WASI has no out-of-band data, so POLLPRI / exceptfds should never
// report ready. However, pselect() should not fail when errorfds is
// provided alongside other fd sets. poll() should only fail when
// POLLPRI is the exclusive set of events being requested.

#include "test.h"
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <sys/select.h>
#include <unistd.h>

#define TEST(c, ...) ((c) ? 1 : (t_error(#c " failed: " __VA_ARGS__), 0))

int main(void) {
char tmp[] = "testsuite-XXXXXX";
int fd;

TEST((fd = open(tmp, O_RDWR | O_CREAT | O_EXCL, 0600)) > 2);
TEST(write(fd, "hello", 5) == 5);
TEST(lseek(fd, 0, SEEK_SET) == 0);

// ---- pselect: readfds + errorfds should succeed ----
{
fd_set rfds, efds;
FD_ZERO(&rfds);
FD_ZERO(&efds);
FD_SET(fd, &rfds);
FD_SET(fd, &efds);

// Use blocking wait (no timeout) like the poll test does.
// A zero timeout may race with event delivery on some runtimes.
int r = pselect(fd + 1, &rfds, NULL, &efds, NULL, NULL);
TEST(r >= 0, "pselect with readfds+errorfds returned %d, errno=%d\n", r,
errno);
// errorfds should be empty (POLLPRI never fires in WASI).
TEST(!FD_ISSET(fd, &efds),
"errorfds should be empty (no OOB data in WASI)\n");
// readfds should have the fd set (file has data).
TEST(FD_ISSET(fd, &rfds), "readfds should have fd set\n");
}

// ---- pselect: only errorfds, no readfds/writefds should fail ----
{
fd_set efds;
FD_ZERO(&efds);
FD_SET(fd, &efds);

struct timespec tv = {0, 0};
errno = 0;
int r = pselect(fd + 1, NULL, NULL, &efds, &tv, NULL);
TEST(r == -1 && errno == ENOSYS,
"pselect with only errorfds should fail with ENOSYS, got r=%d "
"errno=%d\n",
r, errno);
}

// ---- poll: POLLPRI | POLLRDNORM should succeed ----
{
struct pollfd pfd = {
.fd = fd, .events = POLLPRI | POLLRDNORM, .revents = 0};
int r = poll(&pfd, 1, -1);
TEST(r >= 0, "poll with POLLPRI|POLLRDNORM returned %d, errno=%d\n", r,
errno);
// POLLPRI should not be set in revents.
TEST(!(pfd.revents & POLLPRI), "POLLPRI should not be reported in WASI\n");
}

// ---- poll: exclusively POLLPRI should fail ----
{
struct pollfd pfd = {.fd = fd, .events = POLLPRI, .revents = 0};
errno = 0;
int r = poll(&pfd, 1, 0);
TEST(r == -1 && errno == ENOSYS,
"poll with only POLLPRI should fail with ENOSYS, got r=%d errno=%d\n",
r, errno);
}

close(fd);
TEST(unlink(tmp) != -1);

return t_status;
}