/* io.c:
 *
 * Implementation of buffered streams.
 *
 * Copyright (C) 1997 Art Technology Group, Inc.
 * All Rights Reserved.  No use, copying or distribution of this
 * work may be made except in accordance with a valid license
 * agreement from Art Technology Group.  This notice must be
 * included on all copies, modifications and derivatives of this
 * work.
 */

#include "DRPTypes.h"
#include "io.h"
#include "DynSock.h"


#define DEFAULT_BUFFER_SIZE 4098


/*---------------------------------------------------------------
 * External Globals
 */

#ifdef _ISAPI
/*
 * Our own heap for handling frequent memory allocations.
 */
extern HANDLE gHeap;
#endif


static const char *IOStatusMessages[IOStatusCount] = {
    "Succeeded",
    "Generic failure",
    "Feature has not been implemented",
    "No more data",
    "Read error",
    "Write error",
    "Bad URL",
    "Unknown host",
    "No more sockets",
    "Connection refused",
    "Network error",
    "Memory exhausted",
    "No more files can be opened",
    "No such file",
    "Permission denied",
    "Buffer overflow",
    "Protocol error",
    "Protocol version mismatch",
    "Operation failed due to excessive time",
    "Socket system initialization failed",
};

#ifdef WIN32
/*-----------------------------------------------------------------------------
 * Used to manage WinSock subsystem initialization
 */
static BOOL WinsockInitialized = FALSE;

static IOStatus initializeWinsock()
{
    WSADATA info;
    if (WSAStartup(MAKEWORD(1, 1), &info) != 0)
        return IOStatusInitializationFailed;
    return IOStatusOk;
}
#endif                          /* WIN32 */

/*-----------------------------------------------------------------------------
 * Returns a human-readable string describing the indicated IOStatus value.
 */
const char *getIOStatusString(IOStatus status)
{
    if ((status >= 0) && (status < IOStatusCount))
        return IOStatusMessages[(int) status];
    else
        return "Unknown status value";
}

/*-----------------------------------------------------------------------------
 * Creates a buffered input stream.
 * Returns NULL if unable to create a stream because out of memory.
 */
InputStream *createInputStream(void *handle,
                               InputStreamReader reader,
                               InputStreamCloser closer, int bufferSize)
{
#ifdef _ISAPI
    InputStream *retStream = HeapAlloc(gHeap, 0, sizeof(InputStream));
#else
    InputStream *retStream = malloc(sizeof(InputStream));
#endif
    if (retStream == NULL)
        return NULL;

    memset(retStream, 0, sizeof(InputStream));

    if (bufferSize <= 0)
        bufferSize = DEFAULT_BUFFER_SIZE;
    if (retStream != NULL) {
        retStream->handle = handle;
        retStream->reader = reader;
        retStream->closer = closer;
        retStream->bufferSize = bufferSize;
        retStream->timeout = 0;
        retStream->status = IOStatusOk;
#ifdef _ISAPI
        retStream->buffer = HeapAlloc(gHeap, 0, bufferSize);
        if (retStream->buffer == NULL) {
            (void) HeapFree(gHeap, 0, retStream);
            retStream = NULL;
        }
#else
        retStream->buffer = malloc(bufferSize);
        if (retStream->buffer == NULL) {
            free(retStream);
            retStream = NULL;
        }
#endif
    }
    return retStream;
}

/*-----------------------------------------------------------------------------
 * Fills the buffer of an input stream as much as immediately possible.  Waits
 * for more input if none is immediately available.  This is intended for
 * internal use.
 */
IOStatus fillInputStream(InputStream * stream)
{
    int spaceAvail;
    int bytesRead;

    if (stream->readp == stream->avail) /* reset read pointers */
        stream->readp = stream->avail = 0;
    spaceAvail = stream->bufferSize - stream->avail;
    if (spaceAvail > 0) {
        stream->status =
            stream->reader(stream->handle, &stream->buffer[stream->readp],
                           spaceAvail, stream->timeout, &bytesRead);
        if (stream->status == IOStatusOk)
            stream->avail += bytesRead;
    }
    return stream->status;
}

/*-----------------------------------------------------------------------------
 * Reads some data from an input stream.
 */
IOStatus readSomeFromInputStream(InputStream * stream, void *retBuffer,
                                 int size, int *retBytesRead)
{
    /* if the buffer is empty, fill it.
     */
    if (stream->readp == stream->avail)
        fillInputStream(stream);

    /* return as much data from the read buffer as we can.
     */
    if (size > stream->avail - stream->readp)
        size = stream->avail - stream->readp;
    memcpy(retBuffer, &stream->buffer[stream->readp], size);
    stream->readp += size;
    *retBytesRead = size;
    return stream->status;
}

/*-----------------------------------------------------------------------------
 * Reads as much data as is requested from an input stream, if possible.
 */
IOStatus readFromInputStream(InputStream * stream, void *retBuffer,
                             int size, unsigned int *read)
{
    char *bp = retBuffer;
    while ((stream->status == IOStatusOk) && (size > 0)) {
        int bytesToCopy;

        if (stream->avail == stream->readp)
            fillInputStream(stream);
        bytesToCopy = stream->avail - stream->readp;
        if (bytesToCopy > size)
            bytesToCopy = size;
        memcpy(bp, &stream->buffer[stream->readp], bytesToCopy);
        stream->readp += bytesToCopy;
        bp += bytesToCopy;
        size -= bytesToCopy;
    }
    *read = size;
    return stream->status;
}

/*-----------------------------------------------------------------------------
 * Destroys an input stream, closing its source if a closer function was specified.
 */
IOStatus destroyInputStream(InputStream * stream)
{
    IOStatus status = IOStatusOk;
    if (stream->closer != NULL)
        status = stream->closer(stream->handle);
#ifdef _ISAPI
    (void) HeapFree(gHeap, 0, stream->buffer);
    (void) HeapFree(gHeap, 0, stream);
#else
    free(stream->buffer);
    free(stream);
#endif
    return status;
}

/*-----------------------------------------------------------------------------
 * Creates a buffered output stream.
 * Returns NULL if unable to create a stream because out of memory.
 */
OutputStream *createOutputStream(void *handle,
                                 OutputStreamWriter writer,
                                 OutputStreamCloser closer, int bufferSize)
{
#ifdef _ISAPI
    OutputStream *retStream = HeapAlloc(gHeap, 0, sizeof(OutputStream));
#else
    OutputStream *retStream = malloc(sizeof(OutputStream));
#endif
    if (retStream == NULL)
        return NULL;

    memset(retStream, 0, sizeof(OutputStream));

    if (bufferSize <= 0)
        bufferSize = DEFAULT_BUFFER_SIZE;
    if (retStream != NULL) {
        retStream->handle = handle;
        retStream->writer = writer;
        retStream->closer = closer;
        retStream->bufferSize = bufferSize;
        retStream->status = IOStatusOk;
#ifdef _ISAPI
        retStream->buffer = HeapAlloc(gHeap, 0, bufferSize);
        if (retStream->buffer == NULL) {
            (void) HeapFree(gHeap, 0, retStream);
            retStream = NULL;
        }
#else
        retStream->buffer = malloc(bufferSize);
        if (retStream->buffer == NULL) {
            free(retStream);
            retStream = NULL;
        }
#endif
    }
    return retStream;
}

/*-----------------------------------------------------------------------------
 * Writes data to a buffered output stream.
 */
IOStatus writeToOutputStream(OutputStream * stream, const void *buf,
                             int size)
{
    const char *bp = (const char *) buf;
    while ((stream->status == IOStatusOk) && (size > 0)) {
        /* if the buffer is empty and the data to write is larger than
         * the buffer, send it straight through the writer to avoid the copy.
         */
        if ((stream->writep == 0) && (size > stream->bufferSize)) {
            stream->status = stream->writer(stream->handle, bp, size);
            break;
        } else {
            int bytesToCopy = stream->bufferSize - stream->writep;
            if (bytesToCopy > size)
                bytesToCopy = size;
            memcpy(&stream->buffer[stream->writep], bp, bytesToCopy);
            bp += bytesToCopy;
            stream->writep += bytesToCopy;
            size -= bytesToCopy;

            /* if the buffer is full, flush it. */
            if (stream->writep == stream->bufferSize) {
                stream->status =
                    stream->writer(stream->handle, stream->buffer,
                                   stream->bufferSize);
                stream->writep = 0;
            }
        }
    }
    return stream->status;
}

/*-----------------------------------------------------------------------------
 * Flushes buffered data to the output stream.
 */
IOStatus flushOutputStream(OutputStream * stream)
{
    if (stream->writep > 0) {
        stream->status =
            stream->writer(stream->handle, stream->buffer, stream->writep);
        stream->writep = 0;
    }
    return stream->status;
}

/*-----------------------------------------------------------------------------
 * Copies an InputStream to an OutputStream.
 */
IOStatus copyToOutputStream(OutputStream * outStream,
                            InputStream * inStream)
{
    IOStatus status;

    /* copy any buffered input directly to the output
     */
    if (inStream->readp < inStream->avail) {
        status = writeToOutputStream(outStream,
                                     &inStream->buffer[inStream->readp],
                                     inStream->avail - inStream->readp);
        inStream->avail = inStream->readp = 0;
        if (status != IOStatusOk)
            return status;
    }

    /* flush the output buffer
     */
    status = flushOutputStream(outStream);

    /* at this point both the input and output buffers are completely
     * empty, allowing us to read directly into the output buffer and
     * avoid a copy.
     */
    while (status == IOStatusOk) {
        int br;

        /* read a chunk of data */
        status =
            inStream->reader(inStream->handle, outStream->buffer,
                             outStream->bufferSize, inStream->timeout,
                             &br);
        if (status != IOStatusOk) {
            if (status == IOStatusNoMoreData)
                return IOStatusOk;
            else
                break;
        }

        /* write the chunk of data */
        status =
            outStream->writer(outStream->handle, outStream->buffer, br);
    }
    return status;
}

/*-----------------------------------------------------------------------------
 * Destroys an output stream, flushing its buffered data and closing its
 * destination if a closer function was specified.
 */
IOStatus destroyOutputStream(OutputStream * stream)
{
    IOStatus status = IOStatusOk;
    flushOutputStream(stream);
    if (stream->closer != NULL)
        status = stream->closer(stream->handle);
#ifdef _ISAPI
    (void) HeapFree(gHeap, 0, stream->buffer);
    (void) HeapFree(gHeap, 0, stream);
#else
    free(stream->buffer);
    free(stream);
#endif
    return status;
}

/*-----------------------------------------------------------------------------
 * Flushes deferred output buffers to the raw output stream.
 */
static IOStatus flushDeferredOutputStreamBuffers(DeferredOutputStreamHandle
                                                 * doh)
{
    IOStatus status = IOStatusOk;
    DeferredOutputStreamBuffer *dob = doh->deferredBuffers;
    while (dob != NULL) {
        DeferredOutputStreamBuffer *oldDob = dob;
        dob = dob->next;
        if (status == IOStatusOk)
            status =
                writeToOutputStream(doh->rawOutputStream, oldDob->buffer,
                                    oldDob->bufferSize);
#ifdef _ISAPI
        (void) HeapFree(gHeap, 0, oldDob->buffer);
        (void) HeapFree(gHeap, 0, oldDob);
#else
        free(oldDob->buffer);
        free(oldDob);
#endif
    }
    doh->deferredBuffers = doh->lastDeferredBuffer = NULL;
    if (status == IOStatusOk)
        status = flushOutputStream(doh->rawOutputStream);
    return status;
}

/*-----------------------------------------------------------------------------
 * Destroys both an input and output streams and handles the case when
 * the two streams share the same handle.
 * 
 * When you destroy a stream, you close its dynamo handle.  In this
 * case we are closing two streams.  If these streams have the same
 * handle then closing the same handle twice may be bad.  For example
 * closing the same dynamo socket more than once is bad because you
 * will free the same memory more than once.
 *
 * To prevent this from happening in the case where both streams have
 * the same handle we explicitly close the handle once and then set
 * the closer functions of each stream to NULL so that they will not
 * close their handles when they are destroyed.
 */
IOStatus destroyIOStreams(InputStream * pInStream,
                          OutputStream * pOutStream)
{
    IOStatus status = IOStatusOk, tempStatus;
    if (pInStream && pOutStream) {
        if (pInStream->handle && pInStream->handle == pOutStream->handle) {
            tempStatus = pInStream->closer(pInStream->handle);
            pInStream->closer = NULL;
            pOutStream->closer = NULL;
        }
        status = destroyInputStream(pInStream);
        status = destroyOutputStream(pOutStream);
    } else if (pInStream)
        status = destroyInputStream(pInStream);
    else if (pOutStream)
        status = destroyOutputStream(pOutStream);

    return status;
}

/*-----------------------------------------------------------------------------

 * Handles deferred output buffering.
 */
static IOStatus deferredOutputWriter(void *handle, const void *buf,
                                     int size)
{
    DeferredOutputStreamHandle *doh =
        (DeferredOutputStreamHandle *) handle;
    OutputStream *myOutStream = doh->myOutputStream;

    if (size == 0)
        return IOStatusOk;
    if (doh->mode == OutputDeferred) {
#ifdef _ISAPI
        DeferredOutputStreamBuffer *dob = (DeferredOutputStreamBuffer *)
            HeapAlloc(gHeap, HEAP_ZERO_MEMORY,
                      sizeof(DeferredOutputStreamBuffer));
#else
        DeferredOutputStreamBuffer *dob = (DeferredOutputStreamBuffer *)
            calloc(1, sizeof(DeferredOutputStreamBuffer));
#endif
        if (dob == NULL)
            return IOStatusMemoryExhausted;     /* Doh! */

        /* if we're writing the entire output stream buffer, just move the
         * buffer into the buffer list and allocate a new one.  this avoids
         * a buffer copy in some common cases.
         */
        if ((buf == myOutStream->buffer)
            && (size == myOutStream->bufferSize)) {
            dob->buffer = myOutStream->buffer;
            dob->bufferSize = size;

            /* create a new output stream buffer
             */
#ifdef _ISAPI
            myOutStream->buffer =
                HeapAlloc(gHeap, 0, myOutStream->bufferSize);
            if (myOutStream->buffer == NULL) {
                (void) HeapFree(gHeap, 0, dob->buffer);
                (void) HeapFree(gHeap, 0, dob);
                return IOStatusMemoryExhausted;
            }
#else
            myOutStream->buffer = malloc(myOutStream->bufferSize);
            if (myOutStream->buffer == NULL) {
                free(dob->buffer);
                free(dob);
                return IOStatusMemoryExhausted;
            }
#endif
        }

        /* move written data into a buffer in the buffer list.
         */
        else {
#ifdef _ISAPI
            dob->buffer = HeapAlloc(gHeap, 0, size);
            if (dob->buffer == NULL) {
                (void) HeapFree(gHeap, 0, dob);
                return IOStatusMemoryExhausted;
            }
#else
            dob->buffer = malloc(size);
            if (dob->buffer == NULL) {
                free(dob);
                return IOStatusMemoryExhausted;
            }
#endif
            dob->bufferSize = size;
            memcpy(dob->buffer, buf, size);
        }

        /* append the new buffer to the deferred buffer list.
         */
        if (doh->lastDeferredBuffer == NULL)
            doh->deferredBuffers = dob;
        else
            doh->lastDeferredBuffer->next = dob;
        doh->lastDeferredBuffer = dob;

        return IOStatusOk;
    } else                      /* pass-through mode */
        return writeToOutputStream(doh->rawOutputStream, buf, size);
}

/*-----------------------------------------------------------------------------
 * Flushes deferred output and cleans up the handle.
 */
static IOStatus deferredOutputFinalizer(void *handle)
{
    DeferredOutputStreamHandle *doh =
        (DeferredOutputStreamHandle *) handle;
    IOStatus status = flushDeferredOutputStreamBuffers(doh);
    destroyOutputStream(doh->rawOutputStream);
    free(doh);
    return status;
}

/*-----------------------------------------------------------------------------
 * Creates a deferred (buffered) output stream.
 */
IOStatus createDeferredOutputStream(OutputStream * rawOutputStream,
                                    OutputStream ** retStream)
{
    OutputStream *outStream;
#ifdef _ISAPI
    DeferredOutputStreamHandle *handle =
        HeapAlloc(gHeap, HEAP_ZERO_MEMORY,
                  sizeof(DeferredOutputStreamHandle));
#else
    DeferredOutputStreamHandle *handle =
        calloc(1, sizeof(DeferredOutputStreamHandle));
#endif
    if (handle == NULL)
        return IOStatusMemoryExhausted;
    outStream =
        createOutputStream(handle, deferredOutputWriter,
                           deferredOutputFinalizer, -1);
    if (outStream == NULL) {
#ifdef _ISAPI
        (void) HeapFree(gHeap, 0, handle);
#else
        free(handle);
#endif
        return IOStatusMemoryExhausted;
    }
    handle->myOutputStream = outStream;
    handle->rawOutputStream = rawOutputStream;
    handle->mode = OutputDeferred;
    *retStream = outStream;
    return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Changes the deferred output stream mode between buffered and passed-through.
 */
IOStatus setDeferredOutputMode(OutputStream * outStream,
                               DeferredOutputStreamMode mode)
{
    IOStatus status;
    DeferredOutputStreamHandle *doh =
        (DeferredOutputStreamHandle *) outStream->handle;
    switch (mode) {
    case OutputDeferred:
        status = IOStatusOk;
        doh->mode = mode;
        break;
    case OutputPassedThrough:
        status = flushOutputStream(outStream);
        if (status == IOStatusOk)
            status = flushDeferredOutputStreamBuffers(doh);
        doh->mode = mode;
        break;
    default:
        status = IOStatusFailed;        /* silly user, tricks are for kids */
        break;
    }
    return status;
}

/*-----------------------------------------------------------------------------
 * Writes all deferred output to the raw output stream.
 */
IOStatus writeDeferredOutput(OutputStream * outStream)
{
    return flushDeferredOutputStreamBuffers((DeferredOutputStreamHandle *)
                                            outStream->handle);
}

/*-----------------------------------------------------------------------------
 * Given a host name and port number, this returns an internet address.
 */
IOStatus getSocketAddress(const char *hostname, int port,
                          struct sockaddr_in * retAddress)
{
    struct sockaddr_in address;
    struct hostent *hp;

#ifdef WIN32
    if (!WinsockInitialized) {
        IOStatus status = initializeWinsock();
        if (status != IOStatusOk)
            return status;
    }
#endif                          /* WIN32 */

    /* create an internet address from the host name and port number.
     */
    hp = gethostbyname(hostname);
    if (hp == NULL)
        return IOStatusUnknownHost;
    memset(&address, 0, sizeof(struct sockaddr_in));
    memcpy((char *) &address.sin_addr, hp->h_addr, hp->h_length);
    address.sin_family = hp->h_addrtype;
    address.sin_port = htons((unsigned short) port);
    *retAddress = address;
    return IOStatusOk;
}

#ifdef WIN32
/*-----------------------------------------------------------------------------
 * Connects a socket to a server and returns input and output streams for it,
 * or fails if a connection cannot be established in a given number of
 * milliseconds.
 *
 * FIXME: Because createSocketIOStreamsFromAddressWithTimeout() is
 * only supported on WIN32, this function can only be supported on
 * WIN32.
 */
IOStatus createSocketIOStreamsWithTimeout(const char *hostname, int port,
                                          int timeout,
                                          InputStream ** retInStream,
                                          OutputStream ** retOutStream)
{
    struct sockaddr_in address;
    IOStatus status = getSocketAddress(hostname, port, &address);
    if (status != IOStatusOk)
        return status;
    return createSocketIOStreamsFromAddressWithTimeout(address, timeout,
                                                       retInStream,
                                                       retOutStream);
}
#endif

#ifdef WIN32
/****************************************************
 * Win32-specific I/O stream implementations follow *
 ****************************************************/

/*-----------------------------------------------------------------------------
 * Converts a WIN32 "last error" value to an IOStatus value.
 */
static IOStatus lastErrorToIOStatus()
{
    switch (GetLastError()) {
    case ERROR_FILE_NOT_FOUND:
    case ERROR_PATH_NOT_FOUND:
    case ERROR_INVALID_DRIVE:
    case ERROR_BAD_UNIT:
    case ERROR_BAD_NETPATH:
    case ERROR_NETNAME_DELETED:
    case ERROR_BAD_NET_NAME:
        return IOStatusNoSuchFile;
    case ERROR_ACCESS_DENIED:
    case ERROR_WRITE_PROTECT:
    case ERROR_SHARING_VIOLATION:
    case ERROR_LOCK_VIOLATION:
    case ERROR_SHARING_BUFFER_EXCEEDED:
    case ERROR_NETWORK_ACCESS_DENIED:
        return IOStatusPermissionDenied;
        break;
    case ERROR_NO_MORE_FILES:
    case ERROR_TOO_MANY_SESS:
    case ERROR_REQ_NOT_ACCEP:
        return IOStatusNoMoreFiles;
    case ERROR_OUTOFMEMORY:
    case ERROR_NOT_ENOUGH_MEMORY:
    case ERROR_OUT_OF_STRUCTURES:
        return IOStatusMemoryExhausted;
    case ERROR_CRC:
    case ERROR_SEEK:
    case ERROR_SECTOR_NOT_FOUND:
    case ERROR_READ_FAULT:
        return IOStatusReadError;
    case ERROR_WRITE_FAULT:
    case ERROR_HANDLE_DISK_FULL:
    case ERROR_NET_WRITE_FAULT:
    case ERROR_DISK_FULL:
        return IOStatusWriteError;
    case ERROR_HANDLE_EOF:
        return IOStatusNoMoreData;
    case ERROR_REM_NOT_LIST:
    case ERROR_NETWORK_BUSY:
    case ERROR_DEV_NOT_EXIST:
    case ERROR_ADAP_HDW_ERR:
    case ERROR_BAD_NET_RESP:
    case ERROR_UNEXP_NET_ERR:
    case ERROR_BAD_REM_ADAP:
        return IOStatusNetworkError;
    default:
        return IOStatusFailed;
    }
}

/*-----------------------------------------------------------------------------
 */
static IOStatus handleReader(void *handle, void *retBuf, int size,
                             int timeout, int *retBytesRead)
{
    if (!ReadFile
        ((HANDLE) handle, retBuf, size, (LPDWORD) retBytesRead, NULL))
        return IOStatusFailed;
    else if (*retBytesRead == 0)
        return IOStatusNoMoreData;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 */
static IOStatus handleWriter(void *handle, const void *buf, int size)
{
    const char *bp = (const char *) buf;
    while (size > 0) {
        DWORD bytesWritten;
        if (!WriteFile((HANDLE) handle, bp, size, &bytesWritten, NULL))
            return IOStatusFailed;
        bp += bytesWritten;
        size -= bytesWritten;
    }
    return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 */
static IOStatus handleCloser(void *handle)
{
    CloseHandle((HANDLE) handle);
    return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 */
static IOStatus socketReader(void *handle, void *retBuf, int size,
                             int timeout, int *retBytes)
{
    SOCKET sock = (SOCKET) handle;

    for (;;) {
        int br;

        if (timeout > 0) {
            fd_set readfds;
            struct timeval timeout_val;

            FD_ZERO(&readfds);
            FD_SET(sock, &readfds);
            timeout_val.tv_sec = timeout / 1000;
            timeout_val.tv_usec = timeout % 1000;
            if (select(1, &readfds, NULL, NULL, &timeout_val) ==
                SOCKET_ERROR)
                return IOStatusFailed;
            if (!FD_ISSET(sock, &readfds))
                return IOStatusTimeout;
        }

        br = recv(sock, retBuf, size, 0);
        if (br == 0) {
            *retBytes = 0;
            return IOStatusNoMoreData;
        }
        if (br == SOCKET_ERROR) {
            *retBytes = 0;
            switch (WSAGetLastError()) {
            case WSAEINTR:
                continue;
            case WSAECONNRESET:
                return IOStatusNoMoreData;      /* same thing */
            case WSAENETDOWN:
            case WSAENETUNREACH:
            case WSAENETRESET:
            case WSAECONNABORTED:
            case WSAEHOSTUNREACH:
            case WSAEDISCON:
                return IOStatusNetworkError;
            default:
                return IOStatusFailed;
            }
        }
        *retBytes = br;
        return IOStatusOk;
    }
}

/*-----------------------------------------------------------------------------
 */
static IOStatus socketWriter(void *handle, const void *buf, int size)
{
    SOCKET sock = (SOCKET) handle;
    const char *bp = (const char *) buf;

    while (size > 0) {
        int bw;

        bw = send(sock, bp, size, 0);
        if (bw == SOCKET_ERROR) {
            switch (WSAGetLastError()) {
            case WSAEINTR:
                continue;
            case WSAENETDOWN:
            case WSAENETUNREACH:
            case WSAENETRESET:
            case WSAECONNABORTED:
            case WSAECONNRESET:
            case WSAEHOSTUNREACH:
            case WSAEDISCON:
                return IOStatusNetworkError;
            default:
                return IOStatusFailed;
            }
        }
        bp += bw;
        size -= bw;
    }
    return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 */
static IOStatus socketCloser(void *handle)
{
    closesocket((SOCKET) handle);
    return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Creates (opens) a new file input stream.
 */
IOStatus createFileInputStream(const char *filename,
                               InputStream ** retStream)
{
    IOStatus status;
    HANDLE handle = CreateFile(filename,
                               GENERIC_READ,
#if 1
                               (FILE_SHARE_READ | FILE_SHARE_WRITE),
#else
                               FILE_SHARE_READ,
#endif
                               NULL,    /* security */
                               OPEN_EXISTING,
                               (FILE_ATTRIBUTE_NORMAL |
                                FILE_FLAG_SEQUENTIAL_SCAN),
                               INVALID_HANDLE_VALUE);
    if (handle == INVALID_HANDLE_VALUE) {
        status = lastErrorToIOStatus();
    } else {
        status = createHandleInputStream(handle, TRUE, retStream);
        if (status != IOStatusOk)
            CloseHandle(handle);
    }
    return status;
}

/*-----------------------------------------------------------------------------
 * Creates (opens) a new file output stream.
 */
IOStatus createFileOutputStream(const char *filename,
                                OutputStream ** retStream)
{
    IOStatus status;
    HANDLE handle = CreateFile(filename,
                               GENERIC_WRITE,
#if 1
                               (FILE_SHARE_READ | FILE_SHARE_WRITE),
#else
                               0,       /* exclusive write */
#endif
                               NULL,    /* security */
                               (CREATE_ALWAYS /*|TRUNCATE_EXISTING */ ),
                               FILE_ATTRIBUTE_NORMAL,
                               INVALID_HANDLE_VALUE);
    if (handle == INVALID_HANDLE_VALUE) {
        switch (GetLastError()) {
        default:
            status = IOStatusFailed;
        }
    } else {
        status = createHandleOutputStream(handle, TRUE, retStream);
        if (status != IOStatusOk)
            CloseHandle(handle);
    }
    return status;
}

/*-----------------------------------------------------------------------------
 * Wraps a generic I/O handle with an input stream.
 */
IOStatus createHandleInputStream(HANDLE handle, int doClose,
                                 InputStream ** retStream)
{
    *retStream = createInputStream((void *) handle, handleReader,
                                   (doClose ? handleCloser : NULL),
                                   DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Wraps a generic I/O handle with an output stream.
 */
IOStatus createHandleOutputStream(HANDLE handle, int doClose,
                                  OutputStream ** retStream)
{
    *retStream = createOutputStream((void *) handle, handleWriter,
                                    (doClose ? handleCloser : NULL),
                                    DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Wraps a SOCKET with an input stream.
 */
IOStatus createSocketInputStream(SOCKET sock, int doClose,
                                 InputStream ** retStream)
{
    *retStream = createInputStream((void *) sock, socketReader,
                                   (doClose ? socketCloser : NULL),
                                   DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Wraps a SOCKET with an output stream.
 */
IOStatus createSocketOutputStream(SOCKET sock, int doClose,
                                  OutputStream ** retStream)
{
    *retStream = createOutputStream((void *) sock, socketWriter,
                                    (doClose ? socketCloser : NULL),
                                    DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Given a socket address structure, this connects a socket to a server and
 * returns input and output streams for it.
 * FIXME: Needs to be upgraded to support DynSock.
 */
IOStatus createSocketIOStreamsFromAddressWithTimeout(struct sockaddr_in
                                                     address, int timeout,
                                                     InputStream **
                                                     retInStream,
                                                     OutputStream **
                                                     retOutStream)
{
    int address_size = sizeof(struct sockaddr_in);
    SOCKET sock;
    IOStatus status;

    *retInStream = NULL;
    *retOutStream = NULL;
    if (!WinsockInitialized) {
        IOStatus status = initializeWinsock();
        if (status != IOStatusOk)
            return status;
    }
    /* create socket and socket event structures */
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == SOCKET_ERROR) {
        status = IOStatusNoMoreSockets;
        goto cleanupAndFail;
    }

    if (timeout > 0) {
        DWORD blocking = 1;
        if (ioctlsocket(sock, FIONBIO, &blocking) == SOCKET_ERROR) {
            status = IOStatusFailed;
            goto cleanupAndFail;
        }
    }

    if (connect(sock, (struct sockaddr *) &address, address_size) ==
        SOCKET_ERROR) {
        switch (WSAGetLastError()) {
        case WSAEWOULDBLOCK:   /* ok so far */
            break;
        case WSAECONNREFUSED:
            status = IOStatusConnectionRefused;
            goto cleanupAndFail;
        default:
            status = IOStatusFailed;
            goto cleanupAndFail;
        }

        /* wait for socket to connect */
        {
            fd_set writefds;
            fd_set exceptfds;
            struct timeval timeout_val;
            int result;

            FD_ZERO(&writefds);
            FD_SET(sock, &writefds);
            FD_ZERO(&exceptfds);
            FD_SET(sock, &exceptfds);
            timeout_val.tv_sec = timeout / 1000;
            timeout_val.tv_usec = timeout % 1000;
            result = select(1, NULL, &writefds, &exceptfds, &timeout_val);
            if (result == SOCKET_ERROR) {
                status = IOStatusFailed;
                goto cleanupAndFail;
            }

            /* see if connection failed */
            if (FD_ISSET(sock, &exceptfds)) {
                status = IOStatusConnectionRefused;
                goto cleanupAndFail;
            }

            /* see if connection timed out */
            if (!FD_ISSET(sock, &writefds)) {
                status = IOStatusTimeout;
                goto cleanupAndFail;
            }
        }

        /* put socket back into blocking mode */
        if (timeout > 0) {
            DWORD blocking = 0;
            if (ioctlsocket(sock, FIONBIO, &blocking) == SOCKET_ERROR) {
                status = IOStatusFailed;
                goto cleanupAndFail;
            }
        }
    }

    /* wrap the socket with I/O streams */
    status = createSocketInputStream(sock, TRUE, retInStream);
    if (status != IOStatusOk)
        goto cleanupAndFail;
    status = createSocketOutputStream(sock, TRUE, retOutStream);
    if (status != IOStatusOk)
        goto cleanupAndFail;

    /* <bong> thank you for using AT&T */
    return IOStatusOk;

  cleanupAndFail:
    if (*retInStream != NULL)
        destroyInputStream(*retInStream);
    if (*retOutStream != NULL)
        destroyOutputStream(*retOutStream);
    if (sock != SOCKET_ERROR)
        closesocket(sock);
    return status;
}

#else                           /* assume it is UNIX */

/***************************************************
 * Unix-specific I/O stream implementations follow *
 ***************************************************/

#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

/*-----------------------------------------------------------------------------
 * Converts a UNIX errno value to an IOStatus value
 */
static IOStatus errnoToIOStatus(int value)
{
    switch (value) {
    case 0:
        return IOStatusOk;
    case ECONNREFUSED:
        return IOStatusConnectionRefused;
    case ENETDOWN:
    case ENETUNREACH:
    case ENETRESET:
    case ECONNABORTED:
    case ECONNRESET:
    case EHOSTUNREACH:
        return IOStatusNetworkError;
    case ENOENT:
        return IOStatusNoSuchFile;
    case EPERM:
        return IOStatusPermissionDenied;
    default:
        return IOStatusFailed;
    }
}

/*-----------------------------------------------------------------------------
 */
static IOStatus fdReader(void *handle, void *buf, int size,
                         int timeout, int *retBytesRead)
{
    int br;

    for (;;) {
        if (timeout > 0) {
            for (;;) {
                int nfds;
                struct pollfd read_fds[1];
                read_fds[0].fd = (int) handle;
                read_fds[0].events = POLLIN;
                read_fds[0].revents = 0;
                nfds = poll(read_fds, 1, timeout);
                if (nfds < 0) {
                    switch (errno) {
                    case EINTR:
                        continue;
                    default:
                        return IOStatusFailed;
                    }
                } else if (nfds == 0)
                    return IOStatusTimeout;
                else            /* have data */
                    goto doRead;
            }
        }

      doRead:
        br = read((int) handle, buf, size);
        if (br <= 0) {
            *retBytesRead = 0;
            if (br == 0)
                return IOStatusNoMoreData;
            switch (errno) {
            case EINTR:
                continue;
            case EIO:          /* old macdonald had a farm */
                return IOStatusReadError;
            default:
                return errnoToIOStatus(errno);
            }
        }
        *retBytesRead = br;
        return IOStatusOk;
    }
}

/*-----------------------------------------------------------------------------
 */
static IOStatus fdWriter(void *handle, const void *buf, int size)
{
    const char *bp = (const char *) buf;
    while (size > 0) {
        int bw = write((int) handle, bp, size);
        if (bw < 0) {
            switch (errno) {
            case EINTR:
                continue;
            case EIO:          /* old macdonald had a farm */
                return IOStatusWriteError;
            default:
                return errnoToIOStatus(errno);
            }
        }
        bp += bw;
        size -= bw;
    }
    return IOStatusOk;
}

static IOStatus fdCloser(void *handle)
{
    if (close((int) handle) < 0)
        return IOStatusFailed;
    else
        return IOStatusOk;
}

 /**/
/*-----------------------------------------------------------------------------
 */
static IOStatus dynSockReader(void *handle, void *buf, int size,
                              int timeout, int *retBytesRead)
{
    int br;

    for (;;) {
        if (timeout > 0) {
            for (;;) {
                int nfds;
                struct pollfd read_fds[1];
                read_fds[0].fd = dynSocket_socket((dynSocket *) handle);
                read_fds[0].events = POLLIN;
                read_fds[0].revents = 0;
                nfds = poll(read_fds, 1, timeout);
                if (nfds < 0) {
                    switch (errno) {
                    case EINTR:
                        continue;
                    default:
                        return IOStatusFailed;
                    }
                } else if (nfds == 0)
                    return IOStatusTimeout;
                else            /* have data */
                    goto doRead;
            }
        }

      doRead:
        br = dynSocket_read((dynSocket *) handle, buf, size);
        if (br <= 0) {
            *retBytesRead = 0;
            if (br == 0)
                return IOStatusNoMoreData;
            switch (errno) {
            case EINTR:
                continue;
            case EIO:          /* old macdonald had a farm */
                return IOStatusReadError;
            default:
                return errnoToIOStatus(errno);
            }
        }
        *retBytesRead = br;
        return IOStatusOk;
    }
}

/*-----------------------------------------------------------------------------
 */
static IOStatus dynSockWriter(void *handle, const void *buf, int size)
{
    const char *bp = (const char *) buf;
    while (size > 0) {
        int bw = dynSocket_write((dynSocket *) handle, bp, size);
        if (bw < 0) {
            switch (errno) {
            case EINTR:
                continue;
            case EIO:          /* old macdonald had a farm */
                return IOStatusWriteError;
            default:
                return errnoToIOStatus(errno);
            }
        }
        bp += bw;
        size -= bw;
    }
    return IOStatusOk;
}

static IOStatus dynSockCloser(void *handle)
{
    if (dynSocket_close((dynSocket *) handle) == ATG_SOCKET_ERROR)
        return IOStatusFailed;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Creates (opens) a new file input stream.
 */
IOStatus createFileInputStream(const char *filename,
                               InputStream ** retStream)
{
    int fd = open(filename, O_RDONLY);
    if (fd < 0) {
        IOStatus status;
        switch (errno) {
        case EIO:
            status = IOStatusWriteError;
            break;
        default:
            status = errnoToIOStatus(errno);
            break;
        }
        *retStream = NULL;
        return status;
    }
    *retStream = createInputStream((void *) fd, fdReader, fdCloser, -1);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Creates (opens) a new file output stream.
 */
IOStatus createFileOutputStream(const char *filename,
                                OutputStream ** retStream)
{
    int fd = open(filename, O_WRONLY);
    if (fd < 0) {
        IOStatus status;
        switch (errno) {
        case EIO:
            status = IOStatusWriteError;
            break;
        default:
            status = errnoToIOStatus(errno);
            break;
        }
        *retStream = NULL;
        return status;
    }
    *retStream = createOutputStream((void *) fd, fdWriter, fdCloser, -1);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Wraps a file descriptor with an input stream
 */
IOStatus createFdInputStream(int fd, int doClose, InputStream ** retStream)
{
    *retStream = createInputStream((void *) fd, fdReader,
                                   (doClose ? fdCloser : NULL),
                                   DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Wraps a file descriptor with an output stream.
 */
IOStatus createFdOutputStream(int fd, int doClose,
                              OutputStream ** retStream)
{
    *retStream = createOutputStream((void *) fd, fdWriter, fdCloser,
                                    DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Wraps a dynSocket with an input stream
 */
IOStatus createDynSocketInputStream(dynSocket * s, int doClose,
                                    InputStream ** retStream)
{
    *retStream = createInputStream((void *) s, dynSockReader,
                                   (doClose ? dynSockCloser : NULL),
                                   DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Wraps a dynSocket with an output stream.
 */
IOStatus createDynSocketOutputStream(dynSocket * s, int doClose,
                                     OutputStream ** retStream)
{
    *retStream =
        createOutputStream((void *) s, dynSockWriter, dynSockCloser,
                           DEFAULT_BUFFER_SIZE);
    if (*retStream == NULL)
        return IOStatusMemoryExhausted;
    else
        return IOStatusOk;
}

/*-----------------------------------------------------------------------------
 * Sleeps for a given number of milliseconds.
 */
/* NOTE: Currently not used so not defined. - Frank Kim, 12/28/99 */
#if 0
static void millisecondWait(int ms)
{
    poll(NULL, 0, ms);
}
#endif

/*-----------------------------------------------------------------------------
 * Given a socket address structure, this connects a socket to a server and
 * returns input and output streams for it.
 *
 * FIXME: This function never worked reliably on UNIX hence the reason
 * Allan Scott wrote createSocketIOStreamsFromAddressWithBlocking().
 * For now this function is NOT DEFINED so that no one will use it.
 *
 * Here is what Jim Frost, the author of this function, says on
 * 3/2/2001.
 *
 *   I had a lot of trouble getting it to work on Solaris in my tests.
 *   I literally tried every single technique I could think of to do
 *   the timeout -- there were several OS bugs that I found in the
 *   process (usually the connection success would not be reported as
 *   it should have been).  I had thought I'd worked it out, but IIRC
 *   one or another of you guys had to go back in there because it
 *   still wasn't working right.  It may well have been an issue of
 *   Solaris version -- I did my tests on the old Solaris/Intel box,
 *   not on a SPARC box.
 *
 *   I kind of blame this on the really crappy socket implementation
 *   on SysV/Solaris.
 */
#if 0
IOStatus createSocketIOStreamsFromAddressWithTimeout(struct sockaddr_in
                                                     address, int timeout,
                                                     InputStream **
                                                     retInStream,
                                                     OutputStream **
                                                     retOutStream)
{
    int address_size = sizeof(struct sockaddr_in);
    dynSocket *sock;
    IOStatus status, tempStatus;
    int sock_flags, temp;
#ifndef FIONBIO
    int orig_sock_flags;
#endif                          /* !SOLARIS */

    *retInStream = NULL;
    *retOutStream = NULL;
    sock = dynSocket_new((void *) IO_SOCKET);
    if (sock == ATG_INVALID_DYNSOCKET) {
        status = IOStatusNoMoreSockets;
        return status;
    }

    /* if a timeout was specified, set socket up as nonblocking so we can
     * determine if it has been too long.
     */
    if (timeout > 0) {
#ifdef FIONBIO

        /* if FIONBIO is defined, use it.  this is necessary on Solaris where
         * sockets do not honor O_NONBLOCK.
         */
        sock_flags = 1;
        if (ioctl(dynSocket_socket(sock), FIONBIO, &sock_flags) < 0)
            return IOStatusFailed;

#else                           /* !FIONBIO */

        /* if FIONBIO is not defined, try to set nonblocking mode using either
         * the POSIX O_NONBLOCK or the BSD O_NDELAY.
         */
        orig_sock_flags = fcntl(dynSocket_socket(sock), F_GETFL);
        if (orig_sock_flags == -1)
            return IOStatusFailed;
#ifdef O_NONBLOCK               /* POSIX-style nonblocking */
        sock_flags = orig_sock_flags | O_NONBLOCK;
#else                           /* !O_NONBLOCK */
#ifdef O_NDELAY                 /* BSD-style nonblocking */
        sock_flags = orig_sock_flags | O_NDELAY;
#endif                          /* O_NDELAY */
#endif                          /* !O_NONBLOCK */
        if (fcntl(dynSocket_socket(sock), F_SETFL, orig_sock_flags) < 0)
            return IOStatusFailed;
#endif                          /* !FIONBIO */
    }

    /* start the connection request. */
    for (;;) {
        if (dynSocket_connect
            (sock, (struct sockaddr *) &address, address_size)
            == ATG_SOCKET_ERROR) {
            switch (errno) {
            case EINTR:
                continue;
            case EALREADY:
            case EINPROGRESS:
            case EISCONN:
                goto waitForConnection;
            default:
                status = errnoToIOStatus(errno);
                temp = dynSocket_close(sock);
                return status;
            }
        }
    }

  waitForConnection:
    /* if timeout was specified, wait for the connection to finish */
    if (timeout > 0) {
        struct pollfd fds[1];
        int result;

        fds[0].fd = dynSocket_socket(sock);
        fds[0].events = POLLOUT;
        fds[0].revents = 0;

        result = poll(fds, 1, timeout);
        if (result < 0) {
            switch (errno) {
            case EINTR:
                goto waitForConnection;
            default:
                return errnoToIOStatus(errno);
            }
        } else if (result == 0) {
            status = IOStatusTimeout;
            temp = dynSocket_close(sock);
            return status;
        }

        /* check the status of the connect request */
        if (dynSocket_connect
            (sock, (struct sockaddr *) &address, address_size)
            == ATG_SOCKET_ERROR) {
            switch (errno) {
            case EISCONN:
                break;
            default:
                return errnoToIOStatus(errno);
            }
        }

        /* restore socket to blocking mode */
#ifdef FIONBIO
        sock_flags = 0;
        if (ioctl(dynSocket_socket(sock), FIONBIO, &sock_flags) < 0)
            return IOStatusFailed;
#else                           /* !FIONBIO */
        fcntl(dynSocket_socket(sock), F_SETFL, orig_sock_flags);
#endif                          /* !FIONBIO */
    }

    /* wrap the socket with I/O streams */
    status = createDynSocketInputStream(sock, TRUE, retInStream);
    if (status != IOStatusOk) {
        tempStatus = destroyIOStreams(*retInStream, NULL);
        return status;
    }
    status = createDynSocketOutputStream(sock, TRUE, retOutStream);
    if (status != IOStatusOk) {
        tempStatus = destroyIOStreams(*retInStream, *retOutStream);
        return status;
    }

    /* <bong> thank you for using AT&T */
    return IOStatusOk;
}
#endif                          /* #if 0 */

/*-----------------------------------------------------------------------------
 * Given a socket address structure, this connects a socket to a server and
 * returns input and output streams for it.  This uses a blocking socket.
 */
IOStatus
createSocketIOStreamsFromAddressWithBlocking(struct sockaddr_in address,
                                             InputStream ** retInStream,
                                             OutputStream ** retOutStream)
{
    int address_size = sizeof(struct sockaddr_in);
    dynSocket *sock = ATG_INVALID_DYNSOCKET;
    IOStatus status, tempStatus;
    *retInStream = NULL;
    *retOutStream = NULL;

    /* start the connection request. */
    {
        int tries = 0, temp;

        /* try to create a dynamo socket */
        sock = dynSocket_new(NULL);
        if (sock == ATG_INVALID_DYNSOCKET) {
            status = IOStatusNoMoreSockets;
            return status;
        }

        while (1) {

            /* try to connect to server specified by address */
            if (dynSocket_connect
                (sock, (struct sockaddr *) &address, address_size)
                != ATG_SOCKET_ERROR) {
                /* successfully connected, break out of the loop */
                break;
            }

            /* did not successfully connect to server */
            switch (errno) {

            case EINTR:

                /* The connection attempt was interrupted before any data
                   arrived by the delivery of a signal.  Try to connect again.
                   Eventually after several tries if it does not connect you
                   will get an ETIMEDOUT */
                continue;
            case ETIMEDOUT:

                /* Timed out while attempting connection.  The server may be
                   too busy to accept new connections.  Close this socket and
                   try to create another one. */

                status = errnoToIOStatus(errno);

                temp = dynSocket_close(sock);
                sock = dynSocket_new(NULL);
                if (sock == ATG_INVALID_DYNSOCKET) {
                    status = IOStatusNoMoreSockets;
                    return status;
                }
                break;
            default:
                status = errnoToIOStatus(errno);
                temp = dynSocket_close(sock);
                if (status == IOStatusOk)
                    return IOStatusConnectionRefused;
                return status;
            }

            tries++;
            if (tries == 3) {
                temp = dynSocket_close(sock);
                if (status == IOStatusOk)
                    return IOStatusConnectionRefused;
                return status;
            }
        }
    }

    /* At this point we have successfully connected to the server with
       our socket.  Wrap this socket with I/O streams */
    status = createDynSocketInputStream(sock, TRUE, retInStream);
    if (status != IOStatusOk) {
        tempStatus = destroyIOStreams(*retInStream, NULL);
        *retInStream = NULL;
        return status;
    }

    status = createDynSocketOutputStream(sock, TRUE, retOutStream);
    if (status != IOStatusOk) {
        tempStatus = destroyIOStreams(*retInStream, *retOutStream);
        *retInStream = NULL;
        *retOutStream = NULL;
        return status;
    }

    return IOStatusOk;
}

#endif                          /* WIN32 */
