2007-09-02 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mono / io-layer / sockets.c
index 9afc50b529041c92b16acdea72024b23e884f605..33a59810354feacc03655eee88e394c44a6c4f7a 100644 (file)
@@ -15,6 +15,7 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <sys/ioctl.h>
+#include <sys/poll.h>
 #ifdef HAVE_SYS_FILIO_H
 #include <sys/filio.h>     /* defines FIONBIO and FIONREAD */
 #endif
 #include <signal.h>
 #endif
 
-#ifndef PLATFORM_WIN32
-#ifdef HAVE_AIO_H
-#include <aio.h>
-#define USE_AIO        1
-#elif defined(HAVE_SYS_AIO_H)
-#include <sys/aio.h>
-#define USE_AIO 1
-#else
-#undef USE_AIO
-#endif
-#endif
-
 #include <mono/io-layer/wapi.h>
 #include <mono/io-layer/wapi-private.h>
 #include <mono/io-layer/socket-private.h>
 #undef DEBUG
 
 static guint32 startup_count=0;
-static GPtrArray *sockets=NULL;
-static pthread_key_t error_key;
-static mono_once_t error_key_once=MONO_ONCE_INIT;
 
-static void socket_close (gpointer handle);
+static void socket_close (gpointer handle, gpointer data);
 
 struct _WapiHandleOps _wapi_socket_ops = {
        socket_close,           /* close */
        NULL,                   /* signal */
        NULL,                   /* own */
        NULL,                   /* is_owned */
+       NULL,                   /* special_wait */
+       NULL                    /* prewait */
 };
 
 static mono_once_t socket_ops_once=MONO_ONCE_INIT;
@@ -69,7 +57,7 @@ static void socket_ops_init (void)
        /* No capabilities to register */
 }
 
-static void socket_close (gpointer handle)
+static void socket_close (gpointer handle, gpointer data G_GNUC_UNUSED)
 {
        int ret;
 
@@ -82,8 +70,11 @@ static void socket_close (gpointer handle)
                return;
        }
 
-       g_ptr_array_remove_fast (sockets, handle);
-
+       /* Shutdown the socket for reading, to interrupt any potential
+        * receives that may be blocking for data.  See bug 75705.
+        */
+       shutdown (GPOINTER_TO_UINT (handle), SHUT_RD);
+       
        do {
                ret = close (GPOINTER_TO_UINT(handle));
        } while (ret == -1 && errno == EINTR &&
@@ -110,14 +101,10 @@ int WSAStartup(guint32 requested, WapiWSAData *data)
                return(WSAVERNOTSUPPORTED);
        }
 
-       if (startup_count == 0) {
-               sockets = g_ptr_array_new();
-       }
-       
        startup_count++;
 
        /* I've no idea what is the minor version of the spec I read */
-       data->wHighVersion = MAKEWORD(2,0);
+       data->wHighVersion = MAKEWORD(2,2);
        
        data->wVersion = requested < data->wHighVersion? requested:
                data->wHighVersion;
@@ -132,10 +119,15 @@ int WSAStartup(guint32 requested, WapiWSAData *data)
        return(0);
 }
 
+static gboolean
+cleanup_close (gpointer handle, gpointer data)
+{
+       _wapi_handle_ops_close (handle, NULL);
+       return TRUE;
+}
+
 int WSACleanup(void)
 {
-       guint32 i;
-       
 #ifdef DEBUG
        g_message ("%s: cleaning up", __func__);
 #endif
@@ -144,48 +136,19 @@ int WSACleanup(void)
                /* Do nothing */
                return(0);
        }
-       
-       /* Close down all sockets */
-       for (i = 0; i < sockets->len; i++) {
-               gpointer handle;
-
-               handle = g_ptr_array_index (sockets, i);
-               _wapi_handle_ops_close (handle);
-       }
 
-       g_ptr_array_free (sockets, FALSE);
-       sockets = NULL;
-       
+       _wapi_handle_foreach (WAPI_HANDLE_SOCKET, cleanup_close, NULL);
        return(0);
 }
 
-static void error_init(void)
-{
-       int ret;
-       
-       ret = pthread_key_create (&error_key, NULL);
-       g_assert (ret == 0);
-}
-
 void WSASetLastError(int error)
 {
-       int ret;
-       
-       mono_once (&error_key_once, error_init);
-       ret = pthread_setspecific (error_key, GINT_TO_POINTER(error));
-       g_assert (ret == 0);
+       SetLastError (error);
 }
 
 int WSAGetLastError(void)
 {
-       int err;
-       void *errptr;
-       
-       mono_once (&error_key_once, error_init);
-       errptr = pthread_getspecific (error_key);
-       err = GPOINTER_TO_INT(errptr);
-       
-       return(err);
+       return(GetLastError ());
 }
 
 int closesocket(guint32 fd)
@@ -205,18 +168,35 @@ guint32 _wapi_accept(guint32 fd, struct sockaddr *addr, socklen_t *addrlen)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
        gpointer new_handle;
+       struct _WapiHandle_socket *socket_handle;
+       struct _WapiHandle_socket new_socket_handle = {0};
+       gboolean ok;
        int new_fd;
        
        if (startup_count == 0) {
                WSASetLastError (WSANOTINITIALISED);
                return(INVALID_SOCKET);
        }
+
+       if (addr != NULL && *addrlen < sizeof(struct sockaddr)) {
+               WSASetLastError (WSAEFAULT);
+               return(INVALID_SOCKET);
+       }
        
        if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
                WSASetLastError (WSAENOTSOCK);
                return(INVALID_SOCKET);
        }
        
+       ok = _wapi_lookup_handle (handle, WAPI_HANDLE_SOCKET,
+                                 (gpointer *)&socket_handle);
+       if (ok == FALSE) {
+               g_warning ("%s: error looking up socket handle %p",
+                          __func__, handle);
+               WSASetLastError (WSAENOTSOCK);
+               return(INVALID_SOCKET);
+       }
+       
        do {
                new_fd = accept (fd, addr, addrlen);
        } while (new_fd == -1 && errno == EINTR &&
@@ -246,15 +226,19 @@ guint32 _wapi_accept(guint32 fd, struct sockaddr *addr, socklen_t *addrlen)
                return(INVALID_SOCKET);
        }
 
-       new_handle = _wapi_handle_new_fd (WAPI_HANDLE_SOCKET, new_fd, NULL);
+       new_socket_handle.domain = socket_handle->domain;
+       new_socket_handle.type = socket_handle->type;
+       new_socket_handle.protocol = socket_handle->protocol;
+       new_socket_handle.still_readable = 1;
+
+       new_handle = _wapi_handle_new_fd (WAPI_HANDLE_SOCKET, new_fd,
+                                         &new_socket_handle);
        if(new_handle == _WAPI_HANDLE_INVALID) {
                g_warning ("%s: error creating socket handle", __func__);
                WSASetLastError (ERROR_GEN_FAILURE);
                return(INVALID_SOCKET);
        }
 
-       g_ptr_array_add (sockets, new_handle);
-       
 #ifdef DEBUG
        g_message ("%s: returning newly accepted socket handle %p with",
                   __func__, new_handle);
@@ -296,7 +280,8 @@ int _wapi_connect(guint32 fd, const struct sockaddr *serv_addr,
                  socklen_t addrlen)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
-       int ret;
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
        gint errnum;
        
        if (startup_count == 0) {
@@ -309,41 +294,82 @@ int _wapi_connect(guint32 fd, const struct sockaddr *serv_addr,
                return(SOCKET_ERROR);
        }
        
-       do {
-               ret = connect (fd, serv_addr, addrlen);
-       } while (ret==-1 && errno==EINTR && !_wapi_thread_cur_apc_pending());
-
-       if (ret == -1 && errno == EACCES) {
-               /* Try setting SO_BROADCAST and connecting again, but
-                * keep the original errno
-                */
-               int true=1;
+       if (connect (fd, serv_addr, addrlen) == -1) {
+               struct pollfd fds;
+               int so_error;
+               socklen_t len;
                
                errnum = errno;
+               
+               if (errno != EINTR) {
+#ifdef DEBUG
+                       g_message ("%s: connect error: %s", __func__,
+                                  strerror (errnum));
+#endif
 
-               ret = setsockopt (fd, SOL_SOCKET, SO_BROADCAST, &true,
-                                 sizeof(true));
-               if (ret == 0) {
-                       do {
-                               ret = connect (fd, serv_addr, addrlen);
-                       } while (ret==-1 && errno==EINTR &&
-                                !_wapi_thread_cur_apc_pending());
+                       errnum = errno_to_WSA (errnum, __func__);
+                       if (errnum == WSAEINPROGRESS)
+                               errnum = WSAEWOULDBLOCK; /* see bug #73053 */
+
+                       WSASetLastError (errnum);
+               
+                       return(SOCKET_ERROR);
                }
-       } else if (ret == -1) {
-               errnum = errno;
-       }
-       
-       if (ret == -1) {
+
+               fds.fd = fd;
+               fds.events = POLLOUT;
+               while (poll (&fds, 1, -1) == -1 &&
+                      !_wapi_thread_cur_apc_pending ()) {
+                       if (errno != EINTR) {
+                               errnum = errno_to_WSA (errno, __func__);
+
 #ifdef DEBUG
-               g_message ("%s: connect error: %s", __func__,
-                          strerror (errnum));
+                               g_message ("%s: connect poll error: %s",
+                                          __func__, strerror (errno));
 #endif
-               errnum = errno_to_WSA (errnum, __func__);
-               WSASetLastError (errnum);
+
+                               WSASetLastError (errnum);
+                               return(SOCKET_ERROR);
+                       }
+               }
+
+               len = sizeof(so_error);
+               if (getsockopt (fd, SOL_SOCKET, SO_ERROR, &so_error,
+                               &len) == -1) {
+                       errnum = errno_to_WSA (errno, __func__);
+
+#ifdef DEBUG
+                       g_message ("%s: connect getsockopt error: %s",
+                                  __func__, strerror (errno));
+#endif
+
+                       WSASetLastError (errnum);
+                       return(SOCKET_ERROR);
+               }
                
-               return(SOCKET_ERROR);
+               if (so_error != 0) {
+                       errnum = errno_to_WSA (so_error, __func__);
+
+                       /* Need to save this socket error */
+                       ok = _wapi_lookup_handle (handle, WAPI_HANDLE_SOCKET,
+                                                 (gpointer *)&socket_handle);
+                       if (ok == FALSE) {
+                               g_warning ("%s: error looking up socket handle %p", __func__, handle);
+                       } else {
+                               socket_handle->saved_error = errnum;
+                       }
+                       
+#ifdef DEBUG
+                       g_message ("%s: connect getsockopt returned error: %s",
+                                  __func__, strerror (so_error));
+#endif
+
+                       WSASetLastError (errnum);
+                       return(SOCKET_ERROR);
+               }
        }
-       return(ret);
+               
+       return(0);
 }
 
 int _wapi_getpeername(guint32 fd, struct sockaddr *name, socklen_t *namelen)
@@ -415,6 +441,10 @@ int _wapi_getsockopt(guint32 fd, int level, int optname, void *optval,
 {
        gpointer handle = GUINT_TO_POINTER (fd);
        int ret;
+       struct timeval tv;
+       void *tmp_val;
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
        
        if (startup_count == 0) {
                WSASetLastError (WSANOTINITIALISED);
@@ -425,8 +455,15 @@ int _wapi_getsockopt(guint32 fd, int level, int optname, void *optval,
                WSASetLastError (WSAENOTSOCK);
                return(SOCKET_ERROR);
        }
-       
-       ret = getsockopt (fd, level, optname, optval, optlen);
+
+       tmp_val = optval;
+       if (level == SOL_SOCKET &&
+           (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
+               tmp_val = &tv;
+               *optlen = sizeof (tv);
+       }
+
+       ret = getsockopt (fd, level, optname, tmp_val, optlen);
        if (ret == -1) {
                gint errnum = errno;
 #ifdef DEBUG
@@ -439,6 +476,33 @@ int _wapi_getsockopt(guint32 fd, int level, int optname, void *optval,
                
                return(SOCKET_ERROR);
        }
+
+       if (level == SOL_SOCKET &&
+           (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
+               *((int *) optval)  = tv.tv_sec * 1000 + (tv.tv_usec / 1000);    // milli from micro
+               *optlen = sizeof (int);
+       }
+
+       if (optname == SO_ERROR) {
+               ok = _wapi_lookup_handle (handle, WAPI_HANDLE_SOCKET,
+                                         (gpointer *)&socket_handle);
+               if (ok == FALSE) {
+                       g_warning ("%s: error looking up socket handle %p",
+                                  __func__, handle);
+
+                       /* can't extract the last error */
+                       *((int *) optval) = errno_to_WSA (*((int *)optval),
+                                                         __func__);
+               } else {
+                       if (*((int *)optval) != 0) {
+                               *((int *) optval) = errno_to_WSA (*((int *)optval),
+                                                                 __func__);
+                               socket_handle->saved_error = *((int *)optval);
+                       } else {
+                               *((int *)optval) = socket_handle->saved_error;
+                       }
+               }
+       }
        
        return(ret);
 }
@@ -482,10 +546,9 @@ int _wapi_recv(guint32 fd, void *buf, size_t len, int recv_flags)
 int _wapi_recvfrom(guint32 fd, void *buf, size_t len, int recv_flags,
                   struct sockaddr *from, socklen_t *fromlen)
 {
-#ifndef HAVE_MSG_NOSIGNAL
-       void (*old_sigpipe)(int);       // old SIGPIPE handler
-#endif
        gpointer handle = GUINT_TO_POINTER (fd);
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
        int ret;
        
        if (startup_count == 0) {
@@ -498,21 +561,38 @@ int _wapi_recvfrom(guint32 fd, void *buf, size_t len, int recv_flags,
                return(SOCKET_ERROR);
        }
        
-#ifdef HAVE_MSG_NOSIGNAL
-       do {
-               ret = recvfrom (fd, buf, len, recv_flags | MSG_NOSIGNAL, from,
-                               fromlen);
-       } while (ret == -1 && errno == EINTR &&
-                !_wapi_thread_cur_apc_pending ());
-#else
-       old_sigpipe = signal (SIGPIPE, SIG_IGN);
        do {
                ret = recvfrom (fd, buf, len, recv_flags, from, fromlen);
        } while (ret == -1 && errno == EINTR &&
                 !_wapi_thread_cur_apc_pending ());
-       signal (SIGPIPE, old_sigpipe);
-#endif
 
+       if (ret == 0 && len > 0) {
+               /* According to the Linux man page, recvfrom only
+                * returns 0 when the socket has been shut down
+                * cleanly.  Turn this into an EINTR to simulate win32
+                * behaviour of returning EINTR when a socket is
+                * closed while the recvfrom is blocking (we use a
+                * shutdown() in socket_close() to trigger this.) See
+                * bug 75705.
+                */
+               /* Distinguish between the socket being shut down at
+                * the local or remote ends, and reads that request 0
+                * bytes to be read
+                */
+
+               /* If this returns FALSE, it means the socket has been
+                * closed locally.  If it returns TRUE, but
+                * still_readable != 1 then shutdown
+                * (SHUT_RD|SHUT_RDWR) has been called locally.
+                */
+               ok = _wapi_lookup_handle (handle, WAPI_HANDLE_SOCKET,
+                                         (gpointer *)&socket_handle);
+               if (ok == FALSE || socket_handle->still_readable != 1) {
+                       ret = -1;
+                       errno = EINTR;
+               }
+       }
+       
        if (ret == -1) {
                gint errnum = errno;
 #ifdef DEBUG
@@ -529,9 +609,6 @@ int _wapi_recvfrom(guint32 fd, void *buf, size_t len, int recv_flags,
 
 int _wapi_send(guint32 fd, const void *msg, size_t len, int send_flags)
 {
-#ifndef HAVE_MSG_NOSIGNAL
-       void (*old_sigpipe)(int);       // old SIGPIPE handler
-#endif
        gpointer handle = GUINT_TO_POINTER (fd);
        int ret;
        
@@ -545,19 +622,11 @@ int _wapi_send(guint32 fd, const void *msg, size_t len, int send_flags)
                return(SOCKET_ERROR);
        }
 
-#ifdef HAVE_MSG_NOSIGNAL
-       do {
-               ret = send (fd, msg, len, send_flags | MSG_NOSIGNAL);
-       } while (ret == -1 && errno == EINTR &&
-                !_wapi_thread_cur_apc_pending ());
-#else
-       old_sigpipe = signal (SIGPIPE, SIG_IGN);
        do {
                ret = send (fd, msg, len, send_flags);
        } while (ret == -1 && errno == EINTR &&
                 !_wapi_thread_cur_apc_pending ());
-       signal (SIGPIPE, old_sigpipe);
-#endif
+
        if (ret == -1) {
                gint errnum = errno;
 #ifdef DEBUG
@@ -575,9 +644,6 @@ int _wapi_send(guint32 fd, const void *msg, size_t len, int send_flags)
 int _wapi_sendto(guint32 fd, const void *msg, size_t len, int send_flags,
                 const struct sockaddr *to, socklen_t tolen)
 {
-#ifndef HAVE_MSG_NOSIGNAL
-       void (*old_sigpipe)(int);       // old SIGPIPE handler
-#endif
        gpointer handle = GUINT_TO_POINTER (fd);
        int ret;
        
@@ -591,20 +657,11 @@ int _wapi_sendto(guint32 fd, const void *msg, size_t len, int send_flags,
                return(SOCKET_ERROR);
        }
        
-#ifdef HAVE_MSG_NOSIGNAL
-       do {
-               ret = sendto (fd, msg, len, send_flags | MSG_NOSIGNAL, to,
-                             tolen);
-       } while (ret == -1 && errno == EINTR &&
-                !_wapi_thread_cur_apc_pending ());
-#else
-       old_sigpipe = signal (SIGPIPE, SIG_IGN);
        do {
                ret = sendto (fd, msg, len, send_flags, to, tolen);
        } while (ret == -1 && errno == EINTR &&
                 !_wapi_thread_cur_apc_pending ());
-       signal (SIGPIPE, old_sigpipe);
-#endif
+
        if (ret == -1) {
                gint errnum = errno;
 #ifdef DEBUG
@@ -624,6 +681,8 @@ int _wapi_setsockopt(guint32 fd, int level, int optname,
 {
        gpointer handle = GUINT_TO_POINTER (fd);
        int ret;
+       const void *tmp_val;
+       struct timeval tv;
        
        if (startup_count == 0) {
                WSASetLastError (WSANOTINITIALISED);
@@ -634,8 +693,30 @@ int _wapi_setsockopt(guint32 fd, int level, int optname,
                WSASetLastError (WSAENOTSOCK);
                return(SOCKET_ERROR);
        }
-       
-       ret = setsockopt (fd, level, optname, optval, optlen);
+
+       tmp_val = optval;
+       if (level == SOL_SOCKET &&
+           (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
+               int ms = *((int *) optval);
+               tv.tv_sec = ms / 1000;
+               tv.tv_usec = (ms % 1000) * 1000;        // micro from milli
+               tmp_val = &tv;
+               optlen = sizeof (tv);
+#if defined (__linux__)
+       } else if (level == SOL_SOCKET &&
+                  (optname == SO_SNDBUF || optname == SO_RCVBUF)) {
+               /* According to socket(7) the Linux kernel doubles the
+                * buffer sizes "to allow space for bookkeeping
+                * overhead."
+                */
+               int bufsize = *((int *) optval);
+
+               bufsize /= 2;
+               tmp_val = &bufsize;
+#endif
+       }
+               
+       ret = setsockopt (fd, level, optname, tmp_val, optlen);
        if (ret == -1) {
                gint errnum = errno;
 #ifdef DEBUG
@@ -654,6 +735,8 @@ int _wapi_setsockopt(guint32 fd, int level, int optname,
 
 int _wapi_shutdown(guint32 fd, int how)
 {
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
        gpointer handle = GUINT_TO_POINTER (fd);
        int ret;
        
@@ -666,6 +749,20 @@ int _wapi_shutdown(guint32 fd, int how)
                WSASetLastError (WSAENOTSOCK);
                return(SOCKET_ERROR);
        }
+
+       if (how == SHUT_RD ||
+           how == SHUT_RDWR) {
+               ok = _wapi_lookup_handle (handle, WAPI_HANDLE_SOCKET,
+                                         (gpointer *)&socket_handle);
+               if (ok == FALSE) {
+                       g_warning ("%s: error looking up socket handle %p",
+                                  __func__, handle);
+                       WSASetLastError (WSAENOTSOCK);
+                       return(SOCKET_ERROR);
+               }
+               
+               socket_handle->still_readable = 0;
+       }
        
        ret = shutdown (fd, how);
        if (ret == -1) {
@@ -687,13 +784,20 @@ int _wapi_shutdown(guint32 fd, int how)
 guint32 _wapi_socket(int domain, int type, int protocol, void *unused,
                     guint32 unused2, guint32 unused3)
 {
+       struct _WapiHandle_socket socket_handle = {0};
        gpointer handle;
        int fd;
        
+       socket_handle.domain = domain;
+       socket_handle.type = type;
+       socket_handle.protocol = protocol;
+       socket_handle.still_readable = 1;
+       
        fd = socket (domain, type, protocol);
        if (fd == -1 && domain == AF_INET && type == SOCK_RAW &&
            protocol == 0) {
                /* Retry with protocol == 4 (see bug #54565) */
+               socket_handle.protocol = 4;
                fd = socket (AF_INET, SOCK_RAW, 4);
        }
        
@@ -710,7 +814,8 @@ guint32 _wapi_socket(int domain, int type, int protocol, void *unused,
 
        if (fd >= _wapi_fd_reserve) {
 #ifdef DEBUG
-               g_message ("%s: File descriptor is too big", __func__);
+               g_message ("%s: File descriptor is too big (%d >= %d)",
+                          __func__, fd, _wapi_fd_reserve);
 #endif
 
                WSASetLastError (WSASYSCALLFAILURE);
@@ -718,18 +823,52 @@ guint32 _wapi_socket(int domain, int type, int protocol, void *unused,
                
                return(INVALID_SOCKET);
        }
+
+       /* .net seems to set this by default for SOCK_STREAM, not for
+        * SOCK_DGRAM (see bug #36322)
+        *
+        * It seems winsock has a rather different idea of what
+        * SO_REUSEADDR means.  If it's set, then a new socket can be
+        * bound over an existing listening socket.  There's a new
+        * windows-specific option called SO_EXCLUSIVEADDRUSE but
+        * using that means the socket MUST be closed properly, or a
+        * denial of service can occur.  Luckily for us, winsock
+        * behaves as though any other system would when SO_REUSEADDR
+        * is true, so we don't need to do anything else here.  See
+        * bug 53992.
+        */
+       {
+               int ret, true = 1;
+       
+               ret = setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &true,
+                                 sizeof (true));
+               if (ret == -1) {
+                       int errnum = errno;
+
+#ifdef DEBUG
+                       g_message ("%s: Error setting SO_REUSEADDR", __func__);
+#endif
+                       
+                       errnum = errno_to_WSA (errnum, __func__);
+                       WSASetLastError (errnum);
+
+                       close (fd);
+
+                       return(INVALID_SOCKET);                 
+               }
+       }
        
        
        mono_once (&socket_ops_once, socket_ops_init);
        
-       handle = _wapi_handle_new_fd (WAPI_HANDLE_SOCKET, fd, NULL);
+       handle = _wapi_handle_new_fd (WAPI_HANDLE_SOCKET, fd, &socket_handle);
        if (handle == _WAPI_HANDLE_INVALID) {
                g_warning ("%s: error creating socket handle", __func__);
+               WSASetLastError (WSASYSCALLFAILURE);
+               close (fd);
                return(INVALID_SOCKET);
        }
 
-       g_ptr_array_add (sockets, handle);
-
 #ifdef DEBUG
        g_message ("%s: returning socket handle %p", __func__, handle);
 #endif
@@ -778,6 +917,119 @@ struct hostent *_wapi_gethostbyname(const char *hostname)
        return(he);
 }
 
+static gboolean socket_disconnect (guint32 fd)
+{
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
+       gpointer handle = GUINT_TO_POINTER (fd);
+       int newsock, ret;
+       
+       ok = _wapi_lookup_handle (handle, WAPI_HANDLE_SOCKET,
+                                 (gpointer *)&socket_handle);
+       if (ok == FALSE) {
+               g_warning ("%s: error looking up socket handle %p", __func__,
+                          handle);
+               WSASetLastError (WSAENOTSOCK);
+               return(FALSE);
+       }
+       
+       newsock = socket (socket_handle->domain, socket_handle->type,
+                         socket_handle->protocol);
+       if (newsock == -1) {
+               gint errnum = errno;
+
+#ifdef DEBUG
+               g_message ("%s: socket error: %s", __func__, strerror (errno));
+#endif
+
+               errnum = errno_to_WSA (errnum, __func__);
+               WSASetLastError (errnum);
+               
+               return(FALSE);
+       }
+
+       /* According to Stevens "Advanced Programming in the UNIX
+        * Environment: UNIX File I/O" dup2() is atomic so there
+        * should not be a race condition between the old fd being
+        * closed and the new socket fd being copied over
+        */
+       do {
+               ret = dup2 (newsock, fd);
+       } while (ret == -1 && errno == EAGAIN);
+       
+       if (ret == -1) {
+               gint errnum = errno;
+               
+#ifdef DEBUG
+               g_message ("%s: dup2 error: %s", __func__, strerror (errno));
+#endif
+
+               errnum = errno_to_WSA (errnum, __func__);
+               WSASetLastError (errnum);
+               
+               return(FALSE);
+       }
+
+       close (newsock);
+       
+       return(TRUE);
+}
+
+static gboolean wapi_disconnectex (guint32 fd, WapiOverlapped *overlapped,
+                                  guint32 flags, guint32 reserved)
+{
+#ifdef DEBUG
+       g_message ("%s: called on socket %d!", __func__, fd);
+#endif
+       
+       if (reserved != 0) {
+               WSASetLastError (WSAEINVAL);
+               return(FALSE);
+       }
+
+       /* We could check the socket type here and fail unless its
+        * SOCK_STREAM, SOCK_SEQPACKET or SOCK_RDM (according to msdn)
+        * if we really wanted to
+        */
+
+       return(socket_disconnect (fd));
+}
+
+/* NB only supports NULL file handle, NULL buffers and
+ * TF_DISCONNECT|TF_REUSE_SOCKET flags to disconnect the socket fd.
+ * Shouldn't actually ever need to be called anyway though, because we
+ * have DisconnectEx ().
+ */
+static gboolean wapi_transmitfile (guint32 fd, gpointer file,
+                                  guint32 num_write, guint32 num_per_send,
+                                  WapiOverlapped *overlapped,
+                                  WapiTransmitFileBuffers *buffers,
+                                  WapiTransmitFileFlags flags)
+{
+#ifdef DEBUG
+       g_message ("%s: called on socket %d!", __func__, fd);
+#endif
+       
+       g_assert (file == NULL);
+       g_assert (overlapped == NULL);
+       g_assert (buffers == NULL);
+       g_assert (num_write == 0);
+       g_assert (num_per_send == 0);
+       g_assert (flags == (TF_DISCONNECT | TF_REUSE_SOCKET));
+
+       return(socket_disconnect (fd));
+}
+
+static struct 
+{
+       WapiGuid guid;
+       gpointer func;
+} extension_functions[] = {
+       {WSAID_DISCONNECTEX, wapi_disconnectex},
+       {WSAID_TRANSMITFILE, wapi_transmitfile},
+       {{0}, NULL},
+};
+
 int
 WSAIoctl (guint32 fd, gint32 command,
          gchar *input, gint i_len,
@@ -798,6 +1050,46 @@ WSAIoctl (guint32 fd, gint32 command,
                return SOCKET_ERROR;
        }
 
+       if (command == SIO_GET_EXTENSION_FUNCTION_POINTER) {
+               int i = 0;
+               WapiGuid *guid = (WapiGuid *)input;
+               
+               if (i_len < sizeof(WapiGuid)) {
+                       /* As far as I can tell, windows doesn't
+                        * actually set an error here...
+                        */
+                       WSASetLastError (WSAEINVAL);
+                       return(SOCKET_ERROR);
+               }
+
+               if (o_len < sizeof(gpointer)) {
+                       /* Or here... */
+                       WSASetLastError (WSAEINVAL);
+                       return(SOCKET_ERROR);
+               }
+
+               if (output == NULL) {
+                       /* Or here */
+                       WSASetLastError (WSAEINVAL);
+                       return(SOCKET_ERROR);
+               }
+               
+               while(extension_functions[i].func != NULL) {
+                       if (!memcmp (guid, &extension_functions[i].guid,
+                                    sizeof(WapiGuid))) {
+                               memcpy (output, &extension_functions[i].func,
+                                       sizeof(gpointer));
+                               *written = sizeof(gpointer);
+                               return(0);
+                       }
+
+                       i++;
+               }
+               
+               WSASetLastError (WSAEINVAL);
+               return(SOCKET_ERROR);
+       }
+
        if (i_len > 0) {
                buffer = g_memdup (input, i_len);
        }
@@ -822,9 +1114,15 @@ WSAIoctl (guint32 fd, gint32 command,
        } else {
                /* We just copy the buffer to the output. Some ioctls
                 * don't even output any data, but, well...
+                *
+                * NB windows returns WSAEFAULT if o_len is too small
                 */
                i_len = (i_len > o_len) ? o_len : i_len;
-               memcpy (output, buffer, i_len);
+
+               if (i_len > 0 && output != NULL) {
+                       memcpy (output, buffer, i_len);
+               }
+               
                g_free (buffer);
                *written = i_len;
        }
@@ -847,35 +1145,33 @@ int ioctlsocket(guint32 fd, gint32 command, gpointer arg)
                return(SOCKET_ERROR);
        }
 
-       if (command != FIONBIO &&
-           command != FIONREAD &&
-           command != SIOCATMARK) {
-               /* Not listed in the MSDN specs, but ioctl(2) returns
-                * this if command is invalid
-                */
-               WSASetLastError (WSAEINVAL);
-               return(SOCKET_ERROR);
-       }
-
+       switch(command){
+               case FIONBIO:
 #ifdef O_NONBLOCK
-       /* This works better than ioctl(...FIONBIO...) on Linux (it causes
-        * connect to return EINPROGRESS, but the ioctl doesn't seem to)
-        */
-       if (command == FIONBIO) {
-               ret = fcntl (fd, F_GETFL, 0);
-               if (ret != -1) {
-                       if (*(gboolean *)arg) {
-                               ret &= ~O_NONBLOCK;
-                       } else {
-                               ret |= O_NONBLOCK;
+                       /* This works better than ioctl(...FIONBIO...) 
+                        * on Linux (it causes connect to return
+                        * EINPROGRESS, but the ioctl doesn't seem to)
+                        */
+                       ret = fcntl(fd, F_GETFL, 0);
+                       if (ret != -1) {
+                               if (*(gboolean *)arg) {
+                                       ret |= O_NONBLOCK;
+                               } else {
+                                       ret &= ~O_NONBLOCK;
+                               }
+                               ret = fcntl(fd, F_SETFL, ret);
                        }
-                       ret = fcntl (fd, F_SETFL, ret);
-               }
-       } else
+                       break;
 #endif /* O_NONBLOCK */
-       {
-               ret = ioctl (fd, command, arg);
+               case FIONREAD:
+               case SIOCATMARK:
+                       ret = ioctl (fd, command, arg);
+                       break;
+               default:
+                       WSASetLastError (WSAEINVAL);
+                       return(SOCKET_ERROR);
        }
+
        if (ret == -1) {
                gint errnum = errno;
 #ifdef DEBUG
@@ -894,15 +1190,28 @@ int ioctlsocket(guint32 fd, gint32 command, gpointer arg)
 int _wapi_select(int nfds G_GNUC_UNUSED, fd_set *readfds, fd_set *writefds,
                 fd_set *exceptfds, struct timeval *timeout)
 {
-       int ret;
+       int ret, maxfd;
        
        if (startup_count == 0) {
                WSASetLastError (WSANOTINITIALISED);
                return(SOCKET_ERROR);
        }
 
+       for (maxfd = FD_SETSIZE-1; maxfd >= 0; maxfd--) {
+               if ((readfds && FD_ISSET (maxfd, readfds)) ||
+                   (writefds && FD_ISSET (maxfd, writefds)) ||
+                   (exceptfds && FD_ISSET (maxfd, exceptfds))) {
+                       break;
+               }
+       }
+
+       if (maxfd == -1) {
+               WSASetLastError (WSAEINVAL);
+               return(SOCKET_ERROR);
+       }
+
        do {
-               ret = select(getdtablesize (), readfds, writefds, exceptfds,
+               ret = select(maxfd + 1, readfds, writefds, exceptfds,
                             timeout);
        } while (ret == -1 && errno == EINTR &&
                 !_wapi_thread_cur_apc_pending ());
@@ -925,6 +1234,11 @@ void _wapi_FD_CLR(guint32 fd, fd_set *set)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
        
+       if (fd >= FD_SETSIZE) {
+               WSASetLastError (WSAEINVAL);
+               return;
+       }
+       
        if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
                WSASetLastError (WSAENOTSOCK);
                return;
@@ -937,6 +1251,11 @@ int _wapi_FD_ISSET(guint32 fd, fd_set *set)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
        
+       if (fd >= FD_SETSIZE) {
+               WSASetLastError (WSAEINVAL);
+               return(0);
+       }
+       
        if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
                WSASetLastError (WSAENOTSOCK);
                return(0);
@@ -949,117 +1268,16 @@ void _wapi_FD_SET(guint32 fd, fd_set *set)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
        
-       if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
-               WSASetLastError (WSAENOTSOCK);
+       if (fd >= FD_SETSIZE) {
+               WSASetLastError (WSAEINVAL);
                return;
        }
 
-       FD_SET (fd, set);
-}
-
-#ifdef USE_AIO
-
-typedef struct {
-       struct aiocb *aio;
-       gpointer ares;
-       SocketAsyncCB callback;
-} notifier_data_t;
-
-#define SIGPTR(a) a.SIGVAL_PTR
-
-static void
-async_notifier (union sigval sig)
-{
-       notifier_data_t *ndata = SIGPTR (sig);
-       guint32 error;
-       guint32 numbytes;
-
-       error = aio_return (ndata->aio);
-       if (error < 0) {
-               error = _wapi_get_win32_file_error (error);
-               numbytes = 0;
-       } else {
-               numbytes = error;
-               error = 0;
-       }
-
-       ndata->callback (error, numbytes, ndata->ares);
-       g_free (ndata->aio);
-       g_free (ndata);
-}
-
-static gboolean
-do_aio_call (gboolean is_read, gpointer handle, gpointer buffer,
-               guint32 numbytes, guint32 *out_bytes,
-               gpointer ares,
-               SocketAsyncCB callback)
-{
-       int fd = GPOINTER_TO_UINT (handle);
-       struct aiocb *aio;
-       int result;
-       notifier_data_t *ndata;
-       
-       if (handle == NULL ||
-           _wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
+       if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
                WSASetLastError (WSAENOTSOCK);
-               return FALSE;
-       }
-
-       ndata = g_new0 (notifier_data_t, 1);
-       aio = g_new0 (struct aiocb, 1);
-       ndata->ares = ares;
-       ndata->aio = aio;
-       ndata->callback = callback;
-
-       aio->aio_fildes = fd;
-       aio->aio_lio_opcode = (is_read) ? LIO_READ : LIO_WRITE;
-       aio->aio_nbytes = numbytes;
-       aio->aio_offset = 0;
-       aio->aio_buf = buffer;
-       aio->aio_sigevent.sigev_notify = SIGEV_THREAD;
-       aio->aio_sigevent.sigev_notify_function = async_notifier;
-       SIGPTR (aio->aio_sigevent.sigev_value) = ndata;
-
-       if (is_read) {
-               result = aio_read (aio);
-       } else {
-               result = aio_write (aio);
-       }
-
-       if (result == -1) {
-               WSASetLastError (errno_to_WSA (errno, "do_aio_call"));
-               return FALSE;
-       }
-
-       result = aio_error (aio);
-       if (result == 0) {
-               numbytes = aio_return (aio);
-       } else {
-               WSASetLastError (errno_to_WSA (result, "do_aio_call"));
-               return FALSE;
+               return;
        }
 
-       if (out_bytes)
-               *out_bytes = numbytes;
-
-       return TRUE;
-}
-
-gboolean _wapi_socket_async_read (gpointer handle, gpointer buffer,
-                                 guint32 numbytes,
-                                 guint32 *bytesread, gpointer ares,
-                                 SocketAsyncCB callback)
-{
-       return do_aio_call (TRUE, handle, buffer, numbytes, bytesread, ares, callback);
-}
-
-gboolean _wapi_socket_async_write (gpointer handle, gpointer buffer,
-                                 guint32 numbytes,
-                                 guint32 *byteswritten, gpointer ares,
-                                 SocketAsyncCB callback)
-{
-       return do_aio_call (FALSE, handle, buffer, numbytes, byteswritten, ares, callback);
+       FD_SET (fd, set);
 }
 
-#endif /* USE_AIO */
-