/*
 *   linux_loop.c - Pluggable main loop using Linux system call epoll().
 *   Copyright 2010-2017 Citrix Systems, Inc. All rights reserved.
 *
 *   $Id$
 */

#include "citrix.h"

#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <pthread.h>
#include <sys/epoll.h>
#include <fcntl.h>

#include "ctxnevt.h"
#include "mainloop.h"

/* The IS_MAIN_LOOP macro will be defined when this file is built as the
 * program's primary event loop.  Without it a simplified loop that just polls
 * file descriptors is made; that is used by the SDL plugin.
 * With the simplified version, the loop (Epoll_run()) runs in its own thread,
 * with the other functions running in the main thread.

 * The method used to call back on descriptor ready depends on the macro.
 * Some functions are global only in the simplified version.
 */

#undef STATIC

#ifdef IS_MAIN_LOOP

#define STATIC static
#define READ_CALLBACK(fd)  CALL_IT(State.fd_cbs[fd].read)
#define WRITE_CALLBACK(fd) CALL_IT(State.fd_cbs[fd].write)

#else /* Simplified poll loop. */

#include "epoll_simple.h"
#define STATIC

#define READ_CALLBACK(fd)  Poller_callback(fd, 0)
#define WRITE_CALLBACK(fd) Poller_callback(fd, 1)
#endif /* IS_MAIN_LOOP */

/* Temporary definition, missing in PLANB build environment.
 * See also use of EPOLL_CLOEXEC, below.
 */

#ifndef PTHREAD_STACK_MIN
#define PTHREAD_STACK_MIN 16384 /* From /usr/include/bits/local_lim.h. */
#endif


BOOLEAN Done;         /* TEMPORARY test code. */

/* Local state information. */

struct fd_callbacks {
    struct pal_event *read, *write;
};

static struct {
    int                     epoll_fd;
    struct fd_callbacks    *fd_cbs;             /* Dynamic array. */
    int                     num_cbs;            /* Size of fd_cbs. */
#ifdef IS_MAIN_LOOP
    struct mainloop_plugin *pPlug;
    UINT32                  expiration;         /* Time to run next timer. */
    UINT32                (*timer_done)(void);  /* Timer fire callback. */
    pthread_t               main_thread;        /* Thread id of event loop. */
    BOOLEAN                 cross_thread_wake;  /* Set by signal handler. */
#endif /* IS_MAIN_LOOP */
} State;

/* Constants for epoll_xxx() system calls. */

#define MAX_EVENTS    4 /* More than enough. */

#ifdef ANDROID /* Old epoll.h. */
#define EPOLLRDHUP 0
#endif


#ifdef IS_MAIN_LOOP
#define READ_EVENTS  (EPOLLIN + EPOLLRDHUP + EPOLLERR)
#define WRITE_EVENTS  (EPOLLOUT + EPOLLRDHUP + EPOLLERR)
#else /* not IS_MAIN_LOOP */

/* The addition of EPOLLONESHOT causes monitoring of a file descriptor
 * to be suspended as soon as it becomes ready.  It is fully reenabled
 * only after the callback has been made, in the main thread.
 */

#define READ_EVENTS  (EPOLLIN + EPOLLRDHUP + EPOLLERR + EPOLLONESHOT)
#define WRITE_EVENTS  (EPOLLOUT + EPOLLRDHUP + EPOLLERR + EPOLLONESHOT)
#endif /* not IS_MAIN_LOOP */

/* Plugin function to register and remove callbacks for I/O readiness
 * in the style of select() and poll().
 * Only read and write operations are supported.
 * The return values are the EVT_XXXX definitions of the PAL.
 */

STATIC VPSTATUS Select_fd(struct pal_event *evt_p, int fd, BOOLEAN is_write)
{
    struct epoll_event   new_event;
    struct pal_event   **old, **other;
    int                  op;

    if (fd < 0) {

        /* The fd has been closed but we use it as an index so 
         * everything goes bad from here.  We perhaps should crash
         * here to prevent worse things from happening...
         */

        return EVT_OBJ_CREATE_FAILED;
    }

    if (fd >= State.num_cbs) {
        /* Expand the callbacks array. */

        State.fd_cbs = (struct fd_callbacks *)
            realloc(State.fd_cbs,
                    (fd + 1) * sizeof (struct fd_callbacks));
        if (!State.fd_cbs)
            return EVT_OBJ_CREATE_FAILED;
        memset(State.fd_cbs + State.num_cbs,
               0, (fd + 1 - State.num_cbs) * sizeof (struct fd_callbacks));
        State.num_cbs = fd + 1;
    }

    /* Set pointers to suppress tests for read/write later. */

    if (is_write) {
        old = &State.fd_cbs[fd].write;
        other = &State.fd_cbs[fd].read;
    } else {
        old = &State.fd_cbs[fd].read;
        other = &State.fd_cbs[fd].write;
    }    

    if (evt_p) {
        if (*old) {
            *old = evt_p;
            return EVT_SUCCESS; /* Done. */
        }
        op = EPOLL_CTL_ADD;
        new_event.events = is_write ? WRITE_EVENTS : READ_EVENTS;
    } else {
        if (!*old)
            return EVT_SUCCESS; /* Nothing to do. */
        op = EPOLL_CTL_DEL;
        new_event.events = 0;
    }

    /* Record the callback. */

    *old = evt_p;

    /* Check for existing callback on the other side. */

    if (*other) {
        op = EPOLL_CTL_MOD;
        new_event.events |= is_write ? READ_EVENTS : WRITE_EVENTS;
    }

    /* Tell the kernel. */

    new_event.data.fd = fd;
    if (epoll_ctl(State.epoll_fd, op, fd, &new_event) < 0)
        return EVT_OBJ_CREATE_FAILED;
    return EVT_SUCCESS;
}

/* Plugin function to run the event loop.  It never returns. */

STATIC void Epoll_run(void)
{
    sigset_t           no_sigs;
    int                count, interval;
    struct epoll_event ret_events[MAX_EVENTS];
#ifdef IS_MAIN_LOOP
    UINT32             now;
#endif /* IS_MAIN_LOOP */

    sigemptyset(&no_sigs);

    for (;;) {
#ifdef IS_MAIN_LOOP

        /* TEMPORARY test code. */

        if (Done)
            return;

        /* Check and run timers. */

        if (State.expiration) {
            now = Os_GetTickCountInMs();
            if (now >= State.expiration) {
                /* Run timers now. */

                interval = (*State.timer_done)();
                State.expiration = interval ? interval + now : 0;
            } else {
                /* Set timeout. */

                interval = State.expiration - now;
            }
        } else {
            /* No timeout. */

            interval = -1;
        }

        /* Call the client's work function. */

        if (Do_work())
            interval = 0;           /* Skip timeout. */
#else
        interval = -1;              /* Indefinite wait. */
#endif /* IS_MAIN_LOOP */

        /* Wait for event or timeout, signals enabled. */

#ifdef ANDROID /* Not using signals, as pthread_kill seems unreliable. */
        count = epoll_wait(State.epoll_fd, ret_events, MAX_EVENTS,  interval);
#else
        count = epoll_pwait(State.epoll_fd, ret_events, MAX_EVENTS,
                            interval, &no_sigs);
#endif
        if (count > 0) {
            /* Process the I/O ready events by making callbacks. */

            do {
                uint32_t mask;
                int      fd;

                --count;
                mask = ret_events[count].events;
                fd = ret_events[count].data.fd;
                if ((mask & READ_EVENTS) && State.fd_cbs[fd].read)
                    READ_CALLBACK(fd);
                if ((mask & WRITE_EVENTS) && State.fd_cbs[fd].write)
                    WRITE_CALLBACK(fd);
            } while (count > 0);
        } else if (count == 0) {
            continue; /* Timeout. */
        } else if (errno == EINTR) {
#ifdef IS_MAIN_LOOP
            /* Dispatch interrupt callbacks. */

            if (State.cross_thread_wake) {
                State.cross_thread_wake = FALSE;
                On_wake();
            } else {
                Take_signal();
            }
#endif /* IS_MAIN_LOOP */
        } else {
            perror("Unexpected error in epoll_pwait()");
            exit(1);
        }
    }
}

#ifdef IS_MAIN_LOOP
/* Plugin function to start a single one-shot timer.
 */

static void start_timer(UINT32 interval, UINT32 (*timer_done)(void))
{
    State.expiration = interval + Os_GetTickCountInMs();
    State.timer_done = timer_done;
}

/* Dummy signal handler, used below. */

static void input_handler(int n)
{
    State.cross_thread_wake = TRUE;
}

/* The receive thread wakes the event loop by calling this. */

static void wake_main(void)
{
    pthread_kill(State.main_thread, SIGUSR1);
}

#else /* not IS_MAIN_LOOP */

/* The simplified version needs a function to perform the callbacks.
 * This runs in the main thread, and may need to re-enable the one-shot poller.
 */

void Poller_make_call(int fd, int is_write)
{
    struct pal_event   **pev;
    struct epoll_event   new_event;

    pev = is_write ? &State.fd_cbs[fd].write :  &State.fd_cbs[fd].read;
    if (*pev)
        CALL_IT(*pev);

    /* Tell the kernel, if the file descriptor is still to be monitored. */

    new_event.events = (State.fd_cbs[fd].read) ? READ_EVENTS : 0;
    if (State.fd_cbs[fd].write)
        new_event.events |= WRITE_EVENTS;
    if (new_event.events) {
        new_event.data.fd = fd;
        epoll_ctl(State.epoll_fd, EPOLL_CTL_MOD, fd, &new_event);
    }
}
#endif /* not IS_MAIN_LOOP */

/* Initialisation. */

/* Create the epoll set. */

STATIC void Epoll_init(void)
{
#ifdef EPOLL_CLOEXEC
    State.epoll_fd = epoll_create1(EPOLL_CLOEXEC);
#else
    State.epoll_fd = epoll_create(8);

    /* With older (build) systems, the flag is not defined, do it by hand. */

    fcntl(State.epoll_fd, F_SETFD, FD_CLOEXEC);
#endif
}

#ifdef IS_MAIN_LOOP

/* Plugin initialisation. */

void Syscall_loop_init(struct mainloop_plugin *plug)
{
    struct sigaction    sig_act;

    State.pPlug = plug;

    Epoll_init();

    /* Set up our entry points and interface data. */

    plug->start_timer = start_timer;
    plug->select_fd = Select_fd;
    plug->run = Epoll_run;
    plug->create_thread = NULL;
    plug->wake_main = wake_main;

    /* Initialisation for wake_main (above).
     * The receive thread wakes the event loop by sending SIGUSR1.
     */

    State.main_thread = pthread_self();

    sig_act.sa_handler = input_handler;
    sigemptyset(&sig_act.sa_mask);
    sig_act.sa_flags = SA_RESTART;
    sigaction(SIGUSR1, &sig_act, NULL);
}
#endif /* IS_MAIN_LOOP */
