/*
**  pth_high.c -- Pth high-level replacement functions
**
**  Copyright (c) 1999 Ralf S. Engelschall <rse@engelschall.com>
**
**  This file is part of GNU Pth, a non-preemptive thread scheduling
**  library which can be found at http://www.gnu.org/software/pth/.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Library General Public
**  License as published by the Free Software Foundation; either
**  version 2 of the License, or (at your option) any later version.
**
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  Library General Public License for more details.
**
**  You should have received a copy of the GNU Library General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
**  USA, or contact Ralf S. Engelschall <rse@engelschall.com>.
*/

/*
 *  These functions used by the applications instead of the
 *  regular Unix/POSIX functions. When the regular functions would
 *  block these variants let only the tread sleep.
 */

#include "pth_p.h"

/* Pth variant of usleep(3) */
int pth_usleep(unsigned int usec)
{
    pth_time_t until;
    pth_time_t offset;
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;

    /* calculate asleep time */
    offset = pth_time(0, usec);
    pth_time_set(&until, PTH_TIME_NOW);
    pth_time_add(&until, &offset);

    /* and let thread sleep until this time is elapsed */
    ev = pth_event(PTH_EVENT_TIME|PTH_MODE_STATIC, &ev_key, until);
    pth_wait(ev);

    return 0;
}

/* Pth variant of sleep(3) */
unsigned int pth_sleep(unsigned int sec)
{
    pth_time_t until;
    pth_time_t offset;
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;

    /* calculate asleep time */
    offset = pth_time(sec, 0);
    pth_time_set(&until, PTH_TIME_NOW);
    pth_time_add(&until, &offset);
    
    /* and let thread sleep until this time is elapsed */
    ev = pth_event(PTH_EVENT_TIME|PTH_MODE_STATIC, &ev_key, until);
    pth_wait(ev);

    return 0;
}

/* Pth variant of POSIX pthread_sigmask(3) */
int pth_sigmask(int how, const sigset_t *set, sigset_t *oset)
{
    /* that's really easy in PTH, we just call POSIX sigprocmask().
       Because the Pth machine context switching is written to
       safe and restore also the complete signal set */
    return sigprocmask(how, set, oset);
}

/* Pth variant of POSIX sigwait(3) */
int pth_sigwait(const sigset_t *set, int *sigp)
{
    return pth_sigwait_ev(set, sigp, NULL);
}

/* Pth variant of POSIX sigwait(3) with extra events */
int pth_sigwait_ev(const sigset_t *set, int *sigp, pth_event_t ev_extra)
{
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;
    sigset_t pending;
    int sig;

    if (set == NULL || sigp == NULL)
        return_errno(EINVAL, EINVAL);

    /* check whether signal is already pending */
    if (sigpending(&pending) < 0)
        sigemptyset(&pending);
    for (sig = 1; sig < PTH_NSIG; sig++) {
        if (sigismember(set, sig) && sigismember(&pending, sig)) {
            pth_util_sigdelete(sig);
            *sigp = sig;
            return 0;
        }
    }

    /* create event and wait on it */
    ev = pth_event(PTH_EVENT_SIGS|PTH_MODE_STATIC, &ev_key, set, sigp);
    if (ev_extra != NULL)
        pth_event_concat(ev, ev_extra, NULL);
    pth_wait(ev);
    if (ev_extra != NULL) {
        pth_event_isolate(ev);
        if (!pth_event_occurred(ev))
            return_errno(EINTR, EINTR);
    }

    /* nothing to do, scheduler has already set *sigp for us */
    return 0;
}

/* Pth variant of select(2) */
int pth_select(int nfds, fd_set *readfds, fd_set *writefds, 
               fd_set *exceptfds, struct timeval *timeout)
{
    return pth_select_ev(nfds, readfds, writefds, exceptfds, timeout, NULL);
}

/* Pth variant of select(2) with extra events */
int pth_select_ev(int nfd, fd_set *rfds, fd_set *wfds, 
                  fd_set *efds, struct timeval *timeout, pth_event_t ev_extra)
{
    struct timeval delay;
    pth_event_t ev;
    pth_event_t ev_select;
    pth_event_t ev_timeout;
    static pth_key_t ev_key_select  = PTH_KEY_INIT;
    static pth_key_t ev_key_timeout = PTH_KEY_INIT;
    fd_set rspare, wspare, espare;
    fd_set *rtmp, *wtmp, *etmp;
    int selected;
    int rc;

    /* first deal with the special situation of a plain microsecond delay */
    if (nfd == 0 && rfds == NULL && wfds == NULL && efds == NULL && timeout != NULL) {
        if (timeout->tv_sec < 0 || timeout->tv_usec < 0)
            return_errno(-1, EINVAL);
        if (timeout->tv_sec == 0 && timeout->tv_usec < 500000) {
            /* very small delays we can accept to perform directly */
            while (   pth_sc(select)(0, NULL, NULL, NULL, timeout) < 0 
                   && errno == EINTR) ;
        }
        else {
            /* larger delays have to use the scheduler */
            ev = pth_event(PTH_EVENT_TIME|PTH_MODE_STATIC, 
                           &ev_key_timeout, *timeout);
            if (ev_extra != NULL)
                pth_event_concat(ev, ev_extra, NULL);
            pth_wait(ev);
            if (ev_extra != NULL) {
                pth_event_isolate(ev);
                if (!pth_event_occurred(ev))
                    return_errno(-1, EINTR);
            }
        }
        return 0;
    }

    /* first directly poll filedescriptor sets to avoid unneccessary 
       (and resource consuming because of context switches, etc) event
       handling through the scheduler. We've to be carefully here, because not
       all platforms guarranty us that the sets are unmodified when an error
       or timeout occured. */
    delay.tv_sec  = 0; 
    delay.tv_usec = 0;
    rtmp = NULL;
    if (rfds != NULL) {
        rspare = *rfds;
        rtmp = &rspare;
    }
    wtmp = NULL;
    if (wfds != NULL) {
        wspare = *wfds;
        wtmp = &wspare;
    }
    etmp = NULL;
    if (efds != NULL) {
        espare = *efds;
        etmp = &espare;
    }
    while ((rc = pth_sc(select)(nfd, rtmp, wtmp, etmp, &delay)) < 0 && errno == EINTR) ; 
    if (rc > 0) {
        if (rfds != NULL)
            *rfds = rspare;
        if (wfds != NULL)
            *wfds = wspare;
        if (efds != NULL)
            *efds = espare;
        return rc;
    }
    if (rc == 0 && timeout != NULL)
        if (pth_time_cmp(timeout, PTH_TIME_ZERO) == 0)
            return 0;

    /* suspend current thread until one fd is ready or the timeout occurred */
    ev = ev_select = pth_event(PTH_EVENT_SELECT|PTH_MODE_STATIC, 
                               &ev_key_select, &rc, nfd, rfds, wfds, wfds);
    ev_timeout = NULL;
    if (timeout != NULL) {
        ev_timeout = pth_event(PTH_EVENT_TIME|PTH_MODE_STATIC, 
                               &ev_key_timeout, *timeout);
        pth_event_concat(ev, ev_timeout, NULL);
    }
    if (ev_extra != NULL)
        pth_event_concat(ev, ev_extra, NULL);
    pth_wait(ev);
    selected = FALSE;
    pth_event_isolate(ev_select);
    if (pth_event_occurred(ev_select))
        selected = TRUE;
    if (timeout != NULL) {
        pth_event_isolate(ev_timeout);
        if (pth_event_occurred(ev_timeout))
            selected = TRUE;
    }
    if (ev_extra != NULL && !selected)
        return_errno(-1, EINTR);
    return rc;
}

/* Pth variant of select(2) */
int pth_poll(struct pollfd *pfd, unsigned int nfd, int timeout)
{
    return pth_poll_ev(pfd, nfd, timeout, NULL);
}

/* Pth variant of poll(2) with extra events: 
   NOTICE: THIS HAS TO BE BASED ON pth_select(2) BECAUSE
           INTERNALLY THE SCHEDULER IS ONLY select(2) BASED!! */
int pth_poll_ev(struct pollfd *pfd, unsigned int nfd, int timeout, pth_event_t ev_extra)
{
    fd_set rfds, wfds, efds;
    struct timeval tv, *ptv;
    int maxfd, rc, i, ok;
    char data[64];

    /* poll(2) semantics */
    if (pfd == NULL)
        return_errno(-1, EFAULT);

    /* convert timeout number into a timeval structure */
    ptv = &tv;
    if (timeout == 0) {
        /* return immediately */
        ptv->tv_sec  = 0;
        ptv->tv_usec = 0;
    }
    else if (timeout == INFTIM) {
        /* wait forever */
        ptv = NULL;
    }
    else {
        /* return after timeout */
        ptv->tv_sec  = timeout / 1000;
        ptv->tv_usec = (timeout % 1000) * 1000;
    }

    /* create fd sets and determine max fd */
    maxfd = -1;
    FD_ZERO(&rfds);
    FD_ZERO(&wfds);
    FD_ZERO(&efds);
    for(i = 0; i < nfd; i++) {
        if (pfd[i].fd < 0)
            continue;
        if (pfd[i].events & POLLIN)
            FD_SET(pfd[i].fd, &rfds);
        if (pfd[i].events & POLLOUT)
            FD_SET(pfd[i].fd, &wfds);
        if (pfd[i].events & POLLPRI)
            FD_SET(pfd[i].fd, &efds);
        if (pfd[i].fd >= maxfd && (pfd[i].events & (POLLIN|POLLOUT|POLLPRI)))
            maxfd = pfd[i].fd;
    }
    if (maxfd == -1)
        return_errno(-1, EINVAL);

    /* examine fd sets */
    rc = pth_select_ev(maxfd+1, &rfds, &wfds, &efds, ptv, ev_extra);

    /* establish results */
    if (rc > 0) {
        rc = 0;
        for (i = 0; i < nfd; i++) {
            ok = 0;
            pfd[i].revents = 0;
            if (pfd[i].fd < 0) {
                pfd[i].revents |= POLLNVAL;
                continue;
            }
            if (FD_ISSET(pfd[i].fd, &rfds)) {
                pfd[i].revents |= POLLIN;
                ok++;
                /* support for POLLHUP */
                if (recv(pfd[i].fd, data, 64, MSG_PEEK) == -1) {
                    if (   errno == ESHUTDOWN    || errno == ECONNRESET
                        || errno == ECONNABORTED || errno == ENETRESET) {
                        pfd[i].revents &= ~(POLLIN);
                        pfd[i].revents |= POLLHUP;
                        ok--;
                    }
                }
            }
            if (FD_ISSET(pfd[i].fd, &wfds)) {
                pfd[i].revents |= POLLOUT;
                ok++;
            }
            if (FD_ISSET(pfd[i].fd, &efds)) {
                pfd[i].revents |= POLLPRI;
                ok++;
            }
            if (ok)
                rc++;
        }
    }
    return rc;
}

/* Pth variant of connect(2) */
int pth_connect(int s, const struct sockaddr *addr, int addrlen)
{
    return pth_connect_ev(s, addr, addrlen, NULL);
}

/* Pth variant of connect(2) with extra events */
int pth_connect_ev(int s, const struct sockaddr *addr, int addrlen, pth_event_t ev_extra)
{
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;
    int rv, err, errlen;
  
    /* try to connect */
    while (   (rv = pth_sc(connect)(s, (struct sockaddr *)addr, addrlen)) == -1 
           && errno == EINTR)
        ;
    /* when it is still on progress wait until socket is really writeable */
    if (rv == -1 && errno == EINPROGRESS) {
        ev = pth_event(PTH_EVENT_FD|PTH_UNTIL_FD_WRITEABLE|PTH_MODE_STATIC, &ev_key, s);
        if (ev_extra != NULL)
            pth_event_concat(ev, ev_extra, NULL);
        pth_wait(ev);
        if (ev_extra != NULL) {
            pth_event_isolate(ev);
            if (!pth_event_occurred(ev))
                return_errno(-1, EINTR);
        }
        errlen = sizeof(err);
        if (getsockopt(s, SOL_SOCKET, SO_ERROR, (void *)&err, &errlen) == -1)
            return -1;
        if (err == 0) 
            return 0;
        return_errno(rv, err);
    }
    return rv;
}

/* Pth variant of accept(2) */
int pth_accept(int s, struct sockaddr *addr, int *addrlen)
{
    return pth_accept_ev(s, addr, addrlen, NULL);
}

/* Pth variant of accept(2) with extra events */
int pth_accept_ev(int s, struct sockaddr *addr, int *addrlen, pth_event_t ev_extra)
{
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;
    int rv;

    /* poll socket via accept */
    ev = NULL;
    while ((rv = pth_sc(accept)(s, addr, addrlen)) == -1
           && (errno == EAGAIN || errno == EWOULDBLOCK)) {
        /* do lazy event allocation */
        if (ev == NULL) {
            ev = pth_event(PTH_EVENT_FD|PTH_UNTIL_FD_READABLE|PTH_MODE_STATIC, &ev_key, s);
            if (ev_extra != NULL)
                pth_event_concat(ev, ev_extra, NULL);
        }
        /* wait until accept has a chance */
        pth_wait(ev);
        /* check for the extra events */
        if (ev_extra != NULL) {
            pth_event_isolate(ev);
            if (!pth_event_occurred(ev))
                return_errno(-1, EINTR);
        }
    }
    return rv;
}

/* Pth variant of read(2) */
ssize_t pth_read(int fd, void *buf, size_t nbytes)
{
    return pth_read_ev(fd, buf, nbytes, NULL);
}

/* Pth variant of read(2) with extra event(s) */
ssize_t pth_read_ev(int fd, void *buf, size_t nbytes, pth_event_t ev_extra)
{
    struct timeval delay;
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;
    fd_set fds;
    int n;

    pth_implicit_init();
    pth_debug2("pth_read_ev: enter from thread \"%s\"", pth_current->name);

    /* first directly poll filedescriptor for readability 
       to avoid unneccessary (and resource consuming because of context
       switches, etc) event handling through the scheduler */
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    delay.tv_sec  = 0; 
    delay.tv_usec = 0;
    while ((n = pth_sc(select)(fd+1, &fds, NULL, NULL, &delay)) < 0 
           && errno == EINTR) ; 

    /* when filedescriptor is still not readable, 
       let thread sleep until it is or event occurs */
    if (n < 1) {
        ev = pth_event(PTH_EVENT_FD|PTH_UNTIL_FD_READABLE|PTH_MODE_STATIC, &ev_key, fd);
        if (ev_extra != NULL)
            pth_event_concat(ev, ev_extra, NULL);
        n = pth_wait(ev);
        if (ev_extra != NULL) {
            pth_event_isolate(ev);
            if (!pth_event_occurred(ev))
                return_errno(-1, EINTR);
        }
    }

    /* now perform the actual read */
    while ((n = pth_sc(read)(fd, buf, nbytes)) < 0 
           && errno == EINTR) ;

    pth_debug2("pth_read_ev: leave to thread \"%s\"", pth_current->name);
    return n;
}

/* Pth variant of write(2) */
ssize_t pth_write(int fd, const void *buf, size_t nbytes)
{
    return pth_write_ev(fd, buf, nbytes, NULL);
}

/* Pth variant of write(2) with extra event(s) */
ssize_t pth_write_ev(int fd, const void *buf, size_t nbytes, pth_event_t ev_extra)
{
    struct timeval delay;
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;
    fd_set fds;
    int n;

    pth_implicit_init();
    pth_debug2("pth_write_ev: enter from thread \"%s\"", pth_current->name);

    /* first directly poll filedescriptor for writeability 
       to avoid unneccessary (and resource consuming because of context
       switches, etc) event handling through the scheduler */
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    delay.tv_sec  = 0; 
    delay.tv_usec = 0;
    while ((n = pth_sc(select)(fd+1, NULL, &fds, NULL, &delay)) < 0 
           && errno == EINTR) ; 

    /* when filedescriptor is still not writeable, 
       let thread sleep until it is or event occurs */
    if (n < 1) {
        ev = pth_event(PTH_EVENT_FD|PTH_UNTIL_FD_WRITEABLE|PTH_MODE_STATIC, &ev_key, fd);
        if (ev_extra != NULL)
            pth_event_concat(ev, ev_extra, NULL);
        pth_wait(ev);
        if (ev_extra != NULL) {
            pth_event_isolate(ev);
            if (!pth_event_occurred(ev))
                return_errno(-1, EINTR);
        }
    }

    /* now perform the actual write */
    while ((n = pth_sc(write)(fd, buf, nbytes)) < 0 
           && errno == EINTR) ;

    pth_debug2("pth_write_ev: leave to thread \"%s\"", pth_current->name);
    return n;
}

/* Pth variant of waitpid(2) */
pid_t pth_waitpid(pid_t wpid, int *status, int options)
{
    pth_event_t ev;
    static pth_key_t ev_key = PTH_KEY_INIT;
    pid_t pid;
    
    pth_debug2("pth_waitpid: called from thread \"%s\"", pth_current->name);
    
    /* do a non-blocking poll for the pid */
    while ((pid = pth_sc(waitpid)(wpid, status, options|WNOHANG)) < 0 && errno == EINTR) ;
    /* if pid was found or caller requested a polling return immediately */
    if (pid < 0 || pid > 0)
        return pid;
    if (pid == 0 && (options & WNOHANG))
        return 0;

    /* else create a pid event and block current thread until it occurred */
    ev = pth_event(PTH_EVENT_PID|PTH_MODE_STATIC, 
                   &ev_key, wpid, status, options);
    pth_wait(ev);

    /* finally do the waitpid again for the result value and return it (?REALLY?) */
    /* pid = pth_sc(waitpid)(wpid, status, options); */
    return pid;
}

