/* * Public domain * * poll(2) emulation for Windows * * This emulates just-enough poll functionality on Windows to work in the * context of the openssl(1) program. This is not a replacement for * POSIX.1-2001 poll(2), though it may come closer than I care to admit. * * Dongsheng Song * Brent Cook */ /* librist. Copyright © 2019 SipRadius LLC. All right reserved. * Author: Sergio Ammirata, Ph.D. * Adapted for librist * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include 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) { if (fd < 3) return 0; 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 ((object == (int)(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; } static int poll(struct pollfd *pfds, nfds_t nfds, int timeout_ms) { nfds_t i; int timespent_ms, looptime_ms; /* * 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_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_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); } } /* * 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 = WAIT_FAILED; if (timeout_ms < 0) timeout_ms = INFINITE; looptime_ms = timeout_ms > 100 ? 100 : timeout_ms; do { struct timeval tv; tv.tv_sec = 0; tv.tv_usec = looptime_ms * 1000; int handle_signaled = 0; /* * 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 + num_handles - 1)) { tv.tv_usec = 0; handle_signaled = 1; } /* * Check if any sockets have signaled */ rc = select(0, &rfds, &wfds, &efds, &tv); if (!handle_signaled && rc == SOCKET_ERROR) return wsa_select_errno(WSAGetLastError()); if (handle_signaled || (num_sockets && rc > 0)) break; timespent_ms += looptime_ms; } while (timespent_ms < timeout_ms); rc = 0; num_handles = 0; for (i = 0; i < nfds; i++) { pfds[i].revents = 0; if ((int)pfds[i].fd < 0) continue; if (is_socket(pfds[i].fd)) { 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++; } if (pfds[i].revents) rc++; } return rc; }