1 // Copyright (c) 2015-2016 Nuxi, https://nuxi.nl/
2 //
3 // SPDX-License-Identifier: BSD-2-Clause
4
5 #include <common/time.h>
6
7 #include <sys/select.h>
8
9 #include <wasi/api.h>
10 #include <errno.h>
11
pselect(int nfds,fd_set * restrict readfds,fd_set * restrict writefds,fd_set * restrict errorfds,const struct timespec * restrict timeout,const sigset_t * sigmask)12 int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
13 fd_set *restrict errorfds, const struct timespec *restrict timeout,
14 const sigset_t *sigmask) {
15 // Negative file descriptor upperbound.
16 if (nfds < 0) {
17 errno = EINVAL;
18 return -1;
19 }
20
21 // This implementation does not support polling for exceptional
22 // conditions, such as out-of-band data on TCP sockets.
23 if (errorfds != NULL && errorfds->__nfds > 0) {
24 errno = ENOSYS;
25 return -1;
26 }
27
28 // Replace NULL pointers by the empty set.
29 fd_set empty;
30 FD_ZERO(&empty);
31 if (readfds == NULL)
32 readfds = ∅
33 if (writefds == NULL)
34 writefds = ∅
35
36 // Determine the maximum number of events.
37 size_t maxevents = readfds->__nfds + writefds->__nfds + 1;
38 __wasi_subscription_t subscriptions[maxevents];
39 size_t nsubscriptions = 0;
40
41 // Convert the readfds set.
42 for (size_t i = 0; i < readfds->__nfds; ++i) {
43 int fd = readfds->__fds[i];
44 if (fd < nfds) {
45 __wasi_subscription_t *subscription = &subscriptions[nsubscriptions++];
46 *subscription = (__wasi_subscription_t){
47 .userdata = fd,
48 .u.tag = __WASI_EVENTTYPE_FD_READ,
49 .u.u.fd_read.file_descriptor = fd,
50 };
51 }
52 }
53
54 // Convert the writefds set.
55 for (size_t i = 0; i < writefds->__nfds; ++i) {
56 int fd = writefds->__fds[i];
57 if (fd < nfds) {
58 __wasi_subscription_t *subscription = &subscriptions[nsubscriptions++];
59 *subscription = (__wasi_subscription_t){
60 .userdata = fd,
61 .u.tag = __WASI_EVENTTYPE_FD_WRITE,
62 .u.u.fd_write.file_descriptor = fd,
63 };
64 }
65 }
66
67 // Create extra event for the timeout.
68 if (timeout != NULL) {
69 __wasi_subscription_t *subscription = &subscriptions[nsubscriptions++];
70 *subscription = (__wasi_subscription_t){
71 .u.tag = __WASI_EVENTTYPE_CLOCK,
72 .u.u.clock.id = __WASI_CLOCKID_REALTIME,
73 };
74 if (!timespec_to_timestamp_clamp(timeout, &subscription->u.u.clock.timeout)) {
75 errno = EINVAL;
76 return -1;
77 }
78 }
79
80 // Execute poll().
81 size_t nevents;
82 __wasi_event_t events[nsubscriptions];
83 __wasi_errno_t error =
84 __wasi_poll_oneoff(subscriptions, events, nsubscriptions, &nevents);
85 if (error != 0) {
86 // WASI's poll requires at least one subscription, or else it returns
87 // `EINVAL`. Since a `pselect` with nothing to wait for is valid in POSIX,
88 // return `ENOTSUP` to indicate that we don't support that case.
89 //
90 // Wasm has no signal handling, so if none of the user-provided `pollfd`
91 // elements, nor the timeout, led us to producing even one subscription
92 // to wait for, there would be no way for the poll to wake up. WASI
93 // returns `EINVAL` in this case, but for users of `poll`, `ENOTSUP` is
94 // more likely to be understood.
95 if (nsubscriptions == 0)
96 errno = ENOTSUP;
97 else
98 errno = error;
99 return -1;
100 }
101
102 // Test for EBADF.
103 for (size_t i = 0; i < nevents; ++i) {
104 const __wasi_event_t *event = &events[i];
105 if ((event->type == __WASI_EVENTTYPE_FD_READ ||
106 event->type == __WASI_EVENTTYPE_FD_WRITE) &&
107 event->error == __WASI_ERRNO_BADF) {
108 errno = EBADF;
109 return -1;
110 }
111 }
112
113 // Clear and set entries in the result sets.
114 FD_ZERO(readfds);
115 FD_ZERO(writefds);
116 for (size_t i = 0; i < nevents; ++i) {
117 const __wasi_event_t *event = &events[i];
118 if (event->type == __WASI_EVENTTYPE_FD_READ) {
119 readfds->__fds[readfds->__nfds++] = event->userdata;
120 } else if (event->type == __WASI_EVENTTYPE_FD_WRITE) {
121 writefds->__fds[writefds->__nfds++] = event->userdata;
122 }
123 }
124 return readfds->__nfds + writefds->__nfds;
125 }
126