/************************************************************************
 *   IRC - Internet Relay Chat, src/socketengine_kqueue.c
 *   Copyright (C) 2003 Lucas Madar
 *
 * engine functions for the kqueue() socket engine
 *
 */

/* $Id: socketengine_kqueue.c,v 1.1.1.1 2005/06/27 03:02:14 sheik Exp $ */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#include "fds.h"

#include <sys/event.h>
#include <sys/time.h>

#define MAX_EVENT_QUEUE 64

static int kqueue_id = -1;
static struct kevent eventQs[2][MAX_EVENT_QUEUE+1];
static struct kevent *eventQ = eventQs[0];
static int eventQi = 0;
static int numEvents = 0;

static void 
kevent_add(struct kevent *e)
{
    if(kqueue_id == -1)
        abort();

    if(numEvents >= MAX_EVENT_QUEUE)
    {
        if(kevent(kqueue_id, eventQ, numEvents, NULL, 0, NULL) < 0)
            sendto_realops_lev(DEBUG_LEV, "kevent() returned error: %s", 
                               strerror(errno));
        numEvents = 0;
    }
    memcpy(&eventQ[numEvents++], e, sizeof(struct kevent));
}

void 
engine_init()
{
    kqueue_id = kqueue();
    numEvents = 0;
}

void 
engine_add_fd(int fd)
{
    struct kevent e;

    e.ident = fd;
    e.filter = EVFILT_READ;
    e.flags = EV_ADD|EV_DISABLE;
    e.fflags = 0;
    e.data = 0;
    e.udata = NULL;
    kevent_add(&e);

    e.ident = fd;
    e.filter = EVFILT_WRITE;
    e.flags = EV_ADD|EV_DISABLE;
    e.fflags = 0;
    e.data = 0;
    e.udata = NULL;
    kevent_add(&e);

    set_fd_internal(fd, 0);
}

void 
engine_del_fd(int fd)
{
/* we dont accually need to do this, as a close() clears the kevent
 * filters and automagically removes itself from the queue.
 * With the way we handle kevent() calls in kevent_add(), accually
 * running these EV_DELETE routines causes bad file descriptor returns
 * due to the fact that they could be close()'d before the kevent() is
 * run.  --epi
 */

/********

   struct kevent e;

   e.ident = fd;
   e.filter = EVFILT_READ;
   e.flags = EV_DELETE;
   e.fflags = 0;
   e.data = 0;
   e.udata = NULL;
   kevent_add(&e);

   e.ident = fd;
   e.filter = EVFILT_WRITE;
   e.flags = EV_DELETE;
   e.fflags = 0;
   e.data = 0;
   e.udata = NULL;
   kevent_add(&e);

********/

    /* But we should remove this fd from the change queue -- if it was closed
     * and we have a change pending, kevent() will fail later.  What's worse
     * is that when the queue is flushed due to being full, a kevent() failure
     * may leave some changes unprocessed.  Reordering the change queue is not
     * safe, hence the gymnastics below.    -Quension
     */
    int i, j;

    if (!numEvents)
        return;

    /* optimal case: fd isn't in the change queue */
    for (i = 0; i < numEvents; i++)
        if (eventQ[i].ident == fd)
            break;

    /* second optimal case: fd is last, truncate the queue */
    if (i == numEvents - 1)
        numEvents--;

    if (i == numEvents)
        return;

    /* swap array index, copy all fds before this one */
    eventQi ^= 1;
    memcpy(eventQs[eventQi], eventQ, sizeof(struct kevent) * i);

    /* selectively copy remaining fds, skip bad one */
    for (j = i++; i < numEvents; i++)
        if (eventQ[i].ident != fd)
            memcpy(&eventQs[eventQi][j++], &eventQ[i], sizeof(struct kevent));

    /* swap active array */
    numEvents = j;
    eventQ = eventQs[eventQi];
}

void 
engine_change_fd_state(int fd, unsigned int stateplus)
{
    unsigned int oldflags = (unsigned int) get_fd_internal(fd);
    struct kevent e;

    /* Something changed with our read state? */
    if((oldflags ^ stateplus) & FDF_WANTREAD)
    {
        e.ident = fd;
        e.filter = EVFILT_READ;
        e.flags = EV_ADD|((stateplus & FDF_WANTREAD) ? EV_ENABLE : EV_DISABLE);
        e.fflags = 0;
        e.data = 0;
        e.udata = 0;
        kevent_add(&e);
    }

    /* Something changed with our write state? */
    if((oldflags ^ stateplus) & FDF_WANTWRITE)
    {
        e.ident = fd;
        e.filter = EVFILT_WRITE;
        e.flags = EV_ADD|((stateplus & FDF_WANTWRITE) ? EV_ENABLE : EV_DISABLE);
        e.fflags = 0;
        e.data = 0;
        e.udata = 0;
        kevent_add(&e);
    }

    set_fd_internal(fd, (void *) stateplus);
}

#define ENGINE_MAX_EVENTS 512
#define ENGINE_MAX_LOOPS (2 * (MAXCONNECTIONS / 512))

int 
engine_read_message(time_t delay)
{
    static struct kevent events[ENGINE_MAX_EVENTS];

    int nevs, length, i, numloops, eventsfull;
    unsigned int fdflags;
    int fdtype;
    void *fdvalue;
    aClient *cptr;
    aListener *lptr;
    struct timespec wait;

    numloops = 0;
    wait.tv_sec = delay;
    wait.tv_nsec = 0;

    do
    {
        nevs = kevent(kqueue_id, eventQ, numEvents, events, 
                      ENGINE_MAX_EVENTS, &wait);
        numEvents = 0;

        if(nevs == 0)
            return 0;

        if (nevs < 0)
        {
            if((errno == EINTR) || (errno == EAGAIN))
                return -1;

            report_error("kevent %s:%s", &me);
            sleep(5);
            return -1;
        }

        eventsfull = (nevs == ENGINE_MAX_EVENTS) ? 1 : 0;
        if(delay || numloops)
            NOW = timeofday = time(NULL);
        numloops++;
      
        for(i = 0; i < nevs; i++)
        {
            int rr = 0, rw = 0;

            if(events[i].flags & EV_ERROR)
            {
                errno = events[i].data;
                /* this should be handled later i suppose */
                continue;
            }

            get_fd_info(events[i].ident, &fdtype, &fdflags, &fdvalue);

            if(events[i].filter == EVFILT_READ)
                rr = 1;
            else if(events[i].filter == EVFILT_WRITE)
                rw = 1;

            cptr = NULL;
            length = -1;

            switch(fdtype)
            {
                case FDT_NONE:
                    break;

                case FDT_AUTH:
                    cptr = (aClient *) fdvalue;
                    if (rr)
                        read_authports(cptr);
                    else if (rw && cptr->authfd >= 0)
                        send_authports(cptr);
                    check_client_fd(cptr);
                    break;

                case FDT_LISTENER:
                    lptr = (aListener *) fdvalue;
                    if(rr)
                        accept_connection(lptr);
                    break;

                case FDT_RESOLVER:
                    do_dns_async();
                    break;

                case FDT_CLIENT:
                    cptr = (aClient *) fdvalue;
                    readwrite_client(cptr, rr, rw);
                    break;

                case FDT_CALLBACKP:
                {
                    struct fd_callbackp *fdcb = (struct fd_callbackp *) fdvalue;

                    fdcb->rdf = rr;
                    fdcb->wrf = rw;
                    (*fdcb->callback)(fdcb);
                }
                break;

                default:
                    abort(); /* unknown client type? bail! */
            }
        }
    } while(eventsfull && (numloops < ENGINE_MAX_LOOPS));

    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1