/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/*
 * The contents of this file are subject to the Netscape Public License
 * Version 1.0 (the "NPL"); you may not use this file except in
 * compliance with the NPL.  You may obtain a copy of the NPL at
 * http://www.mozilla.org/NPL/
 * 
 * Software distributed under the NPL is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
 * for the specific language governing rights and limitations under the
 * NPL.
 * 
 * The Initial Developer of this code under the NPL is Netscape
 * Communications Corporation.  Portions created by Netscape are
 * Copyright (C) 1998 Netscape Communications Corporation.  All Rights
 * Reserved.
 */

/* 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"

void
_PR_MD_INIT_IO()
{
    sock_init();
}

/* --- SOCKET IO --------------------------------------------------------- */


PRInt32
_PR_MD_SOCKET(int af, int type, int flags)
{
    int sock;
    PRUint32  one = 1;
    PRInt32   rv;
    PRInt32   err;

    sock = socket(af, type, flags);

    if (sock == -1 ) 
    {
        int rv = sock_errno();
        soclose(sock);
		_PR_MD_MAP_SOCKET_ERROR(rv);
        return (PRInt32) -1;
    }

    /*
    ** Make the socket Non-Blocking
    */
    rv = ioctl( sock, FIONBIO, (char *) &one, sizeof(one));
    if ( rv != 0 )
    {
        err = sock_errno();
        return -1;
    }

    return (PRInt32)sock;
}

/*
** _MD_CloseSocket() -- Close a socket
**
*/
PRInt32
_MD_CloseSocket(PRInt32 osfd)
{
    PRInt32 rv = -1;

    rv = soclose((int) osfd );
	if (rv < 0)
		_PR_MD_MAP_SOCKET_ERROR(sock_errno());

    return rv;
}

PRInt32
_MD_SocketAvailable(PRFileDesc *fd)
{
    PRInt32 result;

    if (ioctl(fd->secret->md.osfd, FIONREAD, (char *) &result, sizeof(result)) < 0) {
		PR_SetError(PR_BAD_DESCRIPTOR_ERROR, sock_errno());
        return -1;
    }
    return result;
}

PRInt32
_MD_Accept(PRFileDesc *fd, PRNetAddr *raddr, PRUint32 *rlen,
              PRIntervalTime timeout )
{
    PRInt32 osfd = fd->secret->md.osfd;
    PRInt32 rv, err;
#ifdef BSD_SELECT
    fd_set rd;
    struct timeval tv, *tvp;

    FD_ZERO(&rd);
    FD_SET(osfd, &rd);
#else
    int socks[1];
    socks[0] = osfd; 
#endif
    if (timeout == PR_INTERVAL_NO_TIMEOUT) 
    {
        while ((rv = accept(osfd, (struct sockaddr *) raddr, (int *) rlen)) == -1) 
        {
            if (((err = sock_errno()) == EWOULDBLOCK) 
                && (!fd->secret->nonblocking))
            {
#ifdef BSD_SELECT
                if ((rv = select(osfd + 1, &rd, NULL, NULL,NULL)) == -1) {
#else
                if ((rv = select(socks, 1, 0, 0, -1)) == -1) {
#endif
					_PR_MD_MAP_SELECT_ERROR(sock_errno());
                    break;
            } 
            } 
            else {
				_PR_MD_MAP_ACCEPT_ERROR(err);
                break;
        }
        }
        return(rv);
    } 
    else if (timeout == PR_INTERVAL_NO_WAIT) 
    {
        if ((rv = accept(osfd, (struct sockaddr *) raddr, (int *) rlen)) == -1)
        {
            if (((err = sock_errno()) == EWOULDBLOCK) 
                && (!fd->secret->nonblocking))
            {
				PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
            }
            else
            {
                _PR_MD_MAP_ACCEPT_ERROR(err);
            }
        }
            return(rv);
    } 
    else 
    {
retry:
        if ((rv = accept(osfd, (struct sockaddr *) raddr, (int *) rlen)) == -1) 
        {
            if (((err = sock_errno()) == EWOULDBLOCK) 
                && (!fd->secret->nonblocking))
            {
#ifdef BSD_SELECT
                tv.tv_sec = PR_IntervalToSeconds(timeout);
                tv.tv_usec = PR_IntervalToMicroseconds(
                    timeout - PR_SecondsToInterval(tv.tv_sec));
                tvp = &tv;
                rv = select(osfd + 1, &rd, NULL, NULL, tvp);
#else
                long lTimeout = PR_IntervalToMilliseconds(timeout); 
                rv = select(socks, 1, 0, 0, lTimeout);
#endif
                if (rv > 0) {
                    goto retry;
                } 
                else if (rv == 0) 
                {
					PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
                    rv = -1;
                } else {
					_PR_MD_MAP_SELECT_ERROR(sock_errno());
                }
            } else {
				_PR_MD_MAP_ACCEPT_ERROR(err);
            }
        }
    }
    return(rv);
} /* end _MD_Accept() */



PRInt32
_PR_MD_CONNECT(PRFileDesc *fd, const PRNetAddr *addr, PRUint32 addrlen, 
               PRIntervalTime timeout)
{
    PRInt32 osfd = fd->secret->md.osfd;
    PRInt32 rv;
    int err, len;
#ifdef BSD_SELECT
    fd_set wd, ex;
    struct timeval tv, *tvp;
#else
    int socks[1]; 
    long lTimeout = -1;
#endif

    if ((rv = connect(osfd, (struct sockaddr *) addr, addrlen)) == -1) 
    {
        err = sock_errno();
        if ((!fd->secret->nonblocking) && (err == EINPROGRESS) || (err == EWOULDBLOCK))
        {
#ifdef BSD_SELECT
           if (timeout == PR_INTERVAL_NO_TIMEOUT)
               tvp = NULL;
            else 
            {
                tv.tv_sec = PR_IntervalToSeconds(timeout);
                tv.tv_usec = PR_IntervalToMicroseconds(
                timeout - PR_SecondsToInterval(tv.tv_sec));
                tvp = &tv;
            }

            FD_ZERO(&wd);
            FD_SET(osfd, &wd);
            FD_ZERO(&ex);
            FD_SET(osfd, &ex);
            rv = select(osfd + 1, NULL, &wd, &ex, tvp);
#else
            if (timeout == PR_INTERVAL_NO_TIMEOUT)
                lTimeout = -1;
            else 
            {
                lTimeout = PR_IntervalToMilliseconds(timeout);  
            }
	    
            socks[0] = osfd; 
            rv = select(socks, 0, 1, 1, lTimeout);
#endif
            if (rv > 0) 
            {
#ifdef BSD_SELECT
               if (FD_ISSET(osfd, &ex))
               {
                   DosSleep(0);
                   len = sizeof(err);
                   if (getsockopt(osfd, SOL_SOCKET, SO_ERROR,
                           (char *) &err, &len) < 0)
                   {  
                       _PR_MD_MAP_GETSOCKOPT_ERROR(sock_errno());
                       return -1;
                   }
                   if (err != 0)
                       _PR_MD_MAP_CONNECT_ERROR(err);
                   else
                       PR_SetError(PR_UNKNOWN_ERROR, 0);
                   return -1;
               }
               if (FD_ISSET(osfd, &wd))
               {
                   /* it's connected */
                   return 0;
               }
#else
               if (getsockopt(osfd, SOL_SOCKET, SO_ERROR,
                       (char *) &err, &len) < 0)
               {  
                   _PR_MD_MAP_GETSOCKOPT_ERROR(sock_errno());
                   return -1;
               }
               else
                  return 0; /* It's connected ! */
#endif
            } 
            else if (rv == 0) 
            {
				PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
                return(-1);
            } else if (rv < 0) 
            {
				_PR_MD_MAP_SELECT_ERROR(sock_errno());
                return(-1);
            }
        } 
        _PR_MD_MAP_CONNECT_ERROR(err);
    }
    return rv;
}


PRInt32
_PR_MD_BIND(PRFileDesc *fd, const PRNetAddr *addr, PRUint32 addrlen)
{
    PRInt32 rv;
    int one = 1;

    rv = bind(fd->secret->md.osfd, (struct sockaddr*) &(addr->inet), addrlen);

    if (rv == -1)  {
		_PR_MD_MAP_BIND_ERROR(sock_errno());
        return -1;
	}

    return 0;
}


PRInt32
_PR_MD_RECV(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, 
            PRIntervalTime timeout)
{
    PRInt32 osfd = fd->secret->md.osfd;
    PRInt32 rv, err;
#ifdef BSD_SELECT
    struct timeval tv, *tvp;
    fd_set rd;
#else
    int socks[1]; 
    long lTimeout = -1; 
#endif

    while ((rv = recv( osfd, buf, amount, 0)) == -1) 
    {
        if (((err = sock_errno()) == EWOULDBLOCK) 
            && (!fd->secret->nonblocking))
        {
#ifdef BSD_SELECT
           FD_ZERO(&rd);
           FD_SET(osfd, &rd);
           if (timeout == PR_INTERVAL_NO_TIMEOUT) 
           {
               tvp = NULL;
           } 
           else 
           {
               tv.tv_sec = PR_IntervalToSeconds(timeout);
               tv.tv_usec = PR_IntervalToMicroseconds(
               timeout - PR_SecondsToInterval(tv.tv_sec));
               tvp = &tv;
           }
           if ((rv = select(osfd + 1, &rd, NULL, NULL, tvp)) == -1) 
#else
            socks[0] = osfd; 
            if (timeout == PR_INTERVAL_NO_TIMEOUT) 
            {
                lTimeout = -1; 
            } 
            else 
            {
                lTimeout = PR_IntervalToMilliseconds(timeout); 
            }
            if ((rv = select(socks, 1, 0, 0, lTimeout)) == -1) 
#endif
            {
				_PR_MD_MAP_SELECT_ERROR(sock_errno());
                return -1;
            } 
            else if (rv == 0) 
            {
				PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
                rv = -1;
                break;
            }
        } 
        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)
{
    PRInt32 osfd = fd->secret->md.osfd;
    PRInt32 rv, err;
#ifdef BSD_SELECT
    struct timeval tv, *tvp;
    fd_set wd;
#else
    int socks[1]; 
    long lTimeout = -1; 
#endif
    PRInt32 bytesSent = 0;

    while(bytesSent < amount ) 
    {
        while ((rv = send( osfd, (char *) buf, amount, 0 )) == -1) 
        {
            if (((err = sock_errno()) == EWOULDBLOCK) 
                && (!fd->secret->nonblocking))
            {
#ifdef BSD_SELECT
                if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
                {
                    tvp = NULL;
                } 
                else 
                {
                    tv.tv_sec = PR_IntervalToSeconds(timeout);
                    tv.tv_usec = PR_IntervalToMicroseconds(
                        timeout - PR_SecondsToInterval(tv.tv_sec));
                    tvp = &tv;
                }
                FD_ZERO(&wd);
                FD_SET(osfd, &wd);
                if ((rv = select( osfd + 1, NULL, &wd, NULL,tvp)) == -1) {
#else
                if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
                {
                    lTimeout = -1; 
                } 
                else 
                {
                    lTimeout = PR_IntervalToMilliseconds(timeout); 
                }
                socks[0] = osfd; 
                if ((rv = select( socks, 0, 1, 0, lTimeout)) == -1) {
#endif
					_PR_MD_MAP_SELECT_ERROR(sock_errno());
                    break;
				}
                if (rv == 0) 
                {
					PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
                        return -1;
                }
            } 
            else {
				_PR_MD_MAP_SEND_ERROR(err);
                return -1;
        }
        }
        bytesSent += rv;
        if (fd->secret->nonblocking)
        {
            break;
        }
        if ((rv >= 0) && (bytesSent < amount )) 
        {
#ifdef BSD_SELECT
           if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
           {
               tvp = NULL;
           } 
           else 
           {
               tv.tv_sec = PR_IntervalToSeconds(timeout);
               tv.tv_usec = PR_IntervalToMicroseconds(
                   timeout - PR_SecondsToInterval(tv.tv_sec));
               tvp = &tv;
           }
           FD_ZERO(&wd);
           FD_SET(osfd, &wd);
           if ((rv = select(osfd + 1, NULL, &wd, NULL,tvp)) == -1) {
#else
            if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
            {
                lTimeout = -1; 
            } 
            else 
            {
                lTimeout = PR_IntervalToMilliseconds(timeout); 
            }
            socks[0] = osfd; 
            if ((rv = select(socks, 0, 1, 0,lTimeout)) == -1) {
#endif
				_PR_MD_MAP_SELECT_ERROR(sock_errno());
                break;
			}
            if (rv == 0) 
            {
				PR_SetError(PR_IO_TIMEOUT_ERROR, 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)
{
    PRInt32 osfd = fd->secret->md.osfd;
    PRInt32 rv, err;
    PRInt32 bytesSent = 0;
#ifdef BSD_SELECT
    struct timeval tv, *tvp;
    fd_set wd;
#else
    int socks[1];
    long lTimeout = -1; 
#endif

    while(bytesSent < amount) 
    {
        while ((rv = sendto( osfd, (char *) buf, amount, 0, (struct sockaddr *) addr,
                addrlen)) == -1) 
        {
            if (((err = sock_errno()) == EWOULDBLOCK) 
                && (!fd->secret->nonblocking))
            {
#ifdef BSD_SELECT
               if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
               {
                   tvp = NULL;
               } 
               else 
               {
                   tv.tv_sec = PR_IntervalToSeconds(timeout);
                   tv.tv_usec = PR_IntervalToMicroseconds(
                       timeout - PR_SecondsToInterval(tv.tv_sec));
                   tvp = &tv;
               }
               FD_ZERO(&wd);
               FD_SET(osfd, &wd);
               if ((rv = select(osfd + 1, NULL, &wd, NULL, tvp)) == -1) {
#else
                if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
                {
                    lTimeout = -1; 
                } 
                else 
                {
                    lTimeout = PR_IntervalToMilliseconds(timeout);
                }
                socks[0] = osfd; 
                if ((rv = select(socks, 0, 1, 0, lTimeout)) == -1) {
#endif
					_PR_MD_MAP_SELECT_ERROR(sock_errno());
                    break;
				}
                if (rv == 0) 
                {
					PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
                        return -1;
                }
            } 
            else {
				_PR_MD_MAP_SENDTO_ERROR(err);
                return -1;
        }
        }
        bytesSent += rv;
        if (fd->secret->nonblocking)
        {
            break;
        }
        if ((rv >= 0) && (bytesSent < amount )) 
        {
#ifdef BSD_SELECT
           if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
           {
               tvp = NULL;
           } 
           else 
           {
               tv.tv_sec = PR_IntervalToSeconds(timeout);
               tv.tv_usec = PR_IntervalToMicroseconds(
                   timeout - PR_SecondsToInterval(tv.tv_sec));
               tvp = &tv;
           }
           FD_ZERO(&wd);
           FD_SET(osfd, &wd);
           if ((rv = select( osfd + 1, NULL, &wd, NULL, tvp)) == -1) {
#else
            if ( timeout == PR_INTERVAL_NO_TIMEOUT ) 
            {
                lTimeout = -1; 
            } 
            else 
            {
                lTimeout = PR_IntervalToMilliseconds(timeout);  
            }
            socks[0] = osfd; 
            if ((rv = select( socks, 0, 1, 0, lTimeout)) == -1) {
#endif
				_PR_MD_MAP_SELECT_ERROR(sock_errno());
                break;
			}
            if (rv == 0) 
            {
				PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
                    return -1;
            }
        }
    }
    return bytesSent;
}

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;
    PRUint32 addrlen_temp = *addrlen;
#ifdef BSD_SELECT
    struct timeval tv, *tvp;
    fd_set rd;
#else
    int socks[1]; 
    long lTimeout = -1; 
#endif

    while ((rv = recvfrom( osfd, (char *) buf, amount, 0, (struct sockaddr *) addr,
            (int *) addrlen)) == -1) 
    {
        if (((err = sock_errno()) == EWOULDBLOCK) 
            && (!fd->secret->nonblocking))
        {
#ifdef BSD_SELECT
           if (timeout == PR_INTERVAL_NO_TIMEOUT) 
           {
               tvp = NULL;
           } 
           else 
           {
               tv.tv_sec = PR_IntervalToSeconds(timeout);
               tv.tv_usec = PR_IntervalToMicroseconds(
               timeout - PR_SecondsToInterval(tv.tv_sec));
               tvp = &tv;
           }
           FD_ZERO(&rd);
           FD_SET(osfd, &rd);
           if ((rv = select(osfd + 1, &rd, NULL, NULL, tvp)) == -1) 
#else
            if (timeout == PR_INTERVAL_NO_TIMEOUT) 
            {
                lTimeout = -1;
            } 
            else 
            {
                lTimeout = PR_IntervalToMilliseconds(timeout);  
            }
            socks[0] = osfd; 
            if ((rv = select(socks, 1, 0, 0, lTimeout)) == -1) 
#endif
            {
				_PR_MD_MAP_SELECT_ERROR(sock_errno());
                return -1;
            } else if (rv == 0) 
            {
				PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
                rv = -1;
                break;
            }

            /* recvfrom blows this value away if it fails first time */
            *addrlen = addrlen_temp;
        } 
        else 
        {
			_PR_MD_MAP_RECVFROM_ERROR(err);
            break;
        }
    }
    return(rv);
}

PRInt32
_PR_MD_WRITEV(PRFileDesc *fd, 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(sock_errno());
	return rv;
}

PRStatus
_PR_MD_GETSOCKNAME(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *len)
{
    PRInt32 rv;

    rv = getsockname((int)fd->secret->md.osfd, (struct sockaddr *)addr, (int *) len);
    if (rv==0)
		return PR_SUCCESS;
	else {
		_PR_MD_MAP_GETSOCKNAME_ERROR(sock_errno());
		return PR_FAILURE;
	}
}

PRStatus
_PR_MD_GETPEERNAME(PRFileDesc *fd, PRNetAddr *addr, PRUint32 *len)
{
    PRInt32 rv;

    rv = getpeername((int)fd->secret->md.osfd, (struct sockaddr *)addr, (int *) len);
    if (rv==0)
		return PR_SUCCESS;
	else {
		_PR_MD_MAP_GETPEERNAME_ERROR(sock_errno());
		return PR_FAILURE;
	}
}

PRStatus
_PR_MD_GETSOCKOPT(PRFileDesc *fd, PRInt32 level, PRInt32 optname, char* optval, PRInt32* optlen)
{
    PRInt32 rv;

    rv = getsockopt((int)fd->secret->md.osfd, level, optname, optval, optlen);
    if (rv==0)
		return PR_SUCCESS;
	else {
		_PR_MD_MAP_GETSOCKOPT_ERROR(sock_errno());
		return PR_FAILURE;
	}
}

PRStatus
_PR_MD_SETSOCKOPT(PRFileDesc *fd, PRInt32 level, PRInt32 optname, const char* optval, PRInt32 optlen)
{
    PRInt32 rv;

    rv = setsockopt((int)fd->secret->md.osfd, level, optname, (char *) optval, optlen);
    if (rv==0)
		return PR_SUCCESS;
	else {
		_PR_MD_MAP_SETSOCKOPT_ERROR(sock_errno());
		return PR_FAILURE;
	}
}

void
_MD_MakeNonblock(PRFileDesc *f)
{
    return; /* do nothing! */ 
}
