/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */

#include "primpl.h"

#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/utsname.h>

#ifdef _PR_POLL_AVAILABLE
#  include <poll.h>
#endif

#if defined(ANDROID)
#  include <android/api-level.h>
#endif

#if defined(NTO)
#  include <sys/statvfs.h>
#endif

/*
 * Make sure _PRSockLen_t is 32-bit, because we will cast a PRUint32* or
 * PRInt32* pointer to a _PRSockLen_t* pointer.
 */
#if defined(HAVE_SOCKLEN_T) || (defined(__GLIBC__) && __GLIBC__ >= 2)
#  define _PRSockLen_t socklen_t
#elif defined(HPUX) || defined(SOLARIS) || defined(AIX4_1) || \
    defined(LINUX) || defined(DARWIN) || defined(QNX)
#  define _PRSockLen_t int
#elif (defined(AIX) && !defined(AIX4_1)) || defined(FREEBSD) || \
    defined(NETBSD) || defined(OPENBSD)
|| defined(NTO) ||
    defined(RISCOS)
#  define _PRSockLen_t size_t
#else
#  error "Cannot determine architecture"
#endif

/*
** Global lock variable used to bracket calls into rusty libraries that
** aren't thread safe (like libc, libX, etc).
*/
static PRLock* _pr_unix_rename_lock = NULL;
static PRMonitor* _pr_Xfe_mon = NULL;

static PRInt64 minus_one;

sigset_t timer_set;

#if !defined(_PR_PTHREADS)

static sigset_t empty_set;

#  ifdef SOLARIS
#    include <sys/file.h>
#    include <sys/filio.h>
#  endif

#  ifndef PIPE_BUF
#    define PIPE_BUF 512
#  endif

/*
 * _nspr_noclock - if set clock interrupts are disabled
 */
int _nspr_noclock = 1;

/*
 * There is an assertion in this code that NSPR's definition of PRIOVec
 * is bit compatible with UNIX' definition of a struct iovec. This is
 * applicable to the 'writev()' operations where the types are casually
 * cast to avoid warnings.
 */

int _pr_md_pipefd[2] = {-1, -1};
static char _pr_md_pipebuf[PIPE_BUF];
static PRInt32 local_io_wait(PRInt32 osfd, PRInt32 wait_flag,
                             PRIntervalTime timeout);

_PRInterruptTable _pr_interruptTable[] = {{
                                              "clock",
                                              _PR_MISSED_CLOCK,
                                              _PR_ClockInterrupt,
                                          },
                                          {0}};

void _MD_unix_init_running_cpu(_PRCPU* cpu) {
  PR_INIT_CLIST(&(cpu->md.md_unix.ioQ));
  cpu->md.md_unix.ioq_max_osfd = -1;
  cpu->md.md_unix.ioq_timeout = PR_INTERVAL_NO_TIMEOUT;
}

PRStatus _MD_open_dir(_MDDir* d, const char* name) {
  int err;

  d->d = opendir(name);
  if (!d->d) {
    err = _MD_ERRNO();
    _PR_MD_MAP_OPENDIR_ERROR(err);
    return PR_FAILURE;
  }
  return PR_SUCCESS;
}

PRInt32 _MD_close_dir(_MDDir* d) {
  int rv = 0, err;

  if (d->d) {
    rv = closedir(d->d);
    if (rv == -1) {
      err = _MD_ERRNO();
      _PR_MD_MAP_CLOSEDIR_ERROR(err);
    }
  }
  return rv;
}

char* _MD_read_dir(_MDDir* d, PRIntn flags) {
  struct dirent* de;
  int err;

  for (;;) {
    /*
     * XXX: readdir() is not MT-safe. There is an MT-safe version
     * readdir_r() on some systems.
     */
    _MD_ERRNO() = 0;
    de = readdir(d->d);
    if (!de) {
      err = _MD_ERRNO();
      _PR_MD_MAP_READDIR_ERROR(err);
      return 0;
    }
    if ((flags & PR_SKIP_DOT) && (de->d_name[0] == '.') &&
        (de->d_name[1] == 0)) {
      continue;
    }
    if ((flags & PR_SKIP_DOT_DOT) && (de->d_name[0] == '.') &&
        (de->d_name[1] == '.') && (de->d_name[2] == 0)) {
      continue;
    }
    if ((flags & PR_SKIP_HIDDEN) && (de->d_name[0] == '.')) {
      continue;
    }
    break;
  }
  return de->d_name;
}

PRInt32 _MD_delete(const char* name) {
  PRInt32 rv, err;

  rv = unlink(name);
  if (rv == -1) {
    err = _MD_ERRNO();
    _PR_MD_MAP_UNLINK_ERROR(err);
  }
  return (rv);
}

PRInt32 _MD_rename(const char* from, const char* to) {
  PRInt32 rv = -1, err;

  /*
  ** This is trying to enforce the semantics of WINDOZE' rename
  ** operation. That means one is not allowed to rename over top
  ** of an existing file. Holding a lock across these two function
  ** and the open function is known to be a bad idea, but ....
  */
  if (NULL != _pr_unix_rename_lock) {
    PR_Lock(_pr_unix_rename_lock);
  }
  if (0 == access(to, F_OK)) {
    PR_SetError(PR_FILE_EXISTS_ERROR, 0);
  } else {
    rv = rename(from, to);
    if (rv < 0) {
      err = _MD_ERRNO();
      _PR_MD_MAP_RENAME_ERROR(err);
    }
  }
  if (NULL != _pr_unix_rename_lock) {
    PR_Unlock(_pr_unix_rename_lock);
  }
  return rv;
}

PRInt32 _MD_access(const char* name, PRAccessHow how) {
  PRInt32 rv, err;
  int amode;

  switch (how) {
    case PR_ACCESS_WRITE_OK:
      amode = W_OK;
      break;
    case PR_ACCESS_READ_OK:
      amode = R_OK;
      break;
    case PR_ACCESS_EXISTS:
      amode = F_OK;
      break;
    default:
      PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
      rv = -1;
      goto done;
  }
  rv = access(name, amode);

  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_ACCESS_ERROR(err);
  }

done:
  return (rv);
}

PRInt32 _MD_mkdir(const char* name, PRIntn mode) {
  int rv, err;

  /*
  ** This lock is used to enforce rename semantics as described
  ** in PR_Rename. Look there for more fun details.
  */
  if (NULL != _pr_unix_rename_lock) {
    PR_Lock(_pr_unix_rename_lock);
  }
  rv = mkdir(name, mode);
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_MKDIR_ERROR(err);
  }
  if (NULL != _pr_unix_rename_lock) {
    PR_Unlock(_pr_unix_rename_lock);
  }
  return rv;
}

PRInt32 _MD_rmdir(const char* name) {
  int rv, err;

  rv = rmdir(name);
  if (rv == -1) {
    err = _MD_ERRNO();
    _PR_MD_MAP_RMDIR_ERROR(err);
  }
  return rv;
}

PRInt32 _MD_read(PRFileDesc* fd, void* buf, PRInt32 amount) {
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRInt32 rv, err;
#  ifndef _PR_USE_POLL
  fd_set rd;
#  else
  struct pollfd pfd;
#  endif /* _PR_USE_POLL */
  PRInt32 osfd = fd->secret->md.osfd;

#  ifndef _PR_USE_POLL
  FD_ZERO(&rd);
  FD_SET(osfd, &rd);
#  else
  pfd.fd = osfd;
  pfd.events = POLLIN;
#  endif /* _PR_USE_POLL */
  while ((rv = read(osfd, buf, amount)) == -1) {
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_READ,
                                PR_INTERVAL_NO_TIMEOUT)) < 0) {
          goto done;
        }
      } else {
#  ifndef _PR_USE_POLL
        while ((rv = _MD_SELECT(osfd + 1, &rd, NULL, NULL, NULL)) == -1 &&
               (err = _MD_ERRNO()) == EINTR) {
          /* retry _MD_SELECT() if it is interrupted */
        }
#  else  /* _PR_USE_POLL */
        while ((rv = _MD_POLL(&pfd, 1, -1)) == -1 &&
               (err = _MD_ERRNO()) == EINTR) {
          /* retry _MD_POLL() if it is interrupted */
        }
#  endif /* _PR_USE_POLL */
        if (rv == -1) {
          break;
        }
      }
      if (_PR_PENDING_INTERRUPT(me)) {
        break;
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
      break;
    }
  }
  if (rv < 0) {
    if (_PR_PENDING_INTERRUPT(me)) {
      me->flags &= ~_PR_INTERRUPT;
      PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    } else {
      _PR_MD_MAP_READ_ERROR(err);
    }
  }
done:
  return (rv);
}

PRInt32 _MD_write(PRFileDesc* fd, const void* buf, PRInt32 amount) {
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRInt32 rv, err;
#  ifndef _PR_USE_POLL
  fd_set wd;
#  else
  struct pollfd pfd;
#  endif /* _PR_USE_POLL */
  PRInt32 osfd = fd->secret->md.osfd;

#  ifndef _PR_USE_POLL
  FD_ZERO(&wd);
  FD_SET(osfd, &wd);
#  else
  pfd.fd = osfd;
  pfd.events = POLLOUT;
#  endif /* _PR_USE_POLL */
  while ((rv = write(osfd, buf, amount)) == -1) {
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_WRITE,
                                PR_INTERVAL_NO_TIMEOUT)) < 0) {
          goto done;
        }
      } else {
#  ifndef _PR_USE_POLL
        while ((rv = _MD_SELECT(osfd + 1, NULL, &wd, NULL, NULL)) == -1 &&
               (err = _MD_ERRNO()) == EINTR) {
          /* retry _MD_SELECT() if it is interrupted */
        }
#  else  /* _PR_USE_POLL */
        while ((rv = _MD_POLL(&pfd, 1, -1)) == -1 &&
               (err = _MD_ERRNO()) == EINTR) {
          /* retry _MD_POLL() if it is interrupted */
        }
#  endif /* _PR_USE_POLL */
        if (rv == -1) {
          break;
        }
      }
      if (_PR_PENDING_INTERRUPT(me)) {
        break;
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
      break;
    }
  }
  if (rv < 0) {
    if (_PR_PENDING_INTERRUPT(me)) {
      me->flags &= ~_PR_INTERRUPT;
      PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    } else {
      _PR_MD_MAP_WRITE_ERROR(err);
    }
  }
done:
  return (rv);
}

PRInt32 _MD_fsync(PRFileDesc* fd) {
  PRInt32 rv, err;

  rv = fsync(fd->secret->md.osfd);
  if (rv == -1) {
    err = _MD_ERRNO();
    _PR_MD_MAP_FSYNC_ERROR(err);
  }
  return (rv);
}

PRInt32 _MD_close(PRInt32 osfd) {
  PRInt32 rv, err;

  rv = close(osfd);
  if (rv == -1) {
    err = _MD_ERRNO();
    _PR_MD_MAP_CLOSE_ERROR(err);
  }
  return (rv);
}

PRInt32 _MD_socket(PRInt32 domain, PRInt32 type, PRInt32 proto) {
  PRInt32 osfd, err;

  osfd = socket(domain, type, proto);

  if (osfd == -1) {
    err = _MD_ERRNO();
    _PR_MD_MAP_SOCKET_ERROR(err);
    return (osfd);
  }

  return (osfd);
}

PRInt32 _MD_socketavailable(PRFileDesc* fd) {
  PRInt32 result;

  if (ioctl(fd->secret->md.osfd, FIONREAD, &result) < 0) {
    _PR_MD_MAP_SOCKETAVAILABLE_ERROR(_MD_ERRNO());
    return -1;
  }
  return result;
}

PRInt64 _MD_socketavailable64(PRFileDesc* fd) {
  PRInt64 result;
  LL_I2L(result, _MD_socketavailable(fd));
  return result;
} /* _MD_socketavailable64 */

#  define READ_FD 1
#  define WRITE_FD 2

/*
 * socket_io_wait --
 *
 * wait for socket i/o, periodically checking for interrupt
 *
 * The first implementation uses select(), for platforms without
 * poll().  The second (preferred) implementation uses poll().
 */

#  ifndef _PR_USE_POLL

static PRInt32 socket_io_wait(PRInt32 osfd, PRInt32 fd_type,
                              PRIntervalTime timeout) {
  PRInt32 rv = -1;
  struct timeval tv;
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRIntervalTime epoch, now, elapsed, remaining;
  PRBool wait_for_remaining;
  PRInt32 syserror;
  fd_set rd_wr;

  switch (timeout) {
    case PR_INTERVAL_NO_WAIT:
      PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
      break;
    case PR_INTERVAL_NO_TIMEOUT:
      /*
       * This is a special case of the 'default' case below.
       * Please see the comments there.
       */
      tv.tv_sec = _PR_INTERRUPT_CHECK_INTERVAL_SECS;
      tv.tv_usec = 0;
      FD_ZERO(&rd_wr);
      do {
        FD_SET(osfd, &rd_wr);
        if (fd_type == READ_FD) {
          rv = _MD_SELECT(osfd + 1, &rd_wr, NULL, NULL, &tv);
        } else {
          rv = _MD_SELECT(osfd + 1, NULL, &rd_wr, NULL, &tv);
        }
        if (rv == -1 && (syserror = _MD_ERRNO()) != EINTR) {
          _PR_MD_MAP_SELECT_ERROR(syserror);
          break;
        }
        if (_PR_PENDING_INTERRUPT(me)) {
          me->flags &= ~_PR_INTERRUPT;
          PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
          rv = -1;
          break;
        }
      } while (rv == 0 || (rv == -1 && syserror == EINTR));
      break;
    default:
      now = epoch = PR_IntervalNow();
      remaining = timeout;
      FD_ZERO(&rd_wr);
      do {
        /*
         * We block in _MD_SELECT for at most
         * _PR_INTERRUPT_CHECK_INTERVAL_SECS seconds,
         * so that there is an upper limit on the delay
         * before the interrupt bit is checked.
         */
        wait_for_remaining = PR_TRUE;
        tv.tv_sec = PR_IntervalToSeconds(remaining);
        if (tv.tv_sec > _PR_INTERRUPT_CHECK_INTERVAL_SECS) {
          wait_for_remaining = PR_FALSE;
          tv.tv_sec = _PR_INTERRUPT_CHECK_INTERVAL_SECS;
          tv.tv_usec = 0;
        } else {
          tv.tv_usec = PR_IntervalToMicroseconds(
              remaining - PR_SecondsToInterval(tv.tv_sec));
        }
        FD_SET(osfd, &rd_wr);
        if (fd_type == READ_FD) {
          rv = _MD_SELECT(osfd + 1, &rd_wr, NULL, NULL, &tv);
        } else {
          rv = _MD_SELECT(osfd + 1, NULL, &rd_wr, NULL, &tv);
        }
        /*
         * we don't consider EINTR a real error
         */
        if (rv == -1 && (syserror = _MD_ERRNO()) != EINTR) {
          _PR_MD_MAP_SELECT_ERROR(syserror);
          break;
        }
        if (_PR_PENDING_INTERRUPT(me)) {
          me->flags &= ~_PR_INTERRUPT;
          PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
          rv = -1;
          break;
        }
        /*
         * We loop again if _MD_SELECT timed out or got interrupted
         * by a signal, and the timeout deadline has not passed yet.
         */
        if (rv == 0 || (rv == -1 && syserror == EINTR)) {
          /*
           * If _MD_SELECT timed out, we know how much time
           * we spent in blocking, so we can avoid a
           * PR_IntervalNow() call.
           */
          if (rv == 0) {
            if (wait_for_remaining) {
              now += remaining;
            } else {
              now += PR_SecondsToInterval(tv.tv_sec) +
                     PR_MicrosecondsToInterval(tv.tv_usec);
            }
          } else {
            now = PR_IntervalNow();
          }
          elapsed = (PRIntervalTime)(now - epoch);
          if (elapsed >= timeout) {
            PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
            rv = -1;
            break;
          } else {
            remaining = timeout - elapsed;
          }
        }
      } while (rv == 0 || (rv == -1 && syserror == EINTR));
      break;
  }
  return (rv);
}

#  else /* _PR_USE_POLL */

static PRInt32 socket_io_wait(PRInt32 osfd, PRInt32 fd_type,
                              PRIntervalTime timeout) {
  PRInt32 rv = -1;
  int msecs;
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRIntervalTime epoch, now, elapsed, remaining;
  PRBool wait_for_remaining;
  PRInt32 syserror;
  struct pollfd pfd;

  switch (timeout) {
    case PR_INTERVAL_NO_WAIT:
      PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
      break;
    case PR_INTERVAL_NO_TIMEOUT:
      /*
       * This is a special case of the 'default' case below.
       * Please see the comments there.
       */
      msecs = _PR_INTERRUPT_CHECK_INTERVAL_SECS * 1000;
      pfd.fd = osfd;
      if (fd_type == READ_FD) {
        pfd.events = POLLIN;
      } else {
        pfd.events = POLLOUT;
      }
      do {
        rv = _MD_POLL(&pfd, 1, msecs);
        if (rv == -1 && (syserror = _MD_ERRNO()) != EINTR) {
          _PR_MD_MAP_POLL_ERROR(syserror);
          break;
        }
        /*
         * If POLLERR is set, don't process it; retry the operation
         */
        if ((rv == 1) && (pfd.revents & (POLLHUP | POLLNVAL))) {
          rv = -1;
          _PR_MD_MAP_POLL_REVENTS_ERROR(pfd.revents);
          break;
        }
        if (_PR_PENDING_INTERRUPT(me)) {
          me->flags &= ~_PR_INTERRUPT;
          PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
          rv = -1;
          break;
        }
      } while (rv == 0 || (rv == -1 && syserror == EINTR));
      break;
    default:
      now = epoch = PR_IntervalNow();
      remaining = timeout;
      pfd.fd = osfd;
      if (fd_type == READ_FD) {
        pfd.events = POLLIN;
      } else {
        pfd.events = POLLOUT;
      }
      do {
        /*
         * We block in _MD_POLL for at most
         * _PR_INTERRUPT_CHECK_INTERVAL_SECS seconds,
         * so that there is an upper limit on the delay
         * before the interrupt bit is checked.
         */
        wait_for_remaining = PR_TRUE;
        msecs = PR_IntervalToMilliseconds(remaining);
        if (msecs > _PR_INTERRUPT_CHECK_INTERVAL_SECS * 1000) {
          wait_for_remaining = PR_FALSE;
          msecs = _PR_INTERRUPT_CHECK_INTERVAL_SECS * 1000;
        }
        rv = _MD_POLL(&pfd, 1, msecs);
        /*
         * we don't consider EINTR a real error
         */
        if (rv == -1 && (syserror = _MD_ERRNO()) != EINTR) {
          _PR_MD_MAP_POLL_ERROR(syserror);
          break;
        }
        if (_PR_PENDING_INTERRUPT(me)) {
          me->flags &= ~_PR_INTERRUPT;
          PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
          rv = -1;
          break;
        }
        /*
         * If POLLERR is set, don't process it; retry the operation
         */
        if ((rv == 1) && (pfd.revents & (POLLHUP | POLLNVAL))) {
          rv = -1;
          _PR_MD_MAP_POLL_REVENTS_ERROR(pfd.revents);
          break;
        }
        /*
         * We loop again if _MD_POLL timed out or got interrupted
         * by a signal, and the timeout deadline has not passed yet.
         */
        if (rv == 0 || (rv == -1 && syserror == EINTR)) {
          /*
           * If _MD_POLL timed out, we know how much time
           * we spent in blocking, so we can avoid a
           * PR_IntervalNow() call.
           */
          if (rv == 0) {
            if (wait_for_remaining) {
              now += remaining;
            } else {
              now += PR_MillisecondsToInterval(msecs);
            }
          } else {
            now = PR_IntervalNow();
          }
          elapsed = (PRIntervalTime)(now - epoch);
          if (elapsed >= timeout) {
            PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
            rv = -1;
            break;
          } else {
            remaining = timeout - elapsed;
          }
        }
      } while (rv == 0 || (rv == -1 && syserror == EINTR));
      break;
  }
  return (rv);
}

#  endif /* _PR_USE_POLL */

static PRInt32 local_io_wait(PRInt32 osfd, PRInt32 wait_flag,
                             PRIntervalTime timeout) {
  _PRUnixPollDesc pd;
  PRInt32 rv;

  PR_LOG(_pr_io_lm, PR_LOG_MIN,
         ("waiting to %s on osfd=%d",
          (wait_flag == _PR_UNIX_POLL_READ) ? "read" : "write", osfd));

  if (timeout == PR_INTERVAL_NO_WAIT) {
    return 0;
  }

  pd.osfd = osfd;
  pd.in_flags = wait_flag;
  pd.out_flags = 0;

  rv = _PR_WaitForMultipleFDs(&pd, 1, timeout);

  if (rv == 0) {
    PR_SetError(PR_IO_TIMEOUT_ERROR, 0);
    rv = -1;
  }
  return rv;
}

PRInt32 _MD_recv(PRFileDesc* fd, void* buf, PRInt32 amount, PRInt32 flags,
                 PRIntervalTime timeout) {
  PRInt32 osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  /*
   * Many OS's (ex: Solaris) have a broken recv which won't read
   * from socketpairs.  As long as we don't use flags on socketpairs, this
   * is a decent fix. - mikep
   */
#  if defined(SOLARIS)
  while ((rv = read(osfd, buf, amount)) == -1) {
#  else
  while ((rv = recv(osfd, buf, amount, flags)) == -1) {
#  endif
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_READ, timeout)) < 0) {
          goto done;
        }
      } else {
        if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0) {
          goto done;
        }
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
      break;
    }
  }
  if (rv < 0) {
    _PR_MD_MAP_RECV_ERROR(err);
  }
done:
  return (rv);
}

PRInt32 _MD_recvfrom(PRFileDesc* fd, void* buf, PRInt32 amount, PRIntn flags,
                     PRNetAddr* addr, PRUint32* addrlen,
                     PRIntervalTime timeout) {
  PRInt32 osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  while ((*addrlen = PR_NETADDR_SIZE(addr)),
         ((rv = recvfrom(osfd, buf, amount, flags, (struct sockaddr*)addr,
                         (_PRSockLen_t*)addrlen)) == -1)) {
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_READ, timeout)) < 0) {
          goto done;
        }
      } else {
        if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0) {
          goto done;
        }
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
      break;
    }
  }
  if (rv < 0) {
    _PR_MD_MAP_RECVFROM_ERROR(err);
  }
done:
#  ifdef _PR_HAVE_SOCKADDR_LEN
  if (rv != -1) {
    /* ignore the sa_len field of struct sockaddr */
    if (addr) {
      addr->raw.family = ((struct sockaddr*)addr)->sa_family;
    }
  }
#  endif /* _PR_HAVE_SOCKADDR_LEN */
  return (rv);
}

PRInt32 _MD_send(PRFileDesc* fd, const void* buf, PRInt32 amount, PRInt32 flags,
                 PRIntervalTime timeout) {
  PRInt32 osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRThread* me = _PR_MD_CURRENT_THREAD();
#  if defined(SOLARIS)
  PRInt32 tmp_amount = amount;
#  endif

  /*
   * 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);
  while ((rv = write(osfd, buf, tmp_amount)) == -1) {
#  else
  while ((rv = send(osfd, buf, amount, flags)) == -1) {
#  endif
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_WRITE, timeout)) < 0) {
          goto done;
        }
      } else {
        if ((rv = socket_io_wait(osfd, WRITE_FD, timeout)) < 0) {
          goto done;
        }
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
#  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 (err == ERANGE) {
        if (tmp_amount > 1) {
          tmp_amount = tmp_amount / 2; /* half the bytes */
          continue;
        }
      }
#  endif
      break;
    }
  }
  /*
   * optimization; if bytes sent is less than "amount" call
   * select before returning. This is because it is likely that
   * the next send() call will return EWOULDBLOCK.
   */
  if ((!fd->secret->nonblocking) && (rv > 0) && (rv < amount) &&
      (timeout != PR_INTERVAL_NO_WAIT)) {
    if (_PR_IS_NATIVE_THREAD(me)) {
      if (socket_io_wait(osfd, WRITE_FD, timeout) < 0) {
        rv = -1;
        goto done;
      }
    } else {
      if (local_io_wait(osfd, _PR_UNIX_POLL_WRITE, timeout) < 0) {
        rv = -1;
        goto done;
      }
    }
  }
  if (rv < 0) {
    _PR_MD_MAP_SEND_ERROR(err);
  }
done:
  return (rv);
}

PRInt32 _MD_sendto(PRFileDesc* fd, const void* buf, PRInt32 amount,
                   PRIntn flags, const PRNetAddr* addr, PRUint32 addrlen,
                   PRIntervalTime timeout) {
  PRInt32 osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRThread* me = _PR_MD_CURRENT_THREAD();
#  ifdef _PR_HAVE_SOCKADDR_LEN
  PRNetAddr addrCopy;

  addrCopy = *addr;
  ((struct sockaddr*)&addrCopy)->sa_len = addrlen;
  ((struct sockaddr*)&addrCopy)->sa_family = addr->raw.family;

  while ((rv = sendto(osfd, buf, amount, flags, (struct sockaddr*)&addrCopy,
                      addrlen)) == -1) {
#  else
  while ((rv = sendto(osfd, buf, amount, flags, (struct sockaddr*)addr,
                      addrlen)) == -1) {
#  endif
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_WRITE, timeout)) < 0) {
          goto done;
        }
      } else {
        if ((rv = socket_io_wait(osfd, WRITE_FD, timeout)) < 0) {
          goto done;
        }
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
      break;
    }
  }
  if (rv < 0) {
    _PR_MD_MAP_SENDTO_ERROR(err);
  }
done:
  return (rv);
}

PRInt32 _MD_writev(PRFileDesc* fd, const PRIOVec* iov, PRInt32 iov_size,
                   PRIntervalTime timeout) {
  PRInt32 rv, err;
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRInt32 index, amount = 0;
  PRInt32 osfd = fd->secret->md.osfd;

  /*
   * Calculate the total number of bytes to be sent; needed for
   * optimization later.
   * We could avoid this if this number was passed in; but it is
   * probably not a big deal because iov_size is usually small (less than
   * 3)
   */
  if (!fd->secret->nonblocking) {
    for (index = 0; index < iov_size; index++) {
      amount += iov[index].iov_len;
    }
  }

  while ((rv = writev(osfd, (const struct iovec*)iov, iov_size)) == -1) {
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_WRITE, timeout)) < 0) {
          goto done;
        }
      } else {
        if ((rv = socket_io_wait(osfd, WRITE_FD, timeout)) < 0) {
          goto done;
        }
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
      break;
    }
  }
  /*
   * optimization; if bytes sent is less than "amount" call
   * select before returning. This is because it is likely that
   * the next writev() call will return EWOULDBLOCK.
   */
  if ((!fd->secret->nonblocking) && (rv > 0) && (rv < amount) &&
      (timeout != PR_INTERVAL_NO_WAIT)) {
    if (_PR_IS_NATIVE_THREAD(me)) {
      if (socket_io_wait(osfd, WRITE_FD, timeout) < 0) {
        rv = -1;
        goto done;
      }
    } else {
      if (local_io_wait(osfd, _PR_UNIX_POLL_WRITE, timeout) < 0) {
        rv = -1;
        goto done;
      }
    }
  }
  if (rv < 0) {
    _PR_MD_MAP_WRITEV_ERROR(err);
  }
done:
  return (rv);
}

PRInt32 _MD_accept(PRFileDesc* fd, PRNetAddr* addr, PRUint32* addrlen,
                   PRIntervalTime timeout) {
  PRInt32 osfd = fd->secret->md.osfd;
  PRInt32 rv, err;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  while ((rv = accept(osfd, (struct sockaddr*)addr, (_PRSockLen_t*)addrlen)) ==
         -1) {
    err = _MD_ERRNO();
    if ((err == EAGAIN) || (err == EWOULDBLOCK) || (err == ECONNABORTED)) {
      if (fd->secret->nonblocking) {
        break;
      }
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_READ, timeout)) < 0) {
          goto done;
        }
      } else {
        if ((rv = socket_io_wait(osfd, READ_FD, timeout)) < 0) {
          goto done;
        }
      }
    } else if ((err == EINTR) && (!_PR_PENDING_INTERRUPT(me))) {
      continue;
    } else {
      break;
    }
  }
  if (rv < 0) {
    _PR_MD_MAP_ACCEPT_ERROR(err);
  }
done:
#  ifdef _PR_HAVE_SOCKADDR_LEN
  if (rv != -1) {
    /* ignore the sa_len field of struct sockaddr */
    if (addr) {
      addr->raw.family = ((struct sockaddr*)addr)->sa_family;
    }
  }
#  endif /* _PR_HAVE_SOCKADDR_LEN */
  return (rv);
}

extern int _connect(int s, const struct sockaddr* name, int namelen);
PRInt32 _MD_connect(PRFileDesc* fd, const PRNetAddr* addr, PRUint32 addrlen,
                    PRIntervalTime timeout) {
  PRInt32 rv, err;
  PRThread* me = _PR_MD_CURRENT_THREAD();
  PRInt32 osfd = fd->secret->md.osfd;
#  ifdef _PR_HAVE_SOCKADDR_LEN
  PRNetAddr addrCopy;

  addrCopy = *addr;
  ((struct sockaddr*)&addrCopy)->sa_len = addrlen;
  ((struct sockaddr*)&addrCopy)->sa_family = addr->raw.family;
#  endif

  /*
   * We initiate the connection setup by making a nonblocking connect()
   * call.  If the connect() call fails, there are two cases we handle
   * specially:
   * 1. The connect() call was interrupted by a signal.  In this case
   *    we simply retry connect().
   * 2. The NSPR socket is nonblocking and connect() fails with
   *    EINPROGRESS.  We first wait until the socket becomes writable.
   *    Then we try to find out whether the connection setup succeeded
   *    or failed.
   */

retry:
#  ifdef _PR_HAVE_SOCKADDR_LEN
  if ((rv = connect(osfd, (struct sockaddr*)&addrCopy, addrlen)) == -1) {
#  else
  if ((rv = connect(osfd, (struct sockaddr*)addr, addrlen)) == -1) {
#  endif
    err = _MD_ERRNO();

    if (err == EINTR) {
      if (_PR_PENDING_INTERRUPT(me)) {
        me->flags &= ~_PR_INTERRUPT;
        PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
        return -1;
      }
      goto retry;
    }

    if (!fd->secret->nonblocking && (err == EINPROGRESS)) {
      if (!_PR_IS_NATIVE_THREAD(me)) {
        if ((rv = local_io_wait(osfd, _PR_UNIX_POLL_WRITE, timeout)) < 0) {
          return -1;
        }
      } else {
        /*
         * socket_io_wait() may return -1 or 1.
         */

        rv = socket_io_wait(osfd, WRITE_FD, timeout);
        if (rv == -1) {
          return -1;
        }
      }

      PR_ASSERT(rv == 1);
      if (_PR_PENDING_INTERRUPT(me)) {
        me->flags &= ~_PR_INTERRUPT;
        PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
        return -1;
      }
      err = _MD_unix_get_nonblocking_connect_error(osfd);
      if (err != 0) {
        _PR_MD_MAP_CONNECT_ERROR(err);
        return -1;
      }
      return 0;
    }

    _PR_MD_MAP_CONNECT_ERROR(err);
  }

  return rv;
} /* _MD_connect */

PRInt32 _MD_bind(PRFileDesc* fd, const PRNetAddr* addr, PRUint32 addrlen) {
  PRInt32 rv, err;
#  ifdef _PR_HAVE_SOCKADDR_LEN
  PRNetAddr addrCopy;

  addrCopy = *addr;
  ((struct sockaddr*)&addrCopy)->sa_len = addrlen;
  ((struct sockaddr*)&addrCopy)->sa_family = addr->raw.family;
  rv = bind(fd->secret->md.osfd, (struct sockaddr*)&addrCopy, (int)addrlen);
#  else
  rv = bind(fd->secret->md.osfd, (struct sockaddr*)addr, (int)addrlen);
#  endif
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_BIND_ERROR(err);
  }
  return (rv);
}

PRInt32 _MD_listen(PRFileDesc* fd, PRIntn backlog) {
  PRInt32 rv, err;

  rv = listen(fd->secret->md.osfd, backlog);
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_LISTEN_ERROR(err);
  }
  return (rv);
}

PRInt32 _MD_shutdown(PRFileDesc* fd, PRIntn how) {
  PRInt32 rv, err;

  rv = shutdown(fd->secret->md.osfd, how);
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_SHUTDOWN_ERROR(err);
  }
  return (rv);
}

PRInt32 _MD_socketpair(int af, int type, int flags, PRInt32* osfd) {
  PRInt32 rv, err;

  rv = socketpair(af, type, flags, osfd);
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_SOCKETPAIR_ERROR(err);
  }
  return rv;
}

PRStatus _MD_getsockname(PRFileDesc* fd, PRNetAddr* addr, PRUint32* addrlen) {
  PRInt32 rv, err;

  rv = getsockname(fd->secret->md.osfd, (struct sockaddr*)addr,
                   (_PRSockLen_t*)addrlen);
#  ifdef _PR_HAVE_SOCKADDR_LEN
  if (rv == 0) {
    /* ignore the sa_len field of struct sockaddr */
    if (addr) {
      addr->raw.family = ((struct sockaddr*)addr)->sa_family;
    }
  }
#  endif /* _PR_HAVE_SOCKADDR_LEN */
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_GETSOCKNAME_ERROR(err);
  }
  return rv == 0 ? PR_SUCCESS : PR_FAILURE;
}

PRStatus _MD_getpeername(PRFileDesc* fd, PRNetAddr* addr, PRUint32* addrlen) {
  PRInt32 rv, err;

  rv = getpeername(fd->secret->md.osfd, (struct sockaddr*)addr,
                   (_PRSockLen_t*)addrlen);
#  ifdef _PR_HAVE_SOCKADDR_LEN
  if (rv == 0) {
    /* ignore the sa_len field of struct sockaddr */
    if (addr) {
      addr->raw.family = ((struct sockaddr*)addr)->sa_family;
    }
  }
#  endif /* _PR_HAVE_SOCKADDR_LEN */
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_GETPEERNAME_ERROR(err);
  }
  return rv == 0 ? PR_SUCCESS : PR_FAILURE;
}

PRStatus _MD_getsockopt(PRFileDesc* fd, PRInt32 level, PRInt32 optname,
                        char* optval, PRInt32* optlen) {
  PRInt32 rv, err;

  rv = getsockopt(fd->secret->md.osfd, level, optname, optval,
                  (_PRSockLen_t*)optlen);
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_GETSOCKOPT_ERROR(err);
  }
  return rv == 0 ? PR_SUCCESS : PR_FAILURE;
}

PRStatus _MD_setsockopt(PRFileDesc* fd, PRInt32 level, PRInt32 optname,
                        const char* optval, PRInt32 optlen) {
  PRInt32 rv, err;

  rv = setsockopt(fd->secret->md.osfd, level, optname, optval, optlen);
  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_SETSOCKOPT_ERROR(err);
  }
  return rv == 0 ? PR_SUCCESS : PR_FAILURE;
}

PRStatus _MD_set_fd_inheritable(PRFileDesc* fd, PRBool inheritable) {
  int rv;

  rv = fcntl(fd->secret->md.osfd, F_SETFD, inheritable ? 0 : FD_CLOEXEC);
  if (-1 == rv) {
    PR_SetError(PR_UNKNOWN_ERROR, _MD_ERRNO());
    return PR_FAILURE;
  }
  return PR_SUCCESS;
}

void _MD_init_fd_inheritable(PRFileDesc* fd, PRBool imported) {
  if (imported) {
    fd->secret->inheritable = _PR_TRI_UNKNOWN;
  } else {
    /* By default, a Unix fd is not closed on exec. */
#  ifdef DEBUG
    {
      int flags = fcntl(fd->secret->md.osfd, F_GETFD, 0);
      PR_ASSERT(0 == flags);
    }
#  endif
    fd->secret->inheritable = _PR_TRI_TRUE;
  }
}

/************************************************************************/
#  if !defined(_PR_USE_POLL)

/*
** Scan through io queue and find any bad fd's that triggered the error
** from _MD_SELECT
*/
static void FindBadFDs(void) {
  PRCList* q;
  PRThread* me = _MD_CURRENT_THREAD();

  PR_ASSERT(!_PR_IS_NATIVE_THREAD(me));
  q = (_PR_IOQ(me->cpu)).next;
  _PR_IOQ_MAX_OSFD(me->cpu) = -1;
  _PR_IOQ_TIMEOUT(me->cpu) = PR_INTERVAL_NO_TIMEOUT;
  while (q != &_PR_IOQ(me->cpu)) {
    PRPollQueue* pq = _PR_POLLQUEUE_PTR(q);
    PRBool notify = PR_FALSE;
    _PRUnixPollDesc* pds = pq->pds;
    _PRUnixPollDesc* epds = pds + pq->npds;
    PRInt32 pq_max_osfd = -1;

    q = q->next;
    for (; pds < epds; pds++) {
      PRInt32 osfd = pds->osfd;
      pds->out_flags = 0;
      PR_ASSERT(osfd >= 0 || pds->in_flags == 0);
      if (pds->in_flags == 0) {
        continue; /* skip this fd */
      }
      if (fcntl(osfd, F_GETFL, 0) == -1) {
        /* Found a bad descriptor, remove it from the fd_sets. */
        PR_LOG(_pr_io_lm, PR_LOG_MAX, ("file descriptor %d is bad", osfd));
        pds->out_flags = _PR_UNIX_POLL_NVAL;
        notify = PR_TRUE;
      }
      if (osfd > pq_max_osfd) {
        pq_max_osfd = osfd;
      }
    }

    if (notify) {
      PRIntn pri;
      PR_REMOVE_LINK(&pq->links);
      pq->on_ioq = PR_FALSE;

      /*
       * Decrement the count of descriptors for each desciptor/event
       * because this I/O request is being removed from the
       * ioq
       */
      pds = pq->pds;
      for (; pds < epds; pds++) {
        PRInt32 osfd = pds->osfd;
        PRInt16 in_flags = pds->in_flags;
        PR_ASSERT(osfd >= 0 || in_flags == 0);
        if (in_flags & _PR_UNIX_POLL_READ) {
          if (--(_PR_FD_READ_CNT(me->cpu))[osfd] == 0) {
            FD_CLR(osfd, &_PR_FD_READ_SET(me->cpu));
          }
        }
        if (in_flags & _PR_UNIX_POLL_WRITE) {
          if (--(_PR_FD_WRITE_CNT(me->cpu))[osfd] == 0) {
            FD_CLR(osfd, &_PR_FD_WRITE_SET(me->cpu));
          }
        }
        if (in_flags & _PR_UNIX_POLL_EXCEPT) {
          if (--(_PR_FD_EXCEPTION_CNT(me->cpu))[osfd] == 0) {
            FD_CLR(osfd, &_PR_FD_EXCEPTION_SET(me->cpu));
          }
        }
      }

      _PR_THREAD_LOCK(pq->thr);
      if (pq->thr->flags & (_PR_ON_PAUSEQ | _PR_ON_SLEEPQ)) {
        _PRCPU* cpu = pq->thr->cpu;
        _PR_SLEEPQ_LOCK(pq->thr->cpu);
        _PR_DEL_SLEEPQ(pq->thr, PR_TRUE);
        _PR_SLEEPQ_UNLOCK(pq->thr->cpu);

        if (pq->thr->flags & _PR_SUSPENDING) {
          /*
           * set thread state to SUSPENDED;
           * a Resume operation on the thread
           * will move it to the runQ
           */
          pq->thr->state = _PR_SUSPENDED;
          _PR_MISCQ_LOCK(pq->thr->cpu);
          _PR_ADD_SUSPENDQ(pq->thr, pq->thr->cpu);
          _PR_MISCQ_UNLOCK(pq->thr->cpu);
        } else {
          pri = pq->thr->priority;
          pq->thr->state = _PR_RUNNABLE;

          _PR_RUNQ_LOCK(cpu);
          _PR_ADD_RUNQ(pq->thr, cpu, pri);
          _PR_RUNQ_UNLOCK(cpu);
        }
      }
      _PR_THREAD_UNLOCK(pq->thr);
    } else {
      if (pq->timeout < _PR_IOQ_TIMEOUT(me->cpu)) {
        _PR_IOQ_TIMEOUT(me->cpu) = pq->timeout;
      }
      if (_PR_IOQ_MAX_OSFD(me->cpu) < pq_max_osfd) {
        _PR_IOQ_MAX_OSFD(me->cpu) = pq_max_osfd;
      }
    }
  }
  if (_PR_IS_NATIVE_THREAD_SUPPORTED()) {
    if (_PR_IOQ_MAX_OSFD(me->cpu) < _pr_md_pipefd[0]) {
      _PR_IOQ_MAX_OSFD(me->cpu) = _pr_md_pipefd[0];
    }
  }
}
#  endif /* !defined(_PR_USE_POLL) */

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

/*
** Called by the scheduler when there is nothing to do. This means that
** all threads are blocked on some monitor somewhere.
**
** Note: this code doesn't release the scheduler lock.
*/
/*
** Pause the current CPU. longjmp to the cpu's pause stack
**
** This must be called with the scheduler locked
*/
void _MD_PauseCPU(PRIntervalTime ticks) {
  PRThread* me = _MD_CURRENT_THREAD();
#  ifdef _PR_USE_POLL
  int timeout;
  struct pollfd* pollfds;   /* an array of pollfd structures */
  struct pollfd* pollfdPtr; /* a pointer that steps through the array */
  unsigned long npollfds;   /* number of pollfd structures in array */
  unsigned long pollfds_size;
  int nfd; /* to hold the return value of poll() */
#  else
  struct timeval timeout, *tvp;
  fd_set r, w, e;
  fd_set *rp, *wp, *ep;
  PRInt32 max_osfd, nfd;
#  endif /* _PR_USE_POLL */
  PRInt32 rv;
  PRCList* q;
  PRUint32 min_timeout;
  sigset_t oldset;

  PR_ASSERT(_PR_MD_GET_INTSOFF() != 0);

  _PR_MD_IOQ_LOCK();

#  ifdef _PR_USE_POLL
  /* Build up the pollfd structure array to wait on */

  /* Find out how many pollfd structures are needed */
  npollfds = _PR_IOQ_OSFD_CNT(me->cpu);
  PR_ASSERT(npollfds >= 0);

  /*
   * We use a pipe to wake up a native thread.  An fd is needed
   * for the pipe and we poll it for reading.
   */
  if (_PR_IS_NATIVE_THREAD_SUPPORTED()) {
    npollfds++;
  }

  /*
   * if the cpu's pollfd array is not big enough, release it and allocate a new
   * one
   */
  if (npollfds > _PR_IOQ_POLLFDS_SIZE(me->cpu)) {
    if (_PR_IOQ_POLLFDS(me->cpu) != NULL) {
      PR_DELETE(_PR_IOQ_POLLFDS(me->cpu));
    }
    pollfds_size = PR_MAX(_PR_IOQ_MIN_POLLFDS_SIZE(me->cpu), npollfds);
    pollfds = (struct pollfd*)PR_MALLOC(pollfds_size * sizeof(struct pollfd));
    _PR_IOQ_POLLFDS(me->cpu) = pollfds;
    _PR_IOQ_POLLFDS_SIZE(me->cpu) = pollfds_size;
  } else {
    pollfds = _PR_IOQ_POLLFDS(me->cpu);
  }
  pollfdPtr = pollfds;

  /*
   * If we need to poll the pipe for waking up a native thread,
   * the pipe's fd is the first element in the pollfds array.
   */
  if (_PR_IS_NATIVE_THREAD_SUPPORTED()) {
    pollfdPtr->fd = _pr_md_pipefd[0];
    pollfdPtr->events = POLLIN;
    pollfdPtr++;
  }

  min_timeout = PR_INTERVAL_NO_TIMEOUT;
  for (q = _PR_IOQ(me->cpu).next; q != &_PR_IOQ(me->cpu); q = q->next) {
    PRPollQueue* pq = _PR_POLLQUEUE_PTR(q);
    _PRUnixPollDesc* pds = pq->pds;
    _PRUnixPollDesc* epds = pds + pq->npds;

    if (pq->timeout < min_timeout) {
      min_timeout = pq->timeout;
    }
    for (; pds < epds; pds++, pollfdPtr++) {
      /*
       * Assert that the pollfdPtr pointer does not go
       * beyond the end of the pollfds array
       */
      PR_ASSERT(pollfdPtr < pollfds + npollfds);
      pollfdPtr->fd = pds->osfd;
      /* direct copy of poll flags */
      pollfdPtr->events = pds->in_flags;
    }
  }
  _PR_IOQ_TIMEOUT(me->cpu) = min_timeout;
#  else
  /*
   * assigment of fd_sets
   */
  r = _PR_FD_READ_SET(me->cpu);
  w = _PR_FD_WRITE_SET(me->cpu);
  e = _PR_FD_EXCEPTION_SET(me->cpu);

  rp = &r;
  wp = &w;
  ep = &e;

  max_osfd = _PR_IOQ_MAX_OSFD(me->cpu) + 1;
  min_timeout = _PR_IOQ_TIMEOUT(me->cpu);
#  endif /* _PR_USE_POLL */
  /*
  ** Compute the minimum timeout value: make it the smaller of the
  ** timeouts specified by the i/o pollers or the timeout of the first
  ** sleeping thread.
  */
  q = _PR_SLEEPQ(me->cpu).next;

  if (q != &_PR_SLEEPQ(me->cpu)) {
    PRThread* t = _PR_THREAD_PTR(q);

    if (t->sleep < min_timeout) {
      min_timeout = t->sleep;
    }
  }
  if (min_timeout > ticks) {
    min_timeout = ticks;
  }

#  ifdef _PR_USE_POLL
  if (min_timeout == PR_INTERVAL_NO_TIMEOUT) {
    timeout = -1;
  } else {
    timeout = PR_IntervalToMilliseconds(min_timeout);
  }
#  else
  if (min_timeout == PR_INTERVAL_NO_TIMEOUT) {
    tvp = NULL;
  } else {
    timeout.tv_sec = PR_IntervalToSeconds(min_timeout);
    timeout.tv_usec = PR_IntervalToMicroseconds(min_timeout) % PR_USEC_PER_SEC;
    tvp = &timeout;
  }
#  endif /* _PR_USE_POLL */

  _PR_MD_IOQ_UNLOCK();
  _MD_CHECK_FOR_EXIT();
  /*
   * check for i/o operations
   */
#  ifndef _PR_NO_CLOCK_TIMER
  /*
   * Disable the clock interrupts while we are in select, if clock interrupts
   * are enabled. Otherwise, when the select/poll calls are interrupted, the
   * timer value starts ticking from zero again when the system call is
   * restarted.
   */
  if (!_nspr_noclock) {
    PR_ASSERT(sigismember(&timer_set, SIGALRM));
  }
  sigprocmask(SIG_BLOCK, &timer_set, &oldset);
#  endif /* !_PR_NO_CLOCK_TIMER */

#  ifndef _PR_USE_POLL
  PR_ASSERT(FD_ISSET(_pr_md_pipefd[0], rp));
  nfd = _MD_SELECT(max_osfd, rp, wp, ep, tvp);
#  else
  nfd = _MD_POLL(pollfds, npollfds, timeout);
#  endif /* !_PR_USE_POLL */

#  ifndef _PR_NO_CLOCK_TIMER
  if (!_nspr_noclock) {
    sigprocmask(SIG_SETMASK, &oldset, 0);
  }
#  endif /* !_PR_NO_CLOCK_TIMER */

  _MD_CHECK_FOR_EXIT();

  _PR_MD_primordial_cpu();

  _PR_MD_IOQ_LOCK();
  /*
  ** Notify monitors that are associated with the selected descriptors.
  */
#  ifdef _PR_USE_POLL
  if (nfd > 0) {
    pollfdPtr = pollfds;
    if (_PR_IS_NATIVE_THREAD_SUPPORTED()) {
      /*
       * Assert that the pipe is the first element in the
       * pollfds array.
       */
      PR_ASSERT(pollfds[0].fd == _pr_md_pipefd[0]);
      if ((pollfds[0].revents & POLLIN) && (nfd == 1)) {
        /*
         * woken up by another thread; read all the data
         * in the pipe to empty the pipe
         */
        while ((rv = read(_pr_md_pipefd[0], _pr_md_pipebuf, PIPE_BUF)) ==
               PIPE_BUF) {
        }
        PR_ASSERT((rv > 0) || ((rv == -1) && (errno == EAGAIN)));
      }
      pollfdPtr++;
    }
    for (q = _PR_IOQ(me->cpu).next; q != &_PR_IOQ(me->cpu); q = q->next) {
      PRPollQueue* pq = _PR_POLLQUEUE_PTR(q);
      PRBool notify = PR_FALSE;
      _PRUnixPollDesc* pds = pq->pds;
      _PRUnixPollDesc* epds = pds + pq->npds;

      for (; pds < epds; pds++, pollfdPtr++) {
        /*
         * Assert that the pollfdPtr pointer does not go beyond
         * the end of the pollfds array.
         */
        PR_ASSERT(pollfdPtr < pollfds + npollfds);
        /*
         * Assert that the fd's in the pollfds array (stepped
         * through by pollfdPtr) are in the same order as
         * the fd's in _PR_IOQ() (stepped through by q and pds).
         * This is how the pollfds array was created earlier.
         */
        PR_ASSERT(pollfdPtr->fd == pds->osfd);
        pds->out_flags = pollfdPtr->revents;
        /* Negative fd's are ignored by poll() */
        if (pds->osfd >= 0 && pds->out_flags) {
          notify = PR_TRUE;
        }
      }
      if (notify) {
        PRIntn pri;
        PRThread* thred;

        PR_REMOVE_LINK(&pq->links);
        pq->on_ioq = PR_FALSE;

        thred = pq->thr;
        _PR_THREAD_LOCK(thred);
        if (pq->thr->flags & (_PR_ON_PAUSEQ | _PR_ON_SLEEPQ)) {
          _PRCPU* cpu = pq->thr->cpu;
          _PR_SLEEPQ_LOCK(pq->thr->cpu);
          _PR_DEL_SLEEPQ(pq->thr, PR_TRUE);
          _PR_SLEEPQ_UNLOCK(pq->thr->cpu);

          if (pq->thr->flags & _PR_SUSPENDING) {
            /*
             * set thread state to SUSPENDED;
             * a Resume operation on the thread
             * will move it to the runQ
             */
            pq->thr->state = _PR_SUSPENDED;
            _PR_MISCQ_LOCK(pq->thr->cpu);
            _PR_ADD_SUSPENDQ(pq->thr, pq->thr->cpu);
            _PR_MISCQ_UNLOCK(pq->thr->cpu);
          } else {
            pri = pq->thr->priority;
            pq->thr->state = _PR_RUNNABLE;

            _PR_RUNQ_LOCK(cpu);
            _PR_ADD_RUNQ(pq->thr, cpu, pri);
            _PR_RUNQ_UNLOCK(cpu);
            if (_pr_md_idle_cpus > 1) {
              _PR_MD_WAKEUP_WAITER(thred);
            }
          }
        }
        _PR_THREAD_UNLOCK(thred);
        _PR_IOQ_OSFD_CNT(me->cpu) -= pq->npds;
        PR_ASSERT(_PR_IOQ_OSFD_CNT(me->cpu) >= 0);
      }
    }
  } else if (nfd == -1) {
    PR_LOG(_pr_io_lm, PR_LOG_MAX, ("poll() failed with errno %d", errno));
  }

#  else
  if (nfd > 0) {
    q = _PR_IOQ(me->cpu).next;
    _PR_IOQ_MAX_OSFD(me->cpu) = -1;
    _PR_IOQ_TIMEOUT(me->cpu) = PR_INTERVAL_NO_TIMEOUT;
    while (q != &_PR_IOQ(me->cpu)) {
      PRPollQueue* pq = _PR_POLLQUEUE_PTR(q);
      PRBool notify = PR_FALSE;
      _PRUnixPollDesc* pds = pq->pds;
      _PRUnixPollDesc* epds = pds + pq->npds;
      PRInt32 pq_max_osfd = -1;

      q = q->next;
      for (; pds < epds; pds++) {
        PRInt32 osfd = pds->osfd;
        PRInt16 in_flags = pds->in_flags;
        PRInt16 out_flags = 0;
        PR_ASSERT(osfd >= 0 || in_flags == 0);
        if ((in_flags & _PR_UNIX_POLL_READ) && FD_ISSET(osfd, rp)) {
          out_flags |= _PR_UNIX_POLL_READ;
        }
        if ((in_flags & _PR_UNIX_POLL_WRITE) && FD_ISSET(osfd, wp)) {
          out_flags |= _PR_UNIX_POLL_WRITE;
        }
        if ((in_flags & _PR_UNIX_POLL_EXCEPT) && FD_ISSET(osfd, ep)) {
          out_flags |= _PR_UNIX_POLL_EXCEPT;
        }
        pds->out_flags = out_flags;
        if (out_flags) {
          notify = PR_TRUE;
        }
        if (osfd > pq_max_osfd) {
          pq_max_osfd = osfd;
        }
      }
      if (notify == PR_TRUE) {
        PRIntn pri;
        PRThread* thred;

        PR_REMOVE_LINK(&pq->links);
        pq->on_ioq = PR_FALSE;

        /*
         * Decrement the count of descriptors for each desciptor/event
         * because this I/O request is being removed from the
         * ioq
         */
        pds = pq->pds;
        for (; pds < epds; pds++) {
          PRInt32 osfd = pds->osfd;
          PRInt16 in_flags = pds->in_flags;
          PR_ASSERT(osfd >= 0 || in_flags == 0);
          if (in_flags & _PR_UNIX_POLL_READ) {
            if (--(_PR_FD_READ_CNT(me->cpu))[osfd] == 0) {
              FD_CLR(osfd, &_PR_FD_READ_SET(me->cpu));
            }
          }
          if (in_flags & _PR_UNIX_POLL_WRITE) {
            if (--(_PR_FD_WRITE_CNT(me->cpu))[osfd] == 0) {
              FD_CLR(osfd, &_PR_FD_WRITE_SET(me->cpu));
            }
          }
          if (in_flags & _PR_UNIX_POLL_EXCEPT) {
            if (--(_PR_FD_EXCEPTION_CNT(me->cpu))[osfd] == 0) {
              FD_CLR(osfd, &_PR_FD_EXCEPTION_SET(me->cpu));
            }
          }
        }

        /*
         * Because this thread can run on a different cpu right
         * after being added to the run queue, do not dereference
         * pq
         */
        thred = pq->thr;
        _PR_THREAD_LOCK(thred);
        if (pq->thr->flags & (_PR_ON_PAUSEQ | _PR_ON_SLEEPQ)) {
          _PRCPU* cpu = thred->cpu;
          _PR_SLEEPQ_LOCK(pq->thr->cpu);
          _PR_DEL_SLEEPQ(pq->thr, PR_TRUE);
          _PR_SLEEPQ_UNLOCK(pq->thr->cpu);

          if (pq->thr->flags & _PR_SUSPENDING) {
            /*
             * set thread state to SUSPENDED;
             * a Resume operation on the thread
             * will move it to the runQ
             */
            pq->thr->state = _PR_SUSPENDED;
            _PR_MISCQ_LOCK(pq->thr->cpu);
            _PR_ADD_SUSPENDQ(pq->thr, pq->thr->cpu);
            _PR_MISCQ_UNLOCK(pq->thr->cpu);
          } else {
            pri = pq->thr->priority;
            pq->thr->state = _PR_RUNNABLE;

            pq->thr->cpu = cpu;
            _PR_RUNQ_LOCK(cpu);
            _PR_ADD_RUNQ(pq->thr, cpu, pri);
            _PR_RUNQ_UNLOCK(cpu);
            if (_pr_md_idle_cpus > 1) {
              _PR_MD_WAKEUP_WAITER(thred);
            }
          }
        }
        _PR_THREAD_UNLOCK(thred);
      } else {
        if (pq->timeout < _PR_IOQ_TIMEOUT(me->cpu)) {
          _PR_IOQ_TIMEOUT(me->cpu) = pq->timeout;
        }
        if (_PR_IOQ_MAX_OSFD(me->cpu) < pq_max_osfd) {
          _PR_IOQ_MAX_OSFD(me->cpu) = pq_max_osfd;
        }
      }
    }
    if (_PR_IS_NATIVE_THREAD_SUPPORTED()) {
      if ((FD_ISSET(_pr_md_pipefd[0], rp)) && (nfd == 1)) {
        /*
         * woken up by another thread; read all the data
         * in the pipe to empty the pipe
         */
        while ((rv = read(_pr_md_pipefd[0], _pr_md_pipebuf, PIPE_BUF)) ==
               PIPE_BUF) {
        }
        PR_ASSERT((rv > 0) || ((rv == -1) && (errno == EAGAIN)));
      }
      if (_PR_IOQ_MAX_OSFD(me->cpu) < _pr_md_pipefd[0]) {
        _PR_IOQ_MAX_OSFD(me->cpu) = _pr_md_pipefd[0];
      }
    }
  } else if (nfd < 0) {
    if (errno == EBADF) {
      FindBadFDs();
    } else {
      PR_LOG(_pr_io_lm, PR_LOG_MAX, ("select() failed with errno %d", errno));
    }
  } else {
    PR_ASSERT(nfd == 0);
    /*
     * compute the new value of _PR_IOQ_TIMEOUT
     */
    q = _PR_IOQ(me->cpu).next;
    _PR_IOQ_MAX_OSFD(me->cpu) = -1;
    _PR_IOQ_TIMEOUT(me->cpu) = PR_INTERVAL_NO_TIMEOUT;
    while (q != &_PR_IOQ(me->cpu)) {
      PRPollQueue* pq = _PR_POLLQUEUE_PTR(q);
      _PRUnixPollDesc* pds = pq->pds;
      _PRUnixPollDesc* epds = pds + pq->npds;
      PRInt32 pq_max_osfd = -1;

      q = q->next;
      for (; pds < epds; pds++) {
        if (pds->osfd > pq_max_osfd) {
          pq_max_osfd = pds->osfd;
        }
      }
      if (pq->timeout < _PR_IOQ_TIMEOUT(me->cpu)) {
        _PR_IOQ_TIMEOUT(me->cpu) = pq->timeout;
      }
      if (_PR_IOQ_MAX_OSFD(me->cpu) < pq_max_osfd) {
        _PR_IOQ_MAX_OSFD(me->cpu) = pq_max_osfd;
      }
    }
    if (_PR_IS_NATIVE_THREAD_SUPPORTED()) {
      if (_PR_IOQ_MAX_OSFD(me->cpu) < _pr_md_pipefd[0]) {
        _PR_IOQ_MAX_OSFD(me->cpu) = _pr_md_pipefd[0];
      }
    }
  }
#  endif /* _PR_USE_POLL */
  _PR_MD_IOQ_UNLOCK();
}

void _MD_Wakeup_CPUs() {
  PRInt32 rv, data;

  data = 0;
  rv = write(_pr_md_pipefd[1], &data, 1);

  while ((rv < 0) && (errno == EAGAIN)) {
    /*
     * pipe full, read all data in pipe to empty it
     */
    while ((rv = read(_pr_md_pipefd[0], _pr_md_pipebuf, PIPE_BUF)) ==
           PIPE_BUF) {
    }
    PR_ASSERT((rv > 0) || ((rv == -1) && (errno == EAGAIN)));
    rv = write(_pr_md_pipefd[1], &data, 1);
  }
}

void _MD_InitCPUS() {
  PRInt32 rv, flags;
  PRThread* me = _MD_CURRENT_THREAD();

  rv = pipe(_pr_md_pipefd);
  PR_ASSERT(rv == 0);
  _PR_IOQ_MAX_OSFD(me->cpu) = _pr_md_pipefd[0];
#  ifndef _PR_USE_POLL
  FD_SET(_pr_md_pipefd[0], &_PR_FD_READ_SET(me->cpu));
#  endif

  flags = fcntl(_pr_md_pipefd[0], F_GETFL, 0);
  fcntl(_pr_md_pipefd[0], F_SETFL, flags | O_NONBLOCK);
  flags = fcntl(_pr_md_pipefd[1], F_GETFL, 0);
  fcntl(_pr_md_pipefd[1], F_SETFL, flags | O_NONBLOCK);
}

/*
** Unix SIGALRM (clock) signal handler
*/
static void ClockInterruptHandler() {
  int olderrno;
  PRUintn pri;
  _PRCPU* cpu = _PR_MD_CURRENT_CPU();
  PRThread* me = _MD_CURRENT_THREAD();

#  ifdef SOLARIS
  if (!me || _PR_IS_NATIVE_THREAD(me)) {
    _pr_primordialCPU->u.missed[_pr_primordialCPU->where] |= _PR_MISSED_CLOCK;
    return;
  }
#  endif

  if (_PR_MD_GET_INTSOFF() != 0) {
    cpu->u.missed[cpu->where] |= _PR_MISSED_CLOCK;
    return;
  }
  _PR_MD_SET_INTSOFF(1);

  olderrno = errno;
  _PR_ClockInterrupt();
  errno = olderrno;

  /*
  ** If the interrupt wants a resched or if some other thread at
  ** the same priority needs the cpu, reschedule.
  */
  pri = me->priority;
  if ((cpu->u.missed[3] || (_PR_RUNQREADYMASK(me->cpu) >> pri))) {
#  ifdef _PR_NO_PREEMPT
    cpu->resched = PR_TRUE;
    if (pr_interruptSwitchHook) {
      (*pr_interruptSwitchHook)(pr_interruptSwitchHookArg);
    }
#  else  /* _PR_NO_PREEMPT */
    /*
    ** Re-enable unix interrupts (so that we can use
    ** setjmp/longjmp for context switching without having to
    ** worry about the signal state)
    */
    sigprocmask(SIG_SETMASK, &empty_set, 0);
    PR_LOG(_pr_sched_lm, PR_LOG_MIN, ("clock caused context switch"));

    if (!(me->flags & _PR_IDLE_THREAD)) {
      _PR_THREAD_LOCK(me);
      me->state = _PR_RUNNABLE;
      me->cpu = cpu;
      _PR_RUNQ_LOCK(cpu);
      _PR_ADD_RUNQ(me, cpu, pri);
      _PR_RUNQ_UNLOCK(cpu);
      _PR_THREAD_UNLOCK(me);
    } else {
      me->state = _PR_RUNNABLE;
    }
    _MD_SWITCH_CONTEXT(me);
    PR_LOG(_pr_sched_lm, PR_LOG_MIN, ("clock back from context switch"));
#  endif /* _PR_NO_PREEMPT */
  }
  /*
   * Because this thread could be running on a different cpu after
   * a context switch the current cpu should be accessed and the
   * value of the 'cpu' variable should not be used.
   */
  _PR_MD_SET_INTSOFF(0);
}

/* # of milliseconds per clock tick that we will use */
#  define MSEC_PER_TICK 50

void _MD_StartInterrupts() {
  char* eval;

  if ((eval = getenv("NSPR_NOCLOCK")) != NULL) {
    if (atoi(eval) == 0) {
      _nspr_noclock = 0;
    } else {
      _nspr_noclock = 1;
    }
  }

#  ifndef _PR_NO_CLOCK_TIMER
  if (!_nspr_noclock) {
    _MD_EnableClockInterrupts();
  }
#  endif
}

void _MD_StopInterrupts() { sigprocmask(SIG_BLOCK, &timer_set, 0); }

void _MD_EnableClockInterrupts() {
  struct itimerval itval;
  extern PRUintn _pr_numCPU;
  struct sigaction vtact;

  vtact.sa_handler = (void (*)())ClockInterruptHandler;
  sigemptyset(&vtact.sa_mask);
  vtact.sa_flags = SA_RESTART;
  sigaction(SIGALRM, &vtact, 0);

  PR_ASSERT(_pr_numCPU == 1);
  itval.it_interval.tv_sec = 0;
  itval.it_interval.tv_usec = MSEC_PER_TICK * PR_USEC_PER_MSEC;
  itval.it_value = itval.it_interval;
  setitimer(ITIMER_REAL, &itval, 0);
}

void _MD_DisableClockInterrupts() {
  struct itimerval itval;
  extern PRUintn _pr_numCPU;

  PR_ASSERT(_pr_numCPU == 1);
  itval.it_interval.tv_sec = 0;
  itval.it_interval.tv_usec = 0;
  itval.it_value = itval.it_interval;
  setitimer(ITIMER_REAL, &itval, 0);
}

void _MD_BlockClockInterrupts() { sigprocmask(SIG_BLOCK, &timer_set, 0); }

void _MD_UnblockClockInterrupts() { sigprocmask(SIG_UNBLOCK, &timer_set, 0); }

void _MD_MakeNonblock(PRFileDesc* fd) {
  PRInt32 osfd = fd->secret->md.osfd;
  int flags;

  if (osfd <= 2) {
    /* Don't mess around with stdin, stdout or stderr */
    return;
  }
  flags = fcntl(osfd, F_GETFL, 0);

  /*
   * Use O_NONBLOCK (POSIX-style non-blocking I/O) whenever possible.
   * On SunOS 4, we must use FNDELAY (BSD-style non-blocking I/O),
   * otherwise connect() still blocks and can be interrupted by SIGALRM.
   */

  fcntl(osfd, F_SETFL, flags | O_NONBLOCK);
}

PRInt32 _MD_open(const char* name, PRIntn flags, PRIntn mode) {
  PRInt32 osflags;
  PRInt32 rv, err;

  if (flags & PR_RDWR) {
    osflags = O_RDWR;
  } else if (flags & PR_WRONLY) {
    osflags = O_WRONLY;
  } else {
    osflags = O_RDONLY;
  }

  if (flags & PR_EXCL) {
    osflags |= O_EXCL;
  }
  if (flags & PR_APPEND) {
    osflags |= O_APPEND;
  }
  if (flags & PR_TRUNCATE) {
    osflags |= O_TRUNC;
  }
  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
  }

  /*
  ** On creations we hold the 'create' lock in order to enforce
  ** the semantics of PR_Rename. (see the latter for more details)
  */
  if (flags & PR_CREATE_FILE) {
    osflags |= O_CREAT;
    if (NULL != _pr_unix_rename_lock) {
      PR_Lock(_pr_unix_rename_lock);
    }
  }

#  if defined(ANDROID)
  osflags |= O_LARGEFILE;
#  endif

  rv = _md_iovector._open64(name, osflags, mode);

  if (rv < 0) {
    err = _MD_ERRNO();
    _PR_MD_MAP_OPEN_ERROR(err);
  }

  if ((flags & PR_CREATE_FILE) && (NULL != _pr_unix_rename_lock)) {
    PR_Unlock(_pr_unix_rename_lock);
  }
  return rv;
}

PRIntervalTime intr_timeout_ticks;

#  if defined(SOLARIS)
static void sigsegvhandler() {
  fprintf(stderr, "Received SIGSEGV\n");
  fflush(stderr);
  pause();
}

static void sigaborthandler() {
  fprintf(stderr, "Received SIGABRT\n");
  fflush(stderr);
  pause();
}

static void sigbushandler() {
  fprintf(stderr, "Received SIGBUS\n");
  fflush(stderr);
  pause();
}
#  endif /* SOLARIS */

#endif /* !defined(_PR_PTHREADS) */

void _MD_query_fd_inheritable(PRFileDesc* fd) {
  int flags;

  PR_ASSERT(_PR_TRI_UNKNOWN == fd->secret->inheritable);
  flags = fcntl(fd->secret->md.osfd, F_GETFD, 0);
  PR_ASSERT(-1 != flags);
  fd->secret->inheritable = (flags & FD_CLOEXEC) ? _PR_TRI_FALSE : _PR_TRI_TRUE;
}

PROffset32 _MD_lseek(PRFileDesc* fd, PROffset32 offset, PRSeekWhence whence) {
  PROffset32 rv, where;

  switch (whence) {
    case PR_SEEK_SET:
      where = SEEK_SET;
      break;
    case PR_SEEK_CUR:
      where = SEEK_CUR;
      break;
    case PR_SEEK_END:
      where = SEEK_END;
      break;
    default:
      PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
      rv = -1;
      goto done;
  }
  rv = lseek(fd->secret->md.osfd, offset, where);
  if (rv == -1) {
    PRInt32 syserr = _MD_ERRNO();
    _PR_MD_MAP_LSEEK_ERROR(syserr);
  }
done:
  return (rv);
}

PROffset64 _MD_lseek64(PRFileDesc* fd, PROffset64 offset, PRSeekWhence whence) {
  PRInt32 where;
  PROffset64 rv;

  switch (whence) {
    case PR_SEEK_SET:
      where = SEEK_SET;
      break;
    case PR_SEEK_CUR:
      where = SEEK_CUR;
      break;
    case PR_SEEK_END:
      where = SEEK_END;
      break;
    default:
      PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
      rv = minus_one;
      goto done;
  }
  rv = _md_iovector._lseek64(fd->secret->md.osfd, offset, where);
  if (LL_EQ(rv, minus_one)) {
    PRInt32 syserr = _MD_ERRNO();
    _PR_MD_MAP_LSEEK_ERROR(syserr);
  }
done:
  return rv;
} /* _MD_lseek64 */

/*
** _MD_set_fileinfo_times --
**     Set the modifyTime and creationTime of the PRFileInfo
**     structure using the values in struct stat.
**
** _MD_set_fileinfo64_times --
**     Set the modifyTime and creationTime of the PRFileInfo64
**     structure using the values in _MDStat64.
*/

#if defined(_PR_STAT_HAS_ST_ATIM)
/*
** struct stat has st_atim, st_mtim, and st_ctim fields of
** type timestruc_t.
*/
static void _MD_set_fileinfo_times(const struct stat* sb, PRFileInfo* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtim.tv_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtim.tv_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctim.tv_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctim.tv_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}

static void _MD_set_fileinfo64_times(const _MDStat64* sb, PRFileInfo64* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtim.tv_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtim.tv_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctim.tv_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctim.tv_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}
#elif defined(_PR_STAT_HAS_ST_ATIM_UNION)
/*
** The st_atim, st_mtim, and st_ctim fields in struct stat are
** unions with a st__tim union member of type timestruc_t.
*/
static void _MD_set_fileinfo_times(const struct stat* sb, PRFileInfo* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtim.st__tim.tv_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtim.st__tim.tv_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctim.st__tim.tv_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctim.st__tim.tv_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}

static void _MD_set_fileinfo64_times(const _MDStat64* sb, PRFileInfo64* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtim.st__tim.tv_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtim.st__tim.tv_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctim.st__tim.tv_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctim.st__tim.tv_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}
#elif defined(_PR_STAT_HAS_ST_ATIMESPEC)
/*
** struct stat has st_atimespec, st_mtimespec, and st_ctimespec
** fields of type struct timespec.
*/
#  if defined(_PR_TIMESPEC_HAS_TS_SEC)
static void _MD_set_fileinfo_times(const struct stat* sb, PRFileInfo* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtimespec.ts_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtimespec.ts_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctimespec.ts_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctimespec.ts_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}

static void _MD_set_fileinfo64_times(const _MDStat64* sb, PRFileInfo64* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtimespec.ts_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtimespec.ts_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctimespec.ts_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctimespec.ts_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}
#  else  /* _PR_TIMESPEC_HAS_TS_SEC */
/*
** The POSIX timespec structure has tv_sec and tv_nsec.
*/
static void _MD_set_fileinfo_times(const struct stat* sb, PRFileInfo* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtimespec.tv_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtimespec.tv_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctimespec.tv_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctimespec.tv_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}

static void _MD_set_fileinfo64_times(const _MDStat64* sb, PRFileInfo64* info) {
  PRInt64 us, s2us;

  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(info->modifyTime, sb->st_mtimespec.tv_sec);
  LL_MUL(info->modifyTime, info->modifyTime, s2us);
  LL_I2L(us, sb->st_mtimespec.tv_nsec / 1000);
  LL_ADD(info->modifyTime, info->modifyTime, us);
  LL_I2L(info->creationTime, sb->st_ctimespec.tv_sec);
  LL_MUL(info->creationTime, info->creationTime, s2us);
  LL_I2L(us, sb->st_ctimespec.tv_nsec / 1000);
  LL_ADD(info->creationTime, info->creationTime, us);
}
#  endif /* _PR_TIMESPEC_HAS_TS_SEC */
#elif defined(_PR_STAT_HAS_ONLY_ST_ATIME)
/*
** struct stat only has st_atime, st_mtime, and st_ctime fields
** of type time_t.
*/
static void _MD_set_fileinfo_times(const struct stat* sb, PRFileInfo* info) {
  PRInt64 s, s2us;
  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(s, sb->st_mtime);
  LL_MUL(s, s, s2us);
  info->modifyTime = s;
  LL_I2L(s, sb->st_ctime);
  LL_MUL(s, s, s2us);
  info->creationTime = s;
}

static void _MD_set_fileinfo64_times(const _MDStat64* sb, PRFileInfo64* info) {
  PRInt64 s, s2us;
  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(s, sb->st_mtime);
  LL_MUL(s, s, s2us);
  info->modifyTime = s;
  LL_I2L(s, sb->st_ctime);
  LL_MUL(s, s, s2us);
  info->creationTime = s;
}
#else
#  error "I don't know yet"
#endif

static int _MD_convert_stat_to_fileinfo(const struct stat* sb,
                                        PRFileInfo* info) {
  if (S_IFREG & sb->st_mode) {
    info->type = PR_FILE_FILE;
  } else if (S_IFDIR & sb->st_mode) {
    info->type = PR_FILE_DIRECTORY;
  } else {
    info->type = PR_FILE_OTHER;
  }

#if defined(_PR_HAVE_LARGE_OFF_T)
  if (0x7fffffffL < sb->st_size) {
    PR_SetError(PR_FILE_TOO_BIG_ERROR, 0);
    return -1;
  }
#endif /* defined(_PR_HAVE_LARGE_OFF_T) */
  info->size = sb->st_size;

  _MD_set_fileinfo_times(sb, info);
  return 0;
} /* _MD_convert_stat_to_fileinfo */

static int _MD_convert_stat64_to_fileinfo64(const _MDStat64* sb,
                                            PRFileInfo64* info) {
  if (S_IFREG & sb->st_mode) {
    info->type = PR_FILE_FILE;
  } else if (S_IFDIR & sb->st_mode) {
    info->type = PR_FILE_DIRECTORY;
  } else {
    info->type = PR_FILE_OTHER;
  }

  LL_I2L(info->size, sb->st_size);

  _MD_set_fileinfo64_times(sb, info);
  return 0;
} /* _MD_convert_stat64_to_fileinfo64 */

PRInt32 _MD_getfileinfo(const char* fn, PRFileInfo* info) {
  PRInt32 rv;
  struct stat sb;

  rv = stat(fn, &sb);
  if (rv < 0) {
    _PR_MD_MAP_STAT_ERROR(_MD_ERRNO());
  } else if (NULL != info) {
    rv = _MD_convert_stat_to_fileinfo(&sb, info);
  }
  return rv;
}

PRInt32 _MD_getfileinfo64(const char* fn, PRFileInfo64* info) {
  _MDStat64 sb;
  PRInt32 rv = _md_iovector._stat64(fn, &sb);
  if (rv < 0) {
    _PR_MD_MAP_STAT_ERROR(_MD_ERRNO());
  } else if (NULL != info) {
    rv = _MD_convert_stat64_to_fileinfo64(&sb, info);
  }
  return rv;
}

PRInt32 _MD_getopenfileinfo(const PRFileDesc* fd, PRFileInfo* info) {
  struct stat sb;
  PRInt32 rv = fstat(fd->secret->md.osfd, &sb);
  if (rv < 0) {
    _PR_MD_MAP_FSTAT_ERROR(_MD_ERRNO());
  } else if (NULL != info) {
    rv = _MD_convert_stat_to_fileinfo(&sb, info);
  }
  return rv;
}

PRInt32 _MD_getopenfileinfo64(const PRFileDesc* fd, PRFileInfo64* info) {
  _MDStat64 sb;
  PRInt32 rv = _md_iovector._fstat64(fd->secret->md.osfd, &sb);
  if (rv < 0) {
    _PR_MD_MAP_FSTAT_ERROR(_MD_ERRNO());
  } else if (NULL != info) {
    rv = _MD_convert_stat64_to_fileinfo64(&sb, info);
  }
  return rv;
}

/*
 * _md_iovector._open64 must be initialized to 'open' so that _PR_InitLog can
 * open the log file during NSPR initialization, before _md_iovector is
 * initialized by _PR_MD_FINAL_INIT.  This means the log file cannot be a
 * large file on some platforms.
 */
struct _MD_IOVector _md_iovector = {open};

/*
** These implementations are to emulate large file routines on systems that
** don't have them. Their goal is to check in case overflow occurs. Otherwise
** they will just operate as normal using 32-bit file routines.
**
** The checking might be pre- or post-op, depending on the semantics.
*/

#if defined(SOLARIS2_5)

static PRIntn _MD_solaris25_fstat64(PRIntn osfd, _MDStat64* buf) {
  PRInt32 rv;
  struct stat sb;

  rv = fstat(osfd, &sb);
  if (rv >= 0) {
    /*
    ** I'm only copying the fields that are immediately needed.
    ** If somebody else calls this function, some of the fields
    ** may not be defined.
    */
    (void)memset(buf, 0, sizeof(_MDStat64));
    buf->st_mode = sb.st_mode;
    buf->st_ctim = sb.st_ctim;
    buf->st_mtim = sb.st_mtim;
    buf->st_size = sb.st_size;
  }
  return rv;
} /* _MD_solaris25_fstat64 */

static PRIntn _MD_solaris25_stat64(const char* fn, _MDStat64* buf) {
  PRInt32 rv;
  struct stat sb;

  rv = stat(fn, &sb);
  if (rv >= 0) {
    /*
    ** I'm only copying the fields that are immediately needed.
    ** If somebody else calls this function, some of the fields
    ** may not be defined.
    */
    (void)memset(buf, 0, sizeof(_MDStat64));
    buf->st_mode = sb.st_mode;
    buf->st_ctim = sb.st_ctim;
    buf->st_mtim = sb.st_mtim;
    buf->st_size = sb.st_size;
  }
  return rv;
} /* _MD_solaris25_stat64 */
#endif /* defined(SOLARIS2_5) */

#if defined(_PR_NO_LARGE_FILES) || defined(SOLARIS2_5)

static PROffset64 _MD_Unix_lseek64(PRIntn osfd, PROffset64 offset,
                                   PRIntn whence) {
  PRUint64 maxoff;
  PROffset64 rv = minus_one;
  LL_I2L(maxoff, 0x7fffffff);
  if (LL_CMP(offset, <=, maxoff)) {
    off_t off;
    LL_L2I(off, offset);
    LL_I2L(rv, lseek(osfd, off, whence));
  } else {
    errno = EFBIG; /* we can't go there */
  }
  return rv;
} /* _MD_Unix_lseek64 */

static void* _MD_Unix_mmap64(void* addr, PRSize len, PRIntn prot, PRIntn flags,
                             PRIntn fildes, PRInt64 offset) {
  PR_SetError(PR_FILE_TOO_BIG_ERROR, 0);
  return NULL;
} /* _MD_Unix_mmap64 */
#endif /* defined(_PR_NO_LARGE_FILES) || defined(SOLARIS2_5) */

/* NDK non-unified headers for API < 21 don't have mmap64. However,
 * NDK unified headers do provide mmap64 for all API versions when building
 * with clang. Therefore, we should provide mmap64 here for API < 21 if we're
 * not using clang or if we're using non-unified headers. We check for
 * non-unified headers by the lack of __ANDROID_API_L__ macro. */
#if defined(ANDROID) && __ANDROID_API__ < 21 && \
    (!defined(__clang__) || !defined(__ANDROID_API_L__))
PR_IMPORT(void) * __mmap2(void*, size_t, int, int, int, size_t);

#  define ANDROID_PAGE_SIZE 4096

static void* mmap64(void* addr, size_t len, int prot, int flags, int fd,
                    loff_t offset) {
  if (offset & (ANDROID_PAGE_SIZE - 1)) {
    errno = EINVAL;
    return MAP_FAILED;
  }
  return __mmap2(addr, len, prot, flags, fd, offset / ANDROID_PAGE_SIZE);
}
#endif

static void _PR_InitIOV(void) {
#if defined(SOLARIS2_5)
  PRLibrary* lib;
  void* open64_func;

  open64_func = PR_FindSymbolAndLibrary("open64", &lib);
  if (NULL != open64_func) {
    PR_ASSERT(NULL != lib);
    _md_iovector._open64 = (_MD_Open64)open64_func;
    _md_iovector._mmap64 = (_MD_Mmap64)PR_FindSymbol(lib, "mmap64");
    _md_iovector._fstat64 = (_MD_Fstat64)PR_FindSymbol(lib, "fstat64");
    _md_iovector._stat64 = (_MD_Stat64)PR_FindSymbol(lib, "stat64");
    _md_iovector._lseek64 = (_MD_Lseek64)PR_FindSymbol(lib, "lseek64");
    (void)PR_UnloadLibrary(lib);
  } else {
    _md_iovector._open64 = open;
    _md_iovector._mmap64 = _MD_Unix_mmap64;
    _md_iovector._fstat64 = _MD_solaris25_fstat64;
    _md_iovector._stat64 = _MD_solaris25_stat64;
    _md_iovector._lseek64 = _MD_Unix_lseek64;
  }
#elif defined(_PR_NO_LARGE_FILES)
  _md_iovector._open64 = open;
  _md_iovector._mmap64 = _MD_Unix_mmap64;
  _md_iovector._fstat64 = fstat;
  _md_iovector._stat64 = stat;
  _md_iovector._lseek64 = _MD_Unix_lseek64;
#elif defined(_PR_HAVE_OFF64_T)
#  if (defined(ANDROID) && __ANDROID_API__ < 21)
  /*
   * Android < 21 doesn't have open64.  We pass the O_LARGEFILE flag to open
   * in _MD_open.
   */
  _md_iovector._open64 = open;
#  else
  _md_iovector._open64 = open64;
#  endif
  _md_iovector._mmap64 = mmap64;
#  if (defined(ANDROID) && __ANDROID_API__ < 21)
  /* Same as the open64 case for Android. */
  _md_iovector._fstat64 = (_MD_Fstat64)fstat;
  _md_iovector._stat64 = (_MD_Stat64)stat;
#  else
  _md_iovector._fstat64 = fstat64;
  _md_iovector._stat64 = stat64;
#  endif
  _md_iovector._lseek64 = lseek64;
#elif defined(_PR_HAVE_LARGE_OFF_T)
  _md_iovector._open64 = open;
  _md_iovector._mmap64 = mmap;
  _md_iovector._fstat64 = fstat;
  _md_iovector._stat64 = stat;
  _md_iovector._lseek64 = lseek;
#else
#  error "I don't know yet"
#endif
  LL_I2L(minus_one, -1);
} /* _PR_InitIOV */

void _PR_UnixInit(void) {
  struct sigaction sigact;
  int rv;

  sigemptyset(&timer_set);

#if !defined(_PR_PTHREADS)

  sigaddset(&timer_set, SIGALRM);
  sigemptyset(&empty_set);
  intr_timeout_ticks = PR_SecondsToInterval(_PR_INTERRUPT_CHECK_INTERVAL_SECS);

#  if defined(SOLARIS)

  if (getenv("NSPR_SIGSEGV_HANDLE")) {
    sigact.sa_handler = sigsegvhandler;
    sigact.sa_flags = 0;
    sigact.sa_mask = timer_set;
    sigaction(SIGSEGV, &sigact, 0);
  }

  if (getenv("NSPR_SIGABRT_HANDLE")) {
    sigact.sa_handler = sigaborthandler;
    sigact.sa_flags = 0;
    sigact.sa_mask = timer_set;
    sigaction(SIGABRT, &sigact, 0);
  }

  if (getenv("NSPR_SIGBUS_HANDLE")) {
    sigact.sa_handler = sigbushandler;
    sigact.sa_flags = 0;
    sigact.sa_mask = timer_set;
    sigaction(SIGBUS, &sigact, 0);
  }

#  endif
#endif /* !defined(_PR_PTHREADS) */

  sigact.sa_handler = SIG_IGN;
  sigemptyset(&sigact.sa_mask);
  sigact.sa_flags = 0;
  rv = sigaction(SIGPIPE, &sigact, 0);
  PR_ASSERT(0 == rv);

  _pr_unix_rename_lock = PR_NewLock();
  PR_ASSERT(NULL != _pr_unix_rename_lock);
  _pr_Xfe_mon = PR_NewMonitor();
  PR_ASSERT(NULL != _pr_Xfe_mon);

  _PR_InitIOV(); /* one last hack */
}

void _PR_UnixCleanup(void) {
  if (_pr_unix_rename_lock) {
    PR_DestroyLock(_pr_unix_rename_lock);
    _pr_unix_rename_lock = NULL;
  }
  if (_pr_Xfe_mon) {
    PR_DestroyMonitor(_pr_Xfe_mon);
    _pr_Xfe_mon = NULL;
  }
}

#if !defined(_PR_PTHREADS)

/*
 * Variables used by the GC code, initialized in _MD_InitSegs().
 */
static PRInt32 _pr_zero_fd = -1;
static PRLock* _pr_md_lock = NULL;

/*
 * _MD_InitSegs --
 *
 * This is Unix's version of _PR_MD_INIT_SEGS(), which is
 * called by _PR_InitSegs(), which in turn is called by
 * PR_Init().
 */
void _MD_InitSegs(void) {
#  ifdef DEBUG
  /*
  ** Disable using mmap(2) if NSPR_NO_MMAP is set
  */
  if (getenv("NSPR_NO_MMAP")) {
    _pr_zero_fd = -2;
    return;
  }
#  endif
  _pr_zero_fd = open("/dev/zero", O_RDWR, 0);
  /* Prevent the fd from being inherited by child processes */
  fcntl(_pr_zero_fd, F_SETFD, FD_CLOEXEC);
  _pr_md_lock = PR_NewLock();
}

PRStatus _MD_AllocSegment(PRSegment* seg, PRUint32 size, void* vaddr) {
  static char* lastaddr = (char*)_PR_STACK_VMBASE;
  PRStatus retval = PR_SUCCESS;
  int prot;
  void* rv;

  PR_ASSERT(seg != 0);
  PR_ASSERT(size != 0);

  PR_Lock(_pr_md_lock);
  if (_pr_zero_fd < 0) {
  from_heap:
    seg->vaddr = PR_MALLOC(size);
    if (!seg->vaddr) {
      retval = PR_FAILURE;
    } else {
      seg->size = size;
    }
    goto exit;
  }

  prot = PROT_READ | PROT_WRITE;
  /*
   * On Alpha Linux, the user-level thread stack needs
   * to be made executable because longjmp/signal seem
   * to put machine instructions on the stack.
   */
#  if defined(LINUX) && defined(__alpha)
  prot |= PROT_EXEC;
#  endif
  rv = mmap((vaddr != 0) ? vaddr : lastaddr, size, prot, _MD_MMAP_FLAGS,
            _pr_zero_fd, 0);
  if (rv == (void*)-1) {
    goto from_heap;
  }
  lastaddr += size;
  seg->vaddr = rv;
  seg->size = size;
  seg->flags = _PR_SEG_VM;

exit:
  PR_Unlock(_pr_md_lock);
  return retval;
}

void _MD_FreeSegment(PRSegment* seg) {
  if (seg->flags & _PR_SEG_VM) {
    (void)munmap(seg->vaddr, seg->size);
  } else {
    PR_DELETE(seg->vaddr);
  }
}

#endif /* _PR_PTHREADS */

/*
 *-----------------------------------------------------------------------
 *
 * PR_Now --
 *
 *     Returns the current time in microseconds since the epoch.
 *     The epoch is midnight January 1, 1970 GMT.
 *     The implementation is machine dependent.  This is the Unix
 *     implementation.
 *     Cf. time_t time(time_t *tp)
 *
 *-----------------------------------------------------------------------
 */

PR_IMPLEMENT(PRTime)
PR_Now(void) {
  struct timeval tv;
  PRInt64 s, us, s2us;

  GETTIMEOFDAY(&tv);
  LL_I2L(s2us, PR_USEC_PER_SEC);
  LL_I2L(s, tv.tv_sec);
  LL_I2L(us, tv.tv_usec);
  LL_MUL(s, s, s2us);
  LL_ADD(s, s, us);
  return s;
}

#if defined(_MD_INTERVAL_USE_GTOD)
/*
 * This version of interval times is based on the time of day
 * capability offered by the system. This isn't valid for two reasons:
 * 1) The time of day is neither linear nor montonically increasing
 * 2) The units here are milliseconds. That's not appropriate for our use.
 */
PRIntervalTime _PR_UNIX_GetInterval() {
  struct timeval time;
  PRIntervalTime ticks;

  (void)GETTIMEOFDAY(&time);                       /* fallicy of course */
  ticks = (PRUint32)time.tv_sec * PR_MSEC_PER_SEC; /* that's in milliseconds */
  ticks += (PRUint32)time.tv_usec / PR_USEC_PER_MSEC; /* so's that */
  return ticks;
} /* _PR_UNIX_GetInterval */

PRIntervalTime _PR_UNIX_TicksPerSecond() {
  return 1000; /* this needs some work :) */
}
#endif

#if defined(_PR_HAVE_CLOCK_MONOTONIC)
PRIntervalTime _PR_UNIX_GetInterval2() {
  struct timespec time;
  PRIntervalTime ticks;

  if (clock_gettime(CLOCK_MONOTONIC, &time) != 0) {
    fprintf(stderr, "clock_gettime failed: %d\n", errno);
    abort();
  }

  ticks = (PRUint32)time.tv_sec * PR_MSEC_PER_SEC;
  ticks += (PRUint32)time.tv_nsec / PR_NSEC_PER_MSEC;
  return ticks;
}

PRIntervalTime _PR_UNIX_TicksPerSecond2() { return 1000; }
#endif

#if !defined(_PR_PTHREADS)
/*
 * Wait for I/O on multiple descriptors.
 *
 * Return 0 if timed out, return -1 if interrupted,
 * else return the number of ready descriptors.
 */
PRInt32 _PR_WaitForMultipleFDs(_PRUnixPollDesc* unixpds, PRInt32 pdcnt,
                               PRIntervalTime timeout) {
  PRPollQueue pq;
  PRIntn is;
  PRInt32 rv;
  _PRCPU* io_cpu;
  _PRUnixPollDesc *unixpd, *eunixpd;
  PRThread* me = _PR_MD_CURRENT_THREAD();

  PR_ASSERT(!(me->flags & _PR_IDLE_THREAD));

  if (_PR_PENDING_INTERRUPT(me)) {
    me->flags &= ~_PR_INTERRUPT;
    PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    return -1;
  }

  pq.pds = unixpds;
  pq.npds = pdcnt;

  _PR_INTSOFF(is);
  _PR_MD_IOQ_LOCK();
  _PR_THREAD_LOCK(me);

  pq.thr = me;
  io_cpu = me->cpu;
  pq.on_ioq = PR_TRUE;
  pq.timeout = timeout;
  _PR_ADD_TO_IOQ(pq, me->cpu);

#  if !defined(_PR_USE_POLL)
  eunixpd = unixpds + pdcnt;
  for (unixpd = unixpds; unixpd < eunixpd; unixpd++) {
    PRInt32 osfd = unixpd->osfd;
    if (unixpd->in_flags & _PR_UNIX_POLL_READ) {
      FD_SET(osfd, &_PR_FD_READ_SET(me->cpu));
      _PR_FD_READ_CNT(me->cpu)[osfd]++;
    }
    if (unixpd->in_flags & _PR_UNIX_POLL_WRITE) {
      FD_SET(osfd, &_PR_FD_WRITE_SET(me->cpu));
      (_PR_FD_WRITE_CNT(me->cpu))[osfd]++;
    }
    if (unixpd->in_flags & _PR_UNIX_POLL_EXCEPT) {
      FD_SET(osfd, &_PR_FD_EXCEPTION_SET(me->cpu));
      (_PR_FD_EXCEPTION_CNT(me->cpu))[osfd]++;
    }
    if (osfd > _PR_IOQ_MAX_OSFD(me->cpu)) {
      _PR_IOQ_MAX_OSFD(me->cpu) = osfd;
    }
  }
#  endif /* !defined(_PR_USE_POLL) */

  if (_PR_IOQ_TIMEOUT(me->cpu) > timeout) {
    _PR_IOQ_TIMEOUT(me->cpu) = timeout;
  }

  _PR_IOQ_OSFD_CNT(me->cpu) += pdcnt;

  _PR_SLEEPQ_LOCK(me->cpu);
  _PR_ADD_SLEEPQ(me, timeout);
  me->state = _PR_IO_WAIT;
  me->io_pending = PR_TRUE;
  me->io_suspended = PR_FALSE;
  _PR_SLEEPQ_UNLOCK(me->cpu);
  _PR_THREAD_UNLOCK(me);
  _PR_MD_IOQ_UNLOCK();

  _PR_MD_WAIT(me, timeout);

  me->io_pending = PR_FALSE;
  me->io_suspended = PR_FALSE;

  /*
   * This thread should run on the same cpu on which it was blocked; when
   * the IO request times out the fd sets and fd counts for the
   * cpu are updated below.
   */
  PR_ASSERT(me->cpu == io_cpu);

  /*
  ** If we timed out the pollq might still be on the ioq. Remove it
  ** before continuing.
  */
  if (pq.on_ioq) {
    _PR_MD_IOQ_LOCK();
    /*
     * Need to check pq.on_ioq again
     */
    if (pq.on_ioq) {
      PR_REMOVE_LINK(&pq.links);
#  ifndef _PR_USE_POLL
      eunixpd = unixpds + pdcnt;
      for (unixpd = unixpds; unixpd < eunixpd; unixpd++) {
        PRInt32 osfd = unixpd->osfd;
        PRInt16 in_flags = unixpd->in_flags;

        if (in_flags & _PR_UNIX_POLL_READ) {
          if (--(_PR_FD_READ_CNT(me->cpu))[osfd] == 0) {
            FD_CLR(osfd, &_PR_FD_READ_SET(me->cpu));
          }
        }
        if (in_flags & _PR_UNIX_POLL_WRITE) {
          if (--(_PR_FD_WRITE_CNT(me->cpu))[osfd] == 0) {
            FD_CLR(osfd, &_PR_FD_WRITE_SET(me->cpu));
          }
        }
        if (in_flags & _PR_UNIX_POLL_EXCEPT) {
          if (--(_PR_FD_EXCEPTION_CNT(me->cpu))[osfd] == 0) {
            FD_CLR(osfd, &_PR_FD_EXCEPTION_SET(me->cpu));
          }
        }
      }
#  endif /* _PR_USE_POLL */
      PR_ASSERT(pq.npds == pdcnt);
      _PR_IOQ_OSFD_CNT(me->cpu) -= pdcnt;
      PR_ASSERT(_PR_IOQ_OSFD_CNT(me->cpu) >= 0);
    }
    _PR_MD_IOQ_UNLOCK();
  }
  /* XXX Should we use _PR_FAST_INTSON or _PR_INTSON? */
  if (1 == pdcnt) {
    _PR_FAST_INTSON(is);
  } else {
    _PR_INTSON(is);
  }

  if (_PR_PENDING_INTERRUPT(me)) {
    me->flags &= ~_PR_INTERRUPT;
    PR_SetError(PR_PENDING_INTERRUPT_ERROR, 0);
    return -1;
  }

  rv = 0;
  if (pq.on_ioq == PR_FALSE) {
    /* Count the number of ready descriptors */
    while (--pdcnt >= 0) {
      if (unixpds->out_flags != 0) {
        rv++;
      }
      unixpds++;
    }
  }

  return rv;
}

/*
 * Unblock threads waiting for I/O
 *    used when interrupting threads
 *
 * NOTE: The thread lock should held when this function is called.
 * On return, the thread lock is released.
 */
void _PR_Unblock_IO_Wait(PRThread* thr) {
  int pri = thr->priority;
  _PRCPU* cpu = thr->cpu;

  /*
   * GLOBAL threads wakeup periodically to check for interrupt
   */
  if (_PR_IS_NATIVE_THREAD(thr)) {
    _PR_THREAD_UNLOCK(thr);
    return;
  }

  PR_ASSERT(thr->flags & (_PR_ON_SLEEPQ | _PR_ON_PAUSEQ));
  _PR_SLEEPQ_LOCK(cpu);
  _PR_DEL_SLEEPQ(thr, PR_TRUE);
  _PR_SLEEPQ_UNLOCK(cpu);

  PR_ASSERT(!(thr->flags & _PR_IDLE_THREAD));
  thr->state = _PR_RUNNABLE;
  _PR_RUNQ_LOCK(cpu);
  _PR_ADD_RUNQ(thr, cpu, pri);
  _PR_RUNQ_UNLOCK(cpu);
  _PR_THREAD_UNLOCK(thr);
  _PR_MD_WAKEUP_WAITER(thr);
}
#endif /* !defined(_PR_PTHREADS) */

/*
 * When a nonblocking connect has completed, determine whether it
 * succeeded or failed, and if it failed, what the error code is.
 *
 * The function returns the error code.  An error code of 0 means
 * that the nonblocking connect succeeded.
 */

int _MD_unix_get_nonblocking_connect_error(int osfd) {
#if defined(NTO)
  /* Neutrino does not support the SO_ERROR socket option */
  PRInt32 rv;
  PRNetAddr addr;
  _PRSockLen_t addrlen = sizeof(addr);

  /* Test to see if we are using the Tiny TCP/IP Stack or the Full one. */
  struct statvfs superblock;
  rv = fstatvfs(osfd, &superblock);
  if (rv == 0) {
    if (strcmp(superblock.f_basetype, "ttcpip") == 0) {
      /* Using the Tiny Stack! */
      rv = getpeername(osfd, (struct sockaddr*)&addr, (_PRSockLen_t*)&addrlen);
      if (rv == -1) {
        int errno_copy = errno; /* make a copy so I don't
                                 * accidentally reset */

        if (errno_copy == ENOTCONN) {
          struct stat StatInfo;
          rv = fstat(osfd, &StatInfo);
          if (rv == 0) {
            time_t current_time = time(NULL);

            /*
             * this is a real hack, can't explain why it
             * works it just does
             */
            if (abs(current_time - StatInfo.st_atime) < 5) {
              return ECONNREFUSED;
            } else {
              return ETIMEDOUT;
            }
          } else {
            return ECONNREFUSED;
          }
        } else {
          return errno_copy;
        }
      } else {
        /* No Error */
        return 0;
      }
    } else {
      /* Have the FULL Stack which supports SO_ERROR */
      /* Hasn't been written yet, never been tested! */
      /* Jerry.Kirk@Nexwarecorp.com */

      int err;
      _PRSockLen_t optlen = sizeof(err);

      if (getsockopt(osfd, SOL_SOCKET, SO_ERROR, (char*)&err, &optlen) == -1) {
        return errno;
      } else {
        return err;
      }
    }
  } else {
    return ECONNREFUSED;
  }
#else
  int err;
  _PRSockLen_t optlen = sizeof(err);
  if (getsockopt(osfd, SOL_SOCKET, SO_ERROR, (char*)&err, &optlen) == -1) {
    return errno;
  }
  return err;

#endif
}

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

/*
** Special hacks for xlib. Xlib/Xt/Xm is not re-entrant nor is it thread
** safe.  Unfortunately, neither is mozilla. To make these programs work
** in a pre-emptive threaded environment, we need to use a lock.
*/

void _PR_XLock(void) { PR_EnterMonitor(_pr_Xfe_mon); }

void _PR_XUnlock(void) { PR_ExitMonitor(_pr_Xfe_mon); }

PRBool _PR_XIsLocked(void) {
  return (PR_InMonitor(_pr_Xfe_mon)) ? PR_TRUE : PR_FALSE;
}

#if defined(HAVE_FCNTL_FILE_LOCKING)

PRStatus _MD_LockFile(PRInt32 f) {
  PRInt32 rv;
  struct flock arg;

  arg.l_type = F_WRLCK;
  arg.l_whence = SEEK_SET;
  arg.l_start = 0;
  arg.l_len = 0; /* until EOF */
  rv = fcntl(f, F_SETLKW, &arg);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_FLOCK_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

PRStatus _MD_TLockFile(PRInt32 f) {
  PRInt32 rv;
  struct flock arg;

  arg.l_type = F_WRLCK;
  arg.l_whence = SEEK_SET;
  arg.l_start = 0;
  arg.l_len = 0; /* until EOF */
  rv = fcntl(f, F_SETLK, &arg);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_FLOCK_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

PRStatus _MD_UnlockFile(PRInt32 f) {
  PRInt32 rv;
  struct flock arg;

  arg.l_type = F_UNLCK;
  arg.l_whence = SEEK_SET;
  arg.l_start = 0;
  arg.l_len = 0; /* until EOF */
  rv = fcntl(f, F_SETLK, &arg);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_FLOCK_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

#elif defined(HAVE_BSD_FLOCK)

#  include <sys/file.h>

PRStatus _MD_LockFile(PRInt32 f) {
  PRInt32 rv;
  rv = flock(f, LOCK_EX);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_FLOCK_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

PRStatus _MD_TLockFile(PRInt32 f) {
  PRInt32 rv;
  rv = flock(f, LOCK_EX | LOCK_NB);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_FLOCK_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

PRStatus _MD_UnlockFile(PRInt32 f) {
  PRInt32 rv;
  rv = flock(f, LOCK_UN);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_FLOCK_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}
#else

PRStatus _MD_LockFile(PRInt32 f) {
  PRInt32 rv;
  rv = lockf(f, F_LOCK, 0);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_LOCKF_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

PRStatus _MD_TLockFile(PRInt32 f) {
  PRInt32 rv;
  rv = lockf(f, F_TLOCK, 0);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_LOCKF_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

PRStatus _MD_UnlockFile(PRInt32 f) {
  PRInt32 rv;
  rv = lockf(f, F_ULOCK, 0);
  if (rv == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_LOCKF_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}
#endif

PRStatus _MD_gethostname(char* name, PRUint32 namelen) {
  PRIntn rv;

  rv = gethostname(name, namelen);
  if (0 == rv) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_GETHOSTNAME_ERROR(_MD_ERRNO());
  return PR_FAILURE;
}

PRStatus _MD_getsysinfo(PRSysInfo cmd, char* name, PRUint32 namelen) {
  struct utsname info;

  PR_ASSERT((cmd == PR_SI_SYSNAME) || (cmd == PR_SI_RELEASE) ||
            (cmd == PR_SI_RELEASE_BUILD));

  if (uname(&info) == -1) {
    _PR_MD_MAP_DEFAULT_ERROR(errno);
    return PR_FAILURE;
  }
  if (PR_SI_SYSNAME == cmd) {
    (void)PR_snprintf(name, namelen, info.sysname);
  } else if (PR_SI_RELEASE == cmd) {
    (void)PR_snprintf(name, namelen, info.release);
  } else if (PR_SI_RELEASE_BUILD == cmd) {
    (void)PR_snprintf(name, namelen, info.version);
  } else {
    return PR_FAILURE;
  }
  return PR_SUCCESS;
}

/*
 *******************************************************************
 *
 * Memory-mapped files
 *
 *******************************************************************
 */

PRStatus _MD_CreateFileMap(PRFileMap* fmap, PRInt64 size) {
  PRFileInfo info;
  PRUint32 sz;

  LL_L2UI(sz, size);
  if (sz) {
    if (PR_GetOpenFileInfo(fmap->fd, &info) == PR_FAILURE) {
      return PR_FAILURE;
    }
    if (sz > info.size) {
      /*
       * Need to extend the file
       */
      if (fmap->prot != PR_PROT_READWRITE) {
        PR_SetError(PR_NO_ACCESS_RIGHTS_ERROR, 0);
        return PR_FAILURE;
      }
      if (PR_Seek(fmap->fd, sz - 1, PR_SEEK_SET) == -1) {
        return PR_FAILURE;
      }
      if (PR_Write(fmap->fd, "", 1) != 1) {
        return PR_FAILURE;
      }
    }
  }
  if (fmap->prot == PR_PROT_READONLY) {
    fmap->md.prot = PROT_READ;
#if defined(DARWIN) || defined(ANDROID)
    /*
     * This is needed on OS X because its implementation of
     * POSIX shared memory returns an error for MAP_PRIVATE, even
     * when the mapping is read-only.
     *
     * And this is needed on Android, because mapping ashmem with
     * MAP_PRIVATE creates a mapping of zeroed memory instead of
     * the shm contents.
     */
    fmap->md.flags = MAP_SHARED;
#else
    fmap->md.flags = MAP_PRIVATE;
#endif
  } else if (fmap->prot == PR_PROT_READWRITE) {
    fmap->md.prot = PROT_READ | PROT_WRITE;
    fmap->md.flags = MAP_SHARED;
  } else {
    PR_ASSERT(fmap->prot == PR_PROT_WRITECOPY);
    fmap->md.prot = PROT_READ | PROT_WRITE;
    fmap->md.flags = MAP_PRIVATE;
  }
  return PR_SUCCESS;
}

void* _MD_MemMap(PRFileMap* fmap, PRInt64 offset, PRUint32 len) {
  PRInt32 off;
  void* addr;

  LL_L2I(off, offset);
  if ((addr = mmap(0, len, fmap->md.prot, fmap->md.flags,
                   fmap->fd->secret->md.osfd, off)) == (void*)-1) {
    _PR_MD_MAP_MMAP_ERROR(_MD_ERRNO());
    addr = NULL;
  }
  return addr;
}

PRStatus _MD_MemUnmap(void* addr, PRUint32 len) {
  if (munmap(addr, len) == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_DEFAULT_ERROR(errno);
  return PR_FAILURE;
}

PRStatus _MD_CloseFileMap(PRFileMap* fmap) {
  if (PR_TRUE == fmap->md.isAnonFM) {
    PRStatus rc = PR_Close(fmap->fd);
    if (PR_FAILURE == rc) {
      PR_LOG(_pr_io_lm, PR_LOG_DEBUG,
             ("_MD_CloseFileMap(): error closing anonymnous file map osfd"));
      return PR_FAILURE;
    }
  }
  PR_DELETE(fmap);
  return PR_SUCCESS;
}

PRStatus _MD_SyncMemMap(PRFileDesc* fd, void* addr, PRUint32 len) {
  /* msync(..., MS_SYNC) alone is sufficient to flush modified data to disk
   * synchronously. It is not necessary to call fsync. */
  if (msync(addr, len, MS_SYNC) == 0) {
    return PR_SUCCESS;
  }
  _PR_MD_MAP_DEFAULT_ERROR(errno);
  return PR_FAILURE;
}

#if defined(_PR_NEED_FAKE_POLL)

/*
 * Some platforms don't have poll().  For easier porting of code
 * that calls poll(), we emulate poll() using select().
 */

int poll(struct pollfd* filedes, unsigned long nfds, int timeout) {
  int i;
  int rv;
  int maxfd;
  fd_set rd, wr, ex;
  struct timeval tv, *tvp;

  if (timeout < 0 && timeout != -1) {
    errno = EINVAL;
    return -1;
  }

  if (timeout == -1) {
    tvp = NULL;
  } else {
    tv.tv_sec = timeout / 1000;
    tv.tv_usec = (timeout % 1000) * 1000;
    tvp = &tv;
  }

  maxfd = -1;
  FD_ZERO(&rd);
  FD_ZERO(&wr);
  FD_ZERO(&ex);

  for (i = 0; i < nfds; i++) {
    int osfd = filedes[i].fd;
    int events = filedes[i].events;
    PRBool fdHasEvent = PR_FALSE;

    PR_ASSERT(osfd < FD_SETSIZE);
    if (osfd < 0 || osfd >= FD_SETSIZE) {
      continue; /* Skip this osfd. */
    }

    /*
     * Map the poll events to the select fd_sets.
     *     POLLIN, POLLRDNORM  ===> readable
     *     POLLOUT, POLLWRNORM ===> writable
     *     POLLPRI, POLLRDBAND ===> exception
     *     POLLNORM, POLLWRBAND (and POLLMSG on some platforms)
     *     are ignored.
     *
     * The output events POLLERR and POLLHUP are never turned on.
     * POLLNVAL may be turned on.
     */

    if (events & (POLLIN | POLLRDNORM)) {
      FD_SET(osfd, &rd);
      fdHasEvent = PR_TRUE;
    }
    if (events & (POLLOUT | POLLWRNORM)) {
      FD_SET(osfd, &wr);
      fdHasEvent = PR_TRUE;
    }
    if (events & (POLLPRI | POLLRDBAND)) {
      FD_SET(osfd, &ex);
      fdHasEvent = PR_TRUE;
    }
    if (fdHasEvent && osfd > maxfd) {
      maxfd = osfd;
    }
  }

  rv = select(maxfd + 1, &rd, &wr, &ex, tvp);

  /* Compute poll results */
  if (rv > 0) {
    rv = 0;
    for (i = 0; i < nfds; i++) {
      PRBool fdHasEvent = PR_FALSE;

      filedes[i].revents = 0;
      if (filedes[i].fd < 0) {
        continue;
      }
      if (filedes[i].fd >= FD_SETSIZE) {
        filedes[i].revents |= POLLNVAL;
        continue;
      }
      if (FD_ISSET(filedes[i].fd, &rd)) {
        if (filedes[i].events & POLLIN) {
          filedes[i].revents |= POLLIN;
        }
        if (filedes[i].events & POLLRDNORM) {
          filedes[i].revents |= POLLRDNORM;
        }
        fdHasEvent = PR_TRUE;
      }
      if (FD_ISSET(filedes[i].fd, &wr)) {
        if (filedes[i].events & POLLOUT) {
          filedes[i].revents |= POLLOUT;
        }
        if (filedes[i].events & POLLWRNORM) {
          filedes[i].revents |= POLLWRNORM;
        }
        fdHasEvent = PR_TRUE;
      }
      if (FD_ISSET(filedes[i].fd, &ex)) {
        if (filedes[i].events & POLLPRI) {
          filedes[i].revents |= POLLPRI;
        }
        if (filedes[i].events & POLLRDBAND) {
          filedes[i].revents |= POLLRDBAND;
        }
        fdHasEvent = PR_TRUE;
      }
      if (fdHasEvent) {
        rv++;
      }
    }
    PR_ASSERT(rv > 0);
  } else if (rv == -1 && errno == EBADF) {
    rv = 0;
    for (i = 0; i < nfds; i++) {
      filedes[i].revents = 0;
      if (filedes[i].fd < 0) {
        continue;
      }
      if (fcntl(filedes[i].fd, F_GETFL, 0) == -1) {
        filedes[i].revents = POLLNVAL;
        rv++;
      }
    }
    PR_ASSERT(rv > 0);
  }
  PR_ASSERT(-1 != timeout || rv != 0);

  return rv;
}
#endif /* _PR_NEED_FAKE_POLL */
