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 = &empty;
33   if (writefds == NULL)
34     writefds = &empty;
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