blob: 4e67a2237dfc8f060146057041784d644cd35d75 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is the Netscape Portable Runtime (NSPR).
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998-2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/* Win95 Sockets module
*
*/
#include "primpl.h"
#define READ_FD 1
#define WRITE_FD 2
#define CONNECT_FD 3
static PRInt32 socket_io_wait(
PROsfd osfd,
PRInt32 fd_type,
PRIntervalTime timeout);
/* --- SOCKET IO --------------------------------------------------------- */
/*
* we only want to call WSAIoctl() on Vista and later
* so don't pay for it at build time (and avoid including winsock2.h)
*/
/* from ws2def.h */
#define IOC_IN 0x80000000 /* copy in parameters */
#define IOC_VENDOR 0x18000000
#define _WSAIOW(x,y) (IOC_IN|(x)|(y))
/* from MSWSockDef.h */
#define SIO_SET_COMPATIBILITY_MODE _WSAIOW(IOC_VENDOR,300)
typedef enum _WSA_COMPATIBILITY_BEHAVIOR_ID {
WsaBehaviorAll = 0,
WsaBehaviorReceiveBuffering,
WsaBehaviorAutoTuning
} WSA_COMPATIBILITY_BEHAVIOR_ID, *PWSA_COMPATIBILITY_BEHAVIOR_ID;
/* from sdkddkver.h */
#define NTDDI_WIN6 0x06000000 /* Windows Vista */
/* from winsock2.h */
#define WSAEVENT HANDLE
#define WSAOVERLAPPED OVERLAPPED
typedef struct _OVERLAPPED * LPWSAOVERLAPPED;
typedef void (CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(
IN DWORD dwError,
IN DWORD cbTransferred,
IN LPWSAOVERLAPPED lpOverlapped,
IN DWORD dwFlags
);
typedef int (__stdcall * WSAIOCTLPROC) (
SOCKET s,
DWORD dwIoControlCode,
LPVOID lpvInBuffer,
DWORD cbInBuffer,
LPVOID lpvOutBuffer,
DWORD cbOutBuffer,
LPDWORD lpcbBytesReturned,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
typedef struct _WSA_COMPATIBILITY_MODE {
WSA_COMPATIBILITY_BEHAVIOR_ID BehaviorId;
ULONG TargetOsVersion;
} WSA_COMPATIBILITY_MODE, *PWSA_COMPATIBILITY_MODE;
static HMODULE libWinsock2 = NULL;
static WSAIOCTLPROC wsaioctlProc = NULL;
static PRBool socketSetCompatMode = PR_FALSE;
void _PR_MD_InitSockets(void)
{
OSVERSIONINFO osvi;
memset(&osvi, 0, sizeof(osvi));
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionEx(&osvi);
/* if Vista or later... */
if (osvi.dwMajorVersion >= 6)
{
libWinsock2 = LoadLibraryW(L"Ws2_32.dll");
if (libWinsock2)
{
wsaioctlProc = (WSAIOCTLPROC)GetProcAddress(libWinsock2,
"WSAIoctl");
if (wsaioctlProc)
{
socketSetCompatMode = PR_TRUE;
}
}
}
}
void _PR_MD_CleanupSockets(void)
{
socketSetCompatMode = PR_FALSE;
wsaioctlProc = NULL;
if (libWinsock2)
{
FreeLibrary(libWinsock2);
libWinsock2 = NULL;
}
}
PROsfd
_PR_MD_SOCKET(int af, int type, int flags)
{
SOCKET sock;
u_long one = 1;
sock = socket(af, type, flags);
if (sock == INVALID_SOCKET )
{
_PR_MD_MAP_SOCKET_ERROR(WSAGetLastError());
return (PROsfd)sock;
}
/*
** Make the socket Non-Blocking
*/
if (ioctlsocket( sock, FIONBIO, &one) != 0)
{
PR_SetError(PR_UNKNOWN_ERROR, WSAGetLastError());
closesocket(sock);
return -1;
}
if ((af == AF_INET || af == AF_INET6) &&
type == SOCK_STREAM && socketSetCompatMode)
{
WSA_COMPATIBILITY_MODE mode;
char dummy[4];
int ret_dummy;
mode.BehaviorId = WsaBehaviorAutoTuning;
mode.TargetOsVersion = NTDDI_WIN6;
if (wsaioctlProc(sock, SIO_SET_COMPATIBILITY_MODE,
(char *)&mode, sizeof(mode),
dummy, 4, &ret_dummy, 0, NULL) == SOCKET_ERROR)
{
int err = WSAGetLastError();
PR_LOG(_pr_io_lm, PR_LOG_DEBUG, ("WSAIoctl() failed with %d", err));
/* SIO_SET_COMPATIBILITY_MODE may not be supported.
** If the call to WSAIoctl() fails with WSAEOPNOTSUPP,
** don't close the socket.
*/
}
}
return (PROsfd)sock;
}
/*
** _MD_CloseSocket() -- Close a socket
**
*/
PRInt32
_MD_CloseSocket(PROsfd osfd)
{
PRInt32 rv;
rv = closesocket((SOCKET) osfd );
if (rv < 0)
_PR_MD_MAP_CLOSE_ERROR(WSAGetLastError());
return rv;
}
PRInt32
_MD_SocketAvailable(PRFileDesc *fd)
{
PRInt32 result;
if (ioctlsocket(fd->secret->md.osfd, FIONREAD, &result) < 0) {
PR_SetError(PR_BAD_DESCRIPTOR_ERROR, WSAGetLastError());
return -1;
}
return result;
}
PROsfd _MD_Accept(
PRFileDesc *fd,
PRNetAddr *raddr,
PRUint32 *rlen,
PRIntervalTime timeout )
{
PROsfd osfd = fd->secret->md.osfd;
SOCKET sock;
PRInt32 rv, err;
while ((sock = accept(osfd, (struct sockaddr *) raddr, rlen)) == -1)
{
err = WSAGetLastError();
if ((err == WSAEWOULDBLOCK) && (!fd->secret->nonblocking))
{
if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0)
{
break;
}
}
else
{
_PR_MD_MAP_ACCEPT_ERROR(err);
break;
}
}
return(sock);
} /* end _MD_accept() */
PRInt32
_PR_MD_CONNECT(PRFileDesc *fd, const PRNetAddr *addr, PRUint32 addrlen,
PRIntervalTime timeout)
{
PROsfd osfd = fd->secret->md.osfd;
PRInt32 rv;
int err;
if ((rv = connect(osfd, (struct sockaddr *) addr, addrlen)) == -1)
{
err = WSAGetLastError();
if ((!fd->secret->nonblocking) && (err == WSAEWOULDBLOCK))
{
rv = socket_io_wait(osfd, CONNECT_FD, timeout);
if ( rv < 0 )
{
return(-1);
}
else
{
PR_ASSERT(rv > 0);
/* it's connected */
return(0);
}
}
_PR_MD_MAP_CONNECT_ERROR(err);
}
return rv;
}
PRInt32
_PR_MD_BIND(PRFileDesc *fd, const PRNetAddr *addr, PRUint32 addrlen)
{
PRInt32 rv;
rv = bind(fd->secret->md.osfd, (const struct sockaddr *)&(addr->inet), addrlen);
if (rv == SOCKET_ERROR) {
_PR_MD_MAP_BIND_ERROR(WSAGetLastError());
return -1;
}
return 0;
}
PRInt32
_PR_MD_LISTEN(PRFileDesc *fd, PRIntn backlog)
{
PRInt32 rv;
rv = listen(fd->secret->md.osfd, backlog);
if (rv == SOCKET_ERROR) {
_PR_MD_MAP_DEFAULT_ERROR(WSAGetLastError());
return -1;
}
return 0;
}
PRInt32
_PR_MD_RECV(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
PRIntervalTime timeout)
{
PROsfd osfd = fd->secret->md.osfd;
PRInt32 rv, err;
int osflags;
if (0 == flags) {
osflags = 0;
} else {
PR_ASSERT(PR_MSG_PEEK == flags);
osflags = MSG_PEEK;
}
while ((rv = recv( osfd, buf, amount, osflags)) == -1)
{
if (((err = WSAGetLastError()) == WSAEWOULDBLOCK)
&& (!fd->secret->nonblocking))
{
rv = socket_io_wait(osfd, READ_FD, timeout);
if ( rv < 0 )
{
return -1;
}
}
else
{
_PR_MD_MAP_RECV_ERROR(err);
break;
}
} /* end while() */
return(rv);
}
PRInt32
_PR_MD_SEND(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
PRIntervalTime timeout)
{
PROsfd osfd = fd->secret->md.osfd;
PRInt32 rv, err;
PRInt32 bytesSent = 0;
while(bytesSent < amount )
{
while ((rv = send( osfd, buf, amount, 0 )) == -1)
{
if (((err = WSAGetLastError()) == WSAEWOULDBLOCK)
&& (!fd->secret->nonblocking))
{
rv = socket_io_wait(osfd, WRITE_FD, timeout);
if ( rv < 0 )
{
return -1;
}
}
else
{
_PR_MD_MAP_SEND_ERROR(err);
return -1;
}
}
bytesSent += rv;
if (fd->secret->nonblocking)
{
break;
}
if (bytesSent < amount)
{
rv = socket_io_wait(osfd, WRITE_FD, timeout);
if ( rv < 0 )
{
return -1;
}
}
}
return bytesSent;
}
PRInt32
_PR_MD_SENDTO(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
const PRNetAddr *addr, PRUint32 addrlen, PRIntervalTime timeout)
{
PROsfd osfd = fd->secret->md.osfd;
PRInt32 rv, err;
PRInt32 bytesSent = 0;
while(bytesSent < amount)
{
while ((rv = sendto( osfd, buf, amount, 0, (struct sockaddr *) addr,
addrlen)) == -1)
{
if (((err = WSAGetLastError()) == WSAEWOULDBLOCK)
&& (!fd->secret->nonblocking))
{
rv = socket_io_wait(osfd, WRITE_FD, timeout);
if ( rv < 0 )
{
return -1;
}
}
else
{
_PR_MD_MAP_SENDTO_ERROR(err);
return -1;
}
}
bytesSent += rv;
if (fd->secret->nonblocking)
{
break;
}
if (bytesSent < amount)
{
rv = socket_io_wait(osfd, WRITE_FD, timeout);
if (rv < 0)
{
return -1;
}
}
}
return bytesSent;
}
PRInt32
_PR_MD_RECVFROM(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
PRNetAddr *addr, PRUint32 *addrlen, PRIntervalTime timeout)
{
PROsfd osfd = fd->secret->md.osfd;
PRInt32 rv, err;
while ((rv = recvfrom( osfd, buf, amount, 0, (struct sockaddr *) addr,
addrlen)) == -1)
{
if (((err = WSAGetLastError()) == WSAEWOULDBLOCK)
&& (!fd->secret->nonblocking))
{
rv = socket_io_wait(osfd, READ_FD, timeout);
if ( rv < 0)
{
return -1;
}
}
else
{
_PR_MD_MAP_RECVFROM_ERROR(err);
break;
}
}
return(rv);
}
PRInt32
_PR_MD_WRITEV(PRFileDesc *fd, const PRIOVec *iov, PRInt32 iov_size, PRIntervalTime timeout)
{
int index;
int sent = 0;
int rv;
for (index=0; index < iov_size; index++)
{
rv = _PR_MD_SEND(fd, iov[index].iov_base, iov[index].iov_len, 0, timeout);
if (rv > 0)
sent += rv;
if ( rv != iov[index].iov_len )
{
if (rv < 0)
{
if (fd->secret->nonblocking
&& (PR_GetError() == PR_WOULD_BLOCK_ERROR)
&& (sent > 0))
{
return sent;
}
else
{
return -1;
}
}
/* Only a nonblocking socket can have partial sends */
PR_ASSERT(fd->secret->nonblocking);
return sent;
}
}
return sent;
}
PRInt32
_PR_MD_SHUTDOWN(PRFileDesc *fd, PRIntn how)
{
PRInt32 rv;
rv = shutdown(fd->secret->md.osfd, how);
if (rv < 0)
_PR_MD_MAP_SHUTDOWN_ERROR(WSAGetLastError());
return rv;
}
PRStatus
_PR_MD_GETSOCKNAME(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *len)
{
PRInt32 rv;
rv = getsockname((SOCKET)fd->secret->md.osfd, (struct sockaddr *)addr, len);
if (rv==0) {
return PR_SUCCESS;
} else {
_PR_MD_MAP_GETSOCKNAME_ERROR(WSAGetLastError());
return PR_FAILURE;
}
}
PRStatus
_PR_MD_GETPEERNAME(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *len)
{
PRInt32 rv;
rv = getpeername((SOCKET)fd->secret->md.osfd, (struct sockaddr *)addr, len);
if (rv==0) {
return PR_SUCCESS;
} else {
_PR_MD_MAP_GETPEERNAME_ERROR(WSAGetLastError());
return PR_FAILURE;
}
}
PRStatus
_PR_MD_GETSOCKOPT(PRFileDesc *fd, PRInt32 level, PRInt32 optname, char* optval, PRInt32* optlen)
{
PRInt32 rv;
rv = getsockopt((SOCKET)fd->secret->md.osfd, level, optname, optval, optlen);
if (rv==0) {
return PR_SUCCESS;
} else {
_PR_MD_MAP_GETSOCKOPT_ERROR(WSAGetLastError());
return PR_FAILURE;
}
}
PRStatus
_PR_MD_SETSOCKOPT(PRFileDesc *fd, PRInt32 level, PRInt32 optname, const char* optval, PRInt32 optlen)
{
PRInt32 rv;
rv = setsockopt((SOCKET)fd->secret->md.osfd, level, optname, optval, optlen);
if (rv==0) {
return PR_SUCCESS;
} else {
_PR_MD_MAP_SETSOCKOPT_ERROR(WSAGetLastError());
return PR_FAILURE;
}
}
void
_MD_MakeNonblock(PRFileDesc *f)
{
return; /* do nothing */
}
/*
* socket_io_wait --
*
* Wait for socket i/o, periodically checking for interrupt.
*
* This function returns 1 on success. On failure, it returns
* -1 and sets the error codes. It never returns 0.
*/
#define _PR_INTERRUPT_CHECK_INTERVAL_SECS 5
static PRInt32 socket_io_wait(
PROsfd osfd,
PRInt32 fd_type,
PRIntervalTime timeout)
{
PRInt32 rv = -1;
struct timeval tv;
PRThread *me = _PR_MD_CURRENT_THREAD();
PRIntervalTime elapsed, remaining;
PRBool wait_for_remaining;
fd_set rd_wr, ex;
int err, len;
switch (timeout) {
case PR_INTERVAL_NO_WAIT:
PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
break;
case PR_INTERVAL_NO_TIMEOUT:
/*
* This is a special case of the 'default' case below.
* Please see the comments there.
*/
tv.tv_sec = _PR_INTERRUPT_CHECK_INTERVAL_SECS;
tv.tv_usec = 0;
FD_ZERO(&rd_wr);
FD_ZERO(&ex);
do {
FD_SET(osfd, &rd_wr);
FD_SET(osfd, &ex);
switch( fd_type )
{
case READ_FD:
rv = _MD_SELECT(0, &rd_wr, NULL, NULL, &tv);
break;
case WRITE_FD:
rv = _MD_SELECT(0, NULL, &rd_wr, NULL, &tv);
break;
case CONNECT_FD:
rv = _MD_SELECT(0, NULL, &rd_wr, &ex, &tv);
break;
default:
PR_ASSERT(0);
break;
} /* end switch() */
if (rv == -1 )
{
_PR_MD_MAP_SELECT_ERROR(WSAGetLastError());
break;
}
if ( rv > 0 && fd_type == CONNECT_FD )
{
/*
* Call Sleep(0) to work around a Winsock timing bug.
*/
Sleep(0);
if (FD_ISSET((SOCKET)osfd, &ex))
{
len = sizeof(err);
if (getsockopt(osfd, SOL_SOCKET, SO_ERROR,
(char *) &err, &len) == SOCKET_ERROR)
{
_PR_MD_MAP_GETSOCKOPT_ERROR(WSAGetLastError());
return -1;
}
if (err != 0)
_PR_MD_MAP_CONNECT_ERROR(err);
else
PR_SetError(PR_UNKNOWN_ERROR, 0);
return -1;
}
if (FD_ISSET((SOCKET)osfd, &rd_wr))
{
/* it's connected */
return 1;
}
PR_ASSERT(0);
}
if (_PR_PENDING_INTERRUPT(me)) {
me->flags &= ~_PR_INTERRUPT;
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
rv = -1;
break;
}
} while (rv == 0);
break;
default:
remaining = timeout;
FD_ZERO(&rd_wr);
FD_ZERO(&ex);
do {
/*
* We block in _MD_SELECT for at most
* _PR_INTERRUPT_CHECK_INTERVAL_SECS seconds,
* so that there is an upper limit on the delay
* before the interrupt bit is checked.
*/
wait_for_remaining = PR_TRUE;
tv.tv_sec = PR_IntervalToSeconds(remaining);
if (tv.tv_sec > _PR_INTERRUPT_CHECK_INTERVAL_SECS) {
wait_for_remaining = PR_FALSE;
tv.tv_sec = _PR_INTERRUPT_CHECK_INTERVAL_SECS;
tv.tv_usec = 0;
} else {
tv.tv_usec = PR_IntervalToMicroseconds(
remaining -
PR_SecondsToInterval(tv.tv_sec));
}
FD_SET(osfd, &rd_wr);
FD_SET(osfd, &ex);
switch( fd_type )
{
case READ_FD:
rv = _MD_SELECT(0, &rd_wr, NULL, NULL, &tv);
break;
case WRITE_FD:
rv = _MD_SELECT(0, NULL, &rd_wr, NULL, &tv);
break;
case CONNECT_FD:
rv = _MD_SELECT(0, NULL, &rd_wr, &ex, &tv);
break;
default:
PR_ASSERT(0);
break;
} /* end switch() */
if (rv == -1)
{
_PR_MD_MAP_SELECT_ERROR(WSAGetLastError());
break;
}
if ( rv > 0 && fd_type == CONNECT_FD )
{
/*
* Call Sleep(0) to work around a Winsock timing bug.
*/
Sleep(0);
if (FD_ISSET((SOCKET)osfd, &ex))
{
len = sizeof(err);
if (getsockopt(osfd, SOL_SOCKET, SO_ERROR,
(char *) &err, &len) == SOCKET_ERROR)
{
_PR_MD_MAP_GETSOCKOPT_ERROR(WSAGetLastError());
return -1;
}
if (err != 0)
_PR_MD_MAP_CONNECT_ERROR(err);
else
PR_SetError(PR_UNKNOWN_ERROR, 0);
return -1;
}
if (FD_ISSET((SOCKET)osfd, &rd_wr))
{
/* it's connected */
return 1;
}
PR_ASSERT(0);
}
if (_PR_PENDING_INTERRUPT(me)) {
me->flags &= ~_PR_INTERRUPT;
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
rv = -1;
break;
}
/*
* We loop again if _MD_SELECT timed out and the
* timeout deadline has not passed yet.
*/
if (rv == 0 )
{
if (wait_for_remaining) {
elapsed = remaining;
} else {
elapsed = PR_SecondsToInterval(tv.tv_sec)
+ PR_MicrosecondsToInterval(tv.tv_usec);
}
if (elapsed >= remaining) {
PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
rv = -1;
break;
} else {
remaining = remaining - elapsed;
}
}
} while (rv == 0 );
break;
}
return(rv);
} /* end socket_io_wait() */