Merge remote branch 'upstream/master'
[mono.git] / mono / io-layer / sockets.c
index a1f62bf1f735af61265232f2690c94f20d2c208c..c068f06281d7794ba5f88e7058b10a59a00883cc 100644 (file)
@@ -8,13 +8,21 @@
  */
 
 #include <config.h>
+
+#ifndef DISABLE_SOCKETS
+
 #include <glib.h>
 #include <pthread.h>
 #include <errno.h>
 #include <string.h>
 #include <sys/types.h>
 #include <sys/socket.h>
-#include <sys/ioctl.h>
+#ifdef HAVE_SYS_UIO_H
+#  include <sys/uio.h>
+#endif
+#ifdef HAVE_SYS_IOCTL_H
+#  include <sys/ioctl.h>
+#endif
 #ifdef HAVE_SYS_FILIO_H
 #include <sys/filio.h>     /* defines FIONBIO and FIONREAD */
 #endif
 #include <mono/io-layer/socket-private.h>
 #include <mono/io-layer/handles-private.h>
 #include <mono/io-layer/socket-wrappers.h>
+#include <mono/utils/mono-poll.h>
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#ifdef HAVE_SYS_SENDFILE_H
+#include <sys/sendfile.h>
+#endif
 
 #undef DEBUG
 
 static guint32 startup_count=0;
-static pthread_key_t error_key;
-static mono_once_t error_key_once=MONO_ONCE_INIT;
 
 static void socket_close (gpointer handle, gpointer data);
 
@@ -58,9 +73,10 @@ static void socket_ops_init (void)
        /* No capabilities to register */
 }
 
-static void socket_close (gpointer handle, gpointer data G_GNUC_UNUSED)
+static void socket_close (gpointer handle, gpointer data)
 {
        int ret;
+       struct _WapiHandle_socket *socket_handle = (struct _WapiHandle_socket *)data;
 
 #ifdef DEBUG
        g_message ("%s: closing socket handle %p", __func__, handle);
@@ -71,6 +87,11 @@ static void socket_close (gpointer handle, gpointer data G_GNUC_UNUSED)
                return;
        }
 
+       /* 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 &&
@@ -84,6 +105,8 @@ static void socket_close (gpointer handle, gpointer data G_GNUC_UNUSED)
                errnum = errno_to_WSA (errnum, __func__);
                WSASetLastError (errnum);
        }
+
+       socket_handle->saved_error = 0;
 }
 
 int WSAStartup(guint32 requested, WapiWSAData *data)
@@ -137,33 +160,14 @@ int WSACleanup(void)
        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)
@@ -183,18 +187,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 &&
@@ -224,7 +245,13 @@ 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);
@@ -272,7 +299,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) {
@@ -285,26 +313,100 @@ 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) {
+       if (connect (fd, serv_addr, addrlen) == -1) {
+               mono_pollfd fds;
+               int so_error;
+               socklen_t len;
+               
                errnum = errno;
                
+               if (errno != EINTR) {
 #ifdef DEBUG
-               g_message ("%s: connect error: %s", __func__,
-                          strerror (errnum));
+                       g_message ("%s: connect error: %s", __func__,
+                                  strerror (errnum));
 #endif
-               errnum = errno_to_WSA (errnum, __func__);
-               if (errnum == WSAEINPROGRESS)
-                       errnum = WSAEWOULDBLOCK; /* see bug #73053 */
 
-               WSASetLastError (errnum);
+                       errnum = errno_to_WSA (errnum, __func__);
+                       if (errnum == WSAEINPROGRESS)
+                               errnum = WSAEWOULDBLOCK; /* see bug #73053 */
+
+                       WSASetLastError (errnum);
+
+                       /* 
+                        * On solaris x86 getsockopt (SO_ERROR) is not set after 
+                        * connect () fails so we need to save this error.
+                        *
+                        * But don't do this for EWOULDBLOCK (bug 317315)
+                        */
+                       if (errnum != WSAEWOULDBLOCK) {
+                               ok = _wapi_lookup_handle (handle,
+                                                         WAPI_HANDLE_SOCKET,
+                                                         (gpointer *)&socket_handle);
+                               if (ok == FALSE) {
+                                       /* ECONNRESET means the socket was closed by another thread */
+                                       if (errnum != WSAECONNRESET)
+                                               g_warning ("%s: error looking up socket handle %p (error %d)", __func__, handle, errnum);
+                               } else {
+                                       socket_handle->saved_error = errnum;
+                               }
+                       }
+                       return(SOCKET_ERROR);
+               }
+
+               fds.fd = fd;
+               fds.events = POLLOUT;
+               while (mono_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 poll error: %s",
+                                          __func__, strerror (errno));
+#endif
+
+                               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)
@@ -378,7 +480,9 @@ int _wapi_getsockopt(guint32 fd, int level, int optname, void *optval,
        int ret;
        struct timeval tv;
        void *tmp_val;
-
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
+       
        if (startup_count == 0) {
                WSASetLastError (WSANOTINITIALISED);
                return(SOCKET_ERROR);
@@ -390,7 +494,8 @@ int _wapi_getsockopt(guint32 fd, int level, int optname, void *optval,
        }
 
        tmp_val = optval;
-       if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) {
+       if (level == SOL_SOCKET &&
+           (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO)) {
                tmp_val = &tv;
                *optlen = sizeof (tv);
        }
@@ -409,15 +514,30 @@ int _wapi_getsockopt(guint32 fd, int level, int optname, void *optval,
                return(SOCKET_ERROR);
        }
 
-       if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) {
+       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) {
-               if (*((int *)optval) != 0) {
+               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;
+                       }
                }
        }
        
@@ -464,6 +584,8 @@ int _wapi_recvfrom(guint32 fd, void *buf, size_t len, int recv_flags,
                   struct sockaddr *from, socklen_t *fromlen)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
        int ret;
        
        if (startup_count == 0) {
@@ -481,6 +603,33 @@ int _wapi_recvfrom(guint32 fd, void *buf, size_t len, int recv_flags,
        } while (ret == -1 && errno == EINTR &&
                 !_wapi_thread_cur_apc_pending ());
 
+       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
@@ -495,6 +644,53 @@ int _wapi_recvfrom(guint32 fd, void *buf, size_t len, int recv_flags,
        return(ret);
 }
 
+static int
+_wapi_recvmsg(guint32 fd, struct msghdr *msg, int recv_flags)
+{
+       gpointer handle = GUINT_TO_POINTER (fd);
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
+       int ret;
+       
+       if (startup_count == 0) {
+               WSASetLastError (WSANOTINITIALISED);
+               return(SOCKET_ERROR);
+       }
+       
+       if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
+               WSASetLastError (WSAENOTSOCK);
+               return(SOCKET_ERROR);
+       }
+       
+       do {
+               ret = recvmsg (fd, msg, recv_flags);
+       } while (ret == -1 && errno == EINTR &&
+                !_wapi_thread_cur_apc_pending ());
+
+       if (ret == 0) {
+               /* see _wapi_recvfrom */
+               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
+               g_message ("%s: recvmsg error: %s", __func__, strerror(errno));
+#endif
+
+               errnum = errno_to_WSA (errnum, __func__);
+               WSASetLastError (errnum);
+               
+               return(SOCKET_ERROR);
+       }
+       return(ret);
+}
+
 int _wapi_send(guint32 fd, const void *msg, size_t len, int send_flags)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
@@ -521,6 +717,15 @@ int _wapi_send(guint32 fd, const void *msg, size_t len, int send_flags)
                g_message ("%s: send error: %s", __func__, strerror (errno));
 #endif
 
+#ifdef O_NONBLOCK
+               /* At least linux returns EAGAIN/EWOULDBLOCK when the timeout has been set on
+                * a blocking socket. See bug #599488 */
+               if (errnum == EAGAIN) {
+                       ret = fcntl (fd, F_GETFL, 0);
+                       if (ret != -1 && (ret & O_NONBLOCK) == 0)
+                               errnum = ETIMEDOUT;
+               }
+#endif /* O_NONBLOCK */
                errnum = errno_to_WSA (errnum, __func__);
                WSASetLastError (errnum);
                
@@ -564,6 +769,41 @@ int _wapi_sendto(guint32 fd, const void *msg, size_t len, int send_flags,
        return(ret);
 }
 
+static int
+_wapi_sendmsg(guint32 fd,  const struct msghdr *msg, int send_flags)
+{
+       gpointer handle = GUINT_TO_POINTER (fd);
+       int ret;
+       
+       if (startup_count == 0) {
+               WSASetLastError (WSANOTINITIALISED);
+               return(SOCKET_ERROR);
+       }
+       
+       if (_wapi_handle_type (handle) != WAPI_HANDLE_SOCKET) {
+               WSASetLastError (WSAENOTSOCK);
+               return(SOCKET_ERROR);
+       }
+       
+       do {
+               ret = sendmsg (fd, msg, send_flags);
+       } while (ret == -1 && errno == EINTR &&
+                !_wapi_thread_cur_apc_pending ());
+
+       if (ret == -1) {
+               gint errnum = errno;
+#ifdef DEBUG
+               g_message ("%s: sendmsg error: %s", __func__, strerror (errno));
+#endif
+
+               errnum = errno_to_WSA (errnum, __func__);
+               WSASetLastError (errnum);
+               
+               return(SOCKET_ERROR);
+       }
+       return(ret);
+}
+
 int _wapi_setsockopt(guint32 fd, int level, int optname,
                     const void *optval, socklen_t optlen)
 {
@@ -583,14 +823,16 @@ int _wapi_setsockopt(guint32 fd, int level, int optname,
        }
 
        tmp_val = optval;
-       if (optname == SO_RCVTIMEO || optname == SO_SNDTIMEO) {
+       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 (optname == SO_SNDBUF || optname == SO_RCVBUF) {
+       } 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."
@@ -615,12 +857,27 @@ int _wapi_setsockopt(guint32 fd, int level, int optname,
                
                return(SOCKET_ERROR);
        }
+
+#if defined (SO_REUSEPORT)
+       /* BSD's and MacOS X multicast sockets also need SO_REUSEPORT when SO_REUSEADDR is requested.  */
+       if (level == SOL_SOCKET && optname == SO_REUSEADDR) {
+               int type;
+               socklen_t type_len = sizeof (type);
+
+               if (!getsockopt (fd, level, SO_TYPE, &type, &type_len)) {
+                       if (type == SOCK_DGRAM)
+                               setsockopt (fd, level, SO_REUSEPORT, tmp_val, optlen);
+               }
+       }
+#endif
        
        return(ret);
 }
 
 int _wapi_shutdown(guint32 fd, int how)
 {
+       struct _WapiHandle_socket *socket_handle;
+       gboolean ok;
        gpointer handle = GUINT_TO_POINTER (fd);
        int ret;
        
@@ -633,6 +890,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) {
@@ -661,6 +932,7 @@ guint32 _wapi_socket(int domain, int type, int protocol, void *unused,
        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 &&
@@ -692,6 +964,40 @@ 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);
@@ -699,6 +1005,8 @@ guint32 _wapi_socket(int domain, int type, int protocol, void *unused,
        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);
        }
 
@@ -828,29 +1136,112 @@ static gboolean wapi_disconnectex (guint32 fd, WapiOverlapped *overlapped,
        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)
+#define SF_BUFFER_SIZE 16384
+static gint
+wapi_sendfile (guint32 socket, gpointer fd, guint32 bytes_to_write, guint32 bytes_per_send, guint32 flags)
 {
-#ifdef DEBUG
-       g_message ("%s: called on socket %d!", __func__, fd);
+#if defined(HAVE_SENDFILE) && (defined(__linux__) || defined(DARWIN))
+       gint file = GPOINTER_TO_INT (fd);
+       gint n;
+       gint errnum;
+       gssize res;
+       struct stat statbuf;
+
+       n = fstat (file, &statbuf);
+       if (n == -1) {
+               errnum = errno;
+               errnum = errno_to_WSA (errnum, __func__);
+               WSASetLastError (errnum);
+               return SOCKET_ERROR;
+       }
+       do {
+#ifdef __linux__
+               res = sendfile (socket, file, NULL, statbuf.st_size);
+#elif defined(DARWIN)
+               /* TODO: header/tail could be sent in the 5th argument */
+               /* TODO: Might not send the entire file for non-blocking sockets */
+               res = sendfile (file, socket, 0, &statbuf.st_size, NULL, 0);
 #endif
+       } while (res != -1 && (errno == EINTR || errno == EAGAIN) && !_wapi_thread_cur_apc_pending ());
+       if (res == -1) {
+               errnum = errno;
+               errnum = errno_to_WSA (errnum, __func__);
+               WSASetLastError (errnum);
+               return SOCKET_ERROR;
+       }
+#else
+       /* Default implementation */
+       gint file = GPOINTER_TO_INT (fd);
+       gchar *buffer;
+       gint n;
+
+       buffer = g_malloc (SF_BUFFER_SIZE);
+       do {
+               do {
+                       n = read (file, buffer, SF_BUFFER_SIZE);
+               } while (n == -1 && errno == EINTR && !_wapi_thread_cur_apc_pending ());
+               if (n == -1)
+                       break;
+               if (n == 0) {
+                       g_free (buffer);
+                       return 0; /* We're done reading */
+               }
+               do {
+                       n = send (socket, buffer, n, 0); /* short sends? enclose this in a loop? */
+               } while (n == -1 && errno == EINTR && !_wapi_thread_cur_apc_pending ());
+       } while (n != -1);
+
+       if (n == -1) {
+               gint errnum = errno;
+               errnum = errno_to_WSA (errnum, __func__);
+               WSASetLastError (errnum);
+               g_free (buffer);
+               return SOCKET_ERROR;
+       }
+       g_free (buffer);
+#endif
+       return 0;
+}
+
+gboolean
+TransmitFile (guint32 socket, gpointer file, guint32 bytes_to_write, guint32 bytes_per_send, WapiOverlapped *ol,
+               WapiTransmitFileBuffers *buffers, guint32 flags)
+{
+       gpointer sock = GUINT_TO_POINTER (socket);
+       gint ret;
        
-       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));
+       if (startup_count == 0) {
+               WSASetLastError (WSANOTINITIALISED);
+               return FALSE;
+       }
+       
+       if (_wapi_handle_type (sock) != WAPI_HANDLE_SOCKET) {
+               WSASetLastError (WSAENOTSOCK);
+               return FALSE;
+       }
 
-       return(socket_disconnect (fd));
+       /* Write the header */
+       if (buffers != NULL && buffers->Head != NULL && buffers->HeadLength > 0) {
+               ret = _wapi_send (socket, buffers->Head, buffers->HeadLength, 0);
+               if (ret == SOCKET_ERROR)
+                       return FALSE;
+       }
+
+       ret = wapi_sendfile (socket, file, bytes_to_write, bytes_per_send, flags);
+       if (ret == SOCKET_ERROR)
+               return FALSE;
+
+       /* Write the tail */
+       if (buffers != NULL && buffers->Tail != NULL && buffers->TailLength > 0) {
+               ret = _wapi_send (socket, buffers->Tail, buffers->TailLength, 0);
+               if (ret == SOCKET_ERROR)
+                       return FALSE;
+       }
+
+       if ((flags & TF_DISCONNECT) == TF_DISCONNECT)
+               closesocket (socket);
+
+       return TRUE;
 }
 
 static struct 
@@ -859,7 +1250,7 @@ static struct
        gpointer func;
 } extension_functions[] = {
        {WSAID_DISCONNECTEX, wapi_disconnectex},
-       {WSAID_TRANSMITFILE, wapi_transmitfile},
+       {WSAID_TRANSMITFILE, TransmitFile},
        {{0}, NULL},
 };
 
@@ -963,6 +1354,7 @@ WSAIoctl (guint32 fd, gint32 command,
        return(0);
 }
 
+#ifndef PLATFORM_PORT_PROVIDES_IOCTLSOCKET
 int ioctlsocket(guint32 fd, gint32 command, gpointer arg)
 {
        gpointer handle = GUINT_TO_POINTER (fd);
@@ -996,10 +1388,32 @@ int ioctlsocket(guint32 fd, gint32 command, gpointer arg)
                        }
                        break;
 #endif /* O_NONBLOCK */
-               case FIONREAD:
+                       /* Unused in Mono */
                case SIOCATMARK:
                        ret = ioctl (fd, command, arg);
                        break;
+                       
+               case FIONREAD:
+               {
+#if defined (PLATFORM_MACOSX)
+                       
+                       // ioctl (fd, FIONREAD, XXX) returns the size of
+                       // the UDP header as well on
+                       // Darwin.
+                       //
+                       // Use getsockopt SO_NREAD instead to get the
+                       // right values for TCP and UDP.
+                       // 
+                       // ai_canonname can be null in some cases on darwin, where the runtime assumes it will
+                       // be the value of the ip buffer.
+
+                       socklen_t optlen = sizeof (int);
+                       ret = getsockopt (fd, SOL_SOCKET, SO_NREAD, arg, &optlen);
+#else
+                       ret = ioctl (fd, command, arg);
+#endif
+                       break;
+               }
                default:
                        WSASetLastError (WSAEINVAL);
                        return(SOCKET_ERROR);
@@ -1113,4 +1527,71 @@ void _wapi_FD_SET(guint32 fd, fd_set *set)
 
        FD_SET (fd, set);
 }
+#endif
+
+static void
+wsabuf_to_msghdr (WapiWSABuf *buffers, guint32 count, struct msghdr *hdr)
+{
+       guint32 i;
+
+       memset (hdr, 0, sizeof (struct msghdr));
+       hdr->msg_iovlen = count;
+       hdr->msg_iov = g_new0 (struct iovec, count);
+       for (i = 0; i < count; i++) {
+               hdr->msg_iov [i].iov_base = buffers [i].buf;
+               hdr->msg_iov [i].iov_len  = buffers [i].len;
+       }
+}
+
+static void
+msghdr_iov_free (struct msghdr *hdr)
+{
+       g_free (hdr->msg_iov);
+}
+
+int WSARecv (guint32 fd, WapiWSABuf *buffers, guint32 count, guint32 *received,
+            guint32 *flags, WapiOverlapped *overlapped,
+            WapiOverlappedCB *complete)
+{
+       int ret;
+       struct msghdr hdr;
+
+       g_assert (overlapped == NULL);
+       g_assert (complete == NULL);
+
+       wsabuf_to_msghdr (buffers, count, &hdr);
+       ret = _wapi_recvmsg (fd, &hdr, *flags);
+       msghdr_iov_free (&hdr);
+       
+       if(ret == SOCKET_ERROR) {
+               return(ret);
+       }
+       
+       *received = ret;
+       *flags = hdr.msg_flags;
+
+       return(0);
+}
+
+int WSASend (guint32 fd, WapiWSABuf *buffers, guint32 count, guint32 *sent,
+            guint32 flags, WapiOverlapped *overlapped,
+            WapiOverlappedCB *complete)
+{
+       int ret;
+       struct msghdr hdr;
+
+       g_assert (overlapped == NULL);
+       g_assert (complete == NULL);
+
+       wsabuf_to_msghdr (buffers, count, &hdr);
+       ret = _wapi_sendmsg (fd, &hdr, flags);
+       msghdr_iov_free (&hdr);
+       
+       if(ret == SOCKET_ERROR) 
+               return ret;
+
+       *sent = ret;
+       return 0;
+}
 
+#endif /* ifndef DISABLE_SOCKETS */