root/usr/src/cmd/cmd-inet/usr.lib/wpad/eloop.c
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2003-2004, Jouni Malinen <jkmaline@cc.hut.fi>
 * Sun elects to license this software under the BSD license.
 * See README for more details.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <poll.h>

#include "eloop.h"

static struct eloop_data eloop;
/*
 * Initialize global event loop data - must be called before any other eloop_*
 * function. user_data is a pointer to global data structure and will be passed
 * as eloop_ctx to signal handlers.
 */
void
eloop_init(void *user_data)
{
        (void) memset(&eloop, 0, sizeof (eloop));
        eloop.user_data = user_data;
}

/*
 * Register handler for read event
 */
int
eloop_register_read_sock(int sock,
    void (*handler)(int sock, void *eloop_ctx,
    void *sock_ctx), void *eloop_data, void *user_data)
{
        struct eloop_sock *tmp;

        tmp = (struct eloop_sock *)realloc(eloop.readers,
            (eloop.reader_count + 1) * sizeof (struct eloop_sock));
        if (tmp == NULL)
                return (-1);

        tmp[eloop.reader_count].sock = sock;
        tmp[eloop.reader_count].eloop_data = eloop_data;
        tmp[eloop.reader_count].user_data = user_data;
        tmp[eloop.reader_count].handler = handler;
        eloop.reader_count++;
        eloop.readers = tmp;
        if (sock > eloop.max_sock)
                eloop.max_sock = sock;

        return (0);
}

void
eloop_unregister_read_sock(int sock)
{
        int i;

        if (eloop.readers == NULL || eloop.reader_count == 0)
                return;

        for (i = 0; i < eloop.reader_count; i++) {
                if (eloop.readers[i].sock == sock)
                        break;
        }
        if (i == eloop.reader_count)
                return;
        if (i != eloop.reader_count - 1) {
                (void) memmove(&eloop.readers[i], &eloop.readers[i + 1],
                    (eloop.reader_count - i - 1) *
                    sizeof (struct eloop_sock));
        }
        eloop.reader_count--;
}

/*
 * Register timeout routines
 */
int
eloop_register_timeout(unsigned int secs, unsigned int usecs,
    void (*handler)(void *eloop_ctx, void *timeout_ctx),
    void *eloop_data, void *user_data)
{
        struct eloop_timeout *timeout, *tmp, *prev;

        timeout = (struct eloop_timeout *)malloc(sizeof (*timeout));
        if (timeout == NULL)
                return (-1);
        (void) gettimeofday(&timeout->time, NULL);
        timeout->time.tv_sec += secs;
        timeout->time.tv_usec += usecs;
        while (timeout->time.tv_usec >= 1000000) {
                timeout->time.tv_sec++;
                timeout->time.tv_usec -= 1000000;
        }
        timeout->eloop_data = eloop_data;
        timeout->user_data = user_data;
        timeout->handler = handler;
        timeout->next = NULL;

        if (eloop.timeout == NULL) {
                eloop.timeout = timeout;
                return (0);
        }

        prev = NULL;
        tmp = eloop.timeout;
        while (tmp != NULL) {
                if (timercmp(&timeout->time, &tmp->time, < /* */))
                        break;
                prev = tmp;
                tmp = tmp->next;
        }

        if (prev == NULL) {
                timeout->next = eloop.timeout;
                eloop.timeout = timeout;
        } else {
                timeout->next = prev->next;
                prev->next = timeout;
        }

        return (0);
}

/*
 * Cancel timeouts matching <handler,eloop_data,user_data>.
 * ELOOP_ALL_CTX can be used as a wildcard for cancelling all timeouts
 * regardless of eloop_data/user_data.
 */
void
eloop_cancel_timeout(void (*handler)(void *eloop_ctx, void *sock_ctx),
    void *eloop_data, void *user_data)
{
        struct eloop_timeout *timeout, *prev, *next;

        prev = NULL;
        timeout = eloop.timeout;
        while (timeout != NULL) {
                next = timeout->next;

                if (timeout->handler == handler &&
                    (timeout->eloop_data == eloop_data ||
                    eloop_data == ELOOP_ALL_CTX) &&
                    (timeout->user_data == user_data ||
                    user_data == ELOOP_ALL_CTX)) {
                        if (prev == NULL)
                                eloop.timeout = next;
                        else
                                prev->next = next;
                        free(timeout);
                } else
                        prev = timeout;

                timeout = next;
        }
}

static void eloop_handle_signal(int sig)
{
        int i;

        eloop.signaled++;
        for (i = 0; i < eloop.signal_count; i++) {
                if (eloop.signals[i].sig == sig) {
                        eloop.signals[i].signaled++;
                        break;
                }
        }
}

static void eloop_process_pending_signals(void)
{
        int i;

        if (eloop.signaled == 0)
                return;
        eloop.signaled = 0;

        for (i = 0; i < eloop.signal_count; i++) {
                if (eloop.signals[i].signaled) {
                        eloop.signals[i].signaled = 0;
                        eloop.signals[i].handler(eloop.signals[i].sig,
                            eloop.user_data, eloop.signals[i].user_data);
                }
        }
}

/*
 * Register handler for signal.
 * Note: signals are 'global' events and there is no local eloop_data pointer
 * like with other handlers. The (global) pointer given to eloop_init() will be
 * used as eloop_ctx for signal handlers.
 */
int
eloop_register_signal(int sig,
    void (*handler)(int sig, void *eloop_ctx, void *signal_ctx),
    void *user_data)
{
        struct eloop_signal *tmp;

        tmp = (struct eloop_signal *)
            realloc(eloop.signals,
            (eloop.signal_count + 1) *
            sizeof (struct eloop_signal));
        if (tmp == NULL)
                return (-1);

        tmp[eloop.signal_count].sig = sig;
        tmp[eloop.signal_count].user_data = user_data;
        tmp[eloop.signal_count].handler = handler;
        tmp[eloop.signal_count].signaled = 0;
        eloop.signal_count++;
        eloop.signals = tmp;
        (void) signal(sig, eloop_handle_signal);

        return (0);
}

/*
 * Start event loop and continue running as long as there are any registered
 * event handlers.
 */
void
eloop_run(void)
{
        struct pollfd pfds[MAX_POLLFDS];        /* array of polled fd */
        int i, res;
        int default_t, t;
        struct timeval tv, now;

        default_t = 5 * 1000;   /* 5 seconds */
        while (!eloop.terminate &&
            (eloop.timeout || eloop.reader_count > 0)) {
                if (eloop.timeout) {
                        (void) gettimeofday(&now, NULL);
                        if (timercmp(&now, &eloop.timeout->time, < /* */))
                                timersub(&eloop.timeout->time, &now, &tv);
                        else
                                tv.tv_sec = tv.tv_usec = 0;
                }

                t = (eloop.timeout == NULL ?
                    default_t : (tv.tv_sec * 1000 + tv.tv_usec / 1000));
                for (i = 0; i < eloop.reader_count; i++) {
                        pfds[i].fd = eloop.readers[i].sock;
                        pfds[i].events = POLLIN | POLLPRI;
                }
                res = poll(pfds, eloop.reader_count, t);
                if (res < 0 && errno != EINTR)
                        return;

                eloop_process_pending_signals();

                /* check if some registered timeouts have occurred */
                if (eloop.timeout) {
                        struct eloop_timeout *tmp;

                        (void) gettimeofday(&now, NULL);
                        if (!timercmp(&now, &eloop.timeout->time, < /* */)) {
                                tmp = eloop.timeout;
                                eloop.timeout = eloop.timeout->next;
                                tmp->handler(tmp->eloop_data, tmp->user_data);
                                free(tmp);
                        }

                }

                if (res <= 0)
                        continue;

                for (i = 0; i < eloop.reader_count; i++) {
                        if (pfds[i].revents) {
                                eloop.readers[i].handler(
                                    eloop.readers[i].sock,
                                    eloop.readers[i].eloop_data,
                                    eloop.readers[i].user_data);
                        }
                }
        }
}

/*
 * Terminate event loop even if there are registered events.
 */
void
eloop_terminate(void)
{
        eloop.terminate = 1;
}


/*
 * Free any reserved resources. After calling eloop_destoy(), other eloop_*
 * functions must not be called before re-running eloop_init().
 */
void
eloop_destroy(void)
{
        struct eloop_timeout *timeout, *prev;

        timeout = eloop.timeout;
        while (timeout != NULL) {
                prev = timeout;
                timeout = timeout->next;
                free(prev);
        }
        free(eloop.readers);
        free(eloop.signals);
}