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

/*
 *------------------------------------------------------------------------
 * File: uxwrap.c
 *
 *     Our wrapped versions of the Unix select() and poll() system calls.
 *
 *------------------------------------------------------------------------
 */

#include "primpl.h"

#if defined(_PR_PTHREADS) || defined(_PR_GLOBAL_THREADS_ONLY) || defined(QNX)
/* Do not wrap select() and poll(). */
#else  /* defined(_PR_PTHREADS) || defined(_PR_GLOBAL_THREADS_ONLY) */
/* The include files for select() */
#include <string.h>
#include <sys/types.h>
#include <sys/time.h>

#define ZAP_SET(_to, _width)                      \
    PR_BEGIN_MACRO                        \
    memset(_to, 0,                        \
           ((_width + 8*sizeof(int)-1) / (8*sizeof(int))) \
        * sizeof(int)                     \
           );                         \
    PR_END_MACRO

/* see comments in ns/cmd/xfe/mozilla.c (look for "PR_XGetXtHackFD") */
static int _pr_xt_hack_fd = -1;

int PR_XGetXtHackFD(void)
{
    int fds[2];

    if (_pr_xt_hack_fd == -1) {
        if (!pipe(fds)) {
            _pr_xt_hack_fd = fds[0];
        }
    }
    return _pr_xt_hack_fd;
}

static int (*_pr_xt_hack_okayToReleaseXLock)(void) = 0;

void PR_SetXtHackOkayToReleaseXLockFn(int (*fn)(void))
{
    _pr_xt_hack_okayToReleaseXLock = fn;
}


/*
 *-----------------------------------------------------------------------
 *  select() --
 *
 *    Wrap up the select system call so that we can deschedule
 *    a thread that tries to wait for i/o.
 *
 *-----------------------------------------------------------------------
 */

#if defined(HPUX9)
int select(size_t width, int *rl, int *wl, int *el, const struct timeval *tv)
#elif defined(AIX_RENAME_SELECT)
int wrap_select(unsigned long width, void *rl, void *wl, void *el,
                struct timeval *tv)
#elif defined(_PR_SELECT_CONST_TIMEVAL)
int select(int width, fd_set *rd, fd_set *wr, fd_set *ex,
           const struct timeval *tv)
#else
int select(int width, fd_set *rd, fd_set *wr, fd_set *ex, struct timeval *tv)
#endif
{
    int osfd;
    _PRUnixPollDesc *unixpds, *unixpd, *eunixpd;
    PRInt32 pdcnt;
    PRIntervalTime timeout;
    int retVal;
#if defined(HPUX9) || defined(AIX_RENAME_SELECT)
    fd_set *rd = (fd_set*) rl;
    fd_set *wr = (fd_set*) wl;
    fd_set *ex = (fd_set*) el;
#endif

#if 0
    /*
     * Easy special case: zero timeout.  Simply call the native
     * select() with no fear of blocking.
     */
    if (tv != NULL && tv->tv_sec == 0 && tv->tv_usec == 0) {
#if defined(HPUX9) || defined(AIX_RENAME_SELECT)
        return _MD_SELECT(width, rl, wl, el, tv);
#else
        return _MD_SELECT(width, rd, wr, ex, tv);
#endif
    }
#endif

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

#ifndef _PR_LOCAL_THREADS_ONLY
    if (_PR_IS_NATIVE_THREAD(_PR_MD_CURRENT_THREAD())) {
        return _MD_SELECT(width, rd, wr, ex, tv);
    }
#endif

    if (width < 0 || width > FD_SETSIZE) {
        errno = EINVAL;
        return -1;
    }

    /* Compute timeout */
    if (tv) {
        /*
         * These acceptable ranges for t_sec and t_usec are taken
         * from the select() man pages.
         */
        if (tv->tv_sec < 0 || tv->tv_sec > 100000000
            || tv->tv_usec < 0 || tv->tv_usec >= 1000000) {
            errno = EINVAL;
            return -1;
        }

        /* Convert microseconds to ticks */
        timeout = PR_MicrosecondsToInterval(1000000*tv->tv_sec + tv->tv_usec);
    } else {
        /* tv being a NULL pointer means blocking indefinitely */
        timeout = PR_INTERVAL_NO_TIMEOUT;
    }

    /* Check for no descriptors case (just doing a timeout) */
    if ((!rd && !wr && !ex) || !width) {
        PR_Sleep(timeout);
        return 0;
    }

    /*
     * Set up for PR_Poll().  The PRPollDesc array is allocated
     * dynamically.  If this turns out to have high performance
     * penalty, one can change to use a large PRPollDesc array
     * on the stack, and allocate dynamically only when it turns
     * out to be not large enough.
     *
     * I allocate an array of size 'width', which is the maximum
     * number of fds we may need to poll.
     */
    unixpds = (_PRUnixPollDesc *) PR_CALLOC(width * sizeof(_PRUnixPollDesc));
    if (!unixpds) {
        errno = ENOMEM;
        return -1;
    }

    pdcnt = 0;
    unixpd = unixpds;
    for (osfd = 0; osfd < width; osfd++) {
        int in_flags = 0;
        if (rd && FD_ISSET(osfd, rd)) {
            in_flags |= _PR_UNIX_POLL_READ;
        }
        if (wr && FD_ISSET(osfd, wr)) {
            in_flags |= _PR_UNIX_POLL_WRITE;
        }
        if (ex && FD_ISSET(osfd, ex)) {
            in_flags |= _PR_UNIX_POLL_EXCEPT;
        }
        if (in_flags) {
            unixpd->osfd = osfd;
            unixpd->in_flags = in_flags;
            unixpd->out_flags = 0;
            unixpd++;
            pdcnt++;
        }
    }

    /*
     * see comments in mozilla/cmd/xfe/mozilla.c (look for
     * "PR_XGetXtHackFD")
     */
    {
        int needToLockXAgain;

        needToLockXAgain = 0;
        if (rd && (_pr_xt_hack_fd != -1)
            && FD_ISSET(_pr_xt_hack_fd, rd) && PR_XIsLocked()
            && (!_pr_xt_hack_okayToReleaseXLock
                || _pr_xt_hack_okayToReleaseXLock())) {
            PR_XUnlock();
            needToLockXAgain = 1;
        }

        /* This is the potentially blocking step */
        retVal = _PR_WaitForMultipleFDs(unixpds, pdcnt, timeout);

        if (needToLockXAgain) {
            PR_XLock();
        }
    }

    if (retVal > 0) {
        /* Compute select results */
        if (rd) {
            ZAP_SET(rd, width);
        }
        if (wr) {
            ZAP_SET(wr, width);
        }
        if (ex) {
            ZAP_SET(ex, width);
        }

        /*
         * The return value can be either the number of ready file
         * descriptors or the number of set bits in the three fd_set's.
         */
        retVal = 0;  /* we're going to recompute */
        eunixpd = unixpds + pdcnt;
        for (unixpd = unixpds; unixpd < eunixpd; unixpd++) {
            if (unixpd->out_flags) {
                int nbits = 0;  /* The number of set bits on for this fd */

                if (unixpd->out_flags & _PR_UNIX_POLL_NVAL) {
                    errno = EBADF;
                    PR_LOG(_pr_io_lm, PR_LOG_ERROR,
                           ("select returns EBADF for %d", unixpd->osfd));
                    retVal = -1;
                    break;
                }
                /*
                 * If a socket has a pending error, it is considered
                 * both readable and writable.  (See W. Richard Stevens,
                 * Unix Network Programming, Vol. 1, 2nd Ed., Section 6.3,
                 * pp. 153-154.)  We also consider a socket readable if
                 * it has a hangup condition.
                 */
                if (rd && (unixpd->in_flags & _PR_UNIX_POLL_READ)
                    && (unixpd->out_flags & (_PR_UNIX_POLL_READ
                                             | _PR_UNIX_POLL_ERR | _PR_UNIX_POLL_HUP))) {
                    FD_SET(unixpd->osfd, rd);
                    nbits++;
                }
                if (wr && (unixpd->in_flags & _PR_UNIX_POLL_WRITE)
                    && (unixpd->out_flags & (_PR_UNIX_POLL_WRITE
                                             | _PR_UNIX_POLL_ERR))) {
                    FD_SET(unixpd->osfd, wr);
                    nbits++;
                }
                if (ex && (unixpd->in_flags & _PR_UNIX_POLL_WRITE)
                    && (unixpd->out_flags & PR_POLL_EXCEPT)) {
                    FD_SET(unixpd->osfd, ex);
                    nbits++;
                }
                PR_ASSERT(nbits > 0);
#if defined(HPUX) || defined(SOLARIS) || defined(AIX)
                retVal += nbits;
#endif
            }
        }
    }

    PR_ASSERT(tv || retVal != 0);
    PR_LOG(_pr_io_lm, PR_LOG_MIN, ("select returns %d", retVal));
    PR_DELETE(unixpds);

    return retVal;
}

/*
 * Redefine poll, when supported on platforms, for local threads
 */

/*
 * I am commenting out the poll() wrapper for Linux for now
 * because it is difficult to define _MD_POLL that works on all
 * Linux varieties.  People reported that glibc 2.0.7 on Debian
 * 2.0 Linux machines doesn't have the __syscall_poll symbol
 * defined.  (WTC 30 Nov. 1998)
 */
#if defined(_PR_POLL_AVAILABLE) && !defined(LINUX)

/*
 *-----------------------------------------------------------------------
 * poll() --
 *
 * RETURN VALUES:
 *     -1:  fails, errno indicates the error.
 *      0:  timed out, the revents bitmasks are not set.
 *      positive value: the number of file descriptors for which poll()
 *          has set the revents bitmask.
 *
 *-----------------------------------------------------------------------
 */

#include <poll.h>

#if defined(AIX_RENAME_SELECT)
int wrap_poll(void *listptr, unsigned long nfds, long timeout)
#elif (defined(AIX) && !defined(AIX_RENAME_SELECT))
int poll(void *listptr, unsigned long nfds, long timeout)
#elif defined(HPUX) && !defined(HPUX9)
int poll(struct pollfd filedes[], unsigned int nfds, int timeout)
#elif defined(HPUX9)
int poll(struct pollfd filedes[], int nfds, int timeout)
#elif defined(NETBSD)
int poll(struct pollfd *filedes, nfds_t nfds, int timeout)
#elif defined(OPENBSD)
int poll(struct pollfd filedes[], nfds_t nfds, int timeout)
#elif defined(FREEBSD)
int poll(struct pollfd *filedes, unsigned nfds, int timeout)
#else
int poll(struct pollfd *filedes, unsigned long nfds, int timeout)
#endif
{
#ifdef AIX
    struct pollfd *filedes = (struct pollfd *) listptr;
#endif
    struct pollfd *pfd, *epfd;
    _PRUnixPollDesc *unixpds, *unixpd, *eunixpd;
    PRIntervalTime ticks;
    PRInt32 pdcnt;
    int ready;

    /*
     * Easy special case: zero timeout.  Simply call the native
     * poll() with no fear of blocking.
     */
    if (timeout == 0) {
#if defined(AIX)
        return _MD_POLL(listptr, nfds, timeout);
#else
        return _MD_POLL(filedes, nfds, timeout);
#endif
    }

    if (!_pr_initialized) {
        _PR_ImplicitInitialization();
    }

#ifndef _PR_LOCAL_THREADS_ONLY
    if (_PR_IS_NATIVE_THREAD(_PR_MD_CURRENT_THREAD())) {
        return _MD_POLL(filedes, nfds, timeout);
    }
#endif

    /* We do not support the pollmsg structures on AIX */
#ifdef AIX
    PR_ASSERT((nfds & 0xff00) == 0);
#endif

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

    /* Convert timeout from miliseconds to ticks */
    if (timeout == -1) {
        ticks = PR_INTERVAL_NO_TIMEOUT;
    } else {
        ticks = PR_MillisecondsToInterval(timeout);
    }

    /* Check for no descriptor case (just do a timeout) */
    if (nfds == 0) {
        PR_Sleep(ticks);
        return 0;
    }

    unixpds = (_PRUnixPollDesc *)
              PR_MALLOC(nfds * sizeof(_PRUnixPollDesc));
    if (NULL == unixpds) {
        errno = EAGAIN;
        return -1;
    }

    pdcnt = 0;
    epfd = filedes + nfds;
    unixpd = unixpds;
    for (pfd = filedes; pfd < epfd; pfd++) {
        /*
         * poll() ignores negative fd's.
         */
        if (pfd->fd >= 0) {
            unixpd->osfd = pfd->fd;
#ifdef _PR_USE_POLL
            unixpd->in_flags = pfd->events;
#else
            /*
             * Map the poll events to one of the three that can be
             * represented by 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.
             */
            unixpd->in_flags = 0;
            if (pfd->events & (POLLIN
#ifdef POLLRDNORM
                               | POLLRDNORM
#endif
                              )) {
                unixpd->in_flags |= _PR_UNIX_POLL_READ;
            }
            if (pfd->events & (POLLOUT
#ifdef POLLWRNORM
                               | POLLWRNORM
#endif
                              )) {
                unixpd->in_flags |= _PR_UNIX_POLL_WRITE;
            }
            if (pfd->events & (POLLPRI
#ifdef POLLRDBAND
                               | POLLRDBAND
#endif
                              )) {
                unixpd->in_flags |= PR_POLL_EXCEPT;
            }
#endif  /* _PR_USE_POLL */
            unixpd->out_flags = 0;
            unixpd++;
            pdcnt++;
        }
    }

    ready = _PR_WaitForMultipleFDs(unixpds, pdcnt, ticks);
    if (-1 == ready) {
        if (PR_GetError() == PR_PENDING_INTERRUPT_ERROR) {
            errno = EINTR;  /* XXX we aren't interrupted by a signal, but... */
        } else {
            errno = PR_GetOSError();
        }
    }
    if (ready <= 0) {
        goto done;
    }

    /*
     * Copy the out_flags from the _PRUnixPollDesc structures to the
     * user's pollfd structures and free the allocated memory
     */
    unixpd = unixpds;
    for (pfd = filedes; pfd < epfd; pfd++) {
        pfd->revents = 0;
        if (pfd->fd >= 0) {
#ifdef _PR_USE_POLL
            pfd->revents = unixpd->out_flags;
#else
            if (0 != unixpd->out_flags) {
                if (unixpd->out_flags & _PR_UNIX_POLL_READ) {
                    if (pfd->events & POLLIN) {
                        pfd->revents |= POLLIN;
                    }
#ifdef POLLRDNORM
                    if (pfd->events & POLLRDNORM) {
                        pfd->revents |= POLLRDNORM;
                    }
#endif
                }
                if (unixpd->out_flags & _PR_UNIX_POLL_WRITE) {
                    if (pfd->events & POLLOUT) {
                        pfd->revents |= POLLOUT;
                    }
#ifdef POLLWRNORM
                    if (pfd->events & POLLWRNORM) {
                        pfd->revents |= POLLWRNORM;
                    }
#endif
                }
                if (unixpd->out_flags & _PR_UNIX_POLL_EXCEPT) {
                    if (pfd->events & POLLPRI) {
                        pfd->revents |= POLLPRI;
                    }
#ifdef POLLRDBAND
                    if (pfd->events & POLLRDBAND) {
                        pfd->revents |= POLLRDBAND;
                    }
#endif
                }
                if (unixpd->out_flags & _PR_UNIX_POLL_ERR) {
                    pfd->revents |= POLLERR;
                }
                if (unixpd->out_flags & _PR_UNIX_POLL_NVAL) {
                    pfd->revents |= POLLNVAL;
                }
                if (unixpd->out_flags & _PR_UNIX_POLL_HUP) {
                    pfd->revents |= POLLHUP;
                }
            }
#endif  /* _PR_USE_POLL */
            unixpd++;
        }
    }

done:
    PR_DELETE(unixpds);
    return ready;
}

#endif  /* !defined(LINUX) */

#endif  /* defined(_PR_PTHREADS) || defined(_PR_GLOBAL_THREADS_ONLY) */

/* uxwrap.c */

