bug-gnulib
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[RFT final 2/2] Native win32 implementation of poll


From: Paolo Bonzini
Subject: [RFT final 2/2] Native win32 implementation of poll
Date: Sat, 13 Sep 2008 11:13:45 +0200
User-agent: Thunderbird 2.0.0.16 (Macintosh/20080707)

The attached patch finally provides the native implementation of poll
for Win32.

Testing under native Windows is definitely needed for this, because Wine
has a big tendency to crash on the test:

1) test_tty fails under Wine (so I don't actually know if it works, but
the code was taken from GNU Smalltalk and it should be ok).

2) Wine crashes on test_pipe if test_tty is enabled or if it is not
placed first.

3) the last assertion in test_pipe crashes Wine.

Nevertheless, I was able to obtain good coverage under Wine, which makes
me quite positive that the patch works.

In addition, the getsockopt(socket, SOL_SOCKET, SO_TYPE, ...) trick to
distinguish pipes and sockets does not work under Wine.  Instead I used
WSAEnumNetworkEvents which works (and there is no reason why it should
not under Windows).  I also modified the code to *not* use the Winsock
select at all (again using WSAEnumNetworkEvents).  This is more precise
for passive sockets (this could be the problem Yoann reported on the old
implementation) and it allows to detect hangups more precisely too via
the FD_CLOSE event.

Paolo
commit 2837cb39539acec18af3e7b42743eb3ceb826f94
Author: Paolo Bonzini <address@hidden>
Date:   Fri Aug 29 09:12:11 2008 +0200

    implement a native version of poll for Win32
    
    2008-09-12  Paolo Bonzini  <address@hidden>
    
        * lib/poll.c: Rewrite.
        * modules/poll: Depend on alloca.
        * test/test-poll.c: Add notes on Wine compatibility.

diff --git a/ChangeLog b/ChangeLog
index d6f5c13..c89123f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,11 @@
 2008-09-12  Paolo Bonzini  <address@hidden>
 
+       * lib/poll.c: Rewrite.
+       * modules/poll: Depend on alloca.
+       * test/test-poll.c: Add notes on Wine compatibility.
+
+2008-09-12  Paolo Bonzini  <address@hidden>
+
        * lib/sys_socket.in.h: For Win32, map WinSock error codes so that
        those overlapping with MSVCRT error codes coincide.  Do not
        implement rpl_setsockopt here, instead define prototypes for
diff --git a/lib/poll.c b/lib/poll.c
index e0714f0..24af782 100644
--- a/lib/poll.c
+++ b/lib/poll.c
@@ -20,14 +20,25 @@
    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
 
 #include <config.h>
+#include <alloca.h>
 
 #include <sys/types.h>
 #include "poll.h"
 #include <errno.h>
 #include <limits.h>
+#include <assert.h>
+
+#ifdef __MSVCRT__
+#include <windows.h>
+#include <winsock2.h>
+#include <io.h>
+#include <stdio.h>
+#include <conio.h>
+#else
 #include <sys/socket.h>
 #include <sys/select.h>
 #include <unistd.h>
+#endif
 
 #ifdef HAVE_SYS_IOCTL_H
 #include <sys/ioctl.h>
@@ -48,12 +59,228 @@
 #define MSG_PEEK 0
 #endif
 
+#ifdef __MSVCRT__
+
+/* Declare data structures for ntdll functions.  */
+typedef struct _FILE_PIPE_LOCAL_INFORMATION {
+  ULONG NamedPipeType;
+  ULONG NamedPipeConfiguration;
+  ULONG MaximumInstances;
+  ULONG CurrentInstances;
+  ULONG InboundQuota;
+  ULONG ReadDataAvailable;
+  ULONG OutboundQuota;
+  ULONG WriteQuotaAvailable;
+  ULONG NamedPipeState;
+  ULONG NamedPipeEnd;
+} FILE_PIPE_LOCAL_INFORMATION, *PFILE_PIPE_LOCAL_INFORMATION;
+
+typedef struct _IO_STATUS_BLOCK
+{
+  union {
+    DWORD Status;
+    PVOID Pointer;
+  } u;
+  ULONG_PTR Information;
+} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
+
+typedef enum _FILE_INFORMATION_CLASS {
+  FilePipeLocalInformation = 24
+} FILE_INFORMATION_CLASS, *PFILE_INFORMATION_CLASS;
+
+typedef DWORD (WINAPI *PNtQueryInformationFile)
+        (HANDLE, IO_STATUS_BLOCK *, VOID *, ULONG, FILE_INFORMATION_CLASS);
+
+#ifndef PIPE_BUF
+#define PIPE_BUF       512
+#endif
+
+/* Compute revents values for file handle H.  */
+
+static int
+win32_compute_revents (HANDLE h, int sought)
+{
+  int i, ret, happened;
+  INPUT_RECORD *irbuffer;
+  DWORD avail, nbuffer;
+  BOOL bRet;
+  IO_STATUS_BLOCK iosb;
+  FILE_PIPE_LOCAL_INFORMATION fpli;
+  static PNtQueryInformationFile NtQueryInformationFile;
+  static BOOL once_only;
+
+  switch (GetFileType (h))
+    {
+    case FILE_TYPE_PIPE:
+      if (!once_only)
+       {
+         NtQueryInformationFile = (PNtQueryInformationFile)
+           GetProcAddress (GetModuleHandle ("ntdll.dll"),
+                           "NtQueryInformationFile");
+         once_only = TRUE;
+       }
+
+      happened = 0;
+      if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
+       {
+         if (avail)
+           happened |= sought & (POLLIN | POLLRDNORM);
+       }
+
+      else
+       {
+         /* It was the write-end of the pipe.  Check if it is writable.
+            If NtQueryInformationFile fails, optimistically assume the pipe is
+            writable.  This could happen on Win9x, where NtQueryInformationFile
+            is not available, or if we inherit a pipe that doesn't permit
+            FILE_READ_ATTRIBUTES access on the write end (I think this should
+            not happen since WinXP SP2; WINE seems fine too).  Otherwise,
+            ensure that enough space is available for atomic writes.  */
+          memset (&iosb, 0, sizeof (iosb));
+          memset (&fpli, 0, sizeof (fpli));
+
+          if (!NtQueryInformationFile
+              || NtQueryInformationFile (h, &iosb, &fpli, sizeof (fpli),
+                                        FilePipeLocalInformation)
+             || fpli.WriteQuotaAvailable >= PIPE_BUF
+             || (fpli.OutboundQuota < PIPE_BUF &&
+                 fpli.WriteQuotaAvailable == fpli.OutboundQuota))
+           happened |= sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+       }
+      return happened;
+
+    case FILE_TYPE_CHAR:
+      ret = WaitForSingleObject (h, 0);
+      if (ret == WAIT_OBJECT_0)
+        {
+         nbuffer = avail = 0;
+         bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
+         if (!bRet || nbuffer == 0)
+           return POLLHUP;
+
+         irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
+         bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
+         if (!bRet || avail == 0)
+           return POLLHUP;
+
+         for (i = 0; i < avail; i++)
+           if (irbuffer[i].EventType == KEY_EVENT)
+             return sought & ~(POLLPRI | POLLRDBAND);
+       }
+      break;
+
+    default:
+      ret = WaitForSingleObject (h, 0);
+      if (ret == WAIT_OBJECT_0)
+        return sought & ~(POLLPRI | POLLRDBAND);
+
+      break;
+    }
+
+  return sought & (POLLOUT | POLLWRNORM | POLLWRBAND);
+}
+
+/* Convert fd_sets returned by select into revents values.  */
+
+static int
+win32_compute_revents_socket (SOCKET h, int sought, long lNetworkEvents)
+{
+  int happened = 0;
+
+  if (lNetworkEvents & FD_ACCEPT)
+    happened |= (POLLIN | POLLRDNORM) & sought;
+
+  else if (lNetworkEvents & (FD_READ | FD_CLOSE))
+    {
+      int r, error;
+
+      char data[64];
+      WSASetLastError (0);
+      r = recv (h, data, sizeof (data), MSG_PEEK);
+      error = WSAGetLastError ();
+      WSASetLastError (0);
+
+      if (r > 0)
+        happened |= (POLLIN | POLLRDNORM) & sought;
+
+      /* Distinguish hung-up sockets from other errors.  */
+      else if (r == 0 || error == WSAESHUTDOWN || error == WSAECONNRESET
+              || error == WSAECONNABORTED || error == WSAENETRESET)
+        happened |= POLLHUP;
+
+      else if (error != WSAENOTCONN)
+        happened |= POLLERR;
+    }
+
+  if (lNetworkEvents & (FD_WRITE | FD_CONNECT))
+    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+  if (lNetworkEvents & FD_OOB)
+    happened |= (POLLPRI | POLLRDBAND) & sought;
+
+  return happened;
+}
+
+#else /* !MinGW */
+
+/* Convert select(2) returned fd_sets into poll(2) revents values.  */
+static int
+compute_revents (int fd, int sought, fd_set *rfds, fd_set *wfds, fd_set *efds)
+{
+  int happened = 0;
+  if (FD_ISSET (fd, rfds))
+    {
+      int r;
+      int socket_errno;
+
+#if defined __MACH__ && defined __APPLE__
+      /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
+         for some kinds of descriptors.  Detect if this descriptor is a
+         connected socket, a server socket, or something else using a
+         0-byte recv, and use ioctl(2) to detect POLLHUP.  */
+      r = recv (fd, NULL, 0, MSG_PEEK);
+      socket_errno = (r < 0) ? errno : 0;
+      if (r == 0 || socket_errno == ENOTSOCK)
+       ioctl (fd, FIONREAD, &r);
+#else
+      char data[64];
+      r = recv (fd, data, sizeof (data), MSG_PEEK);
+      socket_errno = (r < 0) ? errno : 0;
+#endif
+      if (r == 0)
+       happened |= POLLHUP;
+
+      /* If the event happened on an unconnected server socket,
+         that's fine. */
+      else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
+       happened |= (POLLIN | POLLRDNORM) & sought;
+
+      /* Distinguish hung-up sockets from other errors.  */
+      else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
+              || socket_errno == ECONNABORTED || socket_errno == ENETRESET)
+       happened |= POLLHUP;
+
+      else
+       happened |= POLLERR;
+    }
+
+  if (FD_ISSET (fd, wfds))
+    happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+
+  if (FD_ISSET (fd, efds))
+    happened |= (POLLPRI | POLLRDBAND) & sought;
+
+  return happened;
+}
+#endif /* !MinGW */
+
 int
 poll (pfd, nfd, timeout)
      struct pollfd *pfd;
      nfds_t nfd;
      int timeout;
 {
+#ifndef __MSVCRT__
   fd_set rfds, wfds, efds;
   struct timeval tv;
   struct timeval *ptv;
@@ -137,16 +364,11 @@ poll (pfd, nfd, timeout)
                               | POLLWRNORM | POLLWRBAND)))
        {
          maxfd = pfd[i].fd;
-
-         /* Windows use a linear array of sockets (of size FD_SETSIZE). The
-            descriptor value is not used to address the array.  */
-#if defined __CYGWIN__ || (!defined _WIN32 && !defined __WIN32__)
          if (maxfd > FD_SETSIZE)
            {
              errno = EOVERFLOW;
              return -1;
            }
-#endif
        }
     }
 
@@ -162,61 +384,142 @@ poll (pfd, nfd, timeout)
       pfd[i].revents = 0;
     else
       {
-       int happened = 0, sought = pfd[i].events;
-       if (FD_ISSET (pfd[i].fd, &rfds))
+        int happened = compute_revents (pfd[i].fd, pfd[i].events,
+                                        &rfds, &wfds, &efds);
+       if (happened)
          {
-           int r;
-           int socket_errno;
+           pfd[i].revents = happened;
+           rc++;
+         }
+      }
 
-#if defined __MACH__ && defined __APPLE__
-           /* There is a bug in Mac OS X that causes it to ignore MSG_PEEK
-              for some kinds of descriptors.  Detect if this descriptor is a
-              connected socket, a server socket, or something else using a
-              0-byte recv, and use ioctl(2) to detect POLLHUP.  */
-           r = recv (pfd[i].fd, NULL, 0, MSG_PEEK);
-           socket_errno = (r < 0) ? errno : 0;
-           if (r == 0 || socket_errno == ENOTSOCK)
-             ioctl(pfd[i].fd, FIONREAD, &r);
+  return rc;
 #else
-           char data[64];
-           r = recv (pfd[i].fd, data, sizeof (data), MSG_PEEK);
-            
-# ifdef WIN32
-           if (r < 0 && GetLastError() == 10057) /* server socket */
-              socket_errno = ENOTCONN;
-           else
-# endif
-           socket_errno = (r < 0) ? errno : 0;
-#endif
-           if (r == 0)
-             happened |= POLLHUP;
+  static struct timeval tv0;
+  struct timeval tv = { 0, 0 };
+  struct timeval *ptv;
+  static HANDLE hEvent;
+  WSANETWORKEVENTS ev;
+  HANDLE h, handle_array[FD_SETSIZE + 2];
+  DWORD ret, wait_timeout, nhandles;
+  int nsock;
+  BOOL bRet;
+  MSG msg;
+  char sockbuf[256];
+  int rc;
+  nfds_t i;
 
-           /* If the event happened on an unconnected server socket,
-              that's fine. */
-           else if (r > 0 || ( /* (r == -1) && */ socket_errno == ENOTCONN))
-             happened |= (POLLIN | POLLRDNORM) & sought;
+  if (nfd < 0 || timeout < -1)
+    {
+      errno = EINVAL;
+      return -1;
+    }
 
-           /* Distinguish hung-up sockets from other errors.  */
-           else if (socket_errno == ESHUTDOWN || socket_errno == ECONNRESET
-                    || socket_errno == ECONNABORTED || socket_errno == 
ENETRESET)
-             happened |= POLLHUP;
+  if (!hEvent)
+    hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
 
-           else
-             happened |= POLLERR;
-         }
+  handle_array[0] = hEvent;
+  nhandles = 1;
+  nsock = 0;
 
-       if (FD_ISSET (pfd[i].fd, &wfds))
-         happened |= (POLLOUT | POLLWRNORM | POLLWRBAND) & sought;
+  /* Classify socket handles and create fd sets. */
+  for (i = 0; i < nfd; i++)
+    {
+      size_t optlen = sizeof(sockbuf);
+      if (pfd[i].fd < 0)
+        continue;
 
-       if (FD_ISSET (pfd[i].fd, &efds))
-         happened |= (POLLPRI | POLLRDBAND) & sought;
+      h = (HANDLE) _get_osfhandle (pfd[i].fd);
+      assert (h != NULL);
 
-       if (happened)
-         {
-           pfd[i].revents = happened;
-           rc++;
-         }
-      }
+      /* Under Wine, it seems that getsockopt returns 0 for pipes too.
+        WSAEnumNetworkEvents instead distinguishes the two correctly.  */
+      ev.lNetworkEvents = 0xDEADBEEF;
+      WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+      if (ev.lNetworkEvents != 0xDEADBEEF)
+        {
+          int requested = FD_CLOSE;
+
+          /* see above; socket handles are mapped onto select.  */
+          if (pfd[i].events & (POLLIN | POLLRDNORM))
+            requested |= FD_READ | FD_ACCEPT;
+          if (pfd[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND))
+            requested |= FD_WRITE | FD_CONNECT;
+          if (pfd[i].events & (POLLPRI | POLLRDBAND))
+            requested |= FD_OOB;
+          if (requested)
+            {
+              WSAEventSelect ((SOCKET) h, hEvent, requested);
+              nsock++;
+            }
+        }
+      else
+        {
+          if (pfd[i].events & (POLLIN | POLLRDNORM |
+                               POLLOUT | POLLWRNORM | POLLWRBAND))
+            handle_array[nhandles++] = h;
+        }
+    }
+
+  if (timeout == INFTIM)
+    wait_timeout = INFINITE;
+  else
+    wait_timeout = timeout;
+
+  for (;;)
+    {
+      ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
+                                      wait_timeout, QS_ALLINPUT);
+
+      if (ret == WAIT_OBJECT_0 + nhandles)
+       {
+          /* new input of some other kind */
+          while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
+            {
+              TranslateMessage (&msg);
+              DispatchMessage (&msg);
+            }
+       }
+      else
+       break;
+    }
+
+  /* Place a sentinel at the end of the array.  */
+  handle_array[nhandles] = NULL;
+  nhandles = 1;
+  for (i = 0; i < nfd; i++)
+    {
+      int happened;
+
+      if (pfd[i].fd < 0)
+        {
+          pfd[i].revents = 0;
+          continue;
+        }
+
+      h = (HANDLE) _get_osfhandle (pfd[i].fd);
+      if (h != handle_array[nhandles])
+        {
+          /* It's a socket.  */
+          WSAEnumNetworkEvents ((SOCKET) h, NULL, &ev);
+         WSAEventSelect ((SOCKET) h, 0, 0);
+          happened = win32_compute_revents_socket ((SOCKET) h, pfd[i].events,
+                                                  ev.lNetworkEvents);
+        }
+      else
+        {
+          /* Not a socket.  */
+          nhandles++;
+          happened = win32_compute_revents (h, pfd[i].events);
+        }
+
+       if (happened)
+        {
+          pfd[i].revents = happened;
+          rc++;
+        }
+    }
 
   return rc;
+#endif
 }
diff --git a/modules/poll b/modules/poll
index 3ac7c8c..dab66d4 100644
--- a/modules/poll
+++ b/modules/poll
@@ -7,6 +7,7 @@ lib/poll.in.h
 m4/poll.m4
 
 Depends-on:
+alloca
 sys_select
 sys_time
 EOVERFLOW
diff --git a/tests/test-poll.c b/tests/test-poll.c
index d339117..51de360 100644
--- a/tests/test-poll.c
+++ b/tests/test-poll.c
@@ -344,6 +344,8 @@ test_pipe (void)
 
   pipe (fd);
   test_pair (fd[0], fd[1]);
+
+  /* Wine crashes on the following test.  */
   close (fd[0]);
   if ((poll1_wait (fd[1], POLLIN | POLLOUT) & (POLLHUP | POLLERR)) == 0)
     failed ("expecting POLLHUP after shutdown");
@@ -366,10 +368,11 @@ main ()
   test (test_tty, "TTY");
 #endif
 
-  result = test (test_connect_first, "Unconnected socket test");
-  result += test (test_socket_pair, "Connected sockets test");
+  /* Note: Wine crashes if you change the order of the tests.  */
+  result = test (test_pipe, "Pipe test");
+  result += test (test_connect_first, "Unconnected socket test");
   result += test (test_accept_first, "General socket test with fork");
-  result += test (test_pipe, "Pipe test");
+  result += test (test_socket_pair, "Connected sockets test");
 
   exit (result);
 }

reply via email to

[Prev in Thread] Current Thread [Next in Thread]