/*
 *  WINGs WMConnection function library
 *
 *  Copyright (c) 1999-2003 Dan Pascu
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


/*
 * TODO:
 * - decide if we want to support connections with external sockets, else
 *   clean up the structure of the unneeded members.
 * - decide what to do with all wwarning() calls that are still there.
 *
 */


#include "wconfig.h"

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <signal.h>
#ifdef __FreeBSD__
#include <sys/signal.h>
#endif

#include "WINGs.h"


/* Some older systems does not define this (linux libc5, maybe others too) */
#ifndef SHUT_RDWR
# define SHUT_RDWR              2
#endif

/* For SunOS */
#ifndef SA_RESTART
# define SA_RESTART             0
#endif

/* For Solaris */
#ifndef INADDR_NONE
# define INADDR_NONE           -1
#endif

/* Stuff for setting the sockets into non-blocking mode. */
/*
#ifdef	__POSIX_SOURCE
# define NONBLOCK_OPT           O_NONBLOCK
#else
# define NONBLOCK_OPT           FNDELAY
#endif
*/

#define NONBLOCK_OPT            O_NONBLOCK

#define NETBUF_SIZE             4096

#define DEF_TIMEOUT             600   /* 600 seconds == 10 minutes */



int WCErrorCode = 0;

static Bool SigInitialized = False;

static unsigned int DefaultTimeout = DEF_TIMEOUT;
static unsigned int OpenTimeout = DEF_TIMEOUT;



typedef struct TimeoutData {
    unsigned timeout;
    WMHandlerID *handler;
} TimeoutData;


typedef struct W_Connection {
    int sock;                     /* the socket we speak through */

    struct {
        WMHandlerID *read;        /* the input read handler */
        WMHandlerID *write;       /* the input write handler */
        WMHandlerID *exception;   /* the input exception handler */
    } handler;

    ConnectionDelegate *delegate; /* client delegates */
    void *clientData;             /* client data */
    unsigned int uflags;          /* flags for the client */

    WMArray *outputQueue;
    unsigned bufPos;

    TimeoutData sendTimeout;
    TimeoutData openTimeout;

    WMConnectionState state;
    WMConnectionTimeoutState timeoutState;

    char *address;
    char *service;
    char *protocol;

    Bool closeOnRelease;
    Bool shutdownOnClose;
    Bool wasNonBlocking;
    Bool isNonBlocking;

} W_Connection;



static void
clearOutputQueue(WMConnection *cPtr)
{
    cPtr->bufPos = 0;
    WMEmptyArray(cPtr->outputQueue);
}


static void
openTimeout(void *cdata)
{
    WMConnection *cPtr = (WMConnection*) cdata;

    cPtr->openTimeout.handler = NULL;
    if (cPtr->handler.write) {
        WMDeleteInputHandler(cPtr->handler.write);
        cPtr->handler.write = NULL;
    }
    if (cPtr->state != WCConnected) {
        cPtr->state = WCTimedOut;
        cPtr->timeoutState = WCTWhileOpening;
        if (cPtr->delegate && cPtr->delegate->didTimeout) {
            (*cPtr->delegate->didTimeout)(cPtr->delegate, cPtr);
        } else {
            WMCloseConnection(cPtr);
            cPtr->state = WCTimedOut; /* the above set state to WCClosed */
        }
    }
}


static void
sendTimeout(void *cdata)
{
    WMConnection *cPtr = (WMConnection*) cdata;

    cPtr->sendTimeout.handler = NULL;
    if (cPtr->handler.write) {
        WMDeleteInputHandler(cPtr->handler.write);
        cPtr->handler.write = NULL;
    }
    if (WMGetArrayItemCount(cPtr->outputQueue)>0) {
        clearOutputQueue(cPtr);
        cPtr->state = WCTimedOut;
        cPtr->timeoutState = WCTWhileSending;
        /* // should we close it no matter what (after calling the didTimeout
           // delegate)? -Dan */
        if (cPtr->delegate && cPtr->delegate->didTimeout) {
            (*cPtr->delegate->didTimeout)(cPtr->delegate, cPtr);
        } else {
            WMCloseConnection(cPtr);
            cPtr->state = WCTimedOut; /* the above set state to WCClosed */
        }
    }
}


static void
inputHandler(int fd, int mask, void *clientData)
{
    WMConnection *cPtr = (WMConnection*)clientData;

    if (cPtr->state==WCClosed || cPtr->state==WCDied)
        return;

    if ((mask & WIWriteMask)) {
        int result;

        if (cPtr->state == WCInProgress) {
            Bool failed;
            int len = sizeof(result);

            WCErrorCode = 0;
            if (getsockopt(cPtr->sock, SOL_SOCKET, SO_ERROR,
                           (void*)&result, &len) == 0 && result != 0) {
                cPtr->state = WCFailed;
                WCErrorCode = result;
                failed = True;
                /* should call wsyserrorwithcode(result, ...) here? */
            } else {
                cPtr->state = WCConnected;
                failed = False;
            }

            if (cPtr->handler.write) {
                WMDeleteInputHandler(cPtr->handler.write);
                cPtr->handler.write = NULL;
            }

            if (cPtr->openTimeout.handler) {
                WMDeleteTimerHandler(cPtr->openTimeout.handler);
                cPtr->openTimeout.handler = NULL;
            }

            if (cPtr->delegate && cPtr->delegate->didInitialize)
                (*cPtr->delegate->didInitialize)(cPtr->delegate, cPtr);

            /* we use failed and not cPtr->state here, because cPtr may be
             * destroyed by the delegate called above if the connection failed
             */
            if (failed)
                return;
        } else if (cPtr->state == WCConnected) {
            result = WMFlushConnection(cPtr);
            if (result>0 && cPtr->delegate && cPtr->delegate->canResumeSending) {
                (*cPtr->delegate->canResumeSending)(cPtr->delegate, cPtr);
            }
        }
    }

    if (!cPtr->delegate)
        return;

    /* if the connection died, may get destroyed in the delegate, so retain */
    wretain(cPtr);

    if ((mask & WIReadMask) && cPtr->delegate->didReceiveInput)
        (*cPtr->delegate->didReceiveInput)(cPtr->delegate, cPtr);

    if ((mask & WIExceptMask) && cPtr->delegate->didCatchException)
        (*cPtr->delegate->didCatchException)(cPtr->delegate, cPtr);

    wrelease(cPtr);
}


static Bool
setSocketNonBlocking(int sock, Bool flag)
{
    int state;
    Bool isNonBlock;

    state = fcntl(sock, F_GETFL, 0);

    if (state < 0) {
        /* set WCErrorCode here? -Dan*/
        return False;
    }

    isNonBlock = (state & NONBLOCK_OPT) != 0;

    if (flag) {
        if (isNonBlock)
            return True;
        state |= NONBLOCK_OPT;
    } else {
        if (!isNonBlock)
            return True;
        state &= ~NONBLOCK_OPT;
    }

    if (fcntl(sock, F_SETFL, state) < 0) {
        /* set WCErrorCode here? -Dan*/
        return False;
    }

    return True;
}


static void
setConnectionAddress(WMConnection *cPtr, struct sockaddr_in *socketaddr)
{
    wassertr(cPtr->address==NULL);

    cPtr->address = wstrdup(inet_ntoa(socketaddr->sin_addr));
    cPtr->service = wmalloc(16);
    sprintf(cPtr->service, "%hu", ntohs(socketaddr->sin_port));
    cPtr->protocol = wstrdup("tcp");
}


static struct sockaddr_in*
getSocketAddress(char* name, char* service, char* protocol)
{
    static struct sockaddr_in socketaddr;
    struct servent *sp;

    if (!protocol || protocol[0]==0)
        protocol = "tcp";

    memset(&socketaddr, 0, sizeof(struct sockaddr_in));
    socketaddr.sin_family = AF_INET;

    /*
     * If we were given a hostname, we use any address for that host.
     * Otherwise we expect the given name to be an address unless it is
     * NULL (any address).
     */
    if (name && name[0]!=0) {
        WMHost *host = WMGetHostWithName(name);

        if (!host)
            return NULL; /* name is not a hostname nor a number and dot adr */

        name = WMGetHostAddress(host);
#ifndef	HAVE_INET_ATON
        if ((socketaddr.sin_addr.s_addr = inet_addr(name)) == INADDR_NONE) {
#else
            if (inet_aton(name, &socketaddr.sin_addr) == 0) {
#endif
            WMReleaseHost(host);
            return NULL;
        }
        WMReleaseHost(host);
    } else {
        socketaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    }

    if (!service || service[0]==0) {
        socketaddr.sin_port = 0;
    } else if ((sp = getservbyname(service, protocol))==0) {
        char *endptr;
        unsigned portNumber;

        portNumber = strtoul(service, &endptr, 10);

        if (service[0]!=0 && *endptr==0 && portNumber<65536) {
            socketaddr.sin_port = htons(portNumber);
        } else {
            return NULL;
        }
    } else {
        socketaddr.sin_port = sp->s_port;
    }

    return &socketaddr;
}


static void
dummyHandler(int signum)
{
}


static WMConnection*
createConnectionWithSocket(int sock, Bool closeOnRelease)
{
    WMConnection *cPtr;
    struct sigaction sig_action;

    cPtr = wmalloc(sizeof(WMConnection));
    wretain(cPtr);
    memset(cPtr, 0, sizeof(WMConnection));

    fcntl(sock, F_SETFD, FD_CLOEXEC); /* by default close on exec */

    cPtr->sock = sock;
    cPtr->openTimeout.timeout = OpenTimeout;
    cPtr->openTimeout.handler = NULL;
    cPtr->sendTimeout.timeout = DefaultTimeout;
    cPtr->sendTimeout.handler = NULL;
    cPtr->closeOnRelease = closeOnRelease;
    cPtr->shutdownOnClose = 1;
    cPtr->outputQueue =
        WMCreateArrayWithDestructor(16, (WMFreeDataProc*)WMReleaseData);
    cPtr->state = WCNotConnected;
    cPtr->timeoutState = WCTNone;

    /* ignore dead pipe */
    if (!SigInitialized) {
        /* Because POSIX mandates that only signal with handlers are reset
         * accross an exec*(), we do not want to propagate ignoring SIGPIPEs
         * to children. Hence the dummy handler. Philippe Troin <phil@fifi.org>
         */
        sig_action.sa_handler = &dummyHandler;
        sig_action.sa_flags = SA_RESTART;
        sigaction(SIGPIPE, &sig_action, NULL);
        SigInitialized = True;
    }

    return cPtr;
}


#if 0
WMConnection*
WMCreateConnectionWithSocket(int sock, Bool closeOnRelease)
{
    WMConnection *cPtr;
    struct sockaddr_in clientname;
    int size;
    int n;

    cPtr = createConnectionWithSocket(sock, closeOnRelease);
    cPtr->wasNonBlocking = WMIsConnectionNonBlocking(cPtr);
    cPtr->isNonBlocking = cPtr->wasNonBlocking;

    /* some way to find out if it is connected, and binded. can't find
     if it listens though!!!
     */

    size = sizeof(clientname);
    n = getpeername(sock, (struct sockaddr*) &clientname, &size);
    if (n==0) {
        /* Since we have a peer, it means we are connected */
        cPtr->state = WCConnected;
    } else {
        size = sizeof(clientname);
        n = getsockname(sock, (struct sockaddr*) &clientname, &size);
        if (n==0) {
            /* We don't have a peer, but we are binded to an address.
             * Assume we are listening on it (we don't know that for sure!)
             */
            cPtr->state = WCListening;
        } else {
            cPtr->state = WCNotConnected;
        }
    }

    return cPtr;
}
#endif


/*
 * host     is the name on which we want to listen for incoming connections,
 *          and it must be a name of this host, or NULL if we want to listen
 *          on any incoming address.
 * service  is either a service name as present in /etc/services, or the port
 *          number we want to listen on. If NULL, a random port between
 *          1024 and 65535 will be assigned to us.
 * protocol is one of "tcp" or "udp". If NULL, "tcp" will be used by default.
 *          currently only "tcp" is supported.
 */
WMConnection*
WMCreateConnectionAsServerAtAddress(char *host, char *service, char *protocol)
{
    WMConnection *cPtr;
    struct sockaddr_in *socketaddr;
    int sock, on;
    int size;

    WCErrorCode = 0;

    if ((socketaddr = getSocketAddress(host, service, protocol)) == NULL) {
        wwarning(_("Bad address-service-protocol combination"));
        return NULL;
    }

    /* Create the actual socket */
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock<0) {
        WCErrorCode = errno;
        return NULL;
    }

    /*
     * Set socket options. We try to make the port reusable and have it
     * close as fast as possible without waiting in unnecessary wait states
     * on close.
     */
    on = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&on, sizeof(on));

    if (bind(sock, (struct sockaddr *)socketaddr, sizeof(*socketaddr)) < 0) {
        WCErrorCode = errno;
        close(sock);
        return NULL;
    }

    if (listen(sock, 10) < 0) {
        WCErrorCode = errno;
        close(sock);
        return NULL;
    }

    /* Find out what is the address/service/protocol we get */
    /* In case some of address/service/protocol were NULL */
    size = sizeof(*socketaddr);
    if (getsockname(sock, (struct sockaddr*)socketaddr, &size) < 0) {
        WCErrorCode = errno;
        close(sock);
        return NULL;
    }

    cPtr = createConnectionWithSocket(sock, True);
    cPtr->state = WCListening;
    WMSetConnectionNonBlocking(cPtr, True);

    setConnectionAddress(cPtr, socketaddr);

    return cPtr;
}


WMConnection*
WMCreateConnectionToAddress(char *host, char *service, char *protocol)
{
    WMConnection *cPtr;
    struct sockaddr_in *socketaddr;
    int sock;

    WCErrorCode = 0;

    wassertrv(service!=NULL && service[0]!=0, NULL);

    if (host==NULL || host[0]==0)
        host = "localhost";

    if ((socketaddr = getSocketAddress(host, service, protocol)) == NULL) {
        wwarning(_("Bad address-service-protocol combination"));
        return NULL;
    }

    /* Create the actual socket */
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock<0) {
        WCErrorCode = errno;
        return NULL;
    }
    /* make socket blocking while we connect. */
    setSocketNonBlocking(sock, False);
    if (connect(sock, (struct sockaddr*)socketaddr, sizeof(*socketaddr)) < 0) {
        WCErrorCode = errno;
        close(sock);
        return NULL;
    }

    cPtr = createConnectionWithSocket(sock, True);
    cPtr->state = WCConnected;
    WMSetConnectionNonBlocking(cPtr, True);
    setConnectionAddress(cPtr, socketaddr);

    return cPtr;
}


WMConnection*
WMCreateConnectionToAddressAndNotify(char *host, char *service, char *protocol)
{
    WMConnection *cPtr;
    struct sockaddr_in *socketaddr;
    int sock;
    Bool isNonBlocking;

    WCErrorCode = 0;

    wassertrv(service!=NULL && service[0]!=0, NULL);

    if (host==NULL || host[0]==0)
        host = "localhost";

    if ((socketaddr = getSocketAddress(host, service, protocol)) == NULL) {
        wwarning(_("Bad address-service-protocol combination"));
        return NULL;
    }

    /* Create the actual socket */
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock<0) {
        WCErrorCode = errno;
        return NULL;
    }
    isNonBlocking = setSocketNonBlocking(sock, True);
    if (connect(sock, (struct sockaddr*)socketaddr, sizeof(*socketaddr)) < 0) {
        if (errno!=EINPROGRESS) {
            WCErrorCode = errno;
            close(sock);
            return NULL;
        }
    }

    cPtr = createConnectionWithSocket(sock, True);
    cPtr->state = WCInProgress;
    cPtr->isNonBlocking = isNonBlocking;

    cPtr->handler.write = WMAddInputHandler(cPtr->sock, WIWriteMask,
                                            inputHandler, cPtr);

    cPtr->openTimeout.handler =
        WMAddTimerHandler(cPtr->openTimeout.timeout*1000, openTimeout, cPtr);

    setConnectionAddress(cPtr, socketaddr);

    return cPtr;
}


static void
removeAllHandlers(WMConnection *cPtr)
{
    if (cPtr->handler.read)
        WMDeleteInputHandler(cPtr->handler.read);
    if (cPtr->handler.write)
        WMDeleteInputHandler(cPtr->handler.write);
    if (cPtr->handler.exception)
        WMDeleteInputHandler(cPtr->handler.exception);
    if (cPtr->openTimeout.handler)
        WMDeleteTimerHandler(cPtr->openTimeout.handler);
    if (cPtr->sendTimeout.handler)
        WMDeleteTimerHandler(cPtr->sendTimeout.handler);

    cPtr->handler.read = NULL;
    cPtr->handler.write = NULL;
    cPtr->handler.exception = NULL;
    cPtr->openTimeout.handler = NULL;
    cPtr->sendTimeout.handler = NULL;
}


void
WMDestroyConnection(WMConnection *cPtr)
{
    if (cPtr->closeOnRelease && cPtr->sock>=0) {
        if (cPtr->shutdownOnClose) {
            shutdown(cPtr->sock, SHUT_RDWR);
        }
        close(cPtr->sock);
    }

    removeAllHandlers(cPtr);
    WMFreeArray(cPtr->outputQueue); /* will also free the items with the destructor */

    if (cPtr->address) {
        wfree(cPtr->address);
        wfree(cPtr->service);
        wfree(cPtr->protocol);
    }

    wrelease(cPtr);
}


void
WMCloseConnection(WMConnection *cPtr)
{
    if (cPtr->sock>=0) {
        if (cPtr->shutdownOnClose) {
            shutdown(cPtr->sock, SHUT_RDWR);
        }
        close(cPtr->sock);
        cPtr->sock = -1;
    }

    removeAllHandlers(cPtr);
    clearOutputQueue(cPtr);

    cPtr->state = WCClosed;
}


WMConnection*
WMAcceptConnection(WMConnection *listener)
{
    struct sockaddr_in clientname;
    int size;
    int newSock;
    WMConnection *newConnection;

    WCErrorCode = 0;
    wassertrv(listener && listener->state==WCListening, NULL);

    size = sizeof(clientname);
    newSock = accept(listener->sock, (struct sockaddr*) &clientname, &size);
    if (newSock<0) {
        WCErrorCode = ((errno!=EAGAIN && errno!=EWOULDBLOCK) ? errno : 0);
        return NULL;
    }

    newConnection = createConnectionWithSocket(newSock, True);
    WMSetConnectionNonBlocking(newConnection, True);
    newConnection->state = WCConnected;
    setConnectionAddress(newConnection, &clientname);

    return newConnection;
}


char*
WMGetConnectionAddress(WMConnection *cPtr)
{
    return cPtr->address;
}


char*
WMGetConnectionService(WMConnection *cPtr)
{
    return cPtr->service;
}


char*
WMGetConnectionProtocol(WMConnection *cPtr)
{
    return cPtr->protocol;
}


int
WMGetConnectionSocket(WMConnection *cPtr)
{
    return cPtr->sock;
}


WMConnectionState
WMGetConnectionState(WMConnection *cPtr)
{
    return cPtr->state;
}


WMConnectionTimeoutState
WMGetConnectionTimeoutState(WMConnection *cPtr)
{
    return cPtr->timeoutState;
}


Bool
WMEnqueueConnectionData(WMConnection *cPtr, WMData *data)
{
    wassertrv(cPtr->state!=WCNotConnected && cPtr->state!=WCListening, False);
    wassertrv(cPtr->state!=WCInProgress && cPtr->state!=WCFailed, False);

    if (cPtr->state!=WCConnected)
        return False;

    WMAddToArray(cPtr->outputQueue, WMRetainData(data));
    return True;
}


/*
 * Return value:
 * -1 - not connected or connection died while sending
 *  0 - couldn't send the data (or part of it). data is saved in a queue
 *      and will be sent when possible. after it is sent the canResumeSending
 *      callback will be called.
 *  1 - data was succesfully sent
 */
int
WMSendConnectionData(WMConnection *cPtr, WMData *data)
{
    int bytes, pos, len;
    TimeoutData *tPtr = &cPtr->sendTimeout;
    const unsigned char *dataBytes;

    wassertrv(cPtr->state!=WCNotConnected && cPtr->state!=WCListening, -1);
    wassertrv(cPtr->state!=WCInProgress && cPtr->state!=WCFailed, -1);

    if (cPtr->state!=WCConnected)
        return -1;

    /* If we have no data just flush the queue, else try to send data */
    if (data && WMGetDataLength(data)>0) {
        WMAddToArray(cPtr->outputQueue, WMRetainData(data));
        /* If there already was something in queue, and also a write input
         * handler is established, it means we were unable to send, so
         * return and let the write handler notify us when we can send.
         */
        if (WMGetArrayItemCount(cPtr->outputQueue)>1 && cPtr->handler.write)
            return 0;
    }

    while (WMGetArrayItemCount(cPtr->outputQueue) > 0) {
        data = WMGetFromArray(cPtr->outputQueue, 0);
        dataBytes = (const unsigned char *)WMDataBytes(data);
        len = WMGetDataLength(data);
        pos = cPtr->bufPos; /* where we're left last time */
        while(pos < len) {
        again:
            bytes = write(cPtr->sock, dataBytes+pos, len - pos);
            if(bytes<0) {
                switch (errno) {
                case EINTR:
                    goto again;
                case EWOULDBLOCK:
                    /* save the position where we're left and add a timeout */
                    cPtr->bufPos = pos;
                    if (!tPtr->handler) {
                        tPtr->handler = WMAddTimerHandler(tPtr->timeout*1000,
                                                          sendTimeout, cPtr);
                    }
                    if (!cPtr->handler.write) {
                        cPtr->handler.write =
                            WMAddInputHandler(cPtr->sock, WIWriteMask,
                                              inputHandler, cPtr);
                    }
                    return 0;
                default:
                    WCErrorCode = errno;
                    cPtr->state = WCDied;
                    removeAllHandlers(cPtr);
                    if (cPtr->delegate && cPtr->delegate->didDie)
                        (*cPtr->delegate->didDie)(cPtr->delegate, cPtr);
                    return -1;
                }
            }
            pos += bytes;
        }
        WMDeleteFromArray(cPtr->outputQueue, 0);
        cPtr->bufPos = 0;
        if (tPtr->handler) {
            WMDeleteTimerHandler(tPtr->handler);
            tPtr->handler = NULL;
        }
        /*if (cPtr->handler.write) {
         WMDeleteInputHandler(cPtr->handler.write);
         cPtr->handler.write = NULL;
         }*/
    }

    if (cPtr->handler.write) {
        WMDeleteInputHandler(cPtr->handler.write);
        cPtr->handler.write = NULL;
    }

    return 1;
}


/*
 * WMGetConnectionAvailableData(connection):
 *
 * will return a WMData structure containing the available data on the
 * specified connection. If connection is non-blocking (default) and no data
 * is available when this function is called, an empty WMData is returned.
 *
 * If an error occurs while reading or the other side closed connection,
 * it will return NULL.
 * Also trying to read from an already died or closed connection is
 * considered to be an error condition, and will return NULL.
 */
WMData*
WMGetConnectionAvailableData(WMConnection *cPtr)
{
    char buffer[NETBUF_SIZE];
    int nbytes;
    WMData *aData;

    wassertrv(cPtr->state!=WCNotConnected && cPtr->state!=WCListening, NULL);
    wassertrv(cPtr->state!=WCInProgress && cPtr->state!=WCFailed, NULL);

    if (cPtr->state!=WCConnected)
        return NULL;

    aData = NULL;

again:
    nbytes = read(cPtr->sock, buffer, NETBUF_SIZE);
    if (nbytes<0) {
        switch (errno) {
        case EINTR:
            goto again;
        case EWOULDBLOCK:
            aData = WMCreateDataWithCapacity(0);
            break;
        default:
            WCErrorCode = errno;
            cPtr->state = WCDied;
            removeAllHandlers(cPtr);
            if (cPtr->delegate && cPtr->delegate->didDie)
                (*cPtr->delegate->didDie)(cPtr->delegate, cPtr);
            break;
        }
    } else if (nbytes==0) {      /* the other side has closed connection */
        cPtr->state = WCClosed;
        removeAllHandlers(cPtr);
        if (cPtr->delegate && cPtr->delegate->didDie)
            (*cPtr->delegate->didDie)(cPtr->delegate, cPtr);
    } else {
        aData = WMCreateDataWithBytes(buffer, nbytes);
    }

    return aData;
}


void
WMSetConnectionDelegate(WMConnection *cPtr, ConnectionDelegate *delegate)
{
    wassertr(cPtr->sock >= 0);
    /* Don't try to set the delegate multiple times */
    wassertr(cPtr->delegate == NULL);

    cPtr->delegate = delegate;
    if (delegate && delegate->didReceiveInput && !cPtr->handler.read)
        cPtr->handler.read = WMAddInputHandler(cPtr->sock, WIReadMask,
                                               inputHandler, cPtr);
    if (delegate && delegate->didCatchException && !cPtr->handler.exception)
        cPtr->handler.exception = WMAddInputHandler(cPtr->sock, WIExceptMask,
                                                    inputHandler, cPtr);
}


#if 0
Bool
WMIsConnectionNonBlocking(WMConnection *cPtr)
{
#if 1
    int state;

    state = fcntl(cPtr->sock, F_GETFL, 0);

    if (state < 0) {
        /* If we can't use fcntl on socket, this probably also means we could
         * not use fcntl to set non-blocking mode, and since a socket defaults
         * to blocking when created, return False as the best assumption */
        return False;
    }

    return ((state & NONBLOCK_OPT)!=0);
#else
    return cPtr->isNonBlocking;
#endif
}
#endif


Bool
WMSetConnectionNonBlocking(WMConnection *cPtr, Bool flag)
{
    wassertrv(cPtr!=NULL && cPtr->sock>=0, False);

    flag = ((flag==0) ? 0 : 1);

    if (cPtr->isNonBlocking == flag)
        return True;

    if (setSocketNonBlocking(cPtr->sock, flag)==True) {
        cPtr->isNonBlocking = flag;
        return True;
    }

    return False;
}


Bool
WMSetConnectionCloseOnExec(WMConnection *cPtr, Bool flag)
{
    wassertrv(cPtr!=NULL && cPtr->sock>=0, False);

    if (fcntl(cPtr->sock, F_SETFD, ((flag==0) ? 0 : FD_CLOEXEC)) < 0) {
        return False;
    }

    return True;
}


void
WMSetConnectionShutdownOnClose(WMConnection *cPtr, Bool flag)
{
    cPtr->shutdownOnClose = ((flag==0) ? 0 : 1);
}


void*
WMGetConnectionClientData(WMConnection *cPtr)
{
    return cPtr->clientData;
}


void
WMSetConnectionClientData(WMConnection *cPtr, void *data)
{
    cPtr->clientData = data;
}


unsigned int
WMGetConnectionFlags(WMConnection *cPtr)
{
    return cPtr->uflags;
}


void
WMSetConnectionFlags(WMConnection *cPtr, unsigned int flags)
{
    cPtr->uflags = flags;
}


WMArray*
WMGetConnectionUnsentData(WMConnection *cPtr)
{
    return cPtr->outputQueue;
}


void
WMSetConnectionDefaultTimeout(unsigned int timeout)
{
    if (timeout == 0) {
        DefaultTimeout = DEF_TIMEOUT;
    } else {
        DefaultTimeout = timeout;
    }
}


void
WMSetConnectionOpenTimeout(unsigned int timeout)
{
    if (timeout == 0) {
        OpenTimeout = DefaultTimeout;
    } else {
        OpenTimeout = timeout;
    }
}


void
WMSetConnectionSendTimeout(WMConnection *cPtr, unsigned int timeout)
{
    if (timeout == 0) {
        cPtr->sendTimeout.timeout = DefaultTimeout;
    } else {
        cPtr->sendTimeout.timeout = timeout;
    }
}




syntax highlighted by Code2HTML, v. 0.9.1