compat: reimplement and make poll() alertable on Windows
authorRémi Denis-Courmont <remi@remlab.net>
Sat, 9 May 2015 11:50:11 +0000 (14:50 +0300)
committerRémi Denis-Courmont <remi@remlab.net>
Sun, 17 May 2015 11:49:43 +0000 (14:49 +0300)
compat/poll.c

index 825713f..3fa4e7a 100644 (file)
 #include <string.h>
 #include <errno.h>
 
-#ifdef _WIN32
-# ifdef FD_SETSIZE
-/* Too late for #undef FD_SETSIZE to work: fd_set is already defined. */
-#  error Header inclusion order compromised!
-# endif
-# define FD_SETSIZE 0
-# include <winsock2.h>
-#else
+#ifndef _WIN32
 # include <sys/time.h>
 # include <sys/select.h>
 # include <fcntl.h>
-#endif
 
 int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
 {
-#ifdef _WIN32
-    size_t setsize = sizeof (fd_set) + nfds * sizeof (SOCKET);
-    fd_set *rdset = malloc (setsize);
-    fd_set *wrset = malloc (setsize);
-    fd_set *exset = malloc (setsize);
-
-    if (rdset == NULL || wrset == NULL || exset == NULL)
-    {
-        free (rdset);
-        free (wrset);
-        free (exset);
-        errno = ENOMEM;
-        return -1;
-    }
-/* Winsock FD_SET uses FD_SETSIZE in its expansion */
-# undef FD_SETSIZE
-# define FD_SETSIZE (nfds)
-#else
     fd_set rdset[1], wrset[1], exset[1];
-#endif
     struct timeval tv = { 0, 0 };
     int val = -1;
 
@@ -81,22 +54,12 @@ int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
          * default, such that it is quite feasible to get fd >= FD_SETSIZE.
          * The next instructions will result in a buffer overflow if run on
          * a POSIX system, and the later FD_ISSET would perform an undefined
-         * memory read.
-         *
-         * With Winsock, fd_set is a table of integers. This is awfully slow.
-         * However, FD_SET and FD_ISSET silently and safely discard excess
-         * entries. Here, overflow cannot happen anyway: fd_set of adequate
-         * size are allocated.
-         * Note that Vista has a much nicer WSAPoll(), but Mingw does not
-         * support it yet.
-         */
-#ifndef _WIN32
+         * memory read. */
         if ((unsigned)fd >= FD_SETSIZE)
         {
             errno = EINVAL;
             return -1;
         }
-#endif
         if (fds[i].events & POLLRDNORM)
             FD_SET (fd, rdset);
         if (fds[i].events & POLLWRNORM)
@@ -116,22 +79,13 @@ int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
                   (timeout >= 0) ? &tv : NULL);
     if (val == -1)
     {
-#ifndef _WIN32
         if (errno != EBADF)
-#else
-        if (WSAGetLastError () != WSAENOTSOCK)
-#endif
             return -1;
 
         val = 0;
 
         for (unsigned i = 0; i < nfds; i++)
-#ifndef _WIN32
             if (fcntl (fds[i].fd, F_GETFD) == -1)
-#else
-            if (getsockopt (fds[i].fd, SOL_SOCKET, SO_REUSEADDR,
-                            &(DWORD){ 0 }, &(int){ sizeof (DWORD) }) != 0)
-#endif
             {
                 fds[i].revents = POLLNVAL;
                 val++;
@@ -149,10 +103,151 @@ int (poll) (struct pollfd *fds, unsigned nfds, int timeout)
                        | (FD_ISSET (fd, wrset) ? POLLWRNORM : 0)
                        | (FD_ISSET (fd, exset) ? POLLPRI : 0);
     }
-#ifdef _WIN32
-    free (exset);
-    free (wrset);
-    free (rdset);
-#endif
     return val;
 }
+#else
+# include <windows.h>
+# include <winsock2.h>
+
+int poll(struct pollfd *fds, unsigned nfds, int timeout)
+{
+    DWORD to = (timeout >= 0) ? (DWORD)timeout : INFINITE;
+
+    if (nfds == 0)
+    {    /* WSAWaitForMultipleEvents() does not allow zero events */
+        if (SleepEx(to, TRUE))
+        {
+            errno = EINTR;
+            return -1;
+        }
+        return 0;
+    }
+
+    WSAEVENT *evts = malloc(nfds * sizeof (WSAEVENT));
+    if (evts == NULL)
+        return -1; /* ENOMEM */
+
+    DWORD ret = WSA_WAIT_FAILED;
+    for (unsigned i = 0; i < nfds; i++)
+    {
+        SOCKET fd = fds[i].fd;
+        long mask = FD_CLOSE;
+        fd_set rdset, wrset, exset;
+
+        FD_ZERO(&rdset);
+        FD_ZERO(&wrset);
+        FD_ZERO(&exset);
+        FD_SET(fd, &exset);
+
+        if (fds[i].events & POLLRDNORM)
+        {
+            mask |= FD_READ | FD_ACCEPT;
+            FD_SET(fd, &rdset);
+        }
+        if (fds[i].events & POLLWRNORM)
+        {
+            mask |= FD_WRITE | FD_CONNECT;
+            FD_SET(fd, &wrset);
+        }
+        if (fds[i].events & POLLPRI)
+            mask |= FD_OOB;
+
+        fds[i].revents = 0;
+
+        evts[i] = WSACreateEvent();
+        if (evts[i] == WSA_INVALID_EVENT)
+        {
+            while (i > 0)
+                WSACloseEvent(evts[--i]);
+            free(evts);
+            errno = ENOMEM;
+            return -1;
+        }
+
+        if (WSAEventSelect(fds[i].fd, evts[i], mask)
+         && WSAGetLastError() == WSAENOTSOCK)
+            fds[i].revents |= POLLNVAL;
+
+        struct timeval tv = { 0, 0 };
+        /* By its horrible design, WSAEnumNetworkEvents() only enumerates
+         * events that were not already signaled (i.e. it is edge-triggered).
+         * WSAPoll() would be better in this respect, but worse in others.
+         * So use WSAEnumNetworkEvents() after manually checking for pending
+         * events. */
+        if (select(0, &rdset, &wrset, &exset, &tv) > 0)
+        {
+            if (FD_ISSET(fd, &rdset))
+                fds[i].revents |= fds[i].events & POLLRDNORM;
+            if (FD_ISSET(fd, &wrset))
+                fds[i].revents |= fds[i].events & POLLWRNORM;
+            if (FD_ISSET(fd, &exset))
+                /* To add pain to injury, POLLERR and POLLPRI cannot be
+                 * distinguished here. */
+                fds[i].revents |= POLLERR | (fds[i].events & POLLPRI);
+        }
+
+        if (fds[i].revents != 0 && ret == WSA_WAIT_FAILED)
+            ret = WSA_WAIT_EVENT_0 + i;
+    }
+
+    if (ret == WSA_WAIT_FAILED)
+        ret = WSAWaitForMultipleEvents(nfds, evts, FALSE, to, TRUE);
+
+    unsigned count = 0;
+    for (unsigned i = 0; i < nfds; i++)
+    {
+        WSANETWORKEVENTS ne;
+
+        if (WSAEnumNetworkEvents(fds[i].fd, evts[i], &ne))
+            memset(&ne, 0, sizeof (ne));
+        WSACloseEvent(evts[i]);
+
+        if (ne.lNetworkEvents & FD_CONNECT)
+        {
+            fds[i].revents |= POLLWRNORM;
+            if (ne.iErrorCode[FD_CONNECT_BIT] != 0)
+                fds[i].revents |= POLLERR;
+        }
+        if (ne.lNetworkEvents & FD_CLOSE)
+        {
+            fds[i].revents |= (fds[i].events & POLLRDNORM) | POLLHUP;
+            if (ne.iErrorCode[FD_CLOSE_BIT] != 0)
+                fds[i].revents |= POLLERR;
+        }
+        if (ne.lNetworkEvents & FD_ACCEPT)
+        {
+            fds[i].revents |= POLLRDNORM;
+            if (ne.iErrorCode[FD_ACCEPT_BIT] != 0)
+                fds[i].revents |= POLLERR;
+        }
+        if (ne.lNetworkEvents & FD_OOB)
+        {
+            fds[i].revents |= POLLPRI;
+            if (ne.iErrorCode[FD_OOB_BIT] != 0)
+                fds[i].revents |= POLLERR;
+        }
+        if (ne.lNetworkEvents & FD_READ)
+        {
+            fds[i].revents |= POLLRDNORM;
+            if (ne.iErrorCode[FD_READ_BIT] != 0)
+                fds[i].revents |= POLLERR;
+        }
+        if (ne.lNetworkEvents & FD_WRITE)
+        {
+            fds[i].revents |= POLLWRNORM;
+            if (ne.iErrorCode[FD_WRITE_BIT] != 0)
+                fds[i].revents |= POLLERR;
+        }
+        count += fds[i].revents != 0;
+    }
+
+    free(evts);
+
+    if (count == 0 && ret == WSA_WAIT_IO_COMPLETION)
+    {
+        errno = EINTR;
+        return -1;
+    }
+    return count;
+}
+#endif