commit abc91e91439e7e515c2560be41abf8ff19b12983
parent fafb55c6ef21d038862116821b249cd3c9da3c81
Author: Yersa Nordman <yersa@finwo.nl>
Date: Sun, 31 Dec 2023 01:36:28 +0100
Added actual implementation of win-compat poll
Diffstat:
3 files changed, 341 insertions(+), 1 deletion(-)
diff --git a/config.mk b/config.mk
@@ -1 +1,2 @@
SRC+=__DIRNAME/src/fpoll.c
+SRC+=__DIRNAME/src/poll_compat.c
diff --git a/src/poll_compat.c b/src/poll_compat.c
@@ -0,0 +1,338 @@
+/*
+ * Public domain
+ *
+ * poll(2) emulation for Windows
+ *
+ * This emulates just-enough poll functionality on Windows to work in the
+ * context of this program. This is not a replacement for POSIX.1-2001 poll(2),
+ * though it may come closer than I care to admit.
+ *
+ * Dongsheng Song <dongsheng.song@gmail.com>
+ * Brent Cook <bcook@openbsd.org>
+ */
+
+#ifdef _WIN32
+
+#include <conio.h>
+#include <errno.h>
+#include <io.h>
+#include <ws2tcpip.h>
+
+#include "poll_compat.h"
+
+static int
+conn_is_closed(int fd)
+{
+ char buf[1];
+ int ret = recv(fd, buf, 1, MSG_PEEK);
+ if (ret == -1) {
+ switch (WSAGetLastError()) {
+ case WSAECONNABORTED:
+ case WSAECONNRESET:
+ case WSAENETRESET:
+ case WSAESHUTDOWN:
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int
+conn_has_oob_data(int fd)
+{
+ char buf[1];
+ return (recv(fd, buf, 1, MSG_PEEK | MSG_OOB) == 1);
+}
+
+static int
+is_socket(int fd)
+{
+ WSANETWORKEVENTS events;
+ return (WSAEnumNetworkEvents((SOCKET)fd, NULL, &events) == 0);
+}
+
+static int
+compute_select_revents(int fd, short events,
+ fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+ int rc = 0;
+
+ if ((events & (POLLIN | POLLRDNORM | POLLRDBAND)) &&
+ FD_ISSET(fd, rfds)) {
+ if (conn_is_closed(fd))
+ rc |= POLLHUP;
+ else
+ rc |= POLLIN | POLLRDNORM;
+ }
+
+ if ((events & (POLLOUT | POLLWRNORM | POLLWRBAND)) &&
+ FD_ISSET(fd, wfds))
+ rc |= POLLOUT;
+
+ if (FD_ISSET(fd, efds)) {
+ if (conn_is_closed(fd))
+ rc |= POLLHUP;
+ else if (conn_has_oob_data(fd))
+ rc |= POLLRDBAND | POLLPRI;
+ }
+
+ return rc;
+}
+
+static int
+compute_wait_revents(HANDLE h, short events, int object, int wait_rc)
+{
+ int rc = 0;
+ INPUT_RECORD record;
+ DWORD num_read;
+
+ /*
+ * Assume we can always write to file handles (probably a bad
+ * assumption but works for now, at least it doesn't block).
+ */
+ if (events & (POLLOUT | POLLWRNORM))
+ rc |= POLLOUT;
+
+ /*
+ * Check if this handle was signaled by WaitForMultipleObjects
+ */
+ if (wait_rc >= WAIT_OBJECT_0 && (object == (wait_rc - WAIT_OBJECT_0))
+ && (events & (POLLIN | POLLRDNORM))) {
+
+ /*
+ * Check if this file is stdin, and if so, if it is a console.
+ */
+ if (h == GetStdHandle(STD_INPUT_HANDLE) &&
+ PeekConsoleInput(h, &record, 1, &num_read) == 1) {
+
+ /*
+ * Handle the input console buffer differently,
+ * since it can signal on other events like
+ * window and mouse, but read can still block.
+ */
+ if (record.EventType == KEY_EVENT &&
+ record.Event.KeyEvent.bKeyDown) {
+ rc |= POLLIN;
+ } else {
+ /*
+ * Flush non-character events from the
+ * console buffer.
+ */
+ ReadConsoleInput(h, &record, 1, &num_read);
+ }
+ } else {
+ rc |= POLLIN;
+ }
+ }
+
+ return rc;
+}
+
+static int
+wsa_select_errno(int err)
+{
+ switch (err) {
+ case WSAEINTR:
+ case WSAEINPROGRESS:
+ errno = EINTR;
+ break;
+ case WSAEFAULT:
+ /*
+ * Windows uses WSAEFAULT for both resource allocation failures
+ * and arguments not being contained in the user's address
+ * space. So, we have to choose EFAULT or ENOMEM.
+ */
+ errno = EFAULT;
+ break;
+ case WSAEINVAL:
+ errno = EINVAL;
+ break;
+ case WSANOTINITIALISED:
+ errno = EPERM;
+ break;
+ case WSAENETDOWN:
+ errno = ENOMEM;
+ break;
+ }
+ return -1;
+}
+
+int
+poll(struct pollfd *pfds, nfds_t nfds, int timeout_ms)
+{
+ nfds_t i;
+ int timespent_ms, looptime_ms;
+
+#define FD_IS_SOCKET (1 << 0)
+ int fd_state[FD_SETSIZE];
+ int num_fds;
+
+ /*
+ * select machinery
+ */
+ fd_set rfds, wfds, efds;
+ int rc;
+ int num_sockets;
+
+ /*
+ * wait machinery
+ */
+ DWORD wait_rc;
+ HANDLE handles[FD_SETSIZE];
+ int num_handles;
+
+ if (pfds == NULL) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (nfds <= 0) {
+ return 0;
+ }
+
+ FD_ZERO(&rfds);
+ FD_ZERO(&wfds);
+ FD_ZERO(&efds);
+ num_fds = 0;
+ num_sockets = 0;
+ num_handles = 0;
+
+ for (i = 0; i < nfds; i++) {
+ if ((int)pfds[i].fd < 0) {
+ continue;
+ }
+
+ if (is_socket(pfds[i].fd)) {
+ if (num_sockets >= FD_SETSIZE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ fd_state[num_fds] = FD_IS_SOCKET;
+
+ FD_SET(pfds[i].fd, &efds);
+
+ if (pfds[i].events &
+ (POLLIN | POLLRDNORM | POLLRDBAND)) {
+ FD_SET(pfds[i].fd, &rfds);
+ }
+
+ if (pfds[i].events &
+ (POLLOUT | POLLWRNORM | POLLWRBAND)) {
+ FD_SET(pfds[i].fd, &wfds);
+ }
+ num_sockets++;
+
+ } else {
+ if (num_handles >= FD_SETSIZE) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ handles[num_handles++] =
+ (HANDLE)_get_osfhandle(pfds[i].fd);
+ }
+
+ num_fds++;
+ }
+
+ /*
+ * Determine if the files, pipes, sockets, consoles, etc. have signaled.
+ *
+ * Do this by alternating a loop between WaitForMultipleObjects for
+ * non-sockets and and select for sockets.
+ *
+ * I tried to implement this all in terms of WaitForMultipleObjects
+ * with a select-based 'poll' of the sockets at the end to get extra
+ * specific socket status.
+ *
+ * However, the cost of setting up an event handle for each socket and
+ * cleaning them up reliably was pretty high. Since the event handle
+ * associated with a socket is also global, creating a new one here
+ * cancels one that may exist externally to this function.
+ *
+ * At any rate, even if global socket event handles were not an issue,
+ * the 'FD_WRITE' status of a socket event handle does not behave in an
+ * expected fashion, being triggered by an edge on a write buffer rather
+ * than simply triggering if there is space available.
+ */
+ timespent_ms = 0;
+ wait_rc = 0;
+
+ if (timeout_ms < 0) {
+ timeout_ms = INFINITE;
+ }
+ looptime_ms = timeout_ms > 100 ? 100 : timeout_ms;
+
+ do {
+ struct timeval tv = {0, looptime_ms * 1000};
+
+ /*
+ * Check if any file handles have signaled
+ */
+ if (num_handles) {
+ wait_rc = WaitForMultipleObjects(num_handles, handles, FALSE, 0);
+ if (wait_rc == WAIT_FAILED) {
+ /*
+ * The documentation for WaitForMultipleObjects
+ * does not specify what values GetLastError
+ * may return here. Rather than enumerate
+ * badness like for wsa_select_errno, assume a
+ * general errno value.
+ */
+ errno = ENOMEM;
+ return 0;
+ }
+ }
+
+ /*
+ * If we signaled on a file handle, don't wait on the sockets.
+ */
+ if (wait_rc >= WAIT_OBJECT_0)
+ tv.tv_usec = 0;
+
+ /*
+ * Check if any sockets have signaled
+ */
+ rc = select(0, &rfds, &wfds, &efds, &tv);
+ if (rc == SOCKET_ERROR) {
+ return wsa_select_errno(WSAGetLastError());
+ }
+
+ if (wait_rc >= WAIT_OBJECT_0 || (num_sockets && rc > 0))
+ break;
+
+ timespent_ms += looptime_ms;
+
+ } while (timespent_ms < timeout_ms);
+
+ rc = 0;
+ num_handles = 0;
+ num_fds = 0;
+ for (i = 0; i < nfds; i++) {
+ pfds[i].revents = 0;
+
+ if ((int)pfds[i].fd < 0)
+ continue;
+
+ if (fd_state[num_fds] & FD_IS_SOCKET) {
+ pfds[i].revents = compute_select_revents(pfds[i].fd,
+ pfds[i].events, &rfds, &wfds, &efds);
+
+ } else {
+ pfds[i].revents = compute_wait_revents(
+ handles[num_handles], pfds[i].events, num_handles,
+ wait_rc);
+ num_handles++;
+ }
+
+ num_fds++;
+
+ if (pfds[i].revents)
+ rc++;
+ }
+
+ return rc;
+}
+
+#endif // _WIN32
diff --git a/src/poll_compat.h b/src/poll_compat.h
@@ -4,7 +4,8 @@
* poll(2) emulation for Windows
*
* This emulates just-enough poll functionality on Windows to work in the
- * context of this program. This is not a replacement for POSIX.1-2001 poll(2).
+ * context of this program. This is not a replacement for POSIX.1-2001 poll(2),
+ * though it may come closer than I care to admit.
*
* Dongsheng Song <dongsheng.song@gmail.com>
* Brent Cook <bcook@openbsd.org>