blob: 6a7e3dc2e1460672f87c70a50372578db483ad6e [file] [log] [blame] [edit]
/* -*- 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 ***** */
/* OS/2 Sockets module
*
*/
/*Note from DSR111297 - it should be noted that there are two flavors of select() on OS/2 */
/*There is standard BSD (which is kind of slow) and a new flavor of select() that takes */
/*an integer list of sockets, the number of read sockets, write sockets, except sockets, and */
/*a millisecond count for timeout. In the interest of performance I have choosen the OS/2 */
/*specific version of select(). See OS/2 TCP/IP Programmer's Toolkit for more info. */
#include "primpl.h"
#include <sys/time.h> /* For timeval. */
#define _PR_INTERRUPT_CHECK_INTERVAL_SECS 5
#define READ_FD 1
#define WRITE_FD 2
/* --- SOCKET IO --------------------------------------------------------- */
PRInt32
_PR_MD_SOCKET(int domain, int type, int flags)
{
PRInt32 osfd, err;
osfd = socket(domain, type, flags);
if (osfd == -1)
{
err = sock_errno();
_PR_MD_MAP_SOCKET_ERROR(err);
}
return(osfd);
}
/*
** _MD_CloseSocket() -- Close a socket
**
*/
PRInt32
_MD_CloseSocket(PRInt32 osfd)
{
PRInt32 rv, err;
rv = soclose(osfd);
if (rv == -1) {
err = sock_errno();
_PR_MD_MAP_CLOSE_ERROR(err);
}
return rv;
}
PRInt32
_MD_SocketAvailable(PRFileDesc *fd)
{
PRInt32 result;
if (so_ioctl(fd->secret->md.osfd, FIONREAD, (char *) &result, sizeof(result)) < 0) {
PR_SetError(PR_BAD_DESCRIPTOR_ERROR, sock_errno());
return -1;
}
return result;
}
static PRInt32
socket_io_wait( PRInt32 osfd, PRInt32 fd_type, PRIntervalTime timeout )
{
PRInt32 rv = -1;
PRThread *me = _PR_MD_CURRENT_THREAD();
PRIntervalTime epoch, now, elapsed, remaining;
PRBool wait_for_remaining;
PRInt32 syserror;
#ifdef BSD_SELECT
struct timeval tv;
fd_set rd_wr;
#else
int socks[1];
long lTimeout;
#endif
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.
*/
#ifdef BSD_SELECT
tv.tv_sec = _PR_INTERRUPT_CHECK_INTERVAL_SECS;
tv.tv_usec = 0;
FD_ZERO(&rd_wr);
do {
FD_SET(osfd, &rd_wr);
if (fd_type == READ_FD)
rv = bsdselect(osfd + 1, &rd_wr, NULL, NULL, &tv);
else
rv = bsdselect(osfd + 1, NULL, &rd_wr, NULL, &tv);
#else
lTimeout = _PR_INTERRUPT_CHECK_INTERVAL_SECS * 1000;
do {
socks[0] = osfd;
if (fd_type == READ_FD)
rv = os2_select(socks, 1, 0, 0, lTimeout);
else
rv = os2_select(socks, 0, 1, 0, lTimeout);
#endif
if (rv == -1 && (syserror = sock_errno()) != EINTR) {
_PR_MD_MAP_SELECT_ERROR(syserror);
break;
}
if (_PR_PENDING_INTERRUPT(me)) {
me->flags &= ~_PR_INTERRUPT;
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
rv = -1;
break;
}
} while (rv == 0 || (rv == -1 && syserror == EINTR));
break;
default:
now = epoch = PR_IntervalNow();
remaining = timeout;
#ifdef BSD_SELECT
FD_ZERO(&rd_wr);
#endif
do {
/*
* We block in 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.
*/
#ifdef BSD_SELECT
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);
if (fd_type == READ_FD)
rv = bsdselect(osfd + 1, &rd_wr, NULL, NULL, &tv);
else
rv = bsdselect(osfd + 1, NULL, &rd_wr, NULL, &tv);
#else
wait_for_remaining = PR_TRUE;
lTimeout = PR_IntervalToMilliseconds(remaining);
if (lTimeout > _PR_INTERRUPT_CHECK_INTERVAL_SECS * 1000) {
wait_for_remaining = PR_FALSE;
lTimeout = _PR_INTERRUPT_CHECK_INTERVAL_SECS * 1000;
}
socks[0] = osfd;
if (fd_type == READ_FD)
rv = os2_select(socks, 1, 0, 0, lTimeout);
else
rv = os2_select(socks, 0, 1, 0, lTimeout);
#endif
/*
* we don't consider EINTR a real error
*/
if (rv == -1 && (syserror = sock_errno()) != EINTR) {
_PR_MD_MAP_SELECT_ERROR(syserror);
break;
}
if (_PR_PENDING_INTERRUPT(me)) {
me->flags &= ~_PR_INTERRUPT;
PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
rv = -1;
break;
}
/*
* We loop again if select timed out or got interrupted
* by a signal, and the timeout deadline has not passed yet.
*/
if (rv == 0 || (rv == -1 && syserror == EINTR)) {
/*
* If select timed out, we know how much time
* we spent in blocking, so we can avoid a
* PR_IntervalNow() call.
*/
if (rv == 0) {
if (wait_for_remaining) {
now += remaining;
} else {
#ifdef BSD_SELECT
now += PR_SecondsToInterval(tv.tv_sec)
+ PR_MicrosecondsToInterval(tv.tv_usec);
#else
now += PR_MillisecondsToInterval(lTimeout);
#endif
}
} else {
now = PR_IntervalNow();
}
elapsed = (PRIntervalTime) (now - epoch);
if (elapsed >= timeout) {
PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
rv = -1;
break;
} else {
remaining = timeout - elapsed;
}
}
} while (rv == 0 || (rv == -1 && syserror == EINTR));
break;
}
return(rv);
}
PRInt32
_MD_Accept(PRFileDesc *fd, PRNetAddr *addr,
PRUint32 *addrlen, PRIntervalTime timeout)
{
PRInt32 osfd = fd->secret->md.osfd;
PRInt32 rv, err;
PRThread *me = _PR_MD_CURRENT_THREAD();
while ((rv = accept(osfd, (struct sockaddr*) addr, (int*)addrlen)) == -1)
{
err = sock_errno();
if ((err == EWOULDBLOCK) || (err == ECONNABORTED))
{
if (fd->secret->nonblocking) {
break;
}
if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0)
goto done;
} else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))){
continue;
} else {
break;
}
}
if (rv < 0) {
_PR_MD_MAP_ACCEPT_ERROR(err);
}
done:
return(rv);
}
PRInt32
_PR_MD_CONNECT(PRFileDesc *fd, const PRNetAddr *addr, PRUint32 addrlen,
PRIntervalTime timeout)
{
PRInt32 rv, err;
PRThread *me = _PR_MD_CURRENT_THREAD();
PRInt32 osfd = fd->secret->md.osfd;
PRNetAddr addrCopy = *addr; /* Work around a bug in OS/2 where connect
* modifies the sockaddr structure.
* See Bugzilla bug 100776. */
/*
* We initiate the connection setup by making a nonblocking connect()
* call. If the connect() call fails, there are two cases we handle
* specially:
* 1. The connect() call was interrupted by a signal. In this case
* we simply retry connect().
* 2. The NSPR socket is nonblocking and connect() fails with
* EINPROGRESS. We first wait until the socket becomes writable.
* Then we try to find out whether the connection setup succeeded
* or failed.
*/
retry:
if ((rv = connect(osfd, (struct sockaddr *)&addrCopy, addrlen)) == -1)
{
err = sock_errno();
if (err == EINTR) {
if (_PR_PENDING_INTERRUPT(me)) {
me->flags &= ~_PR_INTERRUPT;
PR_SetError( PR_PENDING_INTERRUPT_ERROR, 0);
return -1;
}
goto retry;
}
if (!fd->secret->nonblocking && (err == EINPROGRESS))
{
/*
* socket_io_wait() may return -1 or 1.
*/
rv = socket_io_wait(osfd, WRITE_FD, timeout);
if (rv == -1) {
return -1;
}
PR_ASSERT(rv == 1);
if (_PR_PENDING_INTERRUPT(me)) {
me->flags &= ~_PR_INTERRUPT;
PR_SetError( PR_PENDING_INTERRUPT_ERROR, 0);
return -1;
}
err = _MD_os2_get_nonblocking_connect_error(osfd);
if (err != 0) {
_PR_MD_MAP_CONNECT_ERROR(err);
return -1;
}
return 0;
}
_PR_MD_MAP_CONNECT_ERROR(err);
}
return rv;
} /* _MD_connect */
PRInt32
_PR_MD_BIND(PRFileDesc *fd, const PRNetAddr *addr, PRUint32 addrlen)
{
PRInt32 rv, err;
rv = bind(fd->secret->md.osfd, (struct sockaddr *) addr, (int )addrlen);
if (rv < 0) {
err = sock_errno();
_PR_MD_MAP_BIND_ERROR(err);
}
return(rv);
}
PRInt32
_PR_MD_LISTEN(PRFileDesc *fd, PRIntn backlog)
{
PRInt32 rv, err;
rv = listen(fd->secret->md.osfd, backlog);
if (rv < 0) {
err = sock_errno();
_PR_MD_MAP_DEFAULT_ERROR(err);
}
return(rv);
}
PRInt32
_PR_MD_RECV(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
PRIntervalTime timeout)
{
PRInt32 osfd = fd->secret->md.osfd;
PRInt32 rv, err;
PRThread *me = _PR_MD_CURRENT_THREAD();
while ((rv = recv(osfd,buf,amount,flags)) == -1)
{
err = sock_errno();
if ((err == EWOULDBLOCK)) {
if (fd->secret->nonblocking) {
break;
}
if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0)
goto done;
} else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))){
continue;
} else {
break;
}
}
if (rv < 0) {
_PR_MD_MAP_RECV_ERROR(err);
}
done:
return(rv);
}
PRInt32
_PR_MD_SEND(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
PRIntervalTime timeout)
{
PRInt32 osfd = fd->secret->md.osfd;
PRInt32 rv, err;
PRThread *me = _PR_MD_CURRENT_THREAD();
while ((rv = send(osfd,buf,amount,flags)) == -1)
{
err = sock_errno();
if ((err == EWOULDBLOCK)) {
if (fd->secret->nonblocking) {
break;
}
if ((rv = socket_io_wait(osfd, WRITE_FD, timeout)) < 0)
goto done;
} else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))){
continue;
} else {
break;
}
}
/*
* optimization; if bytes sent is less than "amount" call
* select before returning. This is because it is likely that
* the next send() call will return EWOULDBLOCK.
*/
if ((!fd->secret->nonblocking) && (rv > 0) && (rv < amount)
&& (timeout != PR_INTERVAL_NO_WAIT))
{
if (socket_io_wait(osfd, WRITE_FD, timeout)< 0) {
rv = -1;
goto done;
}
}
if (rv < 0) {
_PR_MD_MAP_SEND_ERROR(err);
}
done:
return(rv);
}
PRInt32
_PR_MD_SENDTO(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags,
const PRNetAddr *addr, PRUint32 addrlen, PRIntervalTime timeout)
{
PRInt32 osfd = fd->secret->md.osfd;
PRInt32 rv, err;
PRThread *me = _PR_MD_CURRENT_THREAD();
while ((rv = sendto(osfd, buf, amount, flags,
(struct sockaddr *) addr, addrlen)) == -1)
{
err = sock_errno();
if ((err == EWOULDBLOCK))
{
if (fd->secret->nonblocking) {
break;
}
if ((rv = socket_io_wait(osfd, WRITE_FD, timeout)) < 0)
goto done;
} else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))){
continue;
} else {
break;
}
}
if (rv < 0) {
_PR_MD_MAP_SENDTO_ERROR(err);
}
done:
return(rv);
}
PRInt32
_PR_MD_RECVFROM(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags,
PRNetAddr *addr, PRUint32 *addrlen, PRIntervalTime timeout)
{
PRInt32 osfd = fd->secret->md.osfd;
PRInt32 rv, err;
PRThread *me = _PR_MD_CURRENT_THREAD();
while( (*addrlen = PR_NETADDR_SIZE(addr)),
((rv = recvfrom(osfd, buf, amount, flags,
(struct sockaddr *) addr, (int *)addrlen)) == -1))
{
err = sock_errno();
if ((err == EWOULDBLOCK)) {
if (fd->secret->nonblocking) {
break;
}
if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0)
goto done;
} else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))){
continue;
} else {
break;
}
}
if (rv < 0) {
_PR_MD_MAP_RECVFROM_ERROR(err);
}
done:
return(rv);
}
PRInt32
_PR_MD_WRITEV(PRFileDesc *fd, const PRIOVec *iov, PRInt32 iov_size,
PRIntervalTime timeout)
{
PRInt32 rv, err;
PRThread *me = _PR_MD_CURRENT_THREAD();
PRInt32 index, amount = 0;
PRInt32 osfd = fd->secret->md.osfd;
struct iovec osiov[PR_MAX_IOVECTOR_SIZE];
/* Ensured by PR_Writev */
PR_ASSERT(iov_size <= PR_MAX_IOVECTOR_SIZE);
/*
* We can't pass iov to so_writev because PRIOVec and struct iovec
* may not be binary compatible. Make osiov a copy of iov and
* pass osiov to so_writev .
*/
for (index = 0; index < iov_size; index++) {
osiov[index].iov_base = iov[index].iov_base;
osiov[index].iov_len = iov[index].iov_len;
}
/*
* Calculate the total number of bytes to be sent; needed for
* optimization later.
* We could avoid this if this number was passed in; but it is
* probably not a big deal because iov_size is usually small (less than
* 3)
*/
if (!fd->secret->nonblocking) {
for (index=0; index<iov_size; index++) {
amount += iov[index].iov_len;
}
}
while ((rv = so_writev(osfd, osiov, iov_size)) == -1) {
err = sock_errno();
if ((err == EWOULDBLOCK)) {
if (fd->secret->nonblocking) {
break;
}
if ((rv = socket_io_wait(osfd, WRITE_FD, timeout))<0)
goto done;
} else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))){
continue;
} else {
break;
}
}
/*
* optimization; if bytes sent is less than "amount" call
* select before returning. This is because it is likely that
* the next writev() call will return EWOULDBLOCK.
*/
if ((!fd->secret->nonblocking) && (rv > 0) && (rv < amount)
&& (timeout != PR_INTERVAL_NO_WAIT)) {
if (socket_io_wait(osfd, WRITE_FD, timeout) < 0) {
rv = -1;
goto done;
}
}
if (rv < 0) {
_PR_MD_MAP_WRITEV_ERROR(err);
}
done:
return(rv);
}
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(sock_errno());
return rv;
}
PRInt32
_PR_MD_SOCKETPAIR(int af, int type, int flags, PRInt32 *osfd)
{
PRInt32 rv, err;
rv = socketpair(af, type, flags, osfd);
if (rv < 0) {
err = _MD_ERRNO();
_PR_MD_MAP_SOCKETPAIR_ERROR(err);
}
return rv;
}
PRStatus
_PR_MD_GETSOCKNAME(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *addrlen)
{
PRInt32 rv, err;
rv = getsockname(fd->secret->md.osfd,
(struct sockaddr *) addr, (int *)addrlen);
if (rv < 0) {
err = sock_errno();
_PR_MD_MAP_GETSOCKNAME_ERROR(err);
}
return rv==0?PR_SUCCESS:PR_FAILURE;
}
PRStatus
_PR_MD_GETPEERNAME(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *addrlen)
{
PRInt32 rv, err;
rv = getpeername(fd->secret->md.osfd,
(struct sockaddr *) addr, (int *)addrlen);
if (rv < 0) {
err = sock_errno();
_PR_MD_MAP_GETPEERNAME_ERROR(err);
}
return rv==0?PR_SUCCESS:PR_FAILURE;
}
PRStatus
_PR_MD_GETSOCKOPT(PRFileDesc *fd, PRInt32 level, PRInt32 optname,
char* optval, PRInt32* optlen)
{
PRInt32 rv, err;
rv = getsockopt(fd->secret->md.osfd, level, optname, optval, (int *)optlen);
if (rv < 0) {
err = sock_errno();
_PR_MD_MAP_GETSOCKOPT_ERROR(err);
}
return rv==0?PR_SUCCESS:PR_FAILURE;
}
PRStatus
_PR_MD_SETSOCKOPT(PRFileDesc *fd, PRInt32 level, PRInt32 optname,
const char* optval, PRInt32 optlen)
{
PRInt32 rv, err;
rv = setsockopt(fd->secret->md.osfd, level, optname, optval, optlen);
if (rv < 0) {
err = sock_errno();
_PR_MD_MAP_SETSOCKOPT_ERROR(err);
}
return rv==0?PR_SUCCESS:PR_FAILURE;
}
void
_MD_MakeNonblock(PRFileDesc *fd)
{
PRInt32 osfd = fd->secret->md.osfd;
PRInt32 err;
PRUint32 one = 1;
if (osfd <= 2) {
/* Don't mess around with stdin, stdout or stderr */
return;
}
err = so_ioctl( osfd, FIONBIO, (char *) &one, sizeof(one));
if ( err != 0 )
{
err = sock_errno();
_PR_MD_MAP_SOCKET_ERROR(err);
}
}