changeset 10474:5fc74f43b3d6

implement full-blown select(2) for winsock 2008-09-24 Paolo Bonzini <bonzini@gnu.org> * NEWS: Document increased portability that sys_select now provides. * lib/sys_select.in.h: Install select wrapper. * lib/sys_socket.in.h: Use more descriptive name when there is no select wrapper. * lib/winsock-select.c: New. * m4/sys_select_h.m4: Compile lib/winsock-select.c if WinSock is used. Require gl_HEADER_SYS_SOCKET. * modules/sys_select: Depend on alloca, add lib/winsock-select.c. * modules/sys_select-tests: Copy dependencies from modules/poll-tests. * tests/test-sys_select.c: Add functional tests.
author Paolo Bonzini <bonzini@gnu.org>
date Tue, 23 Sep 2008 15:11:23 +0200
parents 541a421c693f
children b80084824a77
files ChangeLog NEWS lib/sys_select.in.h lib/sys_socket.in.h lib/winsock-select.c m4/sys_select_h.m4 modules/sys_select modules/sys_select-tests tests/test-sys_select.c
diffstat 9 files changed, 812 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2008-09-24  Paolo Bonzini  <bonzini@gnu.org>
+
+	* NEWS: Document increased portability that sys_select now provides.
+
+	* lib/sys_select.in.h: Install select wrapper.
+	* lib/sys_socket.in.h: Use more descriptive name when there is no
+	select wrapper.
+	* lib/winsock-select.c: New.
+	* m4/sys_select_h.m4: Compile lib/winsock-select.c if WinSock is used.
+	Require gl_HEADER_SYS_SOCKET.
+	* modules/sys_select: Depend on alloca, add lib/winsock-select.c.
+	* modules/sys_select-tests: Copy dependencies from modules/poll-tests.
+	* tests/test-sys_select.c: Add functional tests.
+
 2008-09-24  Eric Blake  <ebb9@byu.net>
 
 	open, fopen: close fd leak in last patch
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,12 @@
 
 Date        Modules         Changes
 
+2008-09-24  sys_select      The limitation on `select', introduced 2008-09-23,
+                            was removed.  sys_select now includes a select
+                            wrapper for Winsock.  The wrapper expects socket
+                            and file descriptors to be compatible as arranged
+                            by the sys_socket on MinGW.
+
 2008-09-23  sys_socket      Under Windows (MinGW), the module now adds
                             wrappers around Winsock functions, so that
                             socket descriptors are now compatible with
--- a/lib/sys_select.in.h
+++ b/lib/sys_select.in.h
@@ -39,6 +39,13 @@
 
 # include <sys/socket.h>
 
+# if @HAVE_WINSOCK2_H@
+#  undef select
+#  define select		rpl_select
+
+extern int rpl_select (int, fd_set *, fd_set *, fd_set *, struct timeval *);
+# endif
+
 #endif
 
 #endif /* _GL_SYS_SELECT_H */
--- a/lib/sys_socket.in.h
+++ b/lib/sys_socket.in.h
@@ -156,9 +156,8 @@
 #  define sendto		rpl_sendto
 #  undef setsockopt
 #  define setsockopt		rpl_setsockopt
-
 #  undef select
-#  define select		select_not_supported_under_win32_use_poll
+#  define select		select_used_without_including_sys_select_h
 
 extern int rpl_close(int);
 extern int rpl_socket (int, int, int protocol);
new file mode 100644
--- /dev/null
+++ b/lib/winsock-select.c
@@ -0,0 +1,418 @@
+/* Emulation for select(2)
+   Contributed by Paolo Bonzini.
+
+   Copyright 2008 Free Software Foundation, Inc.
+
+   This file is part of gnulib.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 2, or (at your option)
+   any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License along
+   with this program; if not, write to the Free Software Foundation,
+   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
+
+#include <config.h>
+#include <alloca.h>
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+#include <sys/types.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <winsock2.h>
+#include <windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <conio.h>
+#include <time.h>
+
+struct bitset {
+  unsigned char in[FD_SETSIZE / CHAR_BIT];
+  unsigned char out[FD_SETSIZE / CHAR_BIT];
+};
+
+/* 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 output fd_sets for libc descriptor FD (whose Win32 handle is H).  */
+
+static int
+win32_poll_handle (HANDLE h, int fd, struct bitset *rbits, struct bitset *wbits,
+		   struct bitset *xbits)
+{
+  BOOL read, write, except;
+  int i, ret;
+  INPUT_RECORD *irbuffer;
+  DWORD avail, nbuffer;
+  BOOL bRet;
+  IO_STATUS_BLOCK iosb;
+  FILE_PIPE_LOCAL_INFORMATION fpli;
+  static PNtQueryInformationFile NtQueryInformationFile;
+  static BOOL once_only;
+
+  read = write = except = FALSE;
+  switch (GetFileType (h))
+    {
+    case FILE_TYPE_PIPE:
+      if (!once_only)
+	{
+	  NtQueryInformationFile = (PNtQueryInformationFile)
+	    GetProcAddress (GetModuleHandle ("ntdll.dll"),
+			    "NtQueryInformationFile");
+	  once_only = TRUE;
+	}
+
+      if (PeekNamedPipe (h, NULL, 0, NULL, &avail, NULL) != 0)
+	{
+	  if (avail)
+	    read = TRUE;
+	}
+
+      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))
+	    write = TRUE;
+	}
+      break;
+
+    case FILE_TYPE_CHAR:
+      ret = WaitForSingleObject (h, 0);
+      write = TRUE;
+      if (ret == WAIT_OBJECT_0)
+        {
+	  nbuffer = avail = 0;
+	  bRet = GetNumberOfConsoleInputEvents (h, &nbuffer);
+	  if (!bRet || nbuffer == 0)
+	    except = TRUE;
+
+	  irbuffer = (INPUT_RECORD *) alloca (nbuffer * sizeof (INPUT_RECORD));
+	  bRet = PeekConsoleInput (h, irbuffer, nbuffer, &avail);
+	  if (!bRet || avail == 0)
+	    except = TRUE;
+
+	  for (i = 0; i < avail; i++)
+	    if (irbuffer[i].EventType == KEY_EVENT)
+	      read = TRUE;
+	}
+      break;
+
+    default:
+      ret = WaitForSingleObject (h, 0);
+      write = TRUE;
+      if (ret == WAIT_OBJECT_0)
+        read = TRUE;
+
+      break;
+    }
+
+  ret = 0;
+  if (read && (rbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1)))))
+    {
+      rbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1)));
+      ret++;
+    }
+
+  if (write && (wbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1)))))
+    {
+      wbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1)));
+      ret++;
+    }
+
+  if (except && (xbits->in[fd / CHAR_BIT] & (1 << (fd & (CHAR_BIT - 1)))))
+    {
+      xbits->out[fd / CHAR_BIT] |= (1 << (fd & (CHAR_BIT - 1)));
+      ret++;
+    }
+
+  return ret;
+}
+
+int
+rpl_select (int nfds, fd_set *rfds, fd_set *wfds, fd_set *xfds,
+            struct timeval *timeout)
+{
+  static struct timeval tv0;
+  static HANDLE hEvent;
+  HANDLE h, handle_array[FD_SETSIZE + 2];
+  fd_set handle_rfds, handle_wfds, handle_xfds;
+  struct bitset rbits, wbits, xbits;
+  unsigned char anyfds_in[FD_SETSIZE / CHAR_BIT];
+  DWORD ret, wait_timeout, nhandles, nsock;
+  MSG msg;
+  int i, fd, rc;
+
+  if (nfds > FD_SETSIZE)
+    nfds = FD_SETSIZE;
+
+  if (!timeout)
+    wait_timeout = INFINITE;
+  else
+    {
+      wait_timeout = timeout->tv_sec + timeout->tv_usec / 1000;
+
+      /* select is also used as a portable usleep.  */
+      if (!rfds && !wfds && !xfds)
+        {
+          Sleep (wait_timeout);
+          return 0;
+        }
+    }
+
+  if (!hEvent)
+    hEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
+
+  handle_array[0] = hEvent;
+  nhandles = 1;
+  nsock = 0;
+
+  /* Copy descriptors to bitsets.  */
+  memset (&rbits, 0, sizeof (rbits));
+  memset (&wbits, 0, sizeof (wbits));
+  memset (&xbits, 0, sizeof (xbits));
+  memset (anyfds_in, 0, sizeof (anyfds_in));
+  if (rfds)
+    for (i = 0; i < rfds->fd_count; i++)
+      {
+        fd = rfds->fd_array[i];
+        rbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
+        anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
+      }
+  else
+    rfds = (fd_set *) alloca (sizeof (fd_set));
+
+  if (wfds)
+    for (i = 0; i < wfds->fd_count; i++)
+      {
+        fd = wfds->fd_array[i];
+        wbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
+        anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
+      }
+  else
+    wfds = (fd_set *) alloca (sizeof (fd_set));
+
+  if (xfds)
+    for (i = 0; i < xfds->fd_count; i++)
+      {
+        fd = xfds->fd_array[i];
+        xbits.in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
+        anyfds_in[fd / CHAR_BIT] |= 1 << (fd & (CHAR_BIT - 1));
+      }
+  else
+    xfds = (fd_set *) alloca (sizeof (fd_set));
+
+  /* Zero all the fd_sets, including the application's.  */
+  FD_ZERO (rfds);
+  FD_ZERO (wfds);
+  FD_ZERO (xfds);
+  FD_ZERO (&handle_rfds);
+  FD_ZERO (&handle_wfds);
+  FD_ZERO (&handle_xfds);
+
+  /* Classify handles.  Create fd sets for sockets, poll the others. */
+  for (i = 0; i < nfds; i++)
+    {
+      WSANETWORKEVENTS ev;
+
+      if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0)
+	continue;
+
+      h = (HANDLE) _get_osfhandle (i);
+      if (!h)
+        {
+	  errno = EBADF;
+	  return -1;
+        }
+
+      /* 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, but we
+	     need to map descriptors to handles.  */
+          if (rbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
+	    {
+              requested |= FD_READ | FD_ACCEPT;
+	      FD_SET ((SOCKET) h, rfds);
+	      FD_SET ((SOCKET) h, &handle_rfds);
+	    }
+          if (wbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
+	    {
+              requested |= FD_WRITE | FD_CONNECT;
+	      FD_SET ((SOCKET) h, wfds);
+	      FD_SET ((SOCKET) h, &handle_wfds);
+	    }
+          if (xbits.in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
+	    {
+              requested |= FD_OOB;
+	      FD_SET ((SOCKET) h, xfds);
+	      FD_SET ((SOCKET) h, &handle_xfds);
+	    }
+
+          WSAEventSelect ((SOCKET) h, hEvent, requested);
+	  nsock++;
+        }
+      else
+        {
+          handle_array[nhandles++] = h;
+
+	  /* Poll now.  If we get an event, do not wait below.  */
+          if (wait_timeout != 0
+	      && win32_poll_handle (h, i, &rbits, &wbits, &xbits))
+	    wait_timeout = 0;
+        }
+    }
+
+  if (wait_timeout == 0 || nsock == 0)
+    rc = 0;
+  else
+    {
+      /* See if we need to wait in the loop below.  If any select is ready,
+         do MsgWaitForMultipleObjects anyway to dispatch messages, but
+         no need to call select again.  */
+      rc = select (0, &handle_rfds, &handle_wfds, &handle_xfds, &tv0);
+      if (rc == 0)
+	{
+	  /* Restore the fd_sets for the other select we do below.  */
+          memcpy (&handle_rfds, rfds, sizeof (fd_set));
+          memcpy (&handle_wfds, wfds, sizeof (fd_set));
+          memcpy (&handle_xfds, xfds, sizeof (fd_set));
+	}
+      else
+        wait_timeout = 0;
+    }
+
+  for (;;)
+    {
+      ret = MsgWaitForMultipleObjects (nhandles, handle_array, FALSE,
+				       wait_timeout, QS_ALLINPUT);
+
+      if (ret == WAIT_OBJECT_0 + nhandles)
+	{
+          /* new input of some other kind */
+	  BOOL bRet;
+          while ((bRet = PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) != 0)
+            {
+              TranslateMessage (&msg);
+              DispatchMessage (&msg);
+            }
+	}
+      else
+	break;
+    }
+
+  /* If we haven't done it yet, check the status of the sockets.  */
+  if (rc == 0 && nsock > 0)
+    rc = select (0, &handle_rfds, &handle_wfds, &handle_xfds, &tv0);
+
+  /* Now fill in the results.  */
+  FD_ZERO (rfds);
+  FD_ZERO (wfds);
+  FD_ZERO (xfds);
+
+  /* Place a sentinel at the end of the array.  */
+  handle_array[nhandles] = NULL;
+  nhandles = 1;
+  for (i = 0; i < nfds; i++)
+    {
+      if ((anyfds_in[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1)))) == 0)
+	continue;
+
+      h = (HANDLE) _get_osfhandle (i);
+      if (h != handle_array[nhandles])
+	{
+	  /* Perform handle->descriptor mapping.  Don't update rc, as these
+	     results are counted in the return value of Winsock's select.  */
+          WSAEventSelect ((SOCKET) h, NULL, 0);
+          if (FD_ISSET (h, &handle_rfds))
+            FD_SET (i, rfds);
+          if (FD_ISSET (h, &handle_wfds))
+            FD_SET (i, wfds);
+          if (FD_ISSET (h, &handle_xfds))
+            FD_SET (i, xfds);
+	}
+      else
+        {
+          /* Not a socket.  */
+          nhandles++;
+          win32_poll_handle (h, i, &rbits, &wbits, &xbits);
+          if (rbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
+            {
+	      rc++;
+	      FD_SET (i, rfds);
+	    }
+          if (wbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
+            {
+	      rc++;
+	      FD_SET (i, wfds);
+	    }
+          if (xbits.out[i / CHAR_BIT] & (1 << (i & (CHAR_BIT - 1))))
+            {
+	      rc++;
+	      FD_SET (i, xfds);
+	    }
+        }
+    }
+
+  return rc;
+}
+
+#endif /* Native Win32.  */
--- a/m4/sys_select_h.m4
+++ b/m4/sys_select_h.m4
@@ -6,6 +6,7 @@
 
 AC_DEFUN([gl_HEADER_SYS_SELECT],
 [
+  AC_REQUIRE([gl_HEADER_SYS_SOCKET])
   AC_CACHE_CHECK([whether <sys/select.h> is self-contained],
     [gl_cv_header_sys_select_h_selfcontained],
     [
@@ -26,4 +27,7 @@
     AC_SUBST([HAVE_SYS_SELECT_H])
   fi
   AC_SUBST([SYS_SELECT_H])
+  if test x$ac_cv_header_winsock2_h = xyes; then
+    AC_LIBOBJ(winsock-select)
+  fi
 ])
--- a/modules/sys_select
+++ b/modules/sys_select
@@ -3,9 +3,11 @@
 
 Files:
 lib/sys_select.in.h
+lib/winsock-select.c
 m4/sys_select_h.m4
 
 Depends-on:
+alloca
 include_next
 sys_socket
 
@@ -26,6 +28,7 @@
 	      -e 's|@''PRAGMA_SYSTEM_HEADER''@|@PRAGMA_SYSTEM_HEADER@|g' \
 	      -e 's|@''NEXT_SYS_SELECT_H''@|$(NEXT_SYS_SELECT_H)|g' \
 	      -e 's|@''HAVE_SYS_SELECT_H''@|$(HAVE_SYS_SELECT_H)|g' \
+	      -e 's|@''HAVE_WINSOCK2_H''@|$(HAVE_WINSOCK2_H)|g' \
 	      < $(srcdir)/sys_select.in.h; \
 	} > $@-t
 	mv $@-t $@
--- a/modules/sys_select-tests
+++ b/modules/sys_select-tests
@@ -2,6 +2,15 @@
 tests/test-sys_select.c
 
 Depends-on:
+stdbool
+sys_select
+netinet_in
+arpa_inet
+extensions
+inet_pton
+errno
+perror
+sockets
 
 configure.ac:
 
--- a/tests/test-sys_select.c
+++ b/tests/test-sys_select.c
@@ -1,5 +1,5 @@
 /* Test of <sys/select.h> substitute.
-   Copyright (C) 2007 Free Software Foundation, Inc.
+   Copyright (C) 2007, 2008 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -15,13 +15,361 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
 /* Written by Bruno Haible <bruno@clisp.org>, 2007.  */
+/* Enhanced by Paolo Bonzini, 2008.  */
 
 #include <config.h>
 
+#include <stdio.h>
+#include <string.h>
 #include <sys/select.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include "sockets.h"
+
+enum { SEL_IN = 1, SEL_OUT = 2, SEL_EXC = 4 };
+
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+# define WIN32_NATIVE
+#endif
+
+#ifdef WIN32_NATIVE
+#include <io.h>
+#define pipe(x) _pipe(x, 256, O_BINARY)
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+
+#ifndef SO_REUSEPORT
+#define SO_REUSEPORT    SO_REUSEADDR
+#endif
+
+#define TEST_PORT	12345
+
+
+/* Minimal testing infrastructure.  */
+
+static int failures;
+
+static void
+failed (const char *reason)
+{
+  if (++failures > 1)
+    printf ("  ");
+  printf ("failed (%s)\n", reason);
+}
+
+static int
+test (void (*fn) (void), const char *msg)
+{
+  failures = 0;
+  printf ("%s... ", msg);
+  fflush (stdout);
+  fn ();
+
+  if (!failures)
+    printf ("passed\n");
+
+  return failures;
+}
+
+
+/* Funny socket code.  */
+
+static int
+open_server_socket ()
+{
+  int s, x;
+  struct sockaddr_in ia;
+
+  s = socket (AF_INET, SOCK_STREAM, 0);
+
+  memset (&ia, 0, sizeof (ia));
+  ia.sin_family = AF_INET;
+  inet_pton (AF_INET, "127.0.0.1", &ia.sin_addr);
+  ia.sin_port = htons (TEST_PORT);
+  if (bind (s, (struct sockaddr *) &ia, sizeof (ia)) < 0)
+    {
+      perror ("bind");
+      exit (77);
+    }
+
+  x = 1;
+  setsockopt (s, SOL_SOCKET, SO_REUSEPORT, &x, sizeof (x));
+
+  if (listen (s, 1) < 0)
+    {
+      perror ("listen");
+      exit (77);
+    }
+
+  return s;
+}
+
+static int
+connect_to_socket (int blocking)
+{
+  int s;
+  struct sockaddr_in ia;
+
+  s = socket (AF_INET, SOCK_STREAM, 0);
+
+  memset (&ia, 0, sizeof (ia));
+  ia.sin_family = AF_INET;
+  inet_pton (AF_INET, "127.0.0.1", &ia.sin_addr);
+  ia.sin_port = htons (TEST_PORT);
+
+  if (!blocking)
+    {
+#ifdef WIN32_NATIVE
+      unsigned long iMode = 1;
+      ioctl (s, FIONBIO, (char *) &iMode);
+ 
+#elif defined F_GETFL
+      int oldflags = fcntl (s, F_GETFL, NULL);
+ 
+      if (!(oldflags & O_NONBLOCK))
+        fcntl (s, F_SETFL, oldflags | O_NONBLOCK);
+#endif
+    }
+
+  if (connect (s, (struct sockaddr *) &ia, sizeof (ia)) < 0
+      && (blocking || errno != EINPROGRESS))
+    {
+      perror ("connect");
+      exit (77);
+    }
+
+  return s;
+}
+
+
+/* A slightly more convenient interface to select(2).  */
+
+static int
+do_select (int fd, int ev, struct timeval *tv)
+{
+  fd_set rfds, wfds, xfds;
+  int r, rev;
+
+  FD_ZERO (&rfds);
+  FD_ZERO (&wfds);
+  FD_ZERO (&xfds);
+  if (ev & SEL_IN)
+    FD_SET (fd, &rfds);
+  if (ev & SEL_OUT)
+    FD_SET (fd, &wfds);
+  if (ev & SEL_EXC)
+    FD_SET (fd, &xfds);
+  r = select (fd + 1, &rfds, &wfds, &xfds, tv);
+  if (r < 0)
+    return r;
+
+  rev = 0;
+  if (FD_ISSET (fd, &rfds))
+    rev |= SEL_IN;
+  if (FD_ISSET (fd, &wfds))
+    rev |= SEL_OUT;
+  if (FD_ISSET (fd, &xfds))
+    rev |= SEL_EXC;
+  if (rev && r == 0)
+    failed ("select returned 0");
+  if (rev & ~ev)
+    failed ("select returned unrequested events");
+
+  return rev;
+}
+
+static int
+do_select_nowait (int fd, int ev)
+{
+  static struct timeval tv0;
+  return do_select (fd, ev, &tv0);
+}
+
+static int
+do_select_wait (int fd, int ev)
+{
+  return do_select (fd, ev, NULL);
+}
+
+
+/* Test poll(2) for TTYs.  */
+
+#ifdef INTERACTIVE
+static void
+test_tty (void)
+{
+  if (do_select_nowait (0, SEL_IN) != 0)
+    failed ("can read");
+  if (do_select_nowait (0, SEL_OUT) == 0)
+    failed ("cannot write");
+
+  if (do_select_wait (0, SEL_IN) == 0)
+    failed ("return with infinite timeout");
+
+  getchar ();
+  if (do_select_nowait (0, SEL_IN) != 0)
+    failed ("can read after getc");
+}
+#endif
+
+
+/* Test poll(2) for unconnected nonblocking sockets.  */
+
+static void
+test_connect_first (void)
+{
+  int s = open_server_socket ();
+  struct sockaddr_in ia;
+  socklen_t addrlen;
+
+  int c1, c2;
+
+  if (do_select_nowait (s, SEL_IN | SEL_EXC) != 0)
+    failed ("can read, socket not connected");
+
+  c1 = connect_to_socket (false);
+
+  if (do_select_wait (s, SEL_IN | SEL_EXC) != SEL_IN)
+    failed ("expecting readability on passive socket");
+  if (do_select_nowait (s, SEL_IN | SEL_EXC) != SEL_IN)
+    failed ("expecting readability on passive socket");
+
+  addrlen = sizeof (ia);
+  c2 = accept (s, (struct sockaddr *) &ia, &addrlen);
+  close (s);
+  close (c1);
+  close (c2);
+}
+
+
+/* Test poll(2) for unconnected blocking sockets.  */
+
+static void
+test_accept_first (void)
+{
+#ifndef WIN32_NATIVE
+  int s = open_server_socket ();
+  struct sockaddr_in ia;
+  socklen_t addrlen;
+  char buf[3];
+  int c, pid;
+
+  pid = fork ();
+  if (pid < 0)
+    return;
+
+  if (pid == 0)
+    {
+      addrlen = sizeof (ia);
+      c = accept (s, (struct sockaddr *) &ia, &addrlen);
+      close (s);
+      write (c, "foo", 3);
+      read (c, buf, 3);
+      shutdown (c, SHUT_RD);
+      close (c);
+      exit (0);
+    }
+  else
+    {
+      close (s);
+      c = connect_to_socket (true);
+      if (do_select_nowait (c, SEL_OUT) != SEL_OUT)
+        failed ("cannot write after blocking connect");
+      write (c, "foo", 3);
+      wait (&pid);
+      if (do_select_wait (c, SEL_IN) != SEL_IN)
+        failed ("cannot read data left in the socket by closed process");
+      read (c, buf, 3);
+      write (c, "foo", 3);
+      close (c);
+    }
+#endif
+}
+
+
+/* Common code for pipes and connected sockets.  */
+
+static void
+test_pair (int rd, int wd)
+{
+  char buf[3];
+  if (do_select_wait (wd, SEL_IN | SEL_OUT | SEL_EXC) != SEL_OUT)
+    failed ("expecting writability before writing");
+  if (do_select_nowait (wd, SEL_IN | SEL_OUT | SEL_EXC) != SEL_OUT)
+    failed ("expecting writability before writing");
+
+  write (wd, "foo", 3);
+  if (do_select_wait (rd, SEL_IN) != SEL_IN)
+    failed ("expecting readability after writing");
+  if (do_select_nowait (rd, SEL_IN) != SEL_IN)
+    failed ("expecting readability after writing");
+
+  read (rd, buf, 3);
+}
+
+
+/* Test poll(2) on connected sockets.  */
+
+static void
+test_socket_pair (void)
+{
+  struct sockaddr_in ia;
+
+  socklen_t addrlen = sizeof (ia);
+  int s = open_server_socket ();
+  int c1 = connect_to_socket (false);
+  int c2 = accept (s, (struct sockaddr *) &ia, &addrlen);
+
+  close (s);
+
+  test_pair (c1, c2);
+  close (c1);
+  write (c2, "foo", 3);
+  close (c2);
+}
+
+
+/* Test poll(2) on pipes.  */
+
+static void
+test_pipe (void)
+{
+  int fd[2];
+
+  pipe (fd);
+  test_pair (fd[0], fd[1]);
+  close (fd[0]);
+  close (fd[1]);
+}
+
+
+/* Do them all.  */
 
 int
 main ()
 {
-  return 0;
+  int result;
+
+  gl_sockets_startup (SOCKETS_1_1);
+
+#ifdef INTERACTIVE
+  printf ("Please press Enter\n");
+  test (test_tty, "TTY");
+#endif
+
+  result = test (test_connect_first, "Unconnected socket test");
+  result += test (test_socket_pair, "Connected sockets test");
+  result += test (test_accept_first, "General socket test with fork");
+  result += test (test_pipe, "Pipe test");
+
+  exit (result);
 }