/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
** File:   ptio.c
** Descritpion:  Implemenation of I/O methods for pthreads
*/

#if defined(_PR_PTHREADS)

#if defined(_PR_POLL_WITH_SELECT)
#if !(defined(HPUX) && defined(_USE_BIG_FDS))
/* set fd limit for select(), before including system header files */
#define FD_SETSIZE (16 * 1024)
#endif
#endif

#include <pthread.h>
#include <string.h>  /* for memset() */
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#if defined(DARWIN)
#include <sys/utsname.h> /* for uname */
#endif
#if defined(SOLARIS) || defined(UNIXWARE)
#include <sys/filio.h>  /* to pick up FIONREAD */
#endif
#ifdef _PR_POLL_AVAILABLE
#include <poll.h>
#endif
#ifdef AIX
/* To pick up sysconf() */
#include <unistd.h>
#include <dlfcn.h>  /* for dlopen */
#else
/* To pick up getrlimit() etc. */
#include <sys/time.h>
#include <sys/resource.h>
#endif

#ifdef SOLARIS
/*
 * Define HAVE_SENDFILEV if the system has the sendfilev() system call.
 * Code built this way won't run on a system without sendfilev().
 * We can define HAVE_SENDFILEV by default when the minimum release
 * of Solaris that NSPR supports has sendfilev().
 */
#ifdef HAVE_SENDFILEV

#include <sys/sendfile.h>

#define SOLARIS_SENDFILEV(a, b, c, d) sendfilev((a), (b), (c), (d))

#else

#include <dlfcn.h>  /* for dlopen */

/*
 * Match the definitions in <sys/sendfile.h>.
 */
typedef struct sendfilevec {
    int sfv_fd;       /* input fd */
    uint_t sfv_flag;  /* flags */
    off_t sfv_off;    /* offset to start reading from */
    size_t sfv_len;   /* amount of data */
} sendfilevec_t;

#define SFV_FD_SELF (-2)

/*
 * extern ssize_t sendfilev(int, const struct sendfilevec *, int, size_t *);
 */
static ssize_t (*pt_solaris_sendfilev_fptr)() = NULL;

#define SOLARIS_SENDFILEV(a, b, c, d) \
        (*pt_solaris_sendfilev_fptr)((a), (b), (c), (d))

#endif /* HAVE_SENDFILEV */
#endif /* SOLARIS */

/*
 * The send_file() system call is available in AIX 4.3.2 or later.
 * If this file is compiled on an older AIX system, it attempts to
 * look up the send_file symbol at run time to determine whether
 * we can use the faster PR_SendFile/PR_TransmitFile implementation based on
 * send_file().  On AIX 4.3.2 or later, we can safely skip this
 * runtime function dispatching and just use the send_file based
 * implementation.
 */
#ifdef AIX
#ifdef SF_CLOSE
#define HAVE_SEND_FILE
#endif

#ifdef HAVE_SEND_FILE

#define AIX_SEND_FILE(a, b, c) send_file(a, b, c)

#else /* HAVE_SEND_FILE */

/*
 * The following definitions match those in <sys/socket.h>
 * on AIX 4.3.2.
 */

/*
 * Structure for the send_file() system call
 */
struct sf_parms {
    /* --------- header parms ---------- */
    void      *header_data;         /* Input/Output. Points to header buf */
    uint_t    header_length;        /* Input/Output. Length of the header */
    /* --------- file parms ------------ */
    int       file_descriptor;      /* Input. File descriptor of the file */
    unsigned long long file_size;   /* Output. Size of the file */
    unsigned long long file_offset; /* Input/Output. Starting offset */
    long long file_bytes;           /* Input/Output. no. of bytes to send */
    /* --------- trailer parms --------- */
    void      *trailer_data;        /* Input/Output. Points to trailer buf */
    uint_t    trailer_length;       /* Input/Output. Length of the trailer */
    /* --------- return info ----------- */
    unsigned long long bytes_sent;  /* Output. no. of bytes sent */
};

/*
 * Flags for the send_file() system call
 */
#define SF_CLOSE        0x00000001      /* close the socket after completion */
#define SF_REUSE        0x00000002      /* reuse socket. not supported */
#define SF_DONT_CACHE   0x00000004      /* don't apply network buffer cache */
#define SF_SYNC_CACHE   0x00000008      /* sync/update network buffer cache */

/*
 * prototype: size_t send_file(int *, struct sf_parms *, uint_t);
 */
static ssize_t (*pt_aix_sendfile_fptr)() = NULL;

#define AIX_SEND_FILE(a, b, c) (*pt_aix_sendfile_fptr)(a, b, c)

#endif /* HAVE_SEND_FILE */
#endif /* AIX */

#ifdef LINUX
#include <sys/sendfile.h>
#endif

#include "primpl.h"

#if defined(LINUX) || defined(ANDROID)
#include <netinet/in.h>
#endif

#ifdef DARWIN
#include <netinet/in.h>
#include <netinet/ip.h>
#endif

#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>  /* TCP_NODELAY, TCP_MAXSEG */
#endif

#ifdef LINUX
/* TCP_CORK is not defined in <netinet/tcp.h> on Red Hat Linux 6.0 */
#ifndef TCP_CORK
#define TCP_CORK 3
#endif
#ifndef MSG_FASTOPEN
#define MSG_FASTOPEN    0x20000000
#endif
#endif

#ifdef _PR_IPV6_V6ONLY_PROBE
static PRBool _pr_ipv6_v6only_on_by_default;
#endif

#if (defined(HPUX) && !defined(HPUX10_30) && !defined(HPUX11))
#define _PRSelectFdSetArg_t int *
#elif defined(AIX4_1)
#define _PRSelectFdSetArg_t void *
#elif (defined(AIX) && !defined(AIX4_1)) \
    || defined(SOLARIS) \
    || defined(HPUX10_30) || defined(HPUX11) \
    || defined(LINUX) || defined(__GNU__) || defined(__GLIBC__) \
    || defined(FREEBSD) || defined(NETBSD) || defined(OPENBSD) \
    || defined(BSDI) || defined(NTO) || defined(DARWIN) \
    || defined(UNIXWARE) || defined(RISCOS)
#define _PRSelectFdSetArg_t fd_set *
#else
#error "Cannot determine architecture"
#endif

#if defined(SOLARIS)
#ifndef PROTO_SDP
/* on solaris, SDP is a new type of protocol */
#define PROTO_SDP   257
#endif
#define _PR_HAVE_SDP
#elif defined(LINUX)
#ifndef AF_INET_SDP
/* on linux, SDP is a new type of address family */
#define AF_INET_SDP 27
#endif
#define _PR_HAVE_SDP
#endif /* LINUX */

static PRFileDesc *pt_SetMethods(
    PRIntn osfd, PRDescType type, PRBool isAcceptedSocket, PRBool imported);

static PRLock *_pr_flock_lock;  /* For PR_LockFile() etc. */
static PRCondVar *_pr_flock_cv;  /* For PR_LockFile() etc. */
static PRLock *_pr_rename_lock;  /* For PR_Rename() */

/**************************************************************************/

/* These two functions are only used in assertions. */
#if defined(DEBUG)

PRBool IsValidNetAddr(const PRNetAddr *addr)
{
    if ((addr != NULL)
        && (addr->raw.family != AF_UNIX)
        && (addr->raw.family != PR_AF_INET6)
        && (addr->raw.family != AF_INET)) {
        return PR_FALSE;
    }
    return PR_TRUE;
}

static PRBool IsValidNetAddrLen(const PRNetAddr *addr, PRInt32 addr_len)
{
    /*
     * The definition of the length of a Unix domain socket address
     * is not uniform, so we don't check it.
     */
    if ((addr != NULL)
        && (addr->raw.family != AF_UNIX)
        && (PR_NETADDR_SIZE(addr) != addr_len)) {
#if defined(LINUX) && __GLIBC__ == 2 && __GLIBC_MINOR__ == 1
        /*
         * In glibc 2.1, struct sockaddr_in6 is 24 bytes.  In glibc 2.2
         * and in the 2.4 kernel, struct sockaddr_in6 has the scope_id
         * field and is 28 bytes.  It is possible for socket functions
         * to return an addr_len greater than sizeof(struct sockaddr_in6).
         * We need to allow that.  (Bugzilla bug #77264)
         */
        if ((PR_AF_INET6 == addr->raw.family)
            && (sizeof(addr->ipv6) == addr_len)) {
            return PR_TRUE;
        }
#endif
        return PR_FALSE;
    }
    return PR_TRUE;
}

#endif /* DEBUG */

/*****************************************************************************/
/************************* I/O Continuation machinery ************************/
/*****************************************************************************/

/*
 * The polling interval defines the maximum amount of time that a thread
 * might hang up before an interrupt is noticed.
 */
#define PT_DEFAULT_POLL_MSEC 5000
#if defined(_PR_POLL_WITH_SELECT)
#define PT_DEFAULT_SELECT_SEC (PT_DEFAULT_POLL_MSEC/PR_MSEC_PER_SEC)
#define PT_DEFAULT_SELECT_USEC                          \
        ((PT_DEFAULT_POLL_MSEC % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC)
#endif

/*
 * pt_SockLen is the type for the length of a socket address
 * structure, used in the address length argument to bind,
 * connect, accept, getsockname, getpeername, etc.  Posix.1g
 * defines this type as socklen_t.  It is size_t or int on
 * most current systems.
 */
#if defined(HAVE_SOCKLEN_T) \
    || (defined(__GLIBC__) && __GLIBC__ >= 2)
typedef socklen_t pt_SockLen;
#elif (defined(AIX) && !defined(AIX4_1))
typedef PRSize pt_SockLen;
#else
typedef PRIntn pt_SockLen;
#endif

typedef struct pt_Continuation pt_Continuation;
typedef PRBool (*ContinuationFn)(pt_Continuation *op, PRInt16 revents);

typedef enum pr_ContuationStatus
{
    pt_continuation_pending,
    pt_continuation_done
} pr_ContuationStatus;

struct pt_Continuation
{
    /* The building of the continuation operation */
    ContinuationFn function;                /* what function to continue */
    union {
        PRIntn osfd;
    } arg1;            /* #1 - the op's fd */
    union {
        void* buffer;
    } arg2;           /* #2 - primary transfer buffer */
    union {
        PRSize amount;                      /* #3 - size of 'buffer', or */
        pt_SockLen *addr_len;                  /*    - length of address */
#ifdef HPUX11
        /*
         * For sendfile()
         */
        struct file_spec {
            off_t offset;                       /* offset in file to send */
            size_t nbytes;                      /* length of file data to send */
            size_t st_size;                     /* file size */
        } file_spec;
#endif
    } arg3;
    union {
        PRIntn flags;
    } arg4;           /* #4 - read/write flags */
    union {
        PRNetAddr *addr;
    } arg5;        /* #5 - send/recv address */

#ifdef HPUX11
    /*
     * For sendfile()
     */
    int filedesc;                           /* descriptor of file to send */
    int nbytes_to_send;                     /* size of header and file */
#endif  /* HPUX11 */

#ifdef SOLARIS
    /*
     * For sendfilev()
     */
    int nbytes_to_send;                     /* size of header and file */
#endif  /* SOLARIS */

#ifdef LINUX
    /*
     * For sendfile()
     */
    int in_fd;                              /* descriptor of file to send */
    off_t offset;
    size_t count;
#endif  /* LINUX */

    PRIntervalTime timeout;                 /* client (relative) timeout */

    PRInt16 event;                           /* flags for poll()'s events */

    /*
    ** The representation and notification of the results of the operation.
    ** These function can either return an int return code or a pointer to
    ** some object.
    */
    union {
        PRSize code;
        void *object;
    } result;

    PRIntn syserrno;                        /* in case it failed, why (errno) */
    pr_ContuationStatus status;             /* the status of the operation */
};

#if defined(DEBUG)

PTDebug pt_debug;  /* this is shared between several modules */

PR_IMPLEMENT(void) PT_FPrintStats(PRFileDesc *debug_out, const char *msg)
{
    PTDebug stats;
    char buffer[100];
    PRExplodedTime tod;
    PRInt64 elapsed, aMil;
    stats = pt_debug;  /* a copy */
    PR_ExplodeTime(stats.timeStarted, PR_LocalTimeParameters, &tod);
    (void)PR_FormatTime(buffer, sizeof(buffer), "%T", &tod);

    LL_SUB(elapsed, PR_Now(), stats.timeStarted);
    LL_I2L(aMil, 1000000);
    LL_DIV(elapsed, elapsed, aMil);

    if (NULL != msg) {
        PR_fprintf(debug_out, "%s", msg);
    }
    PR_fprintf(
        debug_out, "\tstarted: %s[%lld]\n", buffer, elapsed);
    PR_fprintf(
        debug_out, "\tlocks [created: %u, destroyed: %u]\n",
        stats.locks_created, stats.locks_destroyed);
    PR_fprintf(
        debug_out, "\tlocks [acquired: %u, released: %u]\n",
        stats.locks_acquired, stats.locks_released);
    PR_fprintf(
        debug_out, "\tcvars [created: %u, destroyed: %u]\n",
        stats.cvars_created, stats.cvars_destroyed);
    PR_fprintf(
        debug_out, "\tcvars [notified: %u, delayed_delete: %u]\n",
        stats.cvars_notified, stats.delayed_cv_deletes);
}  /* PT_FPrintStats */

#else

PR_IMPLEMENT(void) PT_FPrintStats(PRFileDesc *debug_out, const char *msg)
{
    /* do nothing */
}  /* PT_FPrintStats */

#endif  /* DEBUG */

#if defined(_PR_POLL_WITH_SELECT)
/*
 * HPUX report the POLLHUP event for a socket when the
 * shutdown(SHUT_WR) operation is called for the remote end, even though
 * the socket is still writeable. Use select(), instead of poll(), to
 * workaround this problem.
 */
static void pt_poll_now_with_select(pt_Continuation *op)
{
    PRInt32 msecs;
    fd_set rd, wr, *rdp, *wrp;
    struct timeval tv;
    PRIntervalTime epoch, now, elapsed, remaining;
    PRBool wait_for_remaining;
    PRThread *self = PR_GetCurrentThread();

    PR_ASSERT(PR_INTERVAL_NO_WAIT != op->timeout);
    PR_ASSERT(op->arg1.osfd < FD_SETSIZE);

    switch (op->timeout) {
        case PR_INTERVAL_NO_TIMEOUT:
            tv.tv_sec = PT_DEFAULT_SELECT_SEC;
            tv.tv_usec = PT_DEFAULT_SELECT_USEC;
            do
            {
                PRIntn rv;

                if (op->event & POLLIN) {
                    FD_ZERO(&rd);
                    FD_SET(op->arg1.osfd, &rd);
                    rdp = &rd;
                } else {
                    rdp = NULL;
                }
                if (op->event & POLLOUT) {
                    FD_ZERO(&wr);
                    FD_SET(op->arg1.osfd, &wr);
                    wrp = &wr;
                } else {
                    wrp = NULL;
                }

                rv = select(op->arg1.osfd + 1, rdp, wrp, NULL, &tv);

                if (_PT_THREAD_INTERRUPTED(self))
                {
                    self->state &= ~PT_THREAD_ABORTED;
                    op->result.code = -1;
                    op->syserrno = EINTR;
                    op->status = pt_continuation_done;
                    return;
                }

                if ((-1 == rv) && ((errno == EINTR) || (errno == EAGAIN))) {
                    continue;    /* go around the loop again */
                }

                if (rv > 0)
                {
                    PRInt16 revents = 0;

                    if ((op->event & POLLIN) && FD_ISSET(op->arg1.osfd, &rd)) {
                        revents |= POLLIN;
                    }
                    if ((op->event & POLLOUT) && FD_ISSET(op->arg1.osfd, &wr)) {
                        revents |= POLLOUT;
                    }

                    if (op->function(op, revents)) {
                        op->status = pt_continuation_done;
                    }
                } else if (rv == -1) {
                    op->result.code = -1;
                    op->syserrno = errno;
                    op->status = pt_continuation_done;
                }
                /* else, select timed out */
            } while (pt_continuation_done != op->status);
            break;
        default:
            now = epoch = PR_IntervalNow();
            remaining = op->timeout;
            do
            {
                PRIntn rv;

                if (op->event & POLLIN) {
                    FD_ZERO(&rd);
                    FD_SET(op->arg1.osfd, &rd);
                    rdp = &rd;
                } else {
                    rdp = NULL;
                }
                if (op->event & POLLOUT) {
                    FD_ZERO(&wr);
                    FD_SET(op->arg1.osfd, &wr);
                    wrp = &wr;
                } else {
                    wrp = NULL;
                }

                wait_for_remaining = PR_TRUE;
                msecs = (PRInt32)PR_IntervalToMilliseconds(remaining);
                if (msecs > PT_DEFAULT_POLL_MSEC) {
                    wait_for_remaining = PR_FALSE;
                    msecs = PT_DEFAULT_POLL_MSEC;
                }
                tv.tv_sec = msecs/PR_MSEC_PER_SEC;
                tv.tv_usec = (msecs % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC;
                rv = select(op->arg1.osfd + 1, rdp, wrp, NULL, &tv);

                if (_PT_THREAD_INTERRUPTED(self))
                {
                    self->state &= ~PT_THREAD_ABORTED;
                    op->result.code = -1;
                    op->syserrno = EINTR;
                    op->status = pt_continuation_done;
                    return;
                }

                if (rv > 0) {
                    PRInt16 revents = 0;

                    if ((op->event & POLLIN) && FD_ISSET(op->arg1.osfd, &rd)) {
                        revents |= POLLIN;
                    }
                    if ((op->event & POLLOUT) && FD_ISSET(op->arg1.osfd, &wr)) {
                        revents |= POLLOUT;
                    }

                    if (op->function(op, revents)) {
                        op->status = pt_continuation_done;
                    }

                } else if ((rv == 0) ||
                           ((errno == EINTR) || (errno == EAGAIN))) {
                    if (rv == 0) {  /* select timed out */
                        if (wait_for_remaining) {
                            now += remaining;
                        }
                        else {
                            now += PR_MillisecondsToInterval(msecs);
                        }
                    } else {
                        now = PR_IntervalNow();
                    }
                    elapsed = (PRIntervalTime) (now - epoch);
                    if (elapsed >= op->timeout) {
                        op->result.code = -1;
                        op->syserrno = ETIMEDOUT;
                        op->status = pt_continuation_done;
                    } else {
                        remaining = op->timeout - elapsed;
                    }
                } else {
                    op->result.code = -1;
                    op->syserrno = errno;
                    op->status = pt_continuation_done;
                }
            } while (pt_continuation_done != op->status);
            break;
    }

}  /* pt_poll_now_with_select */

#endif  /* _PR_POLL_WITH_SELECT */

static void pt_poll_now(pt_Continuation *op)
{
    PRInt32 msecs;
    PRIntervalTime epoch, now, elapsed, remaining;
    PRBool wait_for_remaining;
    PRThread *self = PR_GetCurrentThread();

    PR_ASSERT(PR_INTERVAL_NO_WAIT != op->timeout);
#if defined (_PR_POLL_WITH_SELECT)
    /*
     * If the fd is small enough call the select-based poll operation
     */
    if (op->arg1.osfd < FD_SETSIZE) {
        pt_poll_now_with_select(op);
        return;
    }
#endif

    switch (op->timeout) {
        case PR_INTERVAL_NO_TIMEOUT:
            msecs = PT_DEFAULT_POLL_MSEC;
            do
            {
                PRIntn rv;
                struct pollfd tmp_pfd;

                tmp_pfd.revents = 0;
                tmp_pfd.fd = op->arg1.osfd;
                tmp_pfd.events = op->event;

                rv = poll(&tmp_pfd, 1, msecs);

                if (_PT_THREAD_INTERRUPTED(self))
                {
                    self->state &= ~PT_THREAD_ABORTED;
                    op->result.code = -1;
                    op->syserrno = EINTR;
                    op->status = pt_continuation_done;
                    return;
                }

                if ((-1 == rv) && ((errno == EINTR) || (errno == EAGAIN))) {
                    continue;    /* go around the loop again */
                }

                if (rv > 0)
                {
                    PRInt16 events = tmp_pfd.events;
                    PRInt16 revents = tmp_pfd.revents;

                    if ((revents & POLLNVAL)  /* busted in all cases */
                        || ((events & POLLOUT) && (revents & POLLHUP)))
                        /* write op & hup */
                    {
                        op->result.code = -1;
                        if (POLLNVAL & revents) {
                            op->syserrno = EBADF;
                        }
                        else if (POLLHUP & revents) {
                            op->syserrno = EPIPE;
                        }
                        op->status = pt_continuation_done;
                    } else {
                        if (op->function(op, revents)) {
                            op->status = pt_continuation_done;
                        }
                    }
                } else if (rv == -1) {
                    op->result.code = -1;
                    op->syserrno = errno;
                    op->status = pt_continuation_done;
                }
                /* else, poll timed out */
            } while (pt_continuation_done != op->status);
            break;
        default:
            now = epoch = PR_IntervalNow();
            remaining = op->timeout;
            do
            {
                PRIntn rv;
                struct pollfd tmp_pfd;

                tmp_pfd.revents = 0;
                tmp_pfd.fd = op->arg1.osfd;
                tmp_pfd.events = op->event;

                wait_for_remaining = PR_TRUE;
                msecs = (PRInt32)PR_IntervalToMilliseconds(remaining);
                if (msecs > PT_DEFAULT_POLL_MSEC)
                {
                    wait_for_remaining = PR_FALSE;
                    msecs = PT_DEFAULT_POLL_MSEC;
                }
                rv = poll(&tmp_pfd, 1, msecs);

                if (_PT_THREAD_INTERRUPTED(self))
                {
                    self->state &= ~PT_THREAD_ABORTED;
                    op->result.code = -1;
                    op->syserrno = EINTR;
                    op->status = pt_continuation_done;
                    return;
                }

                if (rv > 0)
                {
                    PRInt16 events = tmp_pfd.events;
                    PRInt16 revents = tmp_pfd.revents;

                    if ((revents & POLLNVAL)  /* busted in all cases */
                        || ((events & POLLOUT) && (revents & POLLHUP)))
                        /* write op & hup */
                    {
                        op->result.code = -1;
                        if (POLLNVAL & revents) {
                            op->syserrno = EBADF;
                        }
                        else if (POLLHUP & revents) {
                            op->syserrno = EPIPE;
                        }
                        op->status = pt_continuation_done;
                    } else {
                        if (op->function(op, revents))
                        {
                            op->status = pt_continuation_done;
                        }
                    }
                } else if ((rv == 0) ||
                           ((errno == EINTR) || (errno == EAGAIN))) {
                    if (rv == 0)    /* poll timed out */
                    {
                        if (wait_for_remaining) {
                            now += remaining;
                        }
                        else {
                            now += PR_MillisecondsToInterval(msecs);
                        }
                    }
                    else {
                        now = PR_IntervalNow();
                    }
                    elapsed = (PRIntervalTime) (now - epoch);
                    if (elapsed >= op->timeout) {
                        op->result.code = -1;
                        op->syserrno = ETIMEDOUT;
                        op->status = pt_continuation_done;
                    } else {
                        remaining = op->timeout - elapsed;
                    }
                } else {
                    op->result.code = -1;
                    op->syserrno = errno;
                    op->status = pt_continuation_done;
                }
            } while (pt_continuation_done != op->status);
            break;
    }

}  /* pt_poll_now */

static PRIntn pt_Continue(pt_Continuation *op)
{
    op->status = pt_continuation_pending;  /* set default value */
    /*
     * let each thread call poll directly
     */
    pt_poll_now(op);
    PR_ASSERT(pt_continuation_done == op->status);
    return op->result.code;
}  /* pt_Continue */

/*****************************************************************************/
/*********************** specific continuation functions *********************/
/*****************************************************************************/
static PRBool pt_connect_cont(pt_Continuation *op, PRInt16 revents)
{
    op->syserrno = _MD_unix_get_nonblocking_connect_error(op->arg1.osfd);
    if (op->syserrno != 0) {
        op->result.code = -1;
    } else {
        op->result.code = 0;
    }
    return PR_TRUE; /* this one is cooked */
}  /* pt_connect_cont */

static PRBool pt_accept_cont(pt_Continuation *op, PRInt16 revents)
{
    op->syserrno = 0;
    op->result.code = accept(
                          op->arg1.osfd, op->arg2.buffer, op->arg3.addr_len);
    if (-1 == op->result.code)
    {
        op->syserrno = errno;
        if (EWOULDBLOCK == errno || EAGAIN == errno || ECONNABORTED == errno) {
            return PR_FALSE;    /* do nothing - this one ain't finished */
        }
    }
    return PR_TRUE;
}  /* pt_accept_cont */

static PRBool pt_read_cont(pt_Continuation *op, PRInt16 revents)
{
    /*
     * Any number of bytes will complete the operation. It need
     * not (and probably will not) satisfy the request. The only
     * error we continue is EWOULDBLOCK|EAGAIN.
     */
    op->result.code = read(
                          op->arg1.osfd, op->arg2.buffer, op->arg3.amount);
    op->syserrno = errno;
    return ((-1 == op->result.code) &&
            (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ?
           PR_FALSE : PR_TRUE;
}  /* pt_read_cont */

static PRBool pt_recv_cont(pt_Continuation *op, PRInt16 revents)
{
    /*
     * Any number of bytes will complete the operation. It need
     * not (and probably will not) satisfy the request. The only
     * error we continue is EWOULDBLOCK|EAGAIN.
     */
#if defined(SOLARIS)
    if (0 == op->arg4.flags)
        op->result.code = read(
                              op->arg1.osfd, op->arg2.buffer, op->arg3.amount);
    else
        op->result.code = recv(
                              op->arg1.osfd, op->arg2.buffer, op->arg3.amount, op->arg4.flags);
#else
    op->result.code = recv(
                          op->arg1.osfd, op->arg2.buffer, op->arg3.amount, op->arg4.flags);
#endif
    op->syserrno = errno;
    return ((-1 == op->result.code) &&
            (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ?
           PR_FALSE : PR_TRUE;
}  /* pt_recv_cont */

static PRBool pt_send_cont(pt_Continuation *op, PRInt16 revents)
{
    PRIntn bytes;
#if defined(SOLARIS)
    PRInt32 tmp_amount = op->arg3.amount;
#endif
    /*
     * We want to write the entire amount out, no matter how many
     * tries it takes. Keep advancing the buffer and the decrementing
     * the amount until the amount goes away. Return the total bytes
     * (which should be the original amount) when finished (or an
     * error).
     */
#if defined(SOLARIS)
retry:
    bytes = write(op->arg1.osfd, op->arg2.buffer, tmp_amount);
#else
    bytes = send(
                op->arg1.osfd, op->arg2.buffer, op->arg3.amount, op->arg4.flags);
#endif
    op->syserrno = errno;

#if defined(SOLARIS)
    /*
     * The write system call has been reported to return the ERANGE error
     * on occasion. Try to write in smaller chunks to workaround this bug.
     */
    if ((bytes == -1) && (op->syserrno == ERANGE))
    {
        if (tmp_amount > 1)
        {
            tmp_amount = tmp_amount/2;  /* half the bytes */
            goto retry;
        }
    }
#endif

    if (bytes >= 0)  /* this is progress */
    {
        char *bp = (char*)op->arg2.buffer;
        bp += bytes;  /* adjust the buffer pointer */
        op->arg2.buffer = bp;
        op->result.code += bytes;  /* accumulate the number sent */
        op->arg3.amount -= bytes;  /* and reduce the required count */
        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
    }
    if ((EWOULDBLOCK != op->syserrno) && (EAGAIN != op->syserrno))
    {
        op->result.code = -1;
        return PR_TRUE;
    }
    else {
        return PR_FALSE;
    }
}  /* pt_send_cont */

static PRBool pt_write_cont(pt_Continuation *op, PRInt16 revents)
{
    PRIntn bytes;
    /*
     * We want to write the entire amount out, no matter how many
     * tries it takes. Keep advancing the buffer and the decrementing
     * the amount until the amount goes away. Return the total bytes
     * (which should be the original amount) when finished (or an
     * error).
     */
    bytes = write(op->arg1.osfd, op->arg2.buffer, op->arg3.amount);
    op->syserrno = errno;
    if (bytes >= 0)  /* this is progress */
    {
        char *bp = (char*)op->arg2.buffer;
        bp += bytes;  /* adjust the buffer pointer */
        op->arg2.buffer = bp;
        op->result.code += bytes;  /* accumulate the number sent */
        op->arg3.amount -= bytes;  /* and reduce the required count */
        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
    }
    if ((EWOULDBLOCK != op->syserrno) && (EAGAIN != op->syserrno))
    {
        op->result.code = -1;
        return PR_TRUE;
    }
    else {
        return PR_FALSE;
    }
}  /* pt_write_cont */

static PRBool pt_writev_cont(pt_Continuation *op, PRInt16 revents)
{
    PRIntn bytes;
    struct iovec *iov = (struct iovec*)op->arg2.buffer;
    /*
     * Same rules as write, but continuing seems to be a bit more
     * complicated. As the number of bytes sent grows, we have to
     * redefine the vector we're pointing at. We might have to
     * modify an individual vector parms or we might have to eliminate
     * a pair altogether.
     */
    bytes = writev(op->arg1.osfd, iov, op->arg3.amount);
    op->syserrno = errno;
    if (bytes >= 0)  /* this is progress */
    {
        PRIntn iov_index;
        op->result.code += bytes;  /* accumulate the number sent */
        for (iov_index = 0; iov_index < op->arg3.amount; ++iov_index)
        {
            /* how much progress did we make in the i/o vector? */
            if (bytes < iov[iov_index].iov_len)
            {
                /* this element's not done yet */
                char **bp = (char**)&(iov[iov_index].iov_base);
                iov[iov_index].iov_len -= bytes;  /* there's that much left */
                *bp += bytes;  /* starting there */
                break;  /* go off and do that */
            }
            bytes -= iov[iov_index].iov_len;  /* that element's consumed */
        }
        op->arg2.buffer = &iov[iov_index];  /* new start of array */
        op->arg3.amount -= iov_index;  /* and array length */
        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
    }
    if ((EWOULDBLOCK != op->syserrno) && (EAGAIN != op->syserrno))
    {
        op->result.code = -1;
        return PR_TRUE;
    }
    else {
        return PR_FALSE;
    }
}  /* pt_writev_cont */

static PRBool pt_sendto_cont(pt_Continuation *op, PRInt16 revents)
{
    PRIntn bytes = sendto(
                       op->arg1.osfd, op->arg2.buffer, op->arg3.amount, op->arg4.flags,
                       (struct sockaddr*)op->arg5.addr, PR_NETADDR_SIZE(op->arg5.addr));
    op->syserrno = errno;
    if (bytes >= 0)  /* this is progress */
    {
        char *bp = (char*)op->arg2.buffer;
        bp += bytes;  /* adjust the buffer pointer */
        op->arg2.buffer = bp;
        op->result.code += bytes;  /* accumulate the number sent */
        op->arg3.amount -= bytes;  /* and reduce the required count */
        return (0 == op->arg3.amount) ? PR_TRUE : PR_FALSE;
    }
    if ((EWOULDBLOCK != op->syserrno) && (EAGAIN != op->syserrno))
    {
        op->result.code = -1;
        return PR_TRUE;
    }
    else {
        return PR_FALSE;
    }
}  /* pt_sendto_cont */

static PRBool pt_recvfrom_cont(pt_Continuation *op, PRInt16 revents)
{
    pt_SockLen addr_len = sizeof(PRNetAddr);
    op->result.code = recvfrom(
                          op->arg1.osfd, op->arg2.buffer, op->arg3.amount,
                          op->arg4.flags, (struct sockaddr*)op->arg5.addr, &addr_len);
    op->syserrno = errno;
    return ((-1 == op->result.code) &&
            (EWOULDBLOCK == op->syserrno || EAGAIN == op->syserrno)) ?
           PR_FALSE : PR_TRUE;
}  /* pt_recvfrom_cont */

#ifdef AIX
static PRBool pt_aix_sendfile_cont(pt_Continuation *op, PRInt16 revents)
{
    struct sf_parms *sf_struct = (struct sf_parms *) op->arg2.buffer;
    ssize_t rv;
    unsigned long long saved_file_offset;
    long long saved_file_bytes;

    saved_file_offset = sf_struct->file_offset;
    saved_file_bytes = sf_struct->file_bytes;
    sf_struct->bytes_sent = 0;

    if ((sf_struct->file_bytes > 0) && (sf_struct->file_size > 0))
        PR_ASSERT((sf_struct->file_bytes + sf_struct->file_offset) <=
                  sf_struct->file_size);
    rv = AIX_SEND_FILE(&op->arg1.osfd, sf_struct, op->arg4.flags);
    op->syserrno = errno;

    if (rv != -1) {
        op->result.code += sf_struct->bytes_sent;
        /*
         * A bug in AIX 4.3.2 prevents the 'file_bytes' field from
         * being updated. So, 'file_bytes' is maintained by NSPR to
         * avoid conflict when this bug is fixed in AIX, in the future.
         */
        if (saved_file_bytes != -1) {
            saved_file_bytes -= (sf_struct->file_offset - saved_file_offset);
        }
        sf_struct->file_bytes = saved_file_bytes;
    } else if (op->syserrno != EWOULDBLOCK && op->syserrno != EAGAIN) {
        op->result.code = -1;
    } else {
        return PR_FALSE;
    }

    if (rv == 1) {    /* more data to send */
        return PR_FALSE;
    }

    return PR_TRUE;
}
#endif  /* AIX */

#ifdef HPUX11
static PRBool pt_hpux_sendfile_cont(pt_Continuation *op, PRInt16 revents)
{
    struct iovec *hdtrl = (struct iovec *) op->arg2.buffer;
    int count;

    count = sendfile(op->arg1.osfd, op->filedesc, op->arg3.file_spec.offset,
                     op->arg3.file_spec.nbytes, hdtrl, op->arg4.flags);
    PR_ASSERT(count <= op->nbytes_to_send);
    op->syserrno = errno;

    if (count != -1) {
        op->result.code += count;
    } else if (op->syserrno != EWOULDBLOCK && op->syserrno != EAGAIN) {
        op->result.code = -1;
    } else {
        return PR_FALSE;
    }
    if (count != -1 && count < op->nbytes_to_send) {
        if (count < hdtrl[0].iov_len) {
            /* header not sent */

            hdtrl[0].iov_base = ((char *) hdtrl[0].iov_base) + count;
            hdtrl[0].iov_len -= count;

        } else if (count < (hdtrl[0].iov_len + op->arg3.file_spec.nbytes)) {
            /* header sent, file not sent */
            PRUint32 file_nbytes_sent = count - hdtrl[0].iov_len;

            hdtrl[0].iov_base = NULL;
            hdtrl[0].iov_len = 0;

            op->arg3.file_spec.offset += file_nbytes_sent;
            op->arg3.file_spec.nbytes -= file_nbytes_sent;
        } else if (count < (hdtrl[0].iov_len + op->arg3.file_spec.nbytes +
                            hdtrl[1].iov_len)) {
            PRUint32 trailer_nbytes_sent = count - (hdtrl[0].iov_len +
                                                    op->arg3.file_spec.nbytes);

            /* header sent, file sent, trailer not sent */

            hdtrl[0].iov_base = NULL;
            hdtrl[0].iov_len = 0;
            /*
             * set file offset and len so that no more file data is
             * sent
             */
            op->arg3.file_spec.offset = op->arg3.file_spec.st_size;
            op->arg3.file_spec.nbytes = 0;

            hdtrl[1].iov_base =((char *) hdtrl[1].iov_base)+ trailer_nbytes_sent;
            hdtrl[1].iov_len -= trailer_nbytes_sent;
        }
        op->nbytes_to_send -= count;
        return PR_FALSE;
    }

    return PR_TRUE;
}
#endif  /* HPUX11 */

#ifdef SOLARIS
static PRBool pt_solaris_sendfile_cont(pt_Continuation *op, PRInt16 revents)
{
    struct sendfilevec *vec = (struct sendfilevec *) op->arg2.buffer;
    size_t xferred;
    ssize_t count;

    count = SOLARIS_SENDFILEV(op->arg1.osfd, vec, op->arg3.amount, &xferred);
    op->syserrno = errno;
    PR_ASSERT((count == -1) || (count == xferred));

    if (count == -1) {
        if (op->syserrno != EWOULDBLOCK && op->syserrno != EAGAIN
            && op->syserrno != EINTR) {
            op->result.code = -1;
            return PR_TRUE;
        }
        count = xferred;
    } else if (count == 0) {
        /*
         * We are now at EOF. The file was truncated. Solaris sendfile is
         * supposed to return 0 and no error in this case, though some versions
         * may return -1 and EINVAL .
         */
        op->result.code = -1;
        op->syserrno = 0; /* will be treated as EOF */
        return PR_TRUE;
    }
    PR_ASSERT(count <= op->nbytes_to_send);

    op->result.code += count;
    if (count < op->nbytes_to_send) {
        op->nbytes_to_send -= count;

        while (count >= vec->sfv_len) {
            count -= vec->sfv_len;
            vec++;
            op->arg3.amount--;
        }
        PR_ASSERT(op->arg3.amount > 0);

        vec->sfv_off += count;
        vec->sfv_len -= count;
        PR_ASSERT(vec->sfv_len > 0);
        op->arg2.buffer = vec;

        return PR_FALSE;
    }

    return PR_TRUE;
}
#endif  /* SOLARIS */

#ifdef LINUX
static PRBool pt_linux_sendfile_cont(pt_Continuation *op, PRInt16 revents)
{
    ssize_t rv;
    off_t oldoffset;

    oldoffset = op->offset;
    rv = sendfile(op->arg1.osfd, op->in_fd, &op->offset, op->count);
    op->syserrno = errno;

    if (rv == -1) {
        if (op->syserrno != EWOULDBLOCK && op->syserrno != EAGAIN) {
            op->result.code = -1;
            return PR_TRUE;
        }
        rv = 0;
    }
    PR_ASSERT(rv == op->offset - oldoffset);
    op->result.code += rv;
    if (rv < op->count) {
        op->count -= rv;
        return PR_FALSE;
    }
    return PR_TRUE;
}
#endif  /* LINUX */

void _PR_InitIO(void)
{
#if defined(DEBUG)
    memset(&pt_debug, 0, sizeof(PTDebug));
    pt_debug.timeStarted = PR_Now();
#endif

    _pr_flock_lock = PR_NewLock();
    PR_ASSERT(NULL != _pr_flock_lock);
    _pr_flock_cv = PR_NewCondVar(_pr_flock_lock);
    PR_ASSERT(NULL != _pr_flock_cv);
    _pr_rename_lock = PR_NewLock();
    PR_ASSERT(NULL != _pr_rename_lock);

    _PR_InitFdCache();  /* do that */

    _pr_stdin = pt_SetMethods(0, PR_DESC_FILE, PR_FALSE, PR_TRUE);
    _pr_stdout = pt_SetMethods(1, PR_DESC_FILE, PR_FALSE, PR_TRUE);
    _pr_stderr = pt_SetMethods(2, PR_DESC_FILE, PR_FALSE, PR_TRUE);
    PR_ASSERT(_pr_stdin && _pr_stdout && _pr_stderr);

#ifdef _PR_IPV6_V6ONLY_PROBE
    /* In Mac OS X v10.3 Panther Beta the IPV6_V6ONLY socket option
     * is turned on by default, contrary to what RFC 3493, Section
     * 5.3 says.  So we have to turn it off.  Find out whether we
     * are running on such a system.
     */
    {
        int osfd;
        osfd = socket(AF_INET6, SOCK_STREAM, 0);
        if (osfd != -1) {
            int on;
            socklen_t optlen = sizeof(on);
            if (getsockopt(osfd, IPPROTO_IPV6, IPV6_V6ONLY,
                           &on, &optlen) == 0) {
                _pr_ipv6_v6only_on_by_default = on;
            }
            close(osfd);
        }
    }
#endif
}  /* _PR_InitIO */

void _PR_CleanupIO(void)
{
    _PR_Putfd(_pr_stdin);
    _pr_stdin = NULL;
    _PR_Putfd(_pr_stdout);
    _pr_stdout = NULL;
    _PR_Putfd(_pr_stderr);
    _pr_stderr = NULL;

    _PR_CleanupFdCache();

    if (_pr_flock_cv)
    {
        PR_DestroyCondVar(_pr_flock_cv);
        _pr_flock_cv = NULL;
    }
    if (_pr_flock_lock)
    {
        PR_DestroyLock(_pr_flock_lock);
        _pr_flock_lock = NULL;
    }
    if (_pr_rename_lock)
    {
        PR_DestroyLock(_pr_rename_lock);
        _pr_rename_lock = NULL;
    }
}  /* _PR_CleanupIO */

PR_IMPLEMENT(PRFileDesc*) PR_GetSpecialFD(PRSpecialFD osfd)
{
    PRFileDesc *result = NULL;
    PR_ASSERT(osfd >= PR_StandardInput && osfd <= PR_StandardError);

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    switch (osfd)
    {
        case PR_StandardInput: result = _pr_stdin; break;
        case PR_StandardOutput: result = _pr_stdout; break;
        case PR_StandardError: result = _pr_stderr; break;
        default:
            (void)PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
    }
    return result;
}  /* PR_GetSpecialFD */

/*****************************************************************************/
/***************************** I/O private methods ***************************/
/*****************************************************************************/

static PRBool pt_TestAbort(void)
{
    PRThread *me = PR_GetCurrentThread();
    if(_PT_THREAD_INTERRUPTED(me))
    {
        PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
        me->state &= ~PT_THREAD_ABORTED;
        return PR_TRUE;
    }
    return PR_FALSE;
}  /* pt_TestAbort */

static void pt_MapError(void (*mapper)(PRIntn), PRIntn syserrno)
{
    switch (syserrno)
    {
        case EINTR:
            PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0); break;
        case ETIMEDOUT:
            PR_SetError(PR_IO_TIMEOUT_ERROR, 0); break;
        default:
            mapper(syserrno);
    }
}  /* pt_MapError */

static PRStatus pt_Close(PRFileDesc *fd)
{
    if ((NULL == fd) || (NULL == fd->secret)
        || ((_PR_FILEDESC_OPEN != fd->secret->state)
            && (_PR_FILEDESC_CLOSED != fd->secret->state)))
    {
        PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
        return PR_FAILURE;
    }
    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    if (_PR_FILEDESC_OPEN == fd->secret->state)
    {
        if (-1 == close(fd->secret->md.osfd))
        {
            pt_MapError(_PR_MD_MAP_CLOSE_ERROR, errno);
            return PR_FAILURE;
        }
        fd->secret->state = _PR_FILEDESC_CLOSED;
    }
    _PR_Putfd(fd);
    return PR_SUCCESS;
}  /* pt_Close */

static PRInt32 pt_Read(PRFileDesc *fd, void *buf, PRInt32 amount)
{
    PRInt32 syserrno, bytes = -1;

    if (pt_TestAbort()) {
        return bytes;
    }

    bytes = read(fd->secret->md.osfd, buf, amount);
    syserrno = errno;

    if ((bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
        && (!fd->secret->nonblocking))
    {
        pt_Continuation op;
        op.arg1.osfd = fd->secret->md.osfd;
        op.arg2.buffer = buf;
        op.arg3.amount = amount;
        op.timeout = PR_INTERVAL_NO_TIMEOUT;
        op.function = pt_read_cont;
        op.event = POLLIN | POLLPRI;
        bytes = pt_Continue(&op);
        syserrno = op.syserrno;
    }
    if (bytes < 0) {
        pt_MapError(_PR_MD_MAP_READ_ERROR, syserrno);
    }
    return bytes;
}  /* pt_Read */

static PRInt32 pt_Write(PRFileDesc *fd, const void *buf, PRInt32 amount)
{
    PRInt32 syserrno, bytes = -1;
    PRBool fNeedContinue = PR_FALSE;

    if (pt_TestAbort()) {
        return bytes;
    }

    bytes = write(fd->secret->md.osfd, buf, amount);
    syserrno = errno;

    if ( (bytes >= 0) && (bytes < amount) && (!fd->secret->nonblocking) )
    {
        buf = (char *) buf + bytes;
        amount -= bytes;
        fNeedContinue = PR_TRUE;
    }
    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
         && (!fd->secret->nonblocking) )
    {
        bytes = 0;
        fNeedContinue = PR_TRUE;
    }

    if (fNeedContinue == PR_TRUE)
    {
        pt_Continuation op;
        op.arg1.osfd = fd->secret->md.osfd;
        op.arg2.buffer = (void*)buf;
        op.arg3.amount = amount;
        op.timeout = PR_INTERVAL_NO_TIMEOUT;
        op.result.code = bytes;  /* initialize the number sent */
        op.function = pt_write_cont;
        op.event = POLLOUT | POLLPRI;
        bytes = pt_Continue(&op);
        syserrno = op.syserrno;
    }
    if (bytes == -1) {
        pt_MapError(_PR_MD_MAP_WRITE_ERROR, syserrno);
    }
    return bytes;
}  /* pt_Write */

static PRInt32 pt_Writev(
    PRFileDesc *fd, const PRIOVec *iov, PRInt32 iov_len, PRIntervalTime timeout)
{
    PRIntn iov_index;
    PRBool fNeedContinue = PR_FALSE;
    PRInt32 syserrno, bytes, rv = -1;
    struct iovec osiov_local[PR_MAX_IOVECTOR_SIZE], *osiov;
    int osiov_len;

    if (pt_TestAbort()) {
        return rv;
    }

    /* Ensured by PR_Writev */
    PR_ASSERT(iov_len <= PR_MAX_IOVECTOR_SIZE);

    /*
     * We can't pass iov to writev because PRIOVec and struct iovec
     * may not be binary compatible.  Make osiov a copy of iov and
     * pass osiov to writev.  We can modify osiov if we need to
     * continue the operation.
     */
    osiov = osiov_local;
    osiov_len = iov_len;
    for (iov_index = 0; iov_index < osiov_len; iov_index++)
    {
        osiov[iov_index].iov_base = iov[iov_index].iov_base;
        osiov[iov_index].iov_len = iov[iov_index].iov_len;
    }

    rv = bytes = writev(fd->secret->md.osfd, osiov, osiov_len);
    syserrno = errno;

    if (!fd->secret->nonblocking)
    {
        if (bytes >= 0)
        {
            /*
             * If we moved some bytes, how does that implicate the
             * i/o vector list?  In other words, exactly where are
             * we within that array?  What are the parameters for
             * resumption?  Maybe we're done!
             */
            for ( ; osiov_len > 0; osiov++, osiov_len--)
            {
                if (bytes < osiov->iov_len)
                {
                    /* this one's not done yet */
                    osiov->iov_base = (char*)osiov->iov_base + bytes;
                    osiov->iov_len -= bytes;
                    break;  /* go off and do that */
                }
                bytes -= osiov->iov_len;  /* this one's done cooked */
            }
            PR_ASSERT(osiov_len > 0 || bytes == 0);
            if (osiov_len > 0)
            {
                if (PR_INTERVAL_NO_WAIT == timeout)
                {
                    rv = -1;
                    syserrno = ETIMEDOUT;
                }
                else {
                    fNeedContinue = PR_TRUE;
                }
            }
        }
        else if (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
        {
            if (PR_INTERVAL_NO_WAIT == timeout) {
                syserrno = ETIMEDOUT;
            }
            else
            {
                rv = 0;
                fNeedContinue = PR_TRUE;
            }
        }
    }

    if (fNeedContinue == PR_TRUE)
    {
        pt_Continuation op;

        op.arg1.osfd = fd->secret->md.osfd;
        op.arg2.buffer = (void*)osiov;
        op.arg3.amount = osiov_len;
        op.timeout = timeout;
        op.result.code = rv;
        op.function = pt_writev_cont;
        op.event = POLLOUT | POLLPRI;
        rv = pt_Continue(&op);
        syserrno = op.syserrno;
    }
    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_WRITEV_ERROR, syserrno);
    }
    return rv;
}  /* pt_Writev */

static PRInt32 pt_Seek(PRFileDesc *fd, PRInt32 offset, PRSeekWhence whence)
{
    return _PR_MD_LSEEK(fd, offset, whence);
}  /* pt_Seek */

static PRInt64 pt_Seek64(PRFileDesc *fd, PRInt64 offset, PRSeekWhence whence)
{
    return _PR_MD_LSEEK64(fd, offset, whence);
}  /* pt_Seek64 */

static PRInt32 pt_Available_f(PRFileDesc *fd)
{
    PRInt32 result, cur, end;

    cur = _PR_MD_LSEEK(fd, 0, PR_SEEK_CUR);

    if (cur >= 0) {
        end = _PR_MD_LSEEK(fd, 0, PR_SEEK_END);
    }

    if ((cur < 0) || (end < 0)) {
        return -1;
    }

    result = end - cur;
    _PR_MD_LSEEK(fd, cur, PR_SEEK_SET);

    return result;
}  /* pt_Available_f */

static PRInt64 pt_Available64_f(PRFileDesc *fd)
{
    PRInt64 result, cur, end;
    PRInt64 minus_one;

    LL_I2L(minus_one, -1);
    cur = _PR_MD_LSEEK64(fd, LL_ZERO, PR_SEEK_CUR);

    if (LL_GE_ZERO(cur)) {
        end = _PR_MD_LSEEK64(fd, LL_ZERO, PR_SEEK_END);
    }

    if (!LL_GE_ZERO(cur) || !LL_GE_ZERO(end)) {
        return minus_one;
    }

    LL_SUB(result, end, cur);
    (void)_PR_MD_LSEEK64(fd, cur, PR_SEEK_SET);

    return result;
}  /* pt_Available64_f */

static PRInt32 pt_Available_s(PRFileDesc *fd)
{
    PRInt32 rv, bytes = -1;
    if (pt_TestAbort()) {
        return bytes;
    }

    rv = ioctl(fd->secret->md.osfd, FIONREAD, &bytes);

    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_SOCKETAVAILABLE_ERROR, errno);
    }
    return bytes;
}  /* pt_Available_s */

static PRInt64 pt_Available64_s(PRFileDesc *fd)
{
    PRInt64 rv;
    LL_I2L(rv, pt_Available_s(fd));
    return rv;
}  /* pt_Available64_s */

static PRStatus pt_FileInfo(PRFileDesc *fd, PRFileInfo *info)
{
    PRInt32 rv = _PR_MD_GETOPENFILEINFO(fd, info);
    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
}  /* pt_FileInfo */

static PRStatus pt_FileInfo64(PRFileDesc *fd, PRFileInfo64 *info)
{
    PRInt32 rv = _PR_MD_GETOPENFILEINFO64(fd, info);
    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
}  /* pt_FileInfo64 */

static PRStatus pt_Synch(PRFileDesc *fd)
{
    return (NULL == fd) ? PR_FAILURE : PR_SUCCESS;
} /* pt_Synch */

static PRStatus pt_Fsync(PRFileDesc *fd)
{
    PRIntn rv = -1;
    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    rv = fsync(fd->secret->md.osfd);
    if (rv < 0) {
        pt_MapError(_PR_MD_MAP_FSYNC_ERROR, errno);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* pt_Fsync */

static PRStatus pt_Connect(
    PRFileDesc *fd, const PRNetAddr *addr, PRIntervalTime timeout)
{
    PRIntn rv = -1, syserrno;
    pt_SockLen addr_len;
    const PRNetAddr *addrp = addr;
#if defined(_PR_HAVE_SOCKADDR_LEN) || defined(_PR_INET6)
    PRNetAddr addrCopy;
#endif
#ifdef _PR_HAVE_SOCKADDR_LEN
    PRUint16 md_af = addr->raw.family;
#endif

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
    addr_len = PR_NETADDR_SIZE(addr);
#ifdef _PR_INET6
    if (addr->raw.family == PR_AF_INET6) {
#ifdef _PR_HAVE_SOCKADDR_LEN
        md_af = AF_INET6;
#else
        addrCopy = *addr;
        addrCopy.raw.family = AF_INET6;
        addrp = &addrCopy;
#endif
    }
#endif

#ifdef _PR_HAVE_SOCKADDR_LEN
    addrCopy = *addr;
    ((struct sockaddr*)&addrCopy)->sa_len = addr_len;
    ((struct sockaddr*)&addrCopy)->sa_family = md_af;
    addrp = &addrCopy;
#endif
    rv = connect(fd->secret->md.osfd, (struct sockaddr*)addrp, addr_len);
    syserrno = errno;
    if ((-1 == rv) && (EINPROGRESS == syserrno) && (!fd->secret->nonblocking))
    {
        if (PR_INTERVAL_NO_WAIT == timeout) {
            syserrno = ETIMEDOUT;
        }
        else
        {
            pt_Continuation op;
            op.arg1.osfd = fd->secret->md.osfd;
            op.arg2.buffer = (void*)addrp;
            op.arg3.amount = addr_len;
            op.timeout = timeout;
            op.function = pt_connect_cont;
            op.event = POLLOUT | POLLPRI;
            rv = pt_Continue(&op);
            syserrno = op.syserrno;
        }
    }
    if (-1 == rv) {
        pt_MapError(_PR_MD_MAP_CONNECT_ERROR, syserrno);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* pt_Connect */

static PRStatus pt_ConnectContinue(
    PRFileDesc *fd, PRInt16 out_flags)
{
    int err;
    PRInt32 osfd;

    if (out_flags & PR_POLL_NVAL)
    {
        PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
        return PR_FAILURE;
    }
    if ((out_flags & (PR_POLL_WRITE | PR_POLL_EXCEPT | PR_POLL_ERR
                      | PR_POLL_HUP)) == 0)
    {
        PR_ASSERT(out_flags == 0);
        PR_SetError(PR_IN_PROGRESS_ERROR, 0);
        return PR_FAILURE;
    }

    osfd = fd->secret->md.osfd;

    err = _MD_unix_get_nonblocking_connect_error(osfd);
    if (err != 0)
    {
        _PR_MD_MAP_CONNECT_ERROR(err);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* pt_ConnectContinue */

PR_IMPLEMENT(PRStatus) PR_GetConnectStatus(const PRPollDesc *pd)
{
    /* Find the NSPR layer and invoke its connectcontinue method */
    PRFileDesc *bottom = PR_GetIdentitiesLayer(pd->fd, PR_NSPR_IO_LAYER);

    if (NULL == bottom)
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }
    return pt_ConnectContinue(bottom, pd->out_flags);
}  /* PR_GetConnectStatus */

static PRFileDesc* pt_Accept(
    PRFileDesc *fd, PRNetAddr *addr, PRIntervalTime timeout)
{
    PRFileDesc *newfd = NULL;
    PRIntn syserrno, osfd = -1;
    pt_SockLen addr_len = sizeof(PRNetAddr);

    if (pt_TestAbort()) {
        return newfd;
    }

#ifdef _PR_STRICT_ADDR_LEN
    if (addr)
    {
        /*
         * Set addr->raw.family just so that we can use the
         * PR_NETADDR_SIZE macro.
         */
        addr->raw.family = fd->secret->af;
        addr_len = PR_NETADDR_SIZE(addr);
    }
#endif

    osfd = accept(fd->secret->md.osfd, (struct sockaddr*)addr, &addr_len);
    syserrno = errno;

    if (osfd == -1)
    {
        if (fd->secret->nonblocking) {
            goto failed;
        }

        if (EWOULDBLOCK != syserrno && EAGAIN != syserrno
            && ECONNABORTED != syserrno) {
            goto failed;
        }
        else
        {
            if (PR_INTERVAL_NO_WAIT == timeout) {
                syserrno = ETIMEDOUT;
            }
            else
            {
                pt_Continuation op;
                op.arg1.osfd = fd->secret->md.osfd;
                op.arg2.buffer = addr;
                op.arg3.addr_len = &addr_len;
                op.timeout = timeout;
                op.function = pt_accept_cont;
                op.event = POLLIN | POLLPRI;
                osfd = pt_Continue(&op);
                syserrno = op.syserrno;
            }
            if (osfd < 0) {
                goto failed;
            }
        }
    }
#ifdef _PR_HAVE_SOCKADDR_LEN
    /* ignore the sa_len field of struct sockaddr */
    if (addr)
    {
        addr->raw.family = ((struct sockaddr*)addr)->sa_family;
    }
#endif /* _PR_HAVE_SOCKADDR_LEN */
#ifdef _PR_INET6
    if (addr && (AF_INET6 == addr->raw.family)) {
        addr->raw.family = PR_AF_INET6;
    }
#endif
    newfd = pt_SetMethods(osfd, PR_DESC_SOCKET_TCP, PR_TRUE, PR_FALSE);
    if (newfd == NULL) {
        close(osfd);    /* $$$ whoops! this doesn't work $$$ */
    }
    else
    {
        PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
        PR_ASSERT(IsValidNetAddrLen(addr, addr_len) == PR_TRUE);
#ifdef LINUX
        /*
         * On Linux, experiments showed that the accepted sockets
         * inherit the TCP_NODELAY socket option of the listening
         * socket.
         */
        newfd->secret->md.tcp_nodelay = fd->secret->md.tcp_nodelay;
#endif
    }
    return newfd;

failed:
    pt_MapError(_PR_MD_MAP_ACCEPT_ERROR, syserrno);
    return NULL;
}  /* pt_Accept */

static PRStatus pt_Bind(PRFileDesc *fd, const PRNetAddr *addr)
{
    PRIntn rv;
    pt_SockLen addr_len;
    const PRNetAddr *addrp = addr;
#if defined(_PR_HAVE_SOCKADDR_LEN) || defined(_PR_INET6)
    PRNetAddr addrCopy;
#endif
#ifdef _PR_HAVE_SOCKADDR_LEN
    PRUint16 md_af = addr->raw.family;
#endif

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
    if (addr->raw.family == AF_UNIX)
    {
        /* Disallow relative pathnames */
        if (addr->local.path[0] != '/'
#if defined(LINUX)
            /* Linux has abstract socket address support */
            && addr->local.path[0] != 0
#endif
           )
        {
            PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
            return PR_FAILURE;
        }
    }

#ifdef _PR_INET6
    if (addr->raw.family == PR_AF_INET6) {
#ifdef _PR_HAVE_SOCKADDR_LEN
        md_af = AF_INET6;
#else
        addrCopy = *addr;
        addrCopy.raw.family = AF_INET6;
        addrp = &addrCopy;
#endif
    }
#endif

    addr_len = PR_NETADDR_SIZE(addr);
#ifdef _PR_HAVE_SOCKADDR_LEN
    addrCopy = *addr;
    ((struct sockaddr*)&addrCopy)->sa_len = addr_len;
    ((struct sockaddr*)&addrCopy)->sa_family = md_af;
    addrp = &addrCopy;
#endif
    rv = bind(fd->secret->md.osfd, (struct sockaddr*)addrp, addr_len);

    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_BIND_ERROR, errno);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* pt_Bind */

static PRStatus pt_Listen(PRFileDesc *fd, PRIntn backlog)
{
    PRIntn rv;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    rv = listen(fd->secret->md.osfd, backlog);
    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_LISTEN_ERROR, errno);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* pt_Listen */

static PRStatus pt_Shutdown(PRFileDesc *fd, PRIntn how)
{
    PRIntn rv = -1;
    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    rv = shutdown(fd->secret->md.osfd, how);

    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_SHUTDOWN_ERROR, errno);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* pt_Shutdown */

static PRInt16 pt_Poll(PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags)
{
    *out_flags = 0;
    return in_flags;
}  /* pt_Poll */

static PRInt32 pt_Recv(
    PRFileDesc *fd, void *buf, PRInt32 amount,
    PRIntn flags, PRIntervalTime timeout)
{
    PRInt32 syserrno, bytes = -1;
    PRIntn osflags;

    if (0 == flags) {
        osflags = 0;
    }
    else if (PR_MSG_PEEK == flags)
    {
        osflags = MSG_PEEK;
    }
    else
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return bytes;
    }

    if (pt_TestAbort()) {
        return bytes;
    }

    /* recv() is a much slower call on pre-2.6 Solaris than read(). */
#if defined(SOLARIS)
    if (0 == osflags) {
        bytes = read(fd->secret->md.osfd, buf, amount);
    }
    else {
        bytes = recv(fd->secret->md.osfd, buf, amount, osflags);
    }
#else
    bytes = recv(fd->secret->md.osfd, buf, amount, osflags);
#endif
    syserrno = errno;

    if ((bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
        && (!fd->secret->nonblocking))
    {
        if (PR_INTERVAL_NO_WAIT == timeout) {
            syserrno = ETIMEDOUT;
        }
        else
        {
            pt_Continuation op;
            op.arg1.osfd = fd->secret->md.osfd;
            op.arg2.buffer = buf;
            op.arg3.amount = amount;
            op.arg4.flags = osflags;
            op.timeout = timeout;
            op.function = pt_recv_cont;
            op.event = POLLIN | POLLPRI;
            bytes = pt_Continue(&op);
            syserrno = op.syserrno;
        }
    }
    if (bytes < 0) {
        pt_MapError(_PR_MD_MAP_RECV_ERROR, syserrno);
    }
    return bytes;
}  /* pt_Recv */

static PRInt32 pt_SocketRead(PRFileDesc *fd, void *buf, PRInt32 amount)
{
    return pt_Recv(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT);
}  /* pt_SocketRead */

static PRInt32 pt_Send(
    PRFileDesc *fd, const void *buf, PRInt32 amount,
    PRIntn flags, PRIntervalTime timeout)
{
    PRInt32 syserrno, bytes = -1;
    PRBool fNeedContinue = PR_FALSE;
#if defined(SOLARIS)
    PRInt32 tmp_amount = amount;
#endif

    if (pt_TestAbort()) {
        return bytes;
    }

    /*
     * On pre-2.6 Solaris, send() is much slower than write().
     * On 2.6 and beyond, with in-kernel sockets, send() and
     * write() are fairly equivalent in performance.
     */
#if defined(SOLARIS)
    PR_ASSERT(0 == flags);
retry:
    bytes = write(fd->secret->md.osfd, buf, tmp_amount);
#else
    bytes = send(fd->secret->md.osfd, buf, amount, flags);
#endif
    syserrno = errno;

#if defined(SOLARIS)
    /*
     * The write system call has been reported to return the ERANGE error
     * on occasion. Try to write in smaller chunks to workaround this bug.
     */
    if ((bytes == -1) && (syserrno == ERANGE))
    {
        if (tmp_amount > 1)
        {
            tmp_amount = tmp_amount/2;  /* half the bytes */
            goto retry;
        }
    }
#endif

    if ( (bytes >= 0) && (bytes < amount) && (!fd->secret->nonblocking) )
    {
        if (PR_INTERVAL_NO_WAIT == timeout)
        {
            bytes = -1;
            syserrno = ETIMEDOUT;
        }
        else
        {
            buf = (char *) buf + bytes;
            amount -= bytes;
            fNeedContinue = PR_TRUE;
        }
    }
    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
         && (!fd->secret->nonblocking) )
    {
        if (PR_INTERVAL_NO_WAIT == timeout) {
            syserrno = ETIMEDOUT;
        }
        else
        {
            bytes = 0;
            fNeedContinue = PR_TRUE;
        }
    }

    if (fNeedContinue == PR_TRUE)
    {
        pt_Continuation op;
        op.arg1.osfd = fd->secret->md.osfd;
        op.arg2.buffer = (void*)buf;
        op.arg3.amount = amount;
        op.arg4.flags = flags;
        op.timeout = timeout;
        op.result.code = bytes;  /* initialize the number sent */
        op.function = pt_send_cont;
        op.event = POLLOUT | POLLPRI;
        bytes = pt_Continue(&op);
        syserrno = op.syserrno;
    }
    if (bytes == -1) {
        pt_MapError(_PR_MD_MAP_SEND_ERROR, syserrno);
    }
    return bytes;
}  /* pt_Send */

static PRInt32 pt_SocketWrite(PRFileDesc *fd, const void *buf, PRInt32 amount)
{
    return pt_Send(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT);
}  /* pt_SocketWrite */

static PRInt32 pt_SendTo(
    PRFileDesc *fd, const void *buf,
    PRInt32 amount, PRIntn flags, const PRNetAddr *addr,
    PRIntervalTime timeout)
{
    PRInt32 syserrno, bytes = -1;
    PRBool fNeedContinue = PR_FALSE;
    pt_SockLen addr_len;
    const PRNetAddr *addrp = addr;
#if defined(_PR_HAVE_SOCKADDR_LEN) || defined(_PR_INET6)
    PRNetAddr addrCopy;
#endif
#ifdef _PR_HAVE_SOCKADDR_LEN
    PRUint16 md_af = addr->raw.family;
#endif

    if (pt_TestAbort()) {
        return bytes;
    }

    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
#ifdef _PR_INET6
    if (addr->raw.family == PR_AF_INET6) {
#ifdef _PR_HAVE_SOCKADDR_LEN
        md_af = AF_INET6;
#else
        addrCopy = *addr;
        addrCopy.raw.family = AF_INET6;
        addrp = &addrCopy;
#endif
    }
#endif

    addr_len = PR_NETADDR_SIZE(addr);
#ifdef _PR_HAVE_SOCKADDR_LEN
    addrCopy = *addr;
    ((struct sockaddr*)&addrCopy)->sa_len = addr_len;
    ((struct sockaddr*)&addrCopy)->sa_family = md_af;
    addrp = &addrCopy;
#endif
    bytes = sendto(
                fd->secret->md.osfd, buf, amount, flags,
                (struct sockaddr*)addrp, addr_len);
    syserrno = errno;
    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
         && (!fd->secret->nonblocking) )
    {
        if (PR_INTERVAL_NO_WAIT == timeout) {
            syserrno = ETIMEDOUT;
        }
        else {
            fNeedContinue = PR_TRUE;
        }
    }
    if (fNeedContinue == PR_TRUE)
    {
        pt_Continuation op;
        op.arg1.osfd = fd->secret->md.osfd;
        op.arg2.buffer = (void*)buf;
        op.arg3.amount = amount;
        op.arg4.flags = flags;
        op.arg5.addr = (PRNetAddr*)addrp;
        op.timeout = timeout;
        op.result.code = 0;  /* initialize the number sent */
        op.function = pt_sendto_cont;
        op.event = POLLOUT | POLLPRI;
        bytes = pt_Continue(&op);
        syserrno = op.syserrno;
    }
    if (bytes < 0) {
        pt_MapError(_PR_MD_MAP_SENDTO_ERROR, syserrno);
    }
    return bytes;
}  /* pt_SendTo */

#if defined(LINUX) || defined(DARWIN)
/* Linux uses SendTo to send data during TCP Fast Open. OSX uses connectx, but
 * we will make it imitate the Linux's interface. */
static PRInt32 pt_TCP_SendTo(
    PRFileDesc *fd, const void *buf,
    PRInt32 amount, PRIntn flags, const PRNetAddr *addr,
    PRIntervalTime timeout)
{
#if defined(LINUX) || HAS_CONNECTX
    PRInt32 syserrno, bytes = -1;
    PRBool fNeedContinue = PR_FALSE;
    pt_SockLen addr_len;
    const PRNetAddr *addrp = addr;
#if defined(_PR_HAVE_SOCKADDR_LEN) || defined(_PR_INET6)
    PRNetAddr addrCopy;
#endif
#ifdef _PR_HAVE_SOCKADDR_LEN
    PRUint16 md_af = addr->raw.family;
#endif

    if (pt_TestAbort()) {
        return bytes;
    }

    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
    addr_len = PR_NETADDR_SIZE(addr);
#if defined(_PR_INET6)
    if (addr->raw.family == PR_AF_INET6) {
#ifdef _PR_HAVE_SOCKADDR_LEN
        md_af = AF_INET6;
#else
        /* If _PR_INET6 is defined and it is PR_AF_INET6 we set family
         * to AF_INET6. */
        addrCopy = *addr;
        addrCopy.raw.family = AF_INET6;
        addrp = &addrCopy;
#endif
    }
#endif

#ifdef _PR_HAVE_SOCKADDR_LEN
    /* if _PR_HAVE_SOCKADDR_LEN is defined and it is PR_AF_INET6 we set family
     * to AF_INET6 and we set address length. */
    addrCopy = *addr;
    ((struct sockaddr*)&addrCopy)->sa_len = addr_len;
    ((struct sockaddr*)&addrCopy)->sa_family = md_af;
    addrp = &addrCopy;
#endif

#ifndef HAS_CONNECTX
    bytes = sendto(
                fd->secret->md.osfd, buf, amount, MSG_FASTOPEN,
                (struct sockaddr*)addrp, addr_len);
#else
    sa_endpoints_t endpoints;
    endpoints.sae_srcif = 0;
    endpoints.sae_srcaddr = NULL;
    endpoints.sae_srcaddrlen = 0;
    endpoints.sae_dstaddr = (struct sockaddr *)addrp;
    endpoints.sae_dstaddrlen = addr_len;
    struct iovec iov[1];
    iov[0].iov_base = buf;
    iov[0].iov_len = amount;
    PRInt32 rv = connectx(fd->secret->md.osfd, &endpoints, SAE_ASSOCID_ANY,
                          CONNECT_DATA_IDEMPOTENT, iov, 1, &bytes, NULL);
#endif
    syserrno = errno;
    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
         && (!fd->secret->nonblocking) ) {
        if (PR_INTERVAL_NO_WAIT == timeout) {
            syserrno = ETIMEDOUT;
        }
        else {
            fNeedContinue = PR_TRUE;
        }
    }
    if (fNeedContinue == PR_TRUE) {
        pt_Continuation op;
        op.arg1.osfd = fd->secret->md.osfd;
        op.arg2.buffer = (void*)buf;
        op.arg3.amount = amount;
        op.arg4.flags = flags;
        op.arg5.addr = (PRNetAddr*)addrp;
        op.timeout = timeout;
        op.result.code = 0;  /* initialize the number sent */
        op.function = pt_sendto_cont;
        op.event = POLLOUT | POLLPRI;
        bytes = pt_Continue(&op);
        syserrno = op.syserrno;
    }
    if (bytes < 0) {
        pt_MapError(_PR_MD_MAP_SENDTO_ERROR, syserrno);
    }
    return bytes;
#else /* !HAS_CONNECTX */
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return -1;
#endif
}  /* pt_TCP_SendTo */
#endif /* LINUX || DARWIN */

static PRInt32 pt_RecvFrom(PRFileDesc *fd, void *buf, PRInt32 amount,
                           PRIntn flags, PRNetAddr *addr, PRIntervalTime timeout)
{
    PRBool fNeedContinue = PR_FALSE;
    PRInt32 syserrno, bytes = -1;
    pt_SockLen addr_len = sizeof(PRNetAddr);

    if (pt_TestAbort()) {
        return bytes;
    }

    bytes = recvfrom(
                fd->secret->md.osfd, buf, amount, flags,
                (struct sockaddr*)addr, &addr_len);
    syserrno = errno;

    if ( (bytes == -1) && (syserrno == EWOULDBLOCK || syserrno == EAGAIN)
         && (!fd->secret->nonblocking) )
    {
        if (PR_INTERVAL_NO_WAIT == timeout) {
            syserrno = ETIMEDOUT;
        }
        else {
            fNeedContinue = PR_TRUE;
        }
    }

    if (fNeedContinue == PR_TRUE)
    {
        pt_Continuation op;
        op.arg1.osfd = fd->secret->md.osfd;
        op.arg2.buffer = buf;
        op.arg3.amount = amount;
        op.arg4.flags = flags;
        op.arg5.addr = addr;
        op.timeout = timeout;
        op.function = pt_recvfrom_cont;
        op.event = POLLIN | POLLPRI;
        bytes = pt_Continue(&op);
        syserrno = op.syserrno;
    }
    if (bytes >= 0)
    {
#ifdef _PR_HAVE_SOCKADDR_LEN
        /* ignore the sa_len field of struct sockaddr */
        if (addr)
        {
            addr->raw.family = ((struct sockaddr*)addr)->sa_family;
        }
#endif /* _PR_HAVE_SOCKADDR_LEN */
#ifdef _PR_INET6
        if (addr && (AF_INET6 == addr->raw.family)) {
            addr->raw.family = PR_AF_INET6;
        }
#endif
    }
    else {
        pt_MapError(_PR_MD_MAP_RECVFROM_ERROR, syserrno);
    }
    return bytes;
}  /* pt_RecvFrom */

#ifdef AIX
#ifndef HAVE_SEND_FILE
static pthread_once_t pt_aix_sendfile_once_block = PTHREAD_ONCE_INIT;

static void pt_aix_sendfile_init_routine(void)
{
    void *handle = dlopen(NULL, RTLD_NOW | RTLD_GLOBAL);
    pt_aix_sendfile_fptr = (ssize_t (*)()) dlsym(handle, "send_file");
    dlclose(handle);
}

/*
 * pt_AIXDispatchSendFile
 */
static PRInt32 pt_AIXDispatchSendFile(PRFileDesc *sd, PRSendFileData *sfd,
                                      PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    int rv;

    rv = pthread_once(&pt_aix_sendfile_once_block,
                      pt_aix_sendfile_init_routine);
    PR_ASSERT(0 == rv);
    if (pt_aix_sendfile_fptr) {
        return pt_AIXSendFile(sd, sfd, flags, timeout);
    } else {
        return PR_EmulateSendFile(sd, sfd, flags, timeout);
    }
}
#endif /* !HAVE_SEND_FILE */


/*
 * pt_AIXSendFile
 *
 *    Send file sfd->fd across socket sd. If specified, header and trailer
 *    buffers are sent before and after the file, respectively.
 *
 *    PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *    return number of bytes sent or -1 on error
 *
 *      This implementation takes advantage of the send_file() system
 *      call available in AIX 4.3.2.
 */

static PRInt32 pt_AIXSendFile(PRFileDesc *sd, PRSendFileData *sfd,
                              PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    struct sf_parms sf_struct;
    uint_t send_flags;
    ssize_t rv;
    int syserrno;
    PRInt32 count;
    unsigned long long saved_file_offset;
    long long saved_file_bytes;

    sf_struct.header_data = (void *) sfd->header;  /* cast away the 'const' */
    sf_struct.header_length = sfd->hlen;
    sf_struct.file_descriptor = sfd->fd->secret->md.osfd;
    sf_struct.file_size = 0;
    sf_struct.file_offset = sfd->file_offset;
    if (sfd->file_nbytes == 0) {
        sf_struct.file_bytes = -1;
    }
    else {
        sf_struct.file_bytes = sfd->file_nbytes;
    }
    sf_struct.trailer_data = (void *) sfd->trailer;
    sf_struct.trailer_length = sfd->tlen;
    sf_struct.bytes_sent = 0;

    saved_file_offset = sf_struct.file_offset;
    saved_file_bytes = sf_struct.file_bytes;

    send_flags = 0;         /* flags processed at the end */

    /* The first argument to send_file() is int*. */
    PR_ASSERT(sizeof(int) == sizeof(sd->secret->md.osfd));
    do {
        rv = AIX_SEND_FILE(&sd->secret->md.osfd, &sf_struct, send_flags);
    } while (rv == -1 && (syserrno = errno) == EINTR);

    if (rv == -1) {
        if (syserrno == EAGAIN || syserrno == EWOULDBLOCK) {
            count = 0; /* Not a real error.  Need to continue. */
        } else {
            count = -1;
        }
    } else {
        count = sf_struct.bytes_sent;
        /*
         * A bug in AIX 4.3.2 prevents the 'file_bytes' field from
         * being updated. So, 'file_bytes' is maintained by NSPR to
         * avoid conflict when this bug is fixed in AIX, in the future.
         */
        if (saved_file_bytes != -1) {
            saved_file_bytes -= (sf_struct.file_offset - saved_file_offset);
        }
        sf_struct.file_bytes = saved_file_bytes;
    }

    if ((rv == 1) || ((rv == -1) && (count == 0))) {
        pt_Continuation op;

        op.arg1.osfd = sd->secret->md.osfd;
        op.arg2.buffer = &sf_struct;
        op.arg4.flags = send_flags;
        op.result.code = count;
        op.timeout = timeout;
        op.function = pt_aix_sendfile_cont;
        op.event = POLLOUT | POLLPRI;
        count = pt_Continue(&op);
        syserrno = op.syserrno;
    }

    if (count == -1) {
        pt_MapError(_MD_aix_map_sendfile_error, syserrno);
        return -1;
    }
    if (flags & PR_TRANSMITFILE_CLOSE_SOCKET) {
        PR_Close(sd);
    }
    PR_ASSERT(count == (sfd->hlen + sfd->tlen +
                        ((sfd->file_nbytes ==  0) ?
                         sf_struct.file_size - sfd->file_offset :
                         sfd->file_nbytes)));
    return count;
}
#endif /* AIX */

#ifdef HPUX11
/*
 * pt_HPUXSendFile
 *
 *    Send file sfd->fd across socket sd. If specified, header and trailer
 *    buffers are sent before and after the file, respectively.
 *
 *    PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *    return number of bytes sent or -1 on error
 *
 *      This implementation takes advantage of the sendfile() system
 *      call available in HP-UX B.11.00.
 */

static PRInt32 pt_HPUXSendFile(PRFileDesc *sd, PRSendFileData *sfd,
                               PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    struct stat statbuf;
    size_t nbytes_to_send, file_nbytes_to_send;
    struct iovec hdtrl[2];  /* optional header and trailer buffers */
    int send_flags;
    PRInt32 count;
    int syserrno;

    if (sfd->file_nbytes == 0) {
        /* Get file size */
        if (fstat(sfd->fd->secret->md.osfd, &statbuf) == -1) {
            _PR_MD_MAP_FSTAT_ERROR(errno);
            return -1;
        }
        file_nbytes_to_send = statbuf.st_size - sfd->file_offset;
    } else {
        file_nbytes_to_send = sfd->file_nbytes;
    }
    nbytes_to_send = sfd->hlen + sfd->tlen + file_nbytes_to_send;

    hdtrl[0].iov_base = (void *) sfd->header;  /* cast away the 'const' */
    hdtrl[0].iov_len = sfd->hlen;
    hdtrl[1].iov_base = (void *) sfd->trailer;
    hdtrl[1].iov_len = sfd->tlen;
    /*
     * SF_DISCONNECT seems to close the socket even if sendfile()
     * only does a partial send on a nonblocking socket.  This
     * would prevent the subsequent sendfile() calls on that socket
     * from working.  So we don't use the SD_DISCONNECT flag.
     */
    send_flags = 0;

    do {
        count = sendfile(sd->secret->md.osfd, sfd->fd->secret->md.osfd,
                         sfd->file_offset, file_nbytes_to_send, hdtrl, send_flags);
    } while (count == -1 && (syserrno = errno) == EINTR);

    if (count == -1 && (syserrno == EAGAIN || syserrno == EWOULDBLOCK)) {
        count = 0;
    }
    if (count != -1 && count < nbytes_to_send) {
        pt_Continuation op;

        if (count < sfd->hlen) {
            /* header not sent */

            hdtrl[0].iov_base = ((char *) sfd->header) + count;
            hdtrl[0].iov_len = sfd->hlen - count;
            op.arg3.file_spec.offset = sfd->file_offset;
            op.arg3.file_spec.nbytes = file_nbytes_to_send;
        } else if (count < (sfd->hlen + file_nbytes_to_send)) {
            /* header sent, file not sent */

            hdtrl[0].iov_base = NULL;
            hdtrl[0].iov_len = 0;

            op.arg3.file_spec.offset = sfd->file_offset + count - sfd->hlen;
            op.arg3.file_spec.nbytes = file_nbytes_to_send - (count - sfd->hlen);
        } else if (count < (sfd->hlen + file_nbytes_to_send + sfd->tlen)) {
            PRUint32 trailer_nbytes_sent;

            /* header sent, file sent, trailer not sent */

            hdtrl[0].iov_base = NULL;
            hdtrl[0].iov_len = 0;
            /*
             * set file offset and len so that no more file data is
             * sent
             */
            op.arg3.file_spec.offset = statbuf.st_size;
            op.arg3.file_spec.nbytes = 0;

            trailer_nbytes_sent = count - sfd->hlen - file_nbytes_to_send;
            hdtrl[1].iov_base = ((char *) sfd->trailer) + trailer_nbytes_sent;
            hdtrl[1].iov_len = sfd->tlen - trailer_nbytes_sent;
        }

        op.arg1.osfd = sd->secret->md.osfd;
        op.filedesc = sfd->fd->secret->md.osfd;
        op.arg2.buffer = hdtrl;
        op.arg3.file_spec.st_size = statbuf.st_size;
        op.arg4.flags = send_flags;
        op.nbytes_to_send = nbytes_to_send - count;
        op.result.code = count;
        op.timeout = timeout;
        op.function = pt_hpux_sendfile_cont;
        op.event = POLLOUT | POLLPRI;
        count = pt_Continue(&op);
        syserrno = op.syserrno;
    }

    if (count == -1) {
        pt_MapError(_MD_hpux_map_sendfile_error, syserrno);
        return -1;
    }
    if (flags & PR_TRANSMITFILE_CLOSE_SOCKET) {
        PR_Close(sd);
    }
    PR_ASSERT(count == nbytes_to_send);
    return count;
}

#endif  /* HPUX11 */

#ifdef SOLARIS

/*
 *    pt_SolarisSendFile
 *
 *    Send file sfd->fd across socket sd. If specified, header and trailer
 *    buffers are sent before and after the file, respectively.
 *
 *    PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *    return number of bytes sent or -1 on error
 *
 *    This implementation takes advantage of the sendfilev() system
 *    call available in Solaris 8.
 */

static PRInt32 pt_SolarisSendFile(PRFileDesc *sd, PRSendFileData *sfd,
                                  PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    struct stat statbuf;
    size_t nbytes_to_send, file_nbytes_to_send;
    struct sendfilevec sfv_struct[3];
    int sfvcnt = 0;
    size_t xferred;
    PRInt32 count;
    int syserrno;

    if (sfd->file_nbytes == 0) {
        /* Get file size */
        if (fstat(sfd->fd->secret->md.osfd, &statbuf) == -1) {
            _PR_MD_MAP_FSTAT_ERROR(errno);
            return -1;
        }
        file_nbytes_to_send = statbuf.st_size - sfd->file_offset;
    } else {
        file_nbytes_to_send = sfd->file_nbytes;
    }

    nbytes_to_send = sfd->hlen + sfd->tlen + file_nbytes_to_send;

    if (sfd->hlen != 0) {
        sfv_struct[sfvcnt].sfv_fd = SFV_FD_SELF;
        sfv_struct[sfvcnt].sfv_flag = 0;
        sfv_struct[sfvcnt].sfv_off = (off_t) sfd->header;
        sfv_struct[sfvcnt].sfv_len = sfd->hlen;
        sfvcnt++;
    }

    if (file_nbytes_to_send != 0) {
        sfv_struct[sfvcnt].sfv_fd = sfd->fd->secret->md.osfd;
        sfv_struct[sfvcnt].sfv_flag = 0;
        sfv_struct[sfvcnt].sfv_off = sfd->file_offset;
        sfv_struct[sfvcnt].sfv_len = file_nbytes_to_send;
        sfvcnt++;
    }

    if (sfd->tlen != 0) {
        sfv_struct[sfvcnt].sfv_fd = SFV_FD_SELF;
        sfv_struct[sfvcnt].sfv_flag = 0;
        sfv_struct[sfvcnt].sfv_off = (off_t) sfd->trailer;
        sfv_struct[sfvcnt].sfv_len = sfd->tlen;
        sfvcnt++;
    }

    if (0 == sfvcnt) {
        count = 0;
        goto done;
    }

    /*
     * Strictly speaking, we may have sent some bytes when the
     * sendfilev() is interrupted and we should retry it from an
     * updated offset.  We are not doing that here.
     */
    count = SOLARIS_SENDFILEV(sd->secret->md.osfd, sfv_struct,
                              sfvcnt, &xferred);

    PR_ASSERT((count == -1) || (count == xferred));

    if (count == -1) {
        syserrno = errno;
        if (syserrno == EINTR
            || syserrno == EAGAIN || syserrno == EWOULDBLOCK) {
            count = xferred;
        }
    } else if (count == 0) {
        /*
         * We are now at EOF. The file was truncated. Solaris sendfile is
         * supposed to return 0 and no error in this case, though some versions
         * may return -1 and EINVAL .
         */
        count = -1;
        syserrno = 0;  /* will be treated as EOF */
    }

    if (count != -1 && count < nbytes_to_send) {
        pt_Continuation op;
        struct sendfilevec *vec = sfv_struct;
        PRInt32 rem = count;

        while (rem >= vec->sfv_len) {
            rem -= vec->sfv_len;
            vec++;
            sfvcnt--;
        }
        PR_ASSERT(sfvcnt > 0);

        vec->sfv_off += rem;
        vec->sfv_len -= rem;
        PR_ASSERT(vec->sfv_len > 0);

        op.arg1.osfd = sd->secret->md.osfd;
        op.arg2.buffer = vec;
        op.arg3.amount = sfvcnt;
        op.arg4.flags = 0;
        op.nbytes_to_send = nbytes_to_send - count;
        op.result.code = count;
        op.timeout = timeout;
        op.function = pt_solaris_sendfile_cont;
        op.event = POLLOUT | POLLPRI;
        count = pt_Continue(&op);
        syserrno = op.syserrno;
    }

done:
    if (count == -1) {
        pt_MapError(_MD_solaris_map_sendfile_error, syserrno);
        return -1;
    }
    if (flags & PR_TRANSMITFILE_CLOSE_SOCKET) {
        PR_Close(sd);
    }
    PR_ASSERT(count == nbytes_to_send);
    return count;
}

#ifndef HAVE_SENDFILEV
static pthread_once_t pt_solaris_sendfilev_once_block = PTHREAD_ONCE_INIT;

static void pt_solaris_sendfilev_init_routine(void)
{
    void *handle;
    PRBool close_it = PR_FALSE;

    /*
     * We do not want to unload libsendfile.so.  This handle is leaked
     * intentionally.
     */
    handle = dlopen("libsendfile.so", RTLD_LAZY | RTLD_GLOBAL);
    PR_LOG(_pr_io_lm, PR_LOG_DEBUG,
           ("dlopen(libsendfile.so) returns %p", handle));

    if (NULL == handle) {
        /*
         * The dlopen(0, mode) call is to allow for the possibility that
         * sendfilev() may become part of a standard system library in a
         * future Solaris release.
         */
        handle = dlopen(0, RTLD_LAZY | RTLD_GLOBAL);
        PR_LOG(_pr_io_lm, PR_LOG_DEBUG,
               ("dlopen(0) returns %p", handle));
        close_it = PR_TRUE;
    }
    pt_solaris_sendfilev_fptr = (ssize_t (*)()) dlsym(handle, "sendfilev");
    PR_LOG(_pr_io_lm, PR_LOG_DEBUG,
           ("dlsym(sendfilev) returns %p", pt_solaris_sendfilev_fptr));

    if (close_it) {
        dlclose(handle);
    }
}

/*
 * pt_SolarisDispatchSendFile
 */
static PRInt32 pt_SolarisDispatchSendFile(PRFileDesc *sd, PRSendFileData *sfd,
        PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    int rv;

    rv = pthread_once(&pt_solaris_sendfilev_once_block,
                      pt_solaris_sendfilev_init_routine);
    PR_ASSERT(0 == rv);
    if (pt_solaris_sendfilev_fptr) {
        return pt_SolarisSendFile(sd, sfd, flags, timeout);
    } else {
        return PR_EmulateSendFile(sd, sfd, flags, timeout);
    }
}
#endif /* !HAVE_SENDFILEV */

#endif  /* SOLARIS */

#ifdef LINUX
/*
 * pt_LinuxSendFile
 *
 *    Send file sfd->fd across socket sd. If specified, header and trailer
 *    buffers are sent before and after the file, respectively.
 *
 *    PR_TRANSMITFILE_CLOSE_SOCKET flag - close socket after sending file
 *
 *    return number of bytes sent or -1 on error
 *
 *      This implementation takes advantage of the sendfile() system
 *      call available in Linux kernel 2.2 or higher.
 */

static PRInt32 pt_LinuxSendFile(PRFileDesc *sd, PRSendFileData *sfd,
                                PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    struct stat statbuf;
    size_t file_nbytes_to_send;
    PRInt32 count = 0;
    ssize_t rv;
    int syserrno;
    off_t offset;
    PRBool tcp_cork_enabled = PR_FALSE;
    int tcp_cork;

    if (sfd->file_nbytes == 0) {
        /* Get file size */
        if (fstat(sfd->fd->secret->md.osfd, &statbuf) == -1) {
            _PR_MD_MAP_FSTAT_ERROR(errno);
            return -1;
        }
        file_nbytes_to_send = statbuf.st_size - sfd->file_offset;
    } else {
        file_nbytes_to_send = sfd->file_nbytes;
    }

    if ((sfd->hlen != 0 || sfd->tlen != 0)
        && sd->secret->md.tcp_nodelay == 0) {
        tcp_cork = 1;
        if (setsockopt(sd->secret->md.osfd, SOL_TCP, TCP_CORK,
                       &tcp_cork, sizeof tcp_cork) == 0) {
            tcp_cork_enabled = PR_TRUE;
        } else {
            syserrno = errno;
            if (syserrno != EINVAL) {
                _PR_MD_MAP_SETSOCKOPT_ERROR(syserrno);
                return -1;
            }
            /*
             * The most likely reason for the EINVAL error is that
             * TCP_NODELAY is set (with a function other than
             * PR_SetSocketOption).  This is not fatal, so we keep
             * on going.
             */
            PR_LOG(_pr_io_lm, PR_LOG_WARNING,
                   ("pt_LinuxSendFile: "
                    "setsockopt(TCP_CORK) failed with EINVAL\n"));
        }
    }

    if (sfd->hlen != 0) {
        count = PR_Send(sd, sfd->header, sfd->hlen, 0, timeout);
        if (count == -1) {
            goto failed;
        }
    }

    if (file_nbytes_to_send != 0) {
        offset = sfd->file_offset;
        do {
            rv = sendfile(sd->secret->md.osfd, sfd->fd->secret->md.osfd,
                          &offset, file_nbytes_to_send);
        } while (rv == -1 && (syserrno = errno) == EINTR);
        if (rv == -1) {
            if (syserrno != EAGAIN && syserrno != EWOULDBLOCK) {
                _MD_linux_map_sendfile_error(syserrno);
                count = -1;
                goto failed;
            }
            rv = 0;
        }
        PR_ASSERT(rv == offset - sfd->file_offset);
        count += rv;

        if (rv < file_nbytes_to_send) {
            pt_Continuation op;

            op.arg1.osfd = sd->secret->md.osfd;
            op.in_fd = sfd->fd->secret->md.osfd;
            op.offset = offset;
            op.count = file_nbytes_to_send - rv;
            op.result.code = count;
            op.timeout = timeout;
            op.function = pt_linux_sendfile_cont;
            op.event = POLLOUT | POLLPRI;
            count = pt_Continue(&op);
            syserrno = op.syserrno;
            if (count == -1) {
                pt_MapError(_MD_linux_map_sendfile_error, syserrno);
                goto failed;
            }
        }
    }

    if (sfd->tlen != 0) {
        rv = PR_Send(sd, sfd->trailer, sfd->tlen, 0, timeout);
        if (rv == -1) {
            count = -1;
            goto failed;
        }
        count += rv;
    }

failed:
    if (tcp_cork_enabled) {
        tcp_cork = 0;
        if (setsockopt(sd->secret->md.osfd, SOL_TCP, TCP_CORK,
                       &tcp_cork, sizeof tcp_cork) == -1 && count != -1) {
            _PR_MD_MAP_SETSOCKOPT_ERROR(errno);
            count = -1;
        }
    }
    if (count != -1) {
        if (flags & PR_TRANSMITFILE_CLOSE_SOCKET) {
            PR_Close(sd);
        }
        PR_ASSERT(count == sfd->hlen + sfd->tlen + file_nbytes_to_send);
    }
    return count;
}
#endif  /* LINUX */

#ifdef AIX
extern  int _pr_aix_send_file_use_disabled;
#endif

static PRInt32 pt_SendFile(
    PRFileDesc *sd, PRSendFileData *sfd,
    PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    if (pt_TestAbort()) {
        return -1;
    }
    /* The socket must be in blocking mode. */
    if (sd->secret->nonblocking)
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return -1;
    }
#ifdef HPUX11
    return(pt_HPUXSendFile(sd, sfd, flags, timeout));
#elif defined(AIX)
#ifdef HAVE_SEND_FILE
    /*
     * A bug in AIX 4.3.2 results in corruption of data transferred by
     * send_file(); AIX patch PTF U463956 contains the fix.  A user can
     * disable the use of send_file function in NSPR, when this patch is
     * not installed on the system, by setting the envionment variable
     * NSPR_AIX_SEND_FILE_USE_DISABLED to 1.
     */
    if (_pr_aix_send_file_use_disabled) {
        return(PR_EmulateSendFile(sd, sfd, flags, timeout));
    }
    else {
        return(pt_AIXSendFile(sd, sfd, flags, timeout));
    }
#else
    return(PR_EmulateSendFile(sd, sfd, flags, timeout));
    /* return(pt_AIXDispatchSendFile(sd, sfd, flags, timeout));*/
#endif /* HAVE_SEND_FILE */
#elif defined(SOLARIS)
#ifdef HAVE_SENDFILEV
    return(pt_SolarisSendFile(sd, sfd, flags, timeout));
#else
    return(pt_SolarisDispatchSendFile(sd, sfd, flags, timeout));
#endif /* HAVE_SENDFILEV */
#elif defined(LINUX)
    return(pt_LinuxSendFile(sd, sfd, flags, timeout));
#else
    return(PR_EmulateSendFile(sd, sfd, flags, timeout));
#endif
}

static PRInt32 pt_TransmitFile(
    PRFileDesc *sd, PRFileDesc *fd, const void *headers,
    PRInt32 hlen, PRTransmitFileFlags flags, PRIntervalTime timeout)
{
    PRSendFileData sfd;

    sfd.fd = fd;
    sfd.file_offset = 0;
    sfd.file_nbytes = 0;
    sfd.header = headers;
    sfd.hlen = hlen;
    sfd.trailer = NULL;
    sfd.tlen = 0;

    return(pt_SendFile(sd, &sfd, flags, timeout));
}  /* pt_TransmitFile */

static PRInt32 pt_AcceptRead(
    PRFileDesc *sd, PRFileDesc **nd, PRNetAddr **raddr,
    void *buf, PRInt32 amount, PRIntervalTime timeout)
{
    PRInt32 rv = -1;

    if (pt_TestAbort()) {
        return rv;
    }
    /* The socket must be in blocking mode. */
    if (sd->secret->nonblocking)
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return rv;
    }

    rv = PR_EmulateAcceptRead(sd, nd, raddr, buf, amount, timeout);
    return rv;
}  /* pt_AcceptRead */

static PRStatus pt_GetSockName(PRFileDesc *fd, PRNetAddr *addr)
{
    PRIntn rv = -1;
    pt_SockLen addr_len = sizeof(PRNetAddr);

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    rv = getsockname(
             fd->secret->md.osfd, (struct sockaddr*)addr, &addr_len);
    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_GETSOCKNAME_ERROR, errno);
        return PR_FAILURE;
    }
#ifdef _PR_HAVE_SOCKADDR_LEN
    /* ignore the sa_len field of struct sockaddr */
    if (addr)
    {
        addr->raw.family = ((struct sockaddr*)addr)->sa_family;
    }
#endif /* _PR_HAVE_SOCKADDR_LEN */
#ifdef _PR_INET6
    if (AF_INET6 == addr->raw.family) {
        addr->raw.family = PR_AF_INET6;
    }
#endif
    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
    PR_ASSERT(IsValidNetAddrLen(addr, addr_len) == PR_TRUE);
    return PR_SUCCESS;
}  /* pt_GetSockName */

static PRStatus pt_GetPeerName(PRFileDesc *fd, PRNetAddr *addr)
{
    PRIntn rv = -1;
    pt_SockLen addr_len = sizeof(PRNetAddr);

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    rv = getpeername(
             fd->secret->md.osfd, (struct sockaddr*)addr, &addr_len);

    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_GETPEERNAME_ERROR, errno);
        return PR_FAILURE;
    }
#ifdef _PR_HAVE_SOCKADDR_LEN
    /* ignore the sa_len field of struct sockaddr */
    if (addr)
    {
        addr->raw.family = ((struct sockaddr*)addr)->sa_family;
    }
#endif /* _PR_HAVE_SOCKADDR_LEN */
#ifdef _PR_INET6
    if (AF_INET6 == addr->raw.family) {
        addr->raw.family = PR_AF_INET6;
    }
#endif
    PR_ASSERT(IsValidNetAddr(addr) == PR_TRUE);
    PR_ASSERT(IsValidNetAddrLen(addr, addr_len) == PR_TRUE);
    return PR_SUCCESS;
}  /* pt_GetPeerName */

static PRStatus pt_GetSocketOption(PRFileDesc *fd, PRSocketOptionData *data)
{
    PRIntn rv;
    pt_SockLen length;
    PRInt32 level, name;

    /*
     * PR_SockOpt_Nonblocking is a special case that does not
     * translate to a getsockopt() call
     */
    if (PR_SockOpt_Nonblocking == data->option)
    {
        data->value.non_blocking = fd->secret->nonblocking;
        return PR_SUCCESS;
    }

    rv = _PR_MapOptionName(data->option, &level, &name);
    if (PR_SUCCESS == rv)
    {
        switch (data->option)
        {
            case PR_SockOpt_Linger:
            {
                struct linger linger;
                length = sizeof(linger);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name, (char *) &linger, &length);
                PR_ASSERT((-1 == rv) || (sizeof(linger) == length));
                data->value.linger.polarity =
                    (linger.l_onoff) ? PR_TRUE : PR_FALSE;
                data->value.linger.linger =
                    PR_SecondsToInterval(linger.l_linger);
                break;
            }
            case PR_SockOpt_Reuseaddr:
            case PR_SockOpt_Keepalive:
            case PR_SockOpt_NoDelay:
            case PR_SockOpt_Broadcast:
            case PR_SockOpt_Reuseport:
            {
                PRIntn value;
                length = sizeof(PRIntn);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name, (char*)&value, &length);
                PR_ASSERT((-1 == rv) || (sizeof(PRIntn) == length));
                data->value.reuse_addr = (0 == value) ? PR_FALSE : PR_TRUE;
                break;
            }
            case PR_SockOpt_McastLoopback:
            {
                PRUint8 xbool;
                length = sizeof(xbool);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&xbool, &length);
                PR_ASSERT((-1 == rv) || (sizeof(xbool) == length));
                data->value.mcast_loopback = (0 == xbool) ? PR_FALSE : PR_TRUE;
                break;
            }
            case PR_SockOpt_RecvBufferSize:
            case PR_SockOpt_SendBufferSize:
            case PR_SockOpt_MaxSegment:
            {
                PRIntn value;
                length = sizeof(PRIntn);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name, (char*)&value, &length);
                PR_ASSERT((-1 == rv) || (sizeof(PRIntn) == length));
                data->value.recv_buffer_size = value;
                break;
            }
            case PR_SockOpt_IpTimeToLive:
            case PR_SockOpt_IpTypeOfService:
            {
                length = sizeof(PRUintn);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&data->value.ip_ttl, &length);
                PR_ASSERT((-1 == rv) || (sizeof(PRIntn) == length));
                break;
            }
            case PR_SockOpt_McastTimeToLive:
            {
                PRUint8 ttl;
                length = sizeof(ttl);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&ttl, &length);
                PR_ASSERT((-1 == rv) || (sizeof(ttl) == length));
                data->value.mcast_ttl = ttl;
                break;
            }
            case PR_SockOpt_AddMember:
            case PR_SockOpt_DropMember:
            {
                struct ip_mreq mreq;
                length = sizeof(mreq);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name, (char*)&mreq, &length);
                PR_ASSERT((-1 == rv) || (sizeof(mreq) == length));
                data->value.add_member.mcaddr.inet.ip =
                    mreq.imr_multiaddr.s_addr;
                data->value.add_member.ifaddr.inet.ip =
                    mreq.imr_interface.s_addr;
                break;
            }
            case PR_SockOpt_McastInterface:
            {
                length = sizeof(data->value.mcast_if.inet.ip);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&data->value.mcast_if.inet.ip, &length);
                PR_ASSERT((-1 == rv)
                          || (sizeof(data->value.mcast_if.inet.ip) == length));
                break;
            }
            case PR_SockOpt_DontFrag:
            {
#if !defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)
                PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0);
                rv = PR_FAILURE;
#else
                PRIntn value;
                length = sizeof(value);
                rv = getsockopt(
                         fd->secret->md.osfd, level, name, (char*)&value,
                         &length);
#if defined(DARWIN)
                data->value.dont_fragment = value;
#else
                data->value.dont_fragment = (value == IP_PMTUDISC_DO) ? 1 : 0;
#endif
#endif /* !(!defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)) */
                break;
            }
            default:
                PR_NOT_REACHED("Unknown socket option");
                break;
        }
        if (-1 == rv) {
            _PR_MD_MAP_GETSOCKOPT_ERROR(errno);
        }
    }
    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
}  /* pt_GetSocketOption */

static PRStatus pt_SetSocketOption(PRFileDesc *fd, const PRSocketOptionData *data)
{
    PRIntn rv;
    PRInt32 level, name;

    /*
     * PR_SockOpt_Nonblocking is a special case that does not
     * translate to a setsockopt call.
     */
    if (PR_SockOpt_Nonblocking == data->option)
    {
        fd->secret->nonblocking = data->value.non_blocking;
        return PR_SUCCESS;
    }

    rv = _PR_MapOptionName(data->option, &level, &name);
    if (PR_SUCCESS == rv)
    {
        switch (data->option)
        {
            case PR_SockOpt_Linger:
            {
                struct linger linger;
                linger.l_onoff = data->value.linger.polarity;
                linger.l_linger = PR_IntervalToSeconds(data->value.linger.linger);
                rv = setsockopt(
                         fd->secret->md.osfd, level, name, (char*)&linger, sizeof(linger));
                break;
            }
            case PR_SockOpt_Reuseaddr:
            case PR_SockOpt_Keepalive:
            case PR_SockOpt_NoDelay:
            case PR_SockOpt_Broadcast:
            case PR_SockOpt_Reuseport:
            {
                PRIntn value = (data->value.reuse_addr) ? 1 : 0;
                rv = setsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&value, sizeof(PRIntn));
#ifdef LINUX
                /* for pt_LinuxSendFile */
                if (name == TCP_NODELAY && rv == 0) {
                    fd->secret->md.tcp_nodelay = value;
                }
#endif
                break;
            }
            case PR_SockOpt_McastLoopback:
            {
                PRUint8 xbool = data->value.mcast_loopback ? 1 : 0;
                rv = setsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&xbool, sizeof(xbool));
                break;
            }
            case PR_SockOpt_RecvBufferSize:
            case PR_SockOpt_SendBufferSize:
            case PR_SockOpt_MaxSegment:
            {
                PRIntn value = data->value.recv_buffer_size;
                rv = setsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&value, sizeof(PRIntn));
                break;
            }
            case PR_SockOpt_IpTimeToLive:
            case PR_SockOpt_IpTypeOfService:
            {
                rv = setsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&data->value.ip_ttl, sizeof(PRUintn));
                break;
            }
            case PR_SockOpt_McastTimeToLive:
            {
                PRUint8 ttl = data->value.mcast_ttl;
                rv = setsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&ttl, sizeof(ttl));
                break;
            }
            case PR_SockOpt_AddMember:
            case PR_SockOpt_DropMember:
            {
                struct ip_mreq mreq;
                mreq.imr_multiaddr.s_addr =
                    data->value.add_member.mcaddr.inet.ip;
                mreq.imr_interface.s_addr =
                    data->value.add_member.ifaddr.inet.ip;
                rv = setsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&mreq, sizeof(mreq));
                break;
            }
            case PR_SockOpt_McastInterface:
            {
                rv = setsockopt(
                         fd->secret->md.osfd, level, name,
                         (char*)&data->value.mcast_if.inet.ip,
                         sizeof(data->value.mcast_if.inet.ip));
                break;
            }
            case PR_SockOpt_DontFrag:
            {
#if !defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)
                PR_SetError(PR_OPERATION_NOT_SUPPORTED_ERROR, 0);
                rv = PR_FAILURE;
#else
                PRIntn value;
#if defined(LINUX) || defined(ANDROID)
                value = (data->value.dont_fragment) ? IP_PMTUDISC_DO
                                                    : IP_PMTUDISC_DONT;
#elif defined(DARWIN)
                value = data->value.dont_fragment;
#endif
                rv = setsockopt(
                         fd->secret->md.osfd, level, name, (char*)&value,
                         sizeof(value));
#endif /* !defined(DARWIN) && !defined(LINUX) && !defined(ANDROID)) */
                break;
            }
            default:
                PR_NOT_REACHED("Unknown socket option");
                break;
        }
        if (-1 == rv) {
            _PR_MD_MAP_SETSOCKOPT_ERROR(errno);
        }
    }
    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
}  /* pt_SetSocketOption */

/*****************************************************************************/
/****************************** I/O method objects ***************************/
/*****************************************************************************/

static PRIOMethods _pr_file_methods = {
    PR_DESC_FILE,
    pt_Close,
    pt_Read,
    pt_Write,
    pt_Available_f,
    pt_Available64_f,
    pt_Fsync,
    pt_Seek,
    pt_Seek64,
    pt_FileInfo,
    pt_FileInfo64,
    (PRWritevFN)_PR_InvalidInt,
    (PRConnectFN)_PR_InvalidStatus,
    (PRAcceptFN)_PR_InvalidDesc,
    (PRBindFN)_PR_InvalidStatus,
    (PRListenFN)_PR_InvalidStatus,
    (PRShutdownFN)_PR_InvalidStatus,
    (PRRecvFN)_PR_InvalidInt,
    (PRSendFN)_PR_InvalidInt,
    (PRRecvfromFN)_PR_InvalidInt,
    (PRSendtoFN)_PR_InvalidInt,
    pt_Poll,
    (PRAcceptreadFN)_PR_InvalidInt,
    (PRTransmitfileFN)_PR_InvalidInt,
    (PRGetsocknameFN)_PR_InvalidStatus,
    (PRGetpeernameFN)_PR_InvalidStatus,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRGetsocketoptionFN)_PR_InvalidStatus,
    (PRSetsocketoptionFN)_PR_InvalidStatus,
    (PRSendfileFN)_PR_InvalidInt,
    (PRConnectcontinueFN)_PR_InvalidStatus,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt
};

static PRIOMethods _pr_pipe_methods = {
    PR_DESC_PIPE,
    pt_Close,
    pt_Read,
    pt_Write,
    pt_Available_s,
    pt_Available64_s,
    pt_Synch,
    (PRSeekFN)_PR_InvalidInt,
    (PRSeek64FN)_PR_InvalidInt64,
    (PRFileInfoFN)_PR_InvalidStatus,
    (PRFileInfo64FN)_PR_InvalidStatus,
    (PRWritevFN)_PR_InvalidInt,
    (PRConnectFN)_PR_InvalidStatus,
    (PRAcceptFN)_PR_InvalidDesc,
    (PRBindFN)_PR_InvalidStatus,
    (PRListenFN)_PR_InvalidStatus,
    (PRShutdownFN)_PR_InvalidStatus,
    (PRRecvFN)_PR_InvalidInt,
    (PRSendFN)_PR_InvalidInt,
    (PRRecvfromFN)_PR_InvalidInt,
    (PRSendtoFN)_PR_InvalidInt,
    pt_Poll,
    (PRAcceptreadFN)_PR_InvalidInt,
    (PRTransmitfileFN)_PR_InvalidInt,
    (PRGetsocknameFN)_PR_InvalidStatus,
    (PRGetpeernameFN)_PR_InvalidStatus,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRGetsocketoptionFN)_PR_InvalidStatus,
    (PRSetsocketoptionFN)_PR_InvalidStatus,
    (PRSendfileFN)_PR_InvalidInt,
    (PRConnectcontinueFN)_PR_InvalidStatus,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt
};

static PRIOMethods _pr_tcp_methods = {
    PR_DESC_SOCKET_TCP,
    pt_Close,
    pt_SocketRead,
    pt_SocketWrite,
    pt_Available_s,
    pt_Available64_s,
    pt_Synch,
    (PRSeekFN)_PR_InvalidInt,
    (PRSeek64FN)_PR_InvalidInt64,
    (PRFileInfoFN)_PR_InvalidStatus,
    (PRFileInfo64FN)_PR_InvalidStatus,
    pt_Writev,
    pt_Connect,
    pt_Accept,
    pt_Bind,
    pt_Listen,
    pt_Shutdown,
    pt_Recv,
    pt_Send,
    (PRRecvfromFN)_PR_InvalidInt,
#if defined(LINUX) || defined(DARWIN)
    pt_TCP_SendTo, /* This is for TCP Fast Open. Linux uses SendTo function for this. OSX uses connectx, but we imitate Linux. */
#else
    (PRSendtoFN)_PR_InvalidInt,
#endif
    pt_Poll,
    pt_AcceptRead,
    pt_TransmitFile,
    pt_GetSockName,
    pt_GetPeerName,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    pt_GetSocketOption,
    pt_SetSocketOption,
    pt_SendFile,
    pt_ConnectContinue,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt
};

static PRIOMethods _pr_udp_methods = {
    PR_DESC_SOCKET_UDP,
    pt_Close,
    pt_SocketRead,
    pt_SocketWrite,
    pt_Available_s,
    pt_Available64_s,
    pt_Synch,
    (PRSeekFN)_PR_InvalidInt,
    (PRSeek64FN)_PR_InvalidInt64,
    (PRFileInfoFN)_PR_InvalidStatus,
    (PRFileInfo64FN)_PR_InvalidStatus,
    pt_Writev,
    pt_Connect,
    (PRAcceptFN)_PR_InvalidDesc,
    pt_Bind,
    pt_Listen,
    pt_Shutdown,
    pt_Recv,
    pt_Send,
    pt_RecvFrom,
    pt_SendTo,
    pt_Poll,
    (PRAcceptreadFN)_PR_InvalidInt,
    (PRTransmitfileFN)_PR_InvalidInt,
    pt_GetSockName,
    pt_GetPeerName,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    pt_GetSocketOption,
    pt_SetSocketOption,
    (PRSendfileFN)_PR_InvalidInt,
    (PRConnectcontinueFN)_PR_InvalidStatus,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt
};

static PRIOMethods _pr_socketpollfd_methods = {
    (PRDescType) 0,
    (PRCloseFN)_PR_InvalidStatus,
    (PRReadFN)_PR_InvalidInt,
    (PRWriteFN)_PR_InvalidInt,
    (PRAvailableFN)_PR_InvalidInt,
    (PRAvailable64FN)_PR_InvalidInt64,
    (PRFsyncFN)_PR_InvalidStatus,
    (PRSeekFN)_PR_InvalidInt,
    (PRSeek64FN)_PR_InvalidInt64,
    (PRFileInfoFN)_PR_InvalidStatus,
    (PRFileInfo64FN)_PR_InvalidStatus,
    (PRWritevFN)_PR_InvalidInt,
    (PRConnectFN)_PR_InvalidStatus,
    (PRAcceptFN)_PR_InvalidDesc,
    (PRBindFN)_PR_InvalidStatus,
    (PRListenFN)_PR_InvalidStatus,
    (PRShutdownFN)_PR_InvalidStatus,
    (PRRecvFN)_PR_InvalidInt,
    (PRSendFN)_PR_InvalidInt,
    (PRRecvfromFN)_PR_InvalidInt,
    (PRSendtoFN)_PR_InvalidInt,
    pt_Poll,
    (PRAcceptreadFN)_PR_InvalidInt,
    (PRTransmitfileFN)_PR_InvalidInt,
    (PRGetsocknameFN)_PR_InvalidStatus,
    (PRGetpeernameFN)_PR_InvalidStatus,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRGetsocketoptionFN)_PR_InvalidStatus,
    (PRSetsocketoptionFN)_PR_InvalidStatus,
    (PRSendfileFN)_PR_InvalidInt,
    (PRConnectcontinueFN)_PR_InvalidStatus,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt,
    (PRReservedFN)_PR_InvalidInt
};

#if defined(HPUX) || defined(SOLARIS) \
    || defined(LINUX) || defined(__GNU__) || defined(__GLIBC__) \
    || defined(AIX) || defined(FREEBSD) || defined(NETBSD) \
    || defined(OPENBSD) || defined(BSDI) || defined(NTO) \
    || defined(DARWIN) || defined(UNIXWARE) || defined(RISCOS)
#define _PR_FCNTL_FLAGS O_NONBLOCK
#else
#error "Can't determine architecture"
#endif

/*
 * Put a Unix file descriptor in non-blocking mode.
 */
static void pt_MakeFdNonblock(PRIntn osfd)
{
    PRIntn flags;
    flags = fcntl(osfd, F_GETFL, 0);
    flags |= _PR_FCNTL_FLAGS;
    (void)fcntl(osfd, F_SETFL, flags);
}

/*
 * Put a Unix socket fd in non-blocking mode that can
 * ideally be inherited by an accepted socket.
 *
 * Why doesn't pt_MakeFdNonblock do?  This is to deal with
 * the special case of HP-UX.  HP-UX has three kinds of
 * non-blocking modes for sockets: the fcntl() O_NONBLOCK
 * and O_NDELAY flags and ioctl() FIOSNBIO request.  Only
 * the ioctl() FIOSNBIO form of non-blocking mode is
 * inherited by an accepted socket.
 *
 * Other platforms just use the generic pt_MakeFdNonblock
 * to put a socket in non-blocking mode.
 */
#ifdef HPUX
static void pt_MakeSocketNonblock(PRIntn osfd)
{
    PRIntn one = 1;
    (void)ioctl(osfd, FIOSNBIO, &one);
}
#else
#define pt_MakeSocketNonblock pt_MakeFdNonblock
#endif

static PRFileDesc *pt_SetMethods(
    PRIntn osfd, PRDescType type, PRBool isAcceptedSocket, PRBool imported)
{
    PRFileDesc *fd = _PR_Getfd();

    if (fd == NULL) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    }
    else
    {
        fd->secret->md.osfd = osfd;
        fd->secret->state = _PR_FILEDESC_OPEN;
        if (imported) {
            fd->secret->inheritable = _PR_TRI_UNKNOWN;
        }
        else
        {
            /* By default, a Unix fd is not closed on exec. */
#ifdef DEBUG
            PRIntn flags;
            flags = fcntl(osfd, F_GETFD, 0);
            PR_ASSERT(0 == flags);
#endif
            fd->secret->inheritable = _PR_TRI_TRUE;
        }
        switch (type)
        {
            case PR_DESC_FILE:
                fd->methods = PR_GetFileMethods();
                break;
            case PR_DESC_SOCKET_TCP:
                fd->methods = PR_GetTCPMethods();
#ifdef _PR_ACCEPT_INHERIT_NONBLOCK
                if (!isAcceptedSocket) {
                    pt_MakeSocketNonblock(osfd);
                }
#else
                pt_MakeSocketNonblock(osfd);
#endif
                break;
            case PR_DESC_SOCKET_UDP:
                fd->methods = PR_GetUDPMethods();
                pt_MakeFdNonblock(osfd);
                break;
            case PR_DESC_PIPE:
                fd->methods = PR_GetPipeMethods();
                pt_MakeFdNonblock(osfd);
                break;
            default:
                break;
        }
    }
    return fd;
}  /* pt_SetMethods */

PR_IMPLEMENT(const PRIOMethods*) PR_GetFileMethods(void)
{
    return &_pr_file_methods;
}  /* PR_GetFileMethods */

PR_IMPLEMENT(const PRIOMethods*) PR_GetPipeMethods(void)
{
    return &_pr_pipe_methods;
}  /* PR_GetPipeMethods */

PR_IMPLEMENT(const PRIOMethods*) PR_GetTCPMethods(void)
{
    return &_pr_tcp_methods;
}  /* PR_GetTCPMethods */

PR_IMPLEMENT(const PRIOMethods*) PR_GetUDPMethods(void)
{
    return &_pr_udp_methods;
}  /* PR_GetUDPMethods */

static const PRIOMethods* PR_GetSocketPollFdMethods(void)
{
    return &_pr_socketpollfd_methods;
}  /* PR_GetSocketPollFdMethods */

PR_IMPLEMENT(PRFileDesc*) PR_AllocFileDesc(
    PRInt32 osfd, const PRIOMethods *methods)
{
    PRFileDesc *fd = _PR_Getfd();

    if (NULL == fd) {
        goto failed;
    }

    fd->methods = methods;
    fd->secret->md.osfd = osfd;
    /* Make fd non-blocking */
    if (osfd > 2)
    {
        /* Don't mess around with stdin, stdout or stderr */
        if (&_pr_tcp_methods == methods) {
            pt_MakeSocketNonblock(osfd);
        }
        else {
            pt_MakeFdNonblock(osfd);
        }
    }
    fd->secret->state = _PR_FILEDESC_OPEN;
    fd->secret->inheritable = _PR_TRI_UNKNOWN;
    return fd;

failed:
    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    return fd;
}  /* PR_AllocFileDesc */

#if !defined(_PR_INET6) || defined(_PR_INET6_PROBE)
PR_EXTERN(PRStatus) _pr_push_ipv6toipv4_layer(PRFileDesc *fd);
#if defined(_PR_INET6_PROBE)
extern PRBool _pr_ipv6_is_present(void);
PR_IMPLEMENT(PRBool) _pr_test_ipv6_socket()
{
    int osfd;

#if defined(DARWIN)
    /*
     * Disable IPv6 if Darwin version is less than 7.0.0 (OS X 10.3).  IPv6 on
     * lesser versions is not ready for general use (see bug 222031).
     */
    {
        struct utsname u;
        if (uname(&u) != 0 || atoi(u.release) < 7) {
            return PR_FALSE;
        }
    }
#endif

    /*
     * HP-UX only: HP-UX IPv6 Porting Guide (dated February 2001)
     * suggests that we call open("/dev/ip6", O_RDWR) to determine
     * whether IPv6 APIs and the IPv6 stack are on the system.
     * Our portable test below seems to work fine, so I am using it.
     */
    osfd = socket(AF_INET6, SOCK_STREAM, 0);
    if (osfd != -1) {
        close(osfd);
        return PR_TRUE;
    }
    return PR_FALSE;
}
#endif  /* _PR_INET6_PROBE */
#endif

PR_IMPLEMENT(PRFileDesc*) PR_Socket(PRInt32 domain, PRInt32 type, PRInt32 proto)
{
    PRIntn osfd;
    PRDescType ftype;
    PRFileDesc *fd = NULL;
#if defined(_PR_INET6_PROBE) || !defined(_PR_INET6)
    PRInt32 tmp_domain = domain;
#endif

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    if (pt_TestAbort()) {
        return NULL;
    }

    if (PF_INET != domain
        && PR_AF_INET6 != domain
#if defined(_PR_HAVE_SDP)
        && PR_AF_INET_SDP != domain
#if defined(SOLARIS)
        && PR_AF_INET6_SDP != domain
#endif /* SOLARIS */
#endif /* _PR_HAVE_SDP */
        && PF_UNIX != domain)
    {
        PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, 0);
        return fd;
    }
    if (type == SOCK_STREAM) {
        ftype = PR_DESC_SOCKET_TCP;
    }
    else if (type == SOCK_DGRAM) {
        ftype = PR_DESC_SOCKET_UDP;
    }
    else
    {
        (void)PR_SetError(PR_ADDRESS_NOT_SUPPORTED_ERROR, 0);
        return fd;
    }
#if defined(_PR_HAVE_SDP)
#if defined(LINUX)
    if (PR_AF_INET_SDP == domain) {
        domain = AF_INET_SDP;
    }
#elif defined(SOLARIS)
    if (PR_AF_INET_SDP == domain) {
        domain = AF_INET;
        proto = PROTO_SDP;
    } else if(PR_AF_INET6_SDP == domain) {
        domain = AF_INET6;
        proto = PROTO_SDP;
    }
#endif /* SOLARIS */
#endif /* _PR_HAVE_SDP */
#if defined(_PR_INET6_PROBE)
    if (PR_AF_INET6 == domain) {
        domain = _pr_ipv6_is_present() ? AF_INET6 : AF_INET;
    }
#elif defined(_PR_INET6)
    if (PR_AF_INET6 == domain) {
        domain = AF_INET6;
    }
#else
    if (PR_AF_INET6 == domain) {
        domain = AF_INET;
    }
#endif

    osfd = socket(domain, type, proto);
    if (osfd == -1) {
        pt_MapError(_PR_MD_MAP_SOCKET_ERROR, errno);
    }
    else
    {
#ifdef _PR_IPV6_V6ONLY_PROBE
        if ((domain == AF_INET6) && _pr_ipv6_v6only_on_by_default)
        {
            int on = 0;
            (void)setsockopt(osfd, IPPROTO_IPV6, IPV6_V6ONLY,
                             &on, sizeof(on));
        }
#endif
        fd = pt_SetMethods(osfd, ftype, PR_FALSE, PR_FALSE);
        if (fd == NULL) {
            close(osfd);
        }
    }
#ifdef _PR_NEED_SECRET_AF
    if (fd != NULL) {
        fd->secret->af = domain;
    }
#endif
#if defined(_PR_INET6_PROBE) || !defined(_PR_INET6)
    if (fd != NULL) {
        /*
         * For platforms with no support for IPv6
         * create layered socket for IPv4-mapped IPv6 addresses
         */
        if (PR_AF_INET6 == tmp_domain && PR_AF_INET == domain) {
            if (PR_FAILURE == _pr_push_ipv6toipv4_layer(fd)) {
                PR_Close(fd);
                fd = NULL;
            }
        }
    }
#endif
    return fd;
}  /* PR_Socket */

/*****************************************************************************/
/****************************** I/O public methods ***************************/
/*****************************************************************************/

PR_IMPLEMENT(PRFileDesc*) PR_OpenFile(
    const char *name, PRIntn flags, PRIntn mode)
{
    PRFileDesc *fd = NULL;
    PRIntn syserrno, osfd = -1, osflags = 0;;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    if (pt_TestAbort()) {
        return NULL;
    }

    if (flags & PR_RDONLY) {
        osflags |= O_RDONLY;
    }
    if (flags & PR_WRONLY) {
        osflags |= O_WRONLY;
    }
    if (flags & PR_RDWR) {
        osflags |= O_RDWR;
    }
    if (flags & PR_APPEND) {
        osflags |= O_APPEND;
    }
    if (flags & PR_TRUNCATE) {
        osflags |= O_TRUNC;
    }
    if (flags & PR_EXCL) {
        osflags |= O_EXCL;
    }
    if (flags & PR_SYNC)
    {
#if defined(O_SYNC)
        osflags |= O_SYNC;
#elif defined(O_FSYNC)
        osflags |= O_FSYNC;
#else
#error "Neither O_SYNC nor O_FSYNC is defined on this platform"
#endif
    }

    /*
    ** We have to hold the lock across the creation in order to
    ** enforce the sematics of PR_Rename(). (see the latter for
    ** more details)
    */
    if (flags & PR_CREATE_FILE)
    {
        osflags |= O_CREAT;
        if (NULL !=_pr_rename_lock) {
            PR_Lock(_pr_rename_lock);
        }
    }

    osfd = _md_iovector._open64(name, osflags, mode);
    syserrno = errno;

    if ((flags & PR_CREATE_FILE) && (NULL !=_pr_rename_lock)) {
        PR_Unlock(_pr_rename_lock);
    }

    if (osfd == -1) {
        pt_MapError(_PR_MD_MAP_OPEN_ERROR, syserrno);
    }
    else
    {
        fd = pt_SetMethods(osfd, PR_DESC_FILE, PR_FALSE, PR_FALSE);
        if (fd == NULL) {
            close(osfd);    /* $$$ whoops! this is bad $$$ */
        }
    }
    return fd;
}  /* PR_OpenFile */

PR_IMPLEMENT(PRFileDesc*) PR_Open(const char *name, PRIntn flags, PRIntn mode)
{
    return PR_OpenFile(name, flags, mode);
}  /* PR_Open */

PR_IMPLEMENT(PRStatus) PR_Delete(const char *name)
{
    PRIntn rv = -1;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    rv = unlink(name);

    if (rv == -1) {
        pt_MapError(_PR_MD_MAP_UNLINK_ERROR, errno);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* PR_Delete */

PR_IMPLEMENT(PRStatus) PR_Access(const char *name, PRAccessHow how)
{
    PRIntn rv;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    switch (how)
    {
        case PR_ACCESS_READ_OK:
            rv =  access(name, R_OK);
            break;
        case PR_ACCESS_WRITE_OK:
            rv = access(name, W_OK);
            break;
        case PR_ACCESS_EXISTS:
        default:
            rv = access(name, F_OK);
    }
    if (0 == rv) {
        return PR_SUCCESS;
    }
    pt_MapError(_PR_MD_MAP_ACCESS_ERROR, errno);
    return PR_FAILURE;

}  /* PR_Access */

PR_IMPLEMENT(PRStatus) PR_GetFileInfo(const char *fn, PRFileInfo *info)
{
    PRInt32 rv = _PR_MD_GETFILEINFO(fn, info);
    return (0 == rv) ? PR_SUCCESS : PR_FAILURE;
}  /* PR_GetFileInfo */

PR_IMPLEMENT(PRStatus) PR_GetFileInfo64(const char *fn, PRFileInfo64 *info)
{
    PRInt32 rv;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    rv = _PR_MD_GETFILEINFO64(fn, info);
    return (0 == rv) ? PR_SUCCESS : PR_FAILURE;
}  /* PR_GetFileInfo64 */

PR_IMPLEMENT(PRStatus) PR_Rename(const char *from, const char *to)
{
    PRIntn rv = -1;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    /*
    ** We have to acquire a lock here to stiffle anybody trying to create
    ** a new file at the same time. And we have to hold that lock while we
    ** test to see if the file exists and do the rename. The other place
    ** where the lock is held is in PR_Open() when possibly creating a
    ** new file.
    */

    PR_Lock(_pr_rename_lock);
    rv = access(to, F_OK);
    if (0 == rv)
    {
        PR_SetError(PR_FILE_EXISTS_ERROR, 0);
        rv = -1;
    }
    else
    {
        rv = rename(from, to);
        if (rv == -1) {
            pt_MapError(_PR_MD_MAP_RENAME_ERROR, errno);
        }
    }
    PR_Unlock(_pr_rename_lock);
    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
}  /* PR_Rename */

PR_IMPLEMENT(PRStatus) PR_CloseDir(PRDir *dir)
{
    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    if (NULL != dir->md.d)
    {
        if (closedir(dir->md.d) == -1)
        {
            _PR_MD_MAP_CLOSEDIR_ERROR(errno);
            return PR_FAILURE;
        }
        dir->md.d = NULL;
        PR_DELETE(dir);
    }
    return PR_SUCCESS;
}  /* PR_CloseDir */

PR_IMPLEMENT(PRStatus) PR_MakeDir(const char *name, PRIntn mode)
{
    PRInt32 rv = -1;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    /*
    ** This lock is used to enforce rename semantics as described
    ** in PR_Rename.
    */
    if (NULL !=_pr_rename_lock) {
        PR_Lock(_pr_rename_lock);
    }
    rv = mkdir(name, mode);
    if (-1 == rv) {
        pt_MapError(_PR_MD_MAP_MKDIR_ERROR, errno);
    }
    if (NULL !=_pr_rename_lock) {
        PR_Unlock(_pr_rename_lock);
    }

    return (-1 == rv) ? PR_FAILURE : PR_SUCCESS;
}  /* PR_Makedir */

PR_IMPLEMENT(PRStatus) PR_MkDir(const char *name, PRIntn mode)
{
    return PR_MakeDir(name, mode);
}  /* PR_Mkdir */

PR_IMPLEMENT(PRStatus) PR_RmDir(const char *name)
{
    PRInt32 rv;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    rv = rmdir(name);
    if (0 == rv) {
        return PR_SUCCESS;
    }
    pt_MapError(_PR_MD_MAP_RMDIR_ERROR, errno);
    return PR_FAILURE;
}  /* PR_Rmdir */


PR_IMPLEMENT(PRDir*) PR_OpenDir(const char *name)
{
    DIR *osdir;
    PRDir *dir = NULL;

    if (pt_TestAbort()) {
        return dir;
    }

    osdir = opendir(name);
    if (osdir == NULL) {
        pt_MapError(_PR_MD_MAP_OPENDIR_ERROR, errno);
    }
    else
    {
        dir = PR_NEWZAP(PRDir);
        if (dir) {
            dir->md.d = osdir;
        }
        else {
            (void)closedir(osdir);
        }
    }
    return dir;
}  /* PR_OpenDir */

static PRInt32 _pr_poll_with_poll(
    PRPollDesc *pds, PRIntn npds, PRIntervalTime timeout)
{
    PRInt32 ready = 0;
    /*
     * For restarting poll() if it is interrupted by a signal.
     * We use these variables to figure out how much time has
     * elapsed and how much of the timeout still remains.
     */
    PRIntervalTime start = 0, elapsed, remaining;

    if (pt_TestAbort()) {
        return -1;
    }

    if (0 == npds) {
        PR_Sleep(timeout);
    }
    else
    {
#define STACK_POLL_DESC_COUNT 64
        struct pollfd stack_syspoll[STACK_POLL_DESC_COUNT];
        struct pollfd *syspoll;
        PRIntn index, msecs;

        if (npds <= STACK_POLL_DESC_COUNT)
        {
            syspoll = stack_syspoll;
        }
        else
        {
            PRThread *me = PR_GetCurrentThread();
            if (npds > me->syspoll_count)
            {
                PR_Free(me->syspoll_list);
                me->syspoll_list =
                    (struct pollfd*)PR_MALLOC(npds * sizeof(struct pollfd));
                if (NULL == me->syspoll_list)
                {
                    me->syspoll_count = 0;
                    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
                    return -1;
                }
                me->syspoll_count = npds;
            }
            syspoll = me->syspoll_list;
        }

        for (index = 0; index < npds; ++index)
        {
            PRInt16 in_flags_read = 0, in_flags_write = 0;
            PRInt16 out_flags_read = 0, out_flags_write = 0;

            if ((NULL != pds[index].fd) && (0 != pds[index].in_flags))
            {
                if (pds[index].in_flags & PR_POLL_READ)
                {
                    in_flags_read = (pds[index].fd->methods->poll)(
                                        pds[index].fd,
                                        pds[index].in_flags & ~PR_POLL_WRITE,
                                        &out_flags_read);
                }
                if (pds[index].in_flags & PR_POLL_WRITE)
                {
                    in_flags_write = (pds[index].fd->methods->poll)(
                                         pds[index].fd,
                                         pds[index].in_flags & ~PR_POLL_READ,
                                         &out_flags_write);
                }
                if ((0 != (in_flags_read & out_flags_read))
                    || (0 != (in_flags_write & out_flags_write)))
                {
                    /* this one is ready right now */
                    if (0 == ready)
                    {
                        /*
                         * We will return without calling the system
                         * poll function.  So zero the out_flags
                         * fields of all the poll descriptors before
                         * this one.
                         */
                        int i;
                        for (i = 0; i < index; i++)
                        {
                            pds[i].out_flags = 0;
                        }
                    }
                    ready += 1;
                    pds[index].out_flags = out_flags_read | out_flags_write;
                }
                else
                {
                    /* now locate the NSPR layer at the bottom of the stack */
                    PRFileDesc *bottom = PR_GetIdentitiesLayer(
                                             pds[index].fd, PR_NSPR_IO_LAYER);
                    /* ignore a socket without PR_NSPR_IO_LAYER available */

                    pds[index].out_flags = 0;  /* pre-condition */
                    if ((NULL != bottom)
                        && (_PR_FILEDESC_OPEN == bottom->secret->state))
                    {
                        if (0 == ready)
                        {
                            syspoll[index].fd = bottom->secret->md.osfd;
                            syspoll[index].events = 0;
                            if (in_flags_read & PR_POLL_READ)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_READ_SYS_READ;
                                syspoll[index].events |= POLLIN;
                            }
                            if (in_flags_read & PR_POLL_WRITE)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_READ_SYS_WRITE;
                                syspoll[index].events |= POLLOUT;
                            }
                            if (in_flags_write & PR_POLL_READ)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_WRITE_SYS_READ;
                                syspoll[index].events |= POLLIN;
                            }
                            if (in_flags_write & PR_POLL_WRITE)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_WRITE_SYS_WRITE;
                                syspoll[index].events |= POLLOUT;
                            }
                            if (pds[index].in_flags & PR_POLL_EXCEPT) {
                                syspoll[index].events |= POLLPRI;
                            }
                        }
                    }
                    else
                    {
                        if (0 == ready)
                        {
                            int i;
                            for (i = 0; i < index; i++)
                            {
                                pds[i].out_flags = 0;
                            }
                        }
                        ready += 1;  /* this will cause an abrupt return */
                        pds[index].out_flags = PR_POLL_NVAL;  /* bogii */
                    }
                }
            }
            else
            {
                /* make poll() ignore this entry */
                syspoll[index].fd = -1;
                syspoll[index].events = 0;
                pds[index].out_flags = 0;
            }
        }
        if (0 == ready)
        {
            switch (timeout)
            {
                case PR_INTERVAL_NO_WAIT: msecs = 0; break;
                case PR_INTERVAL_NO_TIMEOUT: msecs = -1; break;
                default:
                    msecs = PR_IntervalToMilliseconds(timeout);
                    start = PR_IntervalNow();
            }

retry:
            ready = poll(syspoll, npds, msecs);
            if (-1 == ready)
            {
                PRIntn oserror = errno;

                if (EINTR == oserror)
                {
                    if (timeout == PR_INTERVAL_NO_TIMEOUT) {
                        goto retry;
                    }
                    else if (timeout == PR_INTERVAL_NO_WAIT) {
                        ready = 0;    /* don't retry, just time out */
                    }
                    else
                    {
                        elapsed = (PRIntervalTime) (PR_IntervalNow()
                                                    - start);
                        if (elapsed > timeout) {
                            ready = 0;    /* timed out */
                        }
                        else
                        {
                            remaining = timeout - elapsed;
                            msecs = PR_IntervalToMilliseconds(remaining);
                            goto retry;
                        }
                    }
                }
                else
                {
                    _PR_MD_MAP_POLL_ERROR(oserror);
                }
            }
            else if (ready > 0)
            {
                for (index = 0; index < npds; ++index)
                {
                    PRInt16 out_flags = 0;
                    if ((NULL != pds[index].fd) && (0 != pds[index].in_flags))
                    {
                        if (0 != syspoll[index].revents)
                        {
                            if (syspoll[index].revents & POLLIN)
                            {
                                if (pds[index].out_flags
                                    & _PR_POLL_READ_SYS_READ)
                                {
                                    out_flags |= PR_POLL_READ;
                                }
                                if (pds[index].out_flags
                                    & _PR_POLL_WRITE_SYS_READ)
                                {
                                    out_flags |= PR_POLL_WRITE;
                                }
                            }
                            if (syspoll[index].revents & POLLOUT)
                            {
                                if (pds[index].out_flags
                                    & _PR_POLL_READ_SYS_WRITE)
                                {
                                    out_flags |= PR_POLL_READ;
                                }
                                if (pds[index].out_flags
                                    & _PR_POLL_WRITE_SYS_WRITE)
                                {
                                    out_flags |= PR_POLL_WRITE;
                                }
                            }
                            if (syspoll[index].revents & POLLPRI) {
                                out_flags |= PR_POLL_EXCEPT;
                            }
                            if (syspoll[index].revents & POLLERR) {
                                out_flags |= PR_POLL_ERR;
                            }
                            if (syspoll[index].revents & POLLNVAL) {
                                out_flags |= PR_POLL_NVAL;
                            }
                            if (syspoll[index].revents & POLLHUP) {
                                out_flags |= PR_POLL_HUP;
                            }
                        }
                    }
                    pds[index].out_flags = out_flags;
                }
            }
        }
    }
    return ready;

} /* _pr_poll_with_poll */

#if defined(_PR_POLL_WITH_SELECT)
/*
 * HPUX report the POLLHUP event for a socket when the
 * shutdown(SHUT_WR) operation is called for the remote end, even though
 * the socket is still writeable. Use select(), instead of poll(), to
 * workaround this problem.
 */
static PRInt32 _pr_poll_with_select(
    PRPollDesc *pds, PRIntn npds, PRIntervalTime timeout)
{
    PRInt32 ready = 0;
    /*
     * For restarting select() if it is interrupted by a signal.
     * We use these variables to figure out how much time has
     * elapsed and how much of the timeout still remains.
     */
    PRIntervalTime start = 0, elapsed, remaining;

    if (pt_TestAbort()) {
        return -1;
    }

    if (0 == npds) {
        PR_Sleep(timeout);
    }
    else
    {
#define STACK_POLL_DESC_COUNT 64
        int stack_selectfd[STACK_POLL_DESC_COUNT];
        int *selectfd;
        fd_set rd, wr, ex, *rdp = NULL, *wrp = NULL, *exp = NULL;
        struct timeval tv, *tvp;
        PRIntn index, msecs, maxfd = 0;

        if (npds <= STACK_POLL_DESC_COUNT)
        {
            selectfd = stack_selectfd;
        }
        else
        {
            PRThread *me = PR_GetCurrentThread();
            if (npds > me->selectfd_count)
            {
                PR_Free(me->selectfd_list);
                me->selectfd_list = (int *)PR_MALLOC(npds * sizeof(int));
                if (NULL == me->selectfd_list)
                {
                    me->selectfd_count = 0;
                    PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
                    return -1;
                }
                me->selectfd_count = npds;
            }
            selectfd = me->selectfd_list;
        }
        FD_ZERO(&rd);
        FD_ZERO(&wr);
        FD_ZERO(&ex);

        for (index = 0; index < npds; ++index)
        {
            PRInt16 in_flags_read = 0, in_flags_write = 0;
            PRInt16 out_flags_read = 0, out_flags_write = 0;

            if ((NULL != pds[index].fd) && (0 != pds[index].in_flags))
            {
                if (pds[index].in_flags & PR_POLL_READ)
                {
                    in_flags_read = (pds[index].fd->methods->poll)(
                                        pds[index].fd,
                                        pds[index].in_flags & ~PR_POLL_WRITE,
                                        &out_flags_read);
                }
                if (pds[index].in_flags & PR_POLL_WRITE)
                {
                    in_flags_write = (pds[index].fd->methods->poll)(
                                         pds[index].fd,
                                         pds[index].in_flags & ~PR_POLL_READ,
                                         &out_flags_write);
                }
                if ((0 != (in_flags_read & out_flags_read))
                    || (0 != (in_flags_write & out_flags_write)))
                {
                    /* this one is ready right now */
                    if (0 == ready)
                    {
                        /*
                         * We will return without calling the system
                         * poll function.  So zero the out_flags
                         * fields of all the poll descriptors before
                         * this one.
                         */
                        int i;
                        for (i = 0; i < index; i++)
                        {
                            pds[i].out_flags = 0;
                        }
                    }
                    ready += 1;
                    pds[index].out_flags = out_flags_read | out_flags_write;
                }
                else
                {
                    /* now locate the NSPR layer at the bottom of the stack */
                    PRFileDesc *bottom = PR_GetIdentitiesLayer(
                                             pds[index].fd, PR_NSPR_IO_LAYER);
                    /* ignore a socket without PR_NSPR_IO_LAYER available */

                    pds[index].out_flags = 0;  /* pre-condition */
                    if ((NULL != bottom)
                        && (_PR_FILEDESC_OPEN == bottom->secret->state))
                    {
                        if (0 == ready)
                        {
                            PRBool add_to_rd = PR_FALSE;
                            PRBool add_to_wr = PR_FALSE;
                            PRBool add_to_ex = PR_FALSE;

                            selectfd[index] = bottom->secret->md.osfd;
                            if (in_flags_read & PR_POLL_READ)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_READ_SYS_READ;
                                add_to_rd = PR_TRUE;
                            }
                            if (in_flags_read & PR_POLL_WRITE)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_READ_SYS_WRITE;
                                add_to_wr = PR_TRUE;
                            }
                            if (in_flags_write & PR_POLL_READ)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_WRITE_SYS_READ;
                                add_to_rd = PR_TRUE;
                            }
                            if (in_flags_write & PR_POLL_WRITE)
                            {
                                pds[index].out_flags |=
                                    _PR_POLL_WRITE_SYS_WRITE;
                                add_to_wr = PR_TRUE;
                            }
                            if (pds[index].in_flags & PR_POLL_EXCEPT)
                            {
                                add_to_ex = PR_TRUE;
                            }
                            if ((selectfd[index] > maxfd) &&
                                (add_to_rd || add_to_wr || add_to_ex))
                            {
                                maxfd = selectfd[index];
                                /*
                                 * If maxfd is too large to be used with
                                 * select, fall back to calling poll.
                                 */
                                if (maxfd >= FD_SETSIZE) {
                                    break;
                                }
                            }
                            if (add_to_rd)
                            {
                                FD_SET(bottom->secret->md.osfd, &rd);
                                rdp = &rd;
                            }
                            if (add_to_wr)
                            {
                                FD_SET(bottom->secret->md.osfd, &wr);
                                wrp = &wr;
                            }
                            if (add_to_ex)
                            {
                                FD_SET(bottom->secret->md.osfd, &ex);
                                exp = &ex;
                            }
                        }
                    }
                    else
                    {
                        if (0 == ready)
                        {
                            int i;
                            for (i = 0; i < index; i++)
                            {
                                pds[i].out_flags = 0;
                            }
                        }
                        ready += 1;  /* this will cause an abrupt return */
                        pds[index].out_flags = PR_POLL_NVAL;  /* bogii */
                    }
                }
            }
            else
            {
                pds[index].out_flags = 0;
            }
        }
        if (0 == ready)
        {
            if (maxfd >= FD_SETSIZE)
            {
                /*
                 * maxfd too large to be used with select, fall back to
                 * calling poll
                 */
                return(_pr_poll_with_poll(pds, npds, timeout));
            }
            switch (timeout)
            {
                case PR_INTERVAL_NO_WAIT:
                    tv.tv_sec = 0;
                    tv.tv_usec = 0;
                    tvp = &tv;
                    break;
                case PR_INTERVAL_NO_TIMEOUT:
                    tvp = NULL;
                    break;
                default:
                    msecs = PR_IntervalToMilliseconds(timeout);
                    tv.tv_sec = msecs/PR_MSEC_PER_SEC;
                    tv.tv_usec = (msecs % PR_MSEC_PER_SEC) * PR_USEC_PER_MSEC;
                    tvp = &tv;
                    start = PR_IntervalNow();
            }

retry:
            ready = select(maxfd + 1, rdp, wrp, exp, tvp);
            if (-1 == ready)
            {
                PRIntn oserror = errno;

                if ((EINTR == oserror) || (EAGAIN == oserror))
                {
                    if (timeout == PR_INTERVAL_NO_TIMEOUT) {
                        goto retry;
                    }
                    else if (timeout == PR_INTERVAL_NO_WAIT) {
                        ready = 0;    /* don't retry, just time out */
                    }
                    else
                    {
                        elapsed = (PRIntervalTime) (PR_IntervalNow()
                                                    - start);
                        if (elapsed > timeout) {
                            ready = 0;    /* timed out */
                        }
                        else
                        {
                            remaining = timeout - elapsed;
                            msecs = PR_IntervalToMilliseconds(remaining);
                            tv.tv_sec = msecs/PR_MSEC_PER_SEC;
                            tv.tv_usec = (msecs % PR_MSEC_PER_SEC) *
                                         PR_USEC_PER_MSEC;
                            goto retry;
                        }
                    }
                } else if (EBADF == oserror)
                {
                    /* find all the bad fds */
                    ready = 0;
                    for (index = 0; index < npds; ++index)
                    {
                        pds[index].out_flags = 0;
                        if ((NULL != pds[index].fd) &&
                            (0 != pds[index].in_flags))
                        {
                            if (fcntl(selectfd[index], F_GETFL, 0) == -1)
                            {
                                pds[index].out_flags = PR_POLL_NVAL;
                                ready++;
                            }
                        }
                    }
                } else {
                    _PR_MD_MAP_SELECT_ERROR(oserror);
                }
            }
            else if (ready > 0)
            {
                for (index = 0; index < npds; ++index)
                {
                    PRInt16 out_flags = 0;
                    if ((NULL != pds[index].fd) && (0 != pds[index].in_flags))
                    {
                        if (FD_ISSET(selectfd[index], &rd))
                        {
                            if (pds[index].out_flags
                                & _PR_POLL_READ_SYS_READ)
                            {
                                out_flags |= PR_POLL_READ;
                            }
                            if (pds[index].out_flags
                                & _PR_POLL_WRITE_SYS_READ)
                            {
                                out_flags |= PR_POLL_WRITE;
                            }
                        }
                        if (FD_ISSET(selectfd[index], &wr))
                        {
                            if (pds[index].out_flags
                                & _PR_POLL_READ_SYS_WRITE)
                            {
                                out_flags |= PR_POLL_READ;
                            }
                            if (pds[index].out_flags
                                & _PR_POLL_WRITE_SYS_WRITE)
                            {
                                out_flags |= PR_POLL_WRITE;
                            }
                        }
                        if (FD_ISSET(selectfd[index], &ex)) {
                            out_flags |= PR_POLL_EXCEPT;
                        }
                    }
                    pds[index].out_flags = out_flags;
                }
            }
        }
    }
    return ready;

} /* _pr_poll_with_select */
#endif  /* _PR_POLL_WITH_SELECT */

PR_IMPLEMENT(PRInt32) PR_Poll(
    PRPollDesc *pds, PRIntn npds, PRIntervalTime timeout)
{
#if defined(_PR_POLL_WITH_SELECT)
    return(_pr_poll_with_select(pds, npds, timeout));
#else
    return(_pr_poll_with_poll(pds, npds, timeout));
#endif
}

PR_IMPLEMENT(PRDirEntry*) PR_ReadDir(PRDir *dir, PRDirFlags flags)
{
    struct dirent *dp;

    if (pt_TestAbort()) {
        return NULL;
    }

    for (;;)
    {
        errno = 0;
        dp = readdir(dir->md.d);
        if (NULL == dp)
        {
            pt_MapError(_PR_MD_MAP_READDIR_ERROR, errno);
            return NULL;
        }
        if ((flags & PR_SKIP_DOT)
            && ('.' == dp->d_name[0])
            && (0 == dp->d_name[1])) {
            continue;
        }
        if ((flags & PR_SKIP_DOT_DOT)
            && ('.' == dp->d_name[0])
            && ('.' == dp->d_name[1])
            && (0 == dp->d_name[2])) {
            continue;
        }
        if ((flags & PR_SKIP_HIDDEN) && ('.' == dp->d_name[0])) {
            continue;
        }
        break;
    }
    dir->d.name = dp->d_name;
    return &dir->d;
}  /* PR_ReadDir */

PR_IMPLEMENT(PRFileDesc*) PR_NewUDPSocket(void)
{
    PRIntn domain = PF_INET;

    return PR_Socket(domain, SOCK_DGRAM, 0);
}  /* PR_NewUDPSocket */

PR_IMPLEMENT(PRFileDesc*) PR_NewTCPSocket(void)
{
    PRIntn domain = PF_INET;

    return PR_Socket(domain, SOCK_STREAM, 0);
}  /* PR_NewTCPSocket */

PR_IMPLEMENT(PRFileDesc*) PR_OpenUDPSocket(PRIntn af)
{
    return PR_Socket(af, SOCK_DGRAM, 0);
}  /* PR_NewUDPSocket */

PR_IMPLEMENT(PRFileDesc*) PR_OpenTCPSocket(PRIntn af)
{
    return PR_Socket(af, SOCK_STREAM, 0);
}  /* PR_NewTCPSocket */

PR_IMPLEMENT(PRStatus) PR_NewTCPSocketPair(PRFileDesc *fds[2])
{
    PRInt32 osfd[2];

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, osfd) == -1) {
        pt_MapError(_PR_MD_MAP_SOCKETPAIR_ERROR, errno);
        return PR_FAILURE;
    }

    fds[0] = pt_SetMethods(osfd[0], PR_DESC_SOCKET_TCP, PR_FALSE, PR_FALSE);
    if (fds[0] == NULL) {
        close(osfd[0]);
        close(osfd[1]);
        return PR_FAILURE;
    }
    fds[1] = pt_SetMethods(osfd[1], PR_DESC_SOCKET_TCP, PR_FALSE, PR_FALSE);
    if (fds[1] == NULL) {
        PR_Close(fds[0]);
        close(osfd[1]);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}  /* PR_NewTCPSocketPair */

PR_IMPLEMENT(PRStatus) PR_CreatePipe(
    PRFileDesc **readPipe,
    PRFileDesc **writePipe
)
{
    int pipefd[2];

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    if (pipe(pipefd) == -1)
    {
        /* XXX map pipe error */
        PR_SetError(PR_UNKNOWN_ERROR, errno);
        return PR_FAILURE;
    }
    *readPipe = pt_SetMethods(pipefd[0], PR_DESC_PIPE, PR_FALSE, PR_FALSE);
    if (NULL == *readPipe)
    {
        close(pipefd[0]);
        close(pipefd[1]);
        return PR_FAILURE;
    }
    *writePipe = pt_SetMethods(pipefd[1], PR_DESC_PIPE, PR_FALSE, PR_FALSE);
    if (NULL == *writePipe)
    {
        PR_Close(*readPipe);
        close(pipefd[1]);
        return PR_FAILURE;
    }
    return PR_SUCCESS;
}

/*
** Set the inheritance attribute of a file descriptor.
*/
PR_IMPLEMENT(PRStatus) PR_SetFDInheritable(
    PRFileDesc *fd,
    PRBool inheritable)
{
    /*
     * Only a non-layered, NSPR file descriptor can be inherited
     * by a child process.
     */
    if (fd->identity != PR_NSPR_IO_LAYER)
    {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
        return PR_FAILURE;
    }
    if (fd->secret->inheritable != inheritable)
    {
        if (fcntl(fd->secret->md.osfd, F_SETFD,
                  inheritable ? 0 : FD_CLOEXEC) == -1)
        {
            _PR_MD_MAP_DEFAULT_ERROR(errno);
            return PR_FAILURE;
        }
        fd->secret->inheritable = (_PRTriStateBool) inheritable;
    }
    return PR_SUCCESS;
}

/*****************************************************************************/
/***************************** I/O friends methods ***************************/
/*****************************************************************************/

PR_IMPLEMENT(PRFileDesc*) PR_ImportFile(PRInt32 osfd)
{
    PRFileDesc *fd;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    fd = pt_SetMethods(osfd, PR_DESC_FILE, PR_FALSE, PR_TRUE);
    if (NULL == fd) {
        close(osfd);
    }
    return fd;
}  /* PR_ImportFile */

PR_IMPLEMENT(PRFileDesc*) PR_ImportPipe(PRInt32 osfd)
{
    PRFileDesc *fd;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    fd = pt_SetMethods(osfd, PR_DESC_PIPE, PR_FALSE, PR_TRUE);
    if (NULL == fd) {
        close(osfd);
    }
    return fd;
}  /* PR_ImportPipe */

PR_IMPLEMENT(PRFileDesc*) PR_ImportTCPSocket(PRInt32 osfd)
{
    PRFileDesc *fd;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    fd = pt_SetMethods(osfd, PR_DESC_SOCKET_TCP, PR_FALSE, PR_TRUE);
    if (NULL == fd) {
        close(osfd);
    }
#ifdef _PR_NEED_SECRET_AF
    if (NULL != fd) {
        fd->secret->af = PF_INET;
    }
#endif
    return fd;
}  /* PR_ImportTCPSocket */

PR_IMPLEMENT(PRFileDesc*) PR_ImportUDPSocket(PRInt32 osfd)
{
    PRFileDesc *fd;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }
    fd = pt_SetMethods(osfd, PR_DESC_SOCKET_UDP, PR_FALSE, PR_TRUE);
    if (NULL == fd) {
        close(osfd);
    }
    return fd;
}  /* PR_ImportUDPSocket */

PR_IMPLEMENT(PRFileDesc*) PR_CreateSocketPollFd(PRInt32 osfd)
{
    PRFileDesc *fd;

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

    fd = _PR_Getfd();

    if (fd == NULL) {
        PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0);
    }
    else
    {
        fd->secret->md.osfd = osfd;
        fd->secret->inheritable = _PR_TRI_FALSE;
        fd->secret->state = _PR_FILEDESC_OPEN;
        fd->methods = PR_GetSocketPollFdMethods();
    }

    return fd;
}  /* PR_CreateSocketPollFD */

PR_IMPLEMENT(PRStatus) PR_DestroySocketPollFd(PRFileDesc *fd)
{
    if (NULL == fd)
    {
        PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
        return PR_FAILURE;
    }
    fd->secret->state = _PR_FILEDESC_CLOSED;
    _PR_Putfd(fd);
    return PR_SUCCESS;
}  /* PR_DestroySocketPollFd */

PR_IMPLEMENT(PRInt32) PR_FileDesc2NativeHandle(PRFileDesc *bottom)
{
    PRInt32 osfd = -1;
    bottom = (NULL == bottom) ?
             NULL : PR_GetIdentitiesLayer(bottom, PR_NSPR_IO_LAYER);
    if (NULL == bottom) {
        PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
    }
    else {
        osfd = bottom->secret->md.osfd;
    }
    return osfd;
}  /* PR_FileDesc2NativeHandle */

PR_IMPLEMENT(void) PR_ChangeFileDescNativeHandle(PRFileDesc *fd,
        PRInt32 handle)
{
    if (fd) {
        fd->secret->md.osfd = handle;
    }
}  /*  PR_ChangeFileDescNativeHandle*/

PR_IMPLEMENT(PRStatus) PR_LockFile(PRFileDesc *fd)
{
    PRStatus status = PR_SUCCESS;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    PR_Lock(_pr_flock_lock);
    while (-1 == fd->secret->lockCount) {
        PR_WaitCondVar(_pr_flock_cv, PR_INTERVAL_NO_TIMEOUT);
    }
    if (0 == fd->secret->lockCount)
    {
        fd->secret->lockCount = -1;
        PR_Unlock(_pr_flock_lock);
        status = _PR_MD_LOCKFILE(fd->secret->md.osfd);
        PR_Lock(_pr_flock_lock);
        fd->secret->lockCount = (PR_SUCCESS == status) ? 1 : 0;
        PR_NotifyAllCondVar(_pr_flock_cv);
    }
    else
    {
        fd->secret->lockCount += 1;
    }
    PR_Unlock(_pr_flock_lock);

    return status;
}  /* PR_LockFile */

PR_IMPLEMENT(PRStatus) PR_TLockFile(PRFileDesc *fd)
{
    PRStatus status = PR_SUCCESS;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    PR_Lock(_pr_flock_lock);
    if (0 == fd->secret->lockCount)
    {
        status = _PR_MD_TLOCKFILE(fd->secret->md.osfd);
        if (PR_SUCCESS == status) {
            fd->secret->lockCount = 1;
        }
    }
    else {
        fd->secret->lockCount += 1;
    }
    PR_Unlock(_pr_flock_lock);

    return status;
}  /* PR_TLockFile */

PR_IMPLEMENT(PRStatus) PR_UnlockFile(PRFileDesc *fd)
{
    PRStatus status = PR_SUCCESS;

    if (pt_TestAbort()) {
        return PR_FAILURE;
    }

    PR_Lock(_pr_flock_lock);
    if (fd->secret->lockCount == 1)
    {
        status = _PR_MD_UNLOCKFILE(fd->secret->md.osfd);
        if (PR_SUCCESS == status) {
            fd->secret->lockCount = 0;
        }
    }
    else {
        fd->secret->lockCount -= 1;
    }
    PR_Unlock(_pr_flock_lock);

    return status;
}

/*
 * The next two entry points should not be in the API, but they are
 * defined here for historical (or hysterical) reasons.
 */

PR_IMPLEMENT(PRInt32) PR_GetSysfdTableMax(void)
{
#if defined(AIX)
    return sysconf(_SC_OPEN_MAX);
#else
    struct rlimit rlim;

    if ( getrlimit(RLIMIT_NOFILE, &rlim) < 0) {
        return -1;
    }

    return rlim.rlim_max;
#endif
}

PR_IMPLEMENT(PRInt32) PR_SetSysfdTableSize(PRIntn table_size)
{
#if defined(AIX)
    return -1;
#else
    struct rlimit rlim;
    PRInt32 tableMax = PR_GetSysfdTableMax();

    if (tableMax < 0) {
        return -1;
    }
    rlim.rlim_max = tableMax;

    /* Grow as much as we can; even if too big */
    if ( rlim.rlim_max < table_size ) {
        rlim.rlim_cur = rlim.rlim_max;
    }
    else {
        rlim.rlim_cur = table_size;
    }

    if ( setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
        return -1;
    }

    return rlim.rlim_cur;
#endif
}

/*
 * PR_Stat is supported for backward compatibility; some existing Java
 * code uses it.  New code should use PR_GetFileInfo.
 */

#ifndef NO_NSPR_10_SUPPORT
PR_IMPLEMENT(PRInt32) PR_Stat(const char *name, struct stat *buf)
{
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_Stat", "PR_GetFileInfo");
    }

    if (pt_TestAbort()) {
        return -1;
    }

    if (-1 == stat(name, buf)) {
        pt_MapError(_PR_MD_MAP_STAT_ERROR, errno);
        return -1;
    } else {
        return 0;
    }
}
#endif /* ! NO_NSPR_10_SUPPORT */


PR_IMPLEMENT(void) PR_FD_ZERO(PR_fd_set *set)
{
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_FD_ZERO (PR_Select)", "PR_Poll");
    }
    memset(set, 0, sizeof(PR_fd_set));
}

PR_IMPLEMENT(void) PR_FD_SET(PRFileDesc *fh, PR_fd_set *set)
{
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_FD_SET (PR_Select)", "PR_Poll");
    }
    PR_ASSERT( set->hsize < PR_MAX_SELECT_DESC );

    set->harray[set->hsize++] = fh;
}

PR_IMPLEMENT(void) PR_FD_CLR(PRFileDesc *fh, PR_fd_set *set)
{
    PRUint32 index, index2;
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_FD_CLR (PR_Select)", "PR_Poll");
    }

    for (index = 0; index<set->hsize; index++)
        if (set->harray[index] == fh) {
            for (index2=index; index2 < (set->hsize-1); index2++) {
                set->harray[index2] = set->harray[index2+1];
            }
            set->hsize--;
            break;
        }
}

PR_IMPLEMENT(PRInt32) PR_FD_ISSET(PRFileDesc *fh, PR_fd_set *set)
{
    PRUint32 index;
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_FD_ISSET (PR_Select)", "PR_Poll");
    }
    for (index = 0; index<set->hsize; index++)
        if (set->harray[index] == fh) {
            return 1;
        }
    return 0;
}

PR_IMPLEMENT(void) PR_FD_NSET(PRInt32 fd, PR_fd_set *set)
{
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_FD_NSET (PR_Select)", "PR_Poll");
    }
    PR_ASSERT( set->nsize < PR_MAX_SELECT_DESC );

    set->narray[set->nsize++] = fd;
}

PR_IMPLEMENT(void) PR_FD_NCLR(PRInt32 fd, PR_fd_set *set)
{
    PRUint32 index, index2;
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_FD_NCLR (PR_Select)", "PR_Poll");
    }

    for (index = 0; index<set->nsize; index++)
        if (set->narray[index] == fd) {
            for (index2=index; index2 < (set->nsize-1); index2++) {
                set->narray[index2] = set->narray[index2+1];
            }
            set->nsize--;
            break;
        }
}

PR_IMPLEMENT(PRInt32) PR_FD_NISSET(PRInt32 fd, PR_fd_set *set)
{
    PRUint32 index;
    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete("PR_FD_NISSET (PR_Select)", "PR_Poll");
    }
    for (index = 0; index<set->nsize; index++)
        if (set->narray[index] == fd) {
            return 1;
        }
    return 0;
}

#include <sys/types.h>
#include <sys/time.h>
#if !defined(HPUX) \
    && !defined(LINUX) && !defined(__GNU__) && !defined(__GLIBC__)
#include <sys/select.h>
#endif

static PRInt32
_PR_getset(PR_fd_set *pr_set, fd_set *set)
{
    PRUint32 index;
    PRInt32 max = 0;

    if (!pr_set) {
        return 0;
    }

    FD_ZERO(set);

    /* First set the pr file handle osfds */
    for (index=0; index<pr_set->hsize; index++) {
        FD_SET(pr_set->harray[index]->secret->md.osfd, set);
        if (pr_set->harray[index]->secret->md.osfd > max) {
            max = pr_set->harray[index]->secret->md.osfd;
        }
    }
    /* Second set the native osfds */
    for (index=0; index<pr_set->nsize; index++) {
        FD_SET(pr_set->narray[index], set);
        if (pr_set->narray[index] > max) {
            max = pr_set->narray[index];
        }
    }
    return max;
}

static void
_PR_setset(PR_fd_set *pr_set, fd_set *set)
{
    PRUint32 index, last_used;

    if (!pr_set) {
        return;
    }

    for (last_used=0, index=0; index<pr_set->hsize; index++) {
        if ( FD_ISSET(pr_set->harray[index]->secret->md.osfd, set) ) {
            pr_set->harray[last_used++] = pr_set->harray[index];
        }
    }
    pr_set->hsize = last_used;

    for (last_used=0, index=0; index<pr_set->nsize; index++) {
        if ( FD_ISSET(pr_set->narray[index], set) ) {
            pr_set->narray[last_used++] = pr_set->narray[index];
        }
    }
    pr_set->nsize = last_used;
}

PR_IMPLEMENT(PRInt32) PR_Select(
    PRInt32 unused, PR_fd_set *pr_rd, PR_fd_set *pr_wr,
    PR_fd_set *pr_ex, PRIntervalTime timeout)
{
    fd_set rd, wr, ex;
    struct timeval tv, *tvp;
    PRInt32 max, max_fd;
    PRInt32 rv;
    /*
     * For restarting select() if it is interrupted by a Unix signal.
     * We use these variables to figure out how much time has elapsed
     * and how much of the timeout still remains.
     */
    PRIntervalTime start = 0, elapsed, remaining;

    static PRBool unwarned = PR_TRUE;
    if (unwarned) {
        unwarned = _PR_Obsolete( "PR_Select", "PR_Poll");
    }

    FD_ZERO(&rd);
    FD_ZERO(&wr);
    FD_ZERO(&ex);

    max_fd = _PR_getset(pr_rd, &rd);
    max_fd = (max = _PR_getset(pr_wr, &wr))>max_fd?max:max_fd;
    max_fd = (max = _PR_getset(pr_ex, &ex))>max_fd?max:max_fd;

    if (timeout == PR_INTERVAL_NO_TIMEOUT) {
        tvp = NULL;
    } else {
        tv.tv_sec = (PRInt32)PR_IntervalToSeconds(timeout);
        tv.tv_usec = (PRInt32)PR_IntervalToMicroseconds(
                         timeout - PR_SecondsToInterval(tv.tv_sec));
        tvp = &tv;
        start = PR_IntervalNow();
    }

retry:
    rv = select(max_fd + 1, (_PRSelectFdSetArg_t) &rd,
                (_PRSelectFdSetArg_t) &wr, (_PRSelectFdSetArg_t) &ex, tvp);

    if (rv == -1 && errno == EINTR) {
        if (timeout == PR_INTERVAL_NO_TIMEOUT) {
            goto retry;
        } else {
            elapsed = (PRIntervalTime) (PR_IntervalNow() - start);
            if (elapsed > timeout) {
                rv = 0;  /* timed out */
            } else {
                remaining = timeout - elapsed;
                tv.tv_sec = (PRInt32)PR_IntervalToSeconds(remaining);
                tv.tv_usec = (PRInt32)PR_IntervalToMicroseconds(
                                 remaining - PR_SecondsToInterval(tv.tv_sec));
                goto retry;
            }
        }
    }

    if (rv > 0) {
        _PR_setset(pr_rd, &rd);
        _PR_setset(pr_wr, &wr);
        _PR_setset(pr_ex, &ex);
    } else if (rv == -1) {
        pt_MapError(_PR_MD_MAP_SELECT_ERROR, errno);
    }
    return rv;
}
#endif /* defined(_PR_PTHREADS) */

#ifdef MOZ_UNICODE
/* ================ UTF16 Interfaces ================================ */
PR_IMPLEMENT(PRFileDesc*) PR_OpenFileUTF16(
    const PRUnichar *name, PRIntn flags, PRIntn mode)
{
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return NULL;
}

PR_IMPLEMENT(PRStatus) PR_CloseDirUTF16(PRDir *dir)
{
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}

PR_IMPLEMENT(PRDirUTF16*) PR_OpenDirUTF16(const PRUnichar *name)
{
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return NULL;
}

PR_IMPLEMENT(PRDirEntryUTF16*) PR_ReadDirUTF16(PRDirUTF16 *dir, PRDirFlags flags)
{
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return NULL;
}

PR_IMPLEMENT(PRStatus) PR_GetFileInfo64UTF16(const PRUnichar *fn, PRFileInfo64 *info)
{
    PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
    return PR_FAILURE;
}
/* ================ UTF16 Interfaces ================================ */
#endif /* MOZ_UNICODE */

/* ptio.c */
