#ifndef __DWC_FD_C
#define __DWC_FD_C

/*--------------------------------------------------------------------------
 *  Copyright (c) 2001-2004 Igor Daniloff. 
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at this directory.
 *------------------------------------------------------------------------*/
 
#ifndef lint
static const char rcsid[] = "$Id: dwc_fd.c,v 1.28 2008/03/28 13:13:14 akorkov Exp $";
#endif

#include <stdio.h>
#include <string.h>

#if defined(__UNIX_DW)
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#if defined(DW_HAVE_POLL)
#if !defined(__SUNOS__)
#include <sys/poll.h>
#else /* not HAVE_POLL */
#include <poll.h>
#endif /* not SUNOS */
#endif /* HAVE_POLL */
#include <fcntl.h>
#include <errno.h>
#if defined(EMX_DW)
#include <io.h>
#endif /* EMX_DW */
#endif /* __UNIX_DW */

#include "dwc_timo.h"
#include "dwc_err.h"
#include "dwc_macro.h"
#include "dwc_log.h"
#include "dwc_fd.h"

#if defined(__WIN32_DW)
static const DWC_msg_entry_t  WinsockErrMsgTable[] = {
  { WSABASEERR,         "[0] No Error" },
  { WSAEINTR,           "[10004] Interrupted system call" },
  { WSAEBADF,           "[10009] Bad file number" },
  { WSAEACCES,          "[10013] Permission denied" },
  { WSAEFAULT,          "[10014] Bad address" },
  { WSAEINVAL,          "[10022] Invalid argument" },
  { WSAEMFILE,          "[10024] Too many open files" },
  { WSAEWOULDBLOCK,     "[10035] Operation would block" },
  { WSAEINPROGRESS,     "[10036] Operation now in progress" },
  { WSAEALREADY,        "[10037] Operation already in progress" },
  { WSAENOTSOCK,        "[10038] Socket operation on non-socket" },
  { WSAEDESTADDRREQ,    "[10039] Destination address required" },
  { WSAEMSGSIZE,        "[10040] Message too long" },
  { WSAEPROTOTYPE,      "[10041] Protocol wrong type for socket" },
  { WSAENOPROTOOPT,     "[10042] Bad protocol option" },
  { WSAEPROTONOSUPPORT, "[10043] Protocol not supported" },
  { WSAESOCKTNOSUPPORT, "[10044] Socket type not supported" },
  { WSAEOPNOTSUPP,      "[10045] Operation not supported on socket" },
  { WSAEPFNOSUPPORT,    "[10046] Protocol family not supported" },
  { WSAEAFNOSUPPORT,    "[10047] Address family not supported by protocol family" },
  { WSAEADDRINUSE,      "[10048] Address already in use" },
  { WSAEADDRNOTAVAIL,   "[10049] Can not assign requested address" },
  { WSAENETDOWN,        "[10050] Network is down" },
  { WSAENETUNREACH,     "[10051] Network is unreachable" },
  { WSAENETRESET,       "[10052] Net dropped connection or reset" },
  { WSAECONNABORTED,    "[10053] Software caused connection abort" },
  { WSAECONNRESET,      "[10054] Connection reset by peer" },
  { WSAENOBUFS,         "[10055] No buffer space available" },
  { WSAEISCONN,         "[10056] Socket is already connected" },
  { WSAENOTCONN,        "[10057] Socket is not connected" },
  { WSAESHUTDOWN,       "[10058] Can not send after socket shutdown" },
  { WSAETOOMANYREFS,    "[10059] Too many references, can't splice" },
  { WSAETIMEDOUT,       "[10060] Connection timed out" },
  { WSAECONNREFUSED,    "[10061] Connection refused" },
  { WSAELOOP,           "[10062] Too many levels of symbolic links" },
  { WSAENAMETOOLONG,    "[10063] File name too long" },
  { WSAEHOSTDOWN,       "[10064] Host is down" },
  { WSAEHOSTUNREACH,    "[10065] No Route to Host" },
  { WSAENOTEMPTY,       "[10066] Directory not empty" },
  { WSAEPROCLIM,        "[10067] Too many processes" },
  { WSAEUSERS,          "[10068] Too many users" },
  { WSAEDQUOT,          "[10069] Disc Quota Exceeded" },
  { WSAESTALE,          "[10070] Stale NFS file handle" },
  { WSAEREMOTE,         "[10071] Too many levels of remote in path" },
  { WSASYSNOTREADY,     "[10091] Network SubSystem is unavailable" },
  { WSAVERNOTSUPPORTED, "[10092] WINSOCK DLL Version out of range" },
  { WSANOTINITIALISED,  "[10093] Successful WSASTARTUP not yet performed" },
  { WSAHOST_NOT_FOUND,  "[11001] Host not found" },
  { WSATRY_AGAIN,       "[11002] Non-Authoritative Host not found" },
  { WSANO_RECOVERY,     "[11003] Non-Recoverable errors: FORMERR, REFUSED, NOTIMP" },
  { WSANO_DATA,         "[11004] Valid name, no data record of requested" },
  END_OF_TABLE
};
static const int  WinsockErrMsgTableSize = (sizeof(WinsockErrMsgTable) / sizeof(DWC_msg_entry_t)) - 1;
#endif /* __WIN32_DW */

static DWC_error_code_t     wait_send_socket ( DWC_fdsocket_t sock, DWC_timeout_t timeout );
static DWC_error_code_t     wait_recv_socket ( DWC_fdsocket_t sock, DWC_timeout_t timeout );

/* -------------------------------------------------------------------- */
DWC_bool_t set_nonblocking_fd( DWC_fdsocket_t fd )
{
#if defined(__UNIX_DW)
    int         flags;

    flags = fcntl( fd, F_GETFL, 0 );
    if( flags < 0 )
        return FALSE;
    flags |= O_NONBLOCK;
    if( fcntl( fd, F_SETFL, flags ) < 0 )
        return FALSE;
    else
        return TRUE;
#endif /* __UNIX_DW */
#if defined(__WIN32_DW)
    int     one = 1;

    if( SOCKET_ERROR == ioctlsocket( fd, FIONBIO, &one ) )
        return FALSE;
    else 
        return TRUE;
#endif /* __WIN32_DW */
}

/* -------------------------------------------------------------------- */
DWC_string_t socket_error ( void )
{
#if defined(__WIN32_DW)
    static char error[256];
    static const int esize = sizeof(error) - 1;
    char* str = findWhat( WSAGetLastError(), WinsockErrMsgTable, FIND_EQ );
    strncpy( error, str, esize );
    DWC_Free(str);
    return error;
#endif /* __WIN32_DW */
#if defined(__UNIX_DW)
    return stdlib_error( errno );
#endif /* __UNIX_DW */
}

/* -------------------------------------------------------------------- */
DWC_error_code_t bind_socket ( DWC_fdsocket_t sock, struct sockaddr* addr, DWC_socklen_t len )
{
    if( SOCK_ERR(bind( sock, addr, len )) )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: bind() failed - %s", socket_error() );
        return DWC_IPCSYSTEM;
    }
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t listen_socket ( DWC_fdsocket_t sock, int backlog )
{
    if( SOCK_ERR(listen( sock, backlog )) )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: listen() failed - %s", socket_error() );
        return DWC_IPCSYSTEM;
    }
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t accept_socket ( DWC_fdsocket_t sock, struct sockaddr* addr, DWC_socklen_t* plen, DWC_fdsocket_t *newsock )
{
    *newsock = accept( sock, addr, plen );
    if( SOCK_ERR(*newsock) )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: accept() failed - %s", socket_error() );
        return DWC_IPCSYSTEM;
    }
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t connect_socket ( DWC_fdsocket_t sock, struct sockaddr* addr, DWC_socklen_t len, DWC_timeout_t timeout )
{
    DWC_error_code_t  err;

    if( SOCK_ERR(connect( sock, addr, len )) )
    {
        if( IN_PROGRESS )
        {
            err = wait_send_socket( sock, timeout );
            if( err == DWC_NOERROR  )
            {
                return DWC_NOERROR;
            }
        }
        else
        {
            err = DWC_IPCSYSTEM;
        }
        printLib( DW_LL_ERROR, "dwlib: fd: connect() failed - %s", socket_error() );
        return err;
    }
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t send_socket ( DWC_fdsocket_t sock, DWC_pointer_t buf, DWC_length_t *len, DWC_timeout_t timo )
{
    int sent;
    DWC_error_code_t error;

    if( timo != INFINITE_TIMEOUT )
    {
wait_again:
        error = wait_send_socket( sock, timo );
        if( error != DWC_NOERROR )
        {
            return error;
        }
    }
send_again:
    sent = send( sock, buf, *len, 0 );
    if( SOCK_ERR(sent) )
    {
        if( IN_PROGRESS )
        {
            goto wait_again;
        }
        if( WAS_INTERRUPTED )
        {
            printLib( DW_LL_VERBOSE, "dwlib: fd: send(%d) was interrupted - go again", *len );
            goto send_again;
        }
        printLib( DW_LL_ERROR, "dwlib: fd: send(%d) failed - %s", *len, socket_error() );
        (*len) = 0;
        return DWC_IPCSYSTEM;
    }
    (*len) = sent;
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t recv_socket ( DWC_fdsocket_t sock, DWC_pointer_t buf, DWC_length_t *len, DWC_timeout_t timo )
{
    int              readed, needbytes;
    DWC_error_code_t error;
    
    needbytes = *len;
    readed = 0;
    *len = 0;
    printLib( DW_LL_EXT_DEBUG, "dwlib: fd: recv(%d) called", needbytes );
    if( timo != INFINITE_TIMEOUT )
    {
wait_again:
        error = wait_recv_socket( sock, timo );
        if( error != DWC_NOERROR )
        {
            printLib( DW_LL_DEBUG, "dwlib: fd: recv(%d), readed=%d - wait failed", needbytes, readed );
            return error;
        }
    }
recv_again:
    readed = recv( sock, buf, needbytes, 0 );
    if( SOCK_ERR(readed) )
    {
        if( IN_PROGRESS )
        {
            goto wait_again;
        }
        if( WAS_INTERRUPTED )
        {
            printLib( DW_LL_VERBOSE, "dwlib: fd: recv(%d) was interrupted - go again", needbytes );
            goto recv_again;
        }
        printLib( DW_LL_ERROR, "dwlib: fd: recv(%d) failed - %s", needbytes, socket_error() );
        (*len) = 0;
        return DWC_IPCSYSTEM;
    }
    if( readed == 0 )
    {
        printLib( DW_LL_VERBOSE, "dwlib: fd: recv(%d) - connection closed by remote side", needbytes );
        (*len) = 0;
        return DWC_NODATA;
    }
    if( readed != (*len) )
    {
        printLib( DW_LL_EXT_DEBUG, "dwlib: fd: recv(%d), readed=%d", needbytes, readed );
    }
    (*len) = readed;
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t wait_send_socket( DWC_fdsocket_t sock, DWC_timeout_t timeout )
{
    int            rd;
#if !defined(DW_HAVE_POLL)
    fd_set         wr_set, ex_set;
    struct timeval timo, *pt;

    DWC_FD_CHECK( sock, "dwlib: fd", return DWC_COMMONERROR );
wait_again:
    FD_ZERO( &wr_set );
    FD_ZERO( &ex_set );
    FD_SET( sock, &wr_set );
    FD_SET( sock, &ex_set );    
    pt = timeout_to_timeval( timeout, &timo ); 
    rd = select( sock+1, NULL, &wr_set, &ex_set, pt );
    if( (rd == 0) && pt )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for send failed - timeout expired" );
        return DWC_TIMED;
    }
    if( (rd < 0) && WAS_INTERRUPTED )
    {
        printLib( DW_LL_VERBOSE, "dwlib: fd: wait for send was interrupted - go again" );
        goto wait_again;
    }
    if( rd < 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for send failed - %s", socket_error() );
        return DWC_IPCSYSTEM;
    }
    if( FD_ISSET( sock, &ex_set ) )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for send failed - exceptional situastion on socket" );
        return DWC_IPCSYSTEM;        
    }
#else /* HAVE_POLL */
    struct pollfd  fdp;

wait_again:    
    fdp.fd = sock;
    fdp.events = POLLOUT;
    fdp.revents = 0;
    rd = poll( &fdp, 1, timeout );    
    if( rd == 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for send failed - timeout expired" );
        return DWC_TIMED;
    }
    if( (rd < 0) && WAS_INTERRUPTED )
    {
        printLib( DW_LL_VERBOSE, "dwlib: fd: wait for send was interrupted - go again" );
        goto wait_again;
    }
    if( rd < 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for send failed - %s", socket_error() );
        return DWC_IPCSYSTEM;
    }
    if( !dw_bm_contain( fdp.revents, POLLOUT ) )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for send failed - poll return %#x event mask", fdp.revents );
        return DWC_IPCSYSTEM;
    }    
#endif /* HAVE_POLL */
    return DWC_NOERROR;
}

/* -------------------------------------------------------------------- */
DWC_error_code_t wait_recv_socket( DWC_fdsocket_t sock, DWC_timeout_t timeout )
{
    int            rd;
#if !defined(DW_HAVE_POLL)
    fd_set         rd_set, ex_set;
    struct timeval timo, *pt;

    DWC_FD_CHECK( sock, "dwlib: fd", return DWC_COMMONERROR );
wait_again:
    FD_ZERO( &rd_set );
    FD_ZERO( &ex_set );
    FD_SET( sock, &rd_set );
    FD_SET( sock, &ex_set );    
    pt = timeout_to_timeval( timeout, &timo );
    rd = select( sock+1, &rd_set, NULL, &ex_set, pt );
    if( (rd == 0) && pt )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for recv failed - timeout expired" );
        return DWC_TIMED;
    }
    if( (rd < 0) && WAS_INTERRUPTED )
    {
        printLib( DW_LL_VERBOSE, "dwlib: fd: wait for recv was interrupted - go again" );
        goto wait_again;
    }
    if( rd < 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for recv failed - %s", socket_error() );
        return DWC_IPCSYSTEM;
    }
    if( FD_ISSET( sock, &ex_set ) )
    {
        printLib( DW_LL_ERROR, "dwlib: TCP: wait for recv failed - exceptional situation on socket" );
        return DWC_IPCSYSTEM;        
    }
#else /* HAVE_POLL */
    struct pollfd  fdp;
    
wait_again:
    fdp.fd = sock;
    fdp.events = POLLIN;
    fdp.revents = 0;
    rd = poll( &fdp, 1, timeout );    
    if( rd == 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for recv failed - timeout expired" );
        return DWC_TIMED;
    }
    if( (rd < 0) && WAS_INTERRUPTED )
    {
        printLib( DW_LL_VERBOSE, "dwlib: fd: wait for recv was interrupted - go again" );
        goto wait_again;
    }
    if( rd < 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: fd: wait for recv failed - %s", socket_error() );
        return DWC_IPCSYSTEM;
    }
    if( !dw_bm_contain( fdp.revents, POLLIN ) )
    {
        if( dw_bm_equal( fdp.revents, POLLHUP ) )
        {
            printLib( DW_LL_VERBOSE, "dwlib: fd: wait for recv - connection closed by remote side", fdp.revents );        
            return DWC_NODATA;
        }
        printLib( DW_LL_ERROR, "dwlib: fd: wait for recv failed - poll return %#x event mask", fdp.revents );        
        return DWC_IPCSYSTEM;
    }    
#endif /* HAVE_POLL */
    return DWC_NOERROR;
}

#endif /* __DWC_FD_C */
