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


#  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)
#    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(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(NTO) ||        \
      defined(DARWIN) || 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

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


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);

  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;
  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 -1;
  }

  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

  size_t bytes = 0;
  PRInt32 netResult = 0;
#      ifndef HAS_CONNECTX
  netResult = sendto(fd->secret->md.osfd, buf, amount, MSG_FASTOPEN,
                     (struct sockaddr*)addrp, addr_len);
  if (netResult >= 0) {
    bytes = netResult;
  }
#      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;
  netResult = connectx(fd->secret->md.osfd, &endpoints, SAE_ASSOCID_ANY,
                       CONNECT_DATA_IDEMPOTENT, iov, 1, &bytes, NULL);
#      endif
  syserrno = errno;
  if ((netResult < 0) && (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;
    netResult = pt_Continue(&op);
    if (netResult >= 0) {
      bytes = netResult;
    }
    syserrno = op.syserrno;
  }
  if (netResult < 0) {
    pt_MapError(_PR_MD_MAP_SENDTO_ERROR, syserrno);
    return -1;
  }
  return bytes;
#    else /* !(defined(LINUX) || 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(SOLARIS) || defined(LINUX) ||     \
      defined(__GNU__) || defined(__GLIBC__) || defined(AIX) ||  \
      defined(FREEBSD) || defined(NETBSD) || defined(OPENBSD) || \
      defined(NTO) || defined(DARWIN) || 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.
 */
#    define pt_MakeSocketNonblock pt_MakeFdNonblock

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


PR_IMPLEMENT(PRInt32)
PR_Poll(PRPollDesc* pds, PRIntn npds, PRIntervalTime timeout) {
  return (_pr_poll_with_poll(pds, npds, timeout));
}

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