root/usr.sbin/nsd/mini_event.c
/*
 * mini_event.c - implementation of part of libevent api, portably.
 *
 * Copyright (c) 2007, NLnet Labs. All rights reserved.
 * 
 * This software is open source.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * 
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * 
 * Neither the name of the NLNET LABS nor the names of its contributors may
 * be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

/**
 * \file
 * fake libevent implementation. Less broad in functionality, and only
 * supports select(2).
 */

#include "config.h"
#ifdef HAVE_TIME_H
#include <time.h>
#endif
#include <string.h>
#include <errno.h>
#include <sys/time.h>

#if defined(USE_MINI_EVENT) && !defined(USE_WINSOCK)
#ifdef HAVE_WINSOCK2_H
#define FD_SET_T (u_int)
#else
#define FD_SET_T 
#endif

#include <signal.h>
#include "mini_event.h"
#include "util.h"

/** compare events in tree, based on timevalue, ptr for uniqueness */
int
mini_ev_cmp(const void* a, const void* b)
{
        const struct event* e = (const struct event*)a;
        const struct event* f = (const struct event*)b;
        if(e->ev_timeout.tv_sec < f->ev_timeout.tv_sec)
                return -1;
        if(e->ev_timeout.tv_sec > f->ev_timeout.tv_sec)
                return 1;
        if(e->ev_timeout.tv_usec < f->ev_timeout.tv_usec)
                return -1;
        if(e->ev_timeout.tv_usec > f->ev_timeout.tv_usec)
                return 1;
        if(e < f)
                return -1;
        if(e > f)
                return 1;
        return 0;
}

/** set time */
static int
settime(struct event_base* base)
{
        if(gettimeofday(base->time_tv, NULL) < 0) {
                return -1;
        }
#ifndef S_SPLINT_S
        *base->time_secs = (time_t)base->time_tv->tv_sec;
#endif
        return 0;
}

/** create event base */
void *
event_init(time_t* time_secs, struct timeval* time_tv)
{
        struct event_base* base = (struct event_base*)malloc(
                sizeof(struct event_base));
        if(!base)
                return NULL;
        memset(base, 0, sizeof(*base));
        base->region = region_create(xalloc, free);
        if(!base->region) {
                free(base);
                return NULL;
        }
        base->time_secs = time_secs;
        base->time_tv = time_tv;
        if(settime(base) < 0) {
                event_base_free(base);
                return NULL;
        }
        base->times = rbtree_create(base->region, mini_ev_cmp);
        if(!base->times) {
                event_base_free(base);
                return NULL;
        }
        base->capfd = MAX_FDS;
#ifdef FD_SETSIZE
        if((int)FD_SETSIZE < base->capfd)
                base->capfd = (int)FD_SETSIZE;
#endif
        base->fds = (struct event**)calloc((size_t)base->capfd, 
                sizeof(struct event*));
        if(!base->fds) {
                event_base_free(base);
                return NULL;
        }
        base->signals = (struct event**)calloc(MAX_SIG, sizeof(struct event*));
        if(!base->signals) {
                event_base_free(base);
                return NULL;
        }
#ifndef S_SPLINT_S
        FD_ZERO(&base->reads);
        FD_ZERO(&base->writes);
#endif
        return base;
}

/** get version */
const char *
event_get_version(void)
{
        return "mini-event-"PACKAGE_VERSION;
}

/** get polling method, select */
const char *
event_get_method(void)
{
        return "select";
}

/** call timeouts handlers, and return how long to wait for next one or -1 */
static int
handle_timeouts(struct event_base* base, struct timeval* now, 
        struct timeval* wait)
{
        struct event* p;
        int tofired = 0;
#ifndef S_SPLINT_S
        wait->tv_sec = (time_t)-1;
#endif

        while((rbnode_type*)(p = (struct event*)rbtree_first(base->times))
                !=RBTREE_NULL) {
#ifndef S_SPLINT_S
                if(p->ev_timeout.tv_sec > now->tv_sec ||
                        (p->ev_timeout.tv_sec==now->tv_sec && 
                        p->ev_timeout.tv_usec > now->tv_usec)) {
                        /* there is a next larger timeout. wait for it */
                        wait->tv_sec = p->ev_timeout.tv_sec - now->tv_sec;
                        if(now->tv_usec > p->ev_timeout.tv_usec) {
                                wait->tv_sec--;
                                wait->tv_usec = 1000000 - (now->tv_usec -
                                        p->ev_timeout.tv_usec);
                        } else {
                                wait->tv_usec = p->ev_timeout.tv_usec 
                                        - now->tv_usec;
                        }
                        return tofired;
                }
#endif
                /* event times out, remove it */
                tofired = 1;
                (void)rbtree_delete(base->times, p);
                p->ev_flags &= ~EV_TIMEOUT;
                (*p->ev_callback)(p->ev_fd, EV_TIMEOUT, p->ev_arg);
        }
        return tofired;
}

/** call select and callbacks for that */
static int
handle_select(struct event_base* base, struct timeval* wait)
{
        fd_set r, w;
        int ret, i;

#ifndef S_SPLINT_S
        if(wait->tv_sec==(time_t)-1)
                wait = NULL;
#endif
        memmove(&r, &base->reads, sizeof(fd_set));
        memmove(&w, &base->writes, sizeof(fd_set));
        memmove(&base->ready, &base->content, sizeof(fd_set));

        if((ret = select(base->maxfd+1, &r, &w, NULL, wait)) == -1) {
                ret = errno;
                if(settime(base) < 0)
                        return -1;
                errno = ret;
                if(ret == EAGAIN || ret == EINTR)
                        return 0;
                return -1;
        }
        if(settime(base) < 0)
                return -1;
        
        for(i=0; i<base->maxfd+1; i++) {
                short bits = 0;
                if(!base->fds[i] || !(FD_ISSET(i, &base->ready))) {
                        continue;
                }
                if(FD_ISSET(i, &r)) {
                        bits |= EV_READ;
                        ret--;
                }
                if(FD_ISSET(i, &w)) {
                        bits |= EV_WRITE;
                        ret--;
                }
                bits &= base->fds[i]->ev_flags;
                if(bits) {
                        (*base->fds[i]->ev_callback)(base->fds[i]->ev_fd, 
                                bits, base->fds[i]->ev_arg);
                        if(ret==0)
                                break;
                }
        }
        return 0;
}

/** run select once */
int
event_base_loop(struct event_base* base, int flags)
{
        struct timeval wait;
        if(!(flags & EVLOOP_ONCE))
                return event_base_dispatch(base);
        /* see if timeouts need handling */
        if(handle_timeouts(base, base->time_tv, &wait))
                return 0; /* there were timeouts, end of loop */
        if(base->need_to_exit)
                return 0;
        /* do select */
        if(handle_select(base, &wait) < 0) {
                if(base->need_to_exit)
                        return 0;
                return -1;
        }
        return 0;
}

/** run select in a loop */
int
event_base_dispatch(struct event_base* base)
{
        struct timeval wait;
        if(settime(base) < 0)
                return -1;
        while(!base->need_to_exit)
        {
                /* see if timeouts need handling */
                (void)handle_timeouts(base, base->time_tv, &wait);
                if(base->need_to_exit)
                        return 0;
                /* do select */
                if(handle_select(base, &wait) < 0) {
                        if(base->need_to_exit)
                                return 0;
                        return -1;
                }
        }
        return 0;
}

/** exit that loop */
int
event_base_loopexit(struct event_base* base, 
        struct timeval* ATTR_UNUSED(tv))
{
        base->need_to_exit = 1;
        return 0;
}

int event_base_loopbreak(struct event_base * base)
{
        return event_base_loopexit(base, NULL);
}

/* free event base, free events yourself */
void
event_base_free(struct event_base* base)
{
        if(!base)
                return;
        /* base->times is allocated in region and is freed with the region */
        if(base->fds)
                free(base->fds);
        if(base->signals)
                free(base->signals);
        region_destroy(base->region);
        free(base);
}

/** set content of event */
void
event_set(struct event* ev, int fd, short bits, 
        void (*cb)(int, short, void *), void* arg)
{
        ev->node.key = ev;
        ev->ev_fd = fd;
        ev->ev_flags = bits;
        ev->ev_callback = cb;
        ev->ev_arg = arg;
        ev->added = 0;
}

/* add event to a base */
int
event_base_set(struct event_base* base, struct event* ev)
{
        ev->ev_base = base;
        ev->added = 0;
        return 0;
}

/* add event to make it active, you may not change it with event_set anymore */
int
event_add(struct event* ev, struct timeval* tv)
{
        if(ev->added)
                event_del(ev);
        if(ev->ev_fd != -1 && ev->ev_fd >= ev->ev_base->capfd)
                return -1;
        if( (ev->ev_flags&(EV_READ|EV_WRITE)) && ev->ev_fd != -1) {
                ev->ev_base->fds[ev->ev_fd] = ev;
                if((ev->ev_flags&EV_READ)) {
                        FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->reads);
                }
                if((ev->ev_flags&EV_WRITE)) {
                        FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->writes);
                }
                FD_SET(FD_SET_T ev->ev_fd, &ev->ev_base->content);
                FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->ready);
                if(ev->ev_fd > ev->ev_base->maxfd)
                        ev->ev_base->maxfd = ev->ev_fd;
        }
        if(tv && (ev->ev_flags&EV_TIMEOUT)) {
#ifndef S_SPLINT_S
                struct timeval* now = ev->ev_base->time_tv;
                ev->ev_timeout.tv_sec = tv->tv_sec + now->tv_sec;
                ev->ev_timeout.tv_usec = tv->tv_usec + now->tv_usec;
                while(ev->ev_timeout.tv_usec >= 1000000) {
                        ev->ev_timeout.tv_usec -= 1000000;
                        ev->ev_timeout.tv_sec++;
                }
#endif
                (void)rbtree_insert(ev->ev_base->times, &ev->node);
        }
        ev->added = 1;
        return 0;
}

/* remove event, you may change it again */
int
event_del(struct event* ev)
{
        if(ev->ev_fd != -1 && ev->ev_fd >= ev->ev_base->capfd)
                return -1;
        if((ev->ev_flags&EV_TIMEOUT))
                (void)rbtree_delete(ev->ev_base->times, &ev->node);
        if((ev->ev_flags&(EV_READ|EV_WRITE)) && ev->ev_fd != -1) {
                ev->ev_base->fds[ev->ev_fd] = NULL;
                FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->reads);
                FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->writes);
                FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->ready);
                FD_CLR(FD_SET_T ev->ev_fd, &ev->ev_base->content);
        }
        ev->added = 0;
        return 0;
}

/** which base gets to handle signals */
static struct event_base* signal_base = NULL;

/** signal handler */
static RETSIGTYPE
sigh(int sig)
{
        struct event* ev;
        if(!signal_base || sig < 0 || sig >= MAX_SIG)
                return;
        ev = signal_base->signals[sig];
        if(!ev)
                return;
        (*ev->ev_callback)(sig, EV_SIGNAL, ev->ev_arg);
}

/** install signal handler */
int
signal_add(struct event* ev, struct timeval* ATTR_UNUSED(tv))
{
        struct sigaction action;
        if(ev->ev_fd == -1 || ev->ev_fd >= MAX_SIG)
                return -1;
        signal_base = ev->ev_base;
        ev->ev_base->signals[ev->ev_fd] = ev;
        ev->added = 1;
        action.sa_handler = sigh;
        sigfillset(&action.sa_mask);
        action.sa_flags = 0;
        return sigaction(ev->ev_fd, &action, NULL);
}

/** remove signal handler */
int
signal_del(struct event* ev)
{
        if(ev->ev_fd == -1 || ev->ev_fd >= MAX_SIG)
                return -1;
        ev->ev_base->signals[ev->ev_fd] = NULL;
        ev->added = 0;
        return 0;
}

#else /* USE_MINI_EVENT */
#ifndef USE_WINSOCK
int
mini_ev_cmp(const void* ATTR_UNUSED(a), const void* ATTR_UNUSED(b))
{
        return 0;
}
#endif /* not USE_WINSOCK */
#endif /* USE_MINI_EVENT */