/*
 * $Id$
 *
 * DEBUG: section 05    Socket Functions
 *
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

/*
 * This is a very simple driver for Solaris /dev/poll.
 *
 * The updates are batched, one trip through the comm loop.
 * (like libevent.) We keep a pointer into the structs so we
 * can zero out an entry in the poll list if its active.
 *
 * Ported by Peter Payne from Squid 2.7.STABLE9 comm_devpoll.c
 * on August 11, 2010 at 3pm (GMT+0100 Europe/London).
 *
 * Last modified 2010-10-08
 */


#include "squid.h"
#include "CacheManager.h"
#include "Store.h"
#include "fde.h"
#include "SquidTime.h"

#ifdef USE_DEVPOLL

#if HAVE_SYS_DEVPOLL_H
#include <sys/devpoll.h> /* Solaris /dev/poll support */
#endif

#define DEBUG_DEVPOLL 0

/* OPEN_MAX is defined in <limits.h>, presumably included by sys/devpoll.h */
#define	DEVPOLL_UPDATESIZE	OPEN_MAX
#define	DEVPOLL_QUERYSIZE	OPEN_MAX

/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* define structures */

/* Current state */
struct _devpoll_state {
    char state;
    int update_offset;
};

/* The update list */
struct {
    struct pollfd *pfds;
    int cur;
    int size;
} devpoll_update;


/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* static variables */

static int devpoll_fd;
static struct timespec zero_timespec;
static int max_poll_time = 1000;

static struct _devpoll_state *devpoll_state;
static struct dvpoll do_poll;
static int dpoll_nfds;

/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* prototypes */

static void commDevPollRegisterWithCacheManager(void);


/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* private functions */

static void
comm_flush_updates(void)
{
    int i;
    if (devpoll_update.cur == -1)
	return;

    debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_flush_updates(): " << (devpoll_update.cur + 1) << " fds queued");

    i = write(devpoll_fd, devpoll_update.pfds, (devpoll_update.cur + 1) * sizeof(struct pollfd));
    assert(i > 0);
    assert(i == sizeof(struct pollfd) * (devpoll_update.cur + 1));
    devpoll_update.cur = -1;
}

/*
 * We could be "optimal" and -change- an existing entry if they
 * just add a bit - since the devpoll interface OR's multiple fd
 * updates. We'll need to POLLREMOVE entries which has a bit cleared
 * but for now I'll do whats "easier" and add the smart logic
 * later.
 */
static void
comm_update_fd(int fd, int events)
{
#if DEBUG_DEVPOLL
    char acDebugStr[ 2048 ];

    acDebugStr[0] = '\0';
    if ( events & POLLIN )
        strlcat( acDebugStr, " IN", sizeof(acDebugStr) );
    if ( events & POLLOUT )
        strlcat( acDebugStr, " OUT", sizeof(acDebugStr) );
    if ( events & POLLREMOVE )
        strlcat( acDebugStr, " REMOVE", sizeof(acDebugStr) );
    if ( events & POLLERR )
        strlcat( acDebugStr, " ERR", sizeof(acDebugStr) );
    if ( events & POLLHUP )
        strlcat( acDebugStr, " HUP", sizeof(acDebugStr) );
    if ( events & POLLNVAL )
        strlcat( acDebugStr, " NVAL", sizeof(acDebugStr) );

    debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_update_fd(FD " << fd << ", events=[" << acDebugStr << " ])");
#endif /* DEBUG_DEVPOLL */

    if (devpoll_update.cur != -1 && (devpoll_update.cur == devpoll_update.size))
	comm_flush_updates();
    devpoll_update.cur++;
#if DEBUG_DEVPOLL
    debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_update_fd: -> new slot (" << devpoll_update.cur << ")");
#endif /* DEBUG_DEVPOLL */
    devpoll_state[fd].update_offset = devpoll_update.cur;
    devpoll_update.pfds[devpoll_update.cur].fd = fd;
    devpoll_update.pfds[devpoll_update.cur].events = events;
    devpoll_update.pfds[devpoll_update.cur].revents = 0;
}

static void commIncomingStats(StoreEntry *sentry) {
    StatCounters *f = &statCounter;
    storeAppendPrintf(sentry, "Total number of devpoll loops: %ld\n", statCounter.select_loops);
    storeAppendPrintf(sentry, "Histogram of returned filedescriptors\n");
    statHistDump(&f->select_fds_hist, sentry, statHistIntDumper);
}


static void
commDevPollRegisterWithCacheManager(void)
{
    CacheManager::GetInstance()->
    registerAction("comm_devpoll_incoming",
                   "comm_incoming() stats",
                   commIncomingStats, 0, 1);
}

/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* public functions */

/*
 * comm_select_init
 *
 * This is a needed exported function which will be called to initialise
 * the network loop code.
 */
void
comm_select_init(void)
{
    /* set up static structure */
    zero_timespec.tv_sec = 0;
    zero_timespec.tv_nsec = 0;

    /* allocate memory first before attempting to open poll device */
    /* This tracks the FD devpoll offset+state */
    devpoll_state = (struct _devpoll_state *)xcalloc(
        SQUID_MAXFD, sizeof(struct _devpoll_state)
    );

    /* And this is the stuff we use to read events */
    do_poll.dp_fds = (struct pollfd *)xcalloc(
        DEVPOLL_QUERYSIZE, sizeof(struct pollfd)
    );
    dpoll_nfds = DEVPOLL_QUERYSIZE;

    devpoll_update.pfds = (struct pollfd *)xcalloc(
        DEVPOLL_UPDATESIZE, sizeof(struct pollfd)
    );
    devpoll_update.cur = -1;
    devpoll_update.size = DEVPOLL_UPDATESIZE;

    /* attempt to open /dev/poll device */
    devpoll_fd = open("/dev/poll", O_RDWR);
    if (devpoll_fd < 0)
	fatalf("comm_select_init: can't open /dev/poll: %s\n", xstrerror());

    fd_open(devpoll_fd, FD_UNKNOWN, "devpoll ctl");

    commDevPollRegisterWithCacheManager();
}

/*
 * comm_setselect
 *
 * This is a needed exported function which will be called to register
 * and deregister interest in a pending IO state for a given FD.
 *
 * If handler=NULL then:
 *   if type=COMM_SELECT_READ then STOP polling for input awaiting processing
 *   if type=COMM_SELECT_WRITE then STOP polling for output buffer ready
 * If handler != NULL then:
 *   if type=COMM_SELECT_READ then START polling for input awaiting processing
 *   if type=COMM_SELECT_WRITE then START polling for output buffer ready
 *
 */
void
commSetSelect(int fd, unsigned int type, PF * handler,
              void *client_data, time_t timeout)
{
    assert(fd >= 0);
    debugs(5, DEBUG_DEVPOLL ? 0 : 8, "commSetSelect(FD " << fd << ",type=" << type <<
           ",handler=" << handler << ",client_data=" << client_data <<
           ",timeout=" << timeout << ")");

    /* POLLIN/POLLOUT are defined in <sys/poll.h> */
    fde *F = &fd_table[fd];
    if (!F->flags.open) {
        /* remove from poll set */
        comm_update_fd( fd, POLLREMOVE );
        devpoll_state[fd].state = 0;
        return;
    }

    int st_new = 0; /* new state (derive from old state and function args) */

    if ( type & COMM_SELECT_READ ) {
        if ( handler != NULL ) {
            /* we want to POLLIN */
            st_new |= POLLIN;
        } else {
            ; /* we want to clear POLLIN because handler is NULL */
        }

        F->read_handler = handler;
        F->read_data = client_data;
    } else if ( devpoll_state[fd].state & POLLIN ) {
        /* we're not changing reading state so take from existing */
        st_new |= POLLIN;
    }

    if ( type & COMM_SELECT_WRITE ) {
        if ( handler != NULL ) {
            /* we want to POLLOUT */
            st_new |= POLLOUT;
        } else {
            ; /* we want to clear POLLOUT because handler is NULL */
        }

        F->write_handler = handler;
        F->write_data = client_data;
    } else if ( devpoll_state[fd].state & POLLOUT ) {
        /* we're not changing writing state so take from existing */
        st_new |= POLLOUT;
    }

    if ( devpoll_state[fd].state ^ st_new ) {
        /* something has changed, update /dev/poll of what to listen for */
        comm_update_fd( fd, POLLREMOVE );
        if ( st_new )
            comm_update_fd( fd, st_new );
        devpoll_state[fd].state = st_new;
    }

    if (timeout)
        F->timeout = squid_curtime + timeout;
}


void
commResetSelect(int fd)
{
    commSetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
    commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
}


/*
 * comm_select
 * Check all connections for new connections and input data that is to be
 * processed. Also check for connections with data queued and whether we can
 * write it out.
 *
 * Called to do the new-style IO, courtesy of of squid (like most of this
 * new IO code). This routine handles the stuff we've hidden in
 * comm_setselect and fd_table[] and calls callbacks for IO ready
 * events.
 */

comm_err_t
comm_select(int msec)
{
    int num, i;
    fde *F;
    PF *hdl;

    PROF_start(comm_check_incoming);

    if (msec > max_poll_time)
        msec = max_poll_time;

    for (;;) {
        do_poll.dp_timeout = msec;
        do_poll.dp_nfds = dpoll_nfds;

        comm_flush_updates();

        num = ioctl(devpoll_fd, DP_POLL, &do_poll);
        ++statCounter.select_loops;

        if (num >= 0)
            break;

        if (ignoreErrno(errno))
            break;

        getCurrentTime();

        PROF_stop(comm_check_incoming);

        return COMM_ERROR;
    }

    PROF_stop(comm_check_incoming);
    getCurrentTime();

    statHistCount(&statCounter.select_fds_hist, num);

    if (num == 0)
        return COMM_TIMEOUT; /* No error.. */

    PROF_start(comm_handle_ready_fd);

    for (i = 0; i < num; i++) {
	int fd = (int)do_poll.dp_fds[i].fd;
        F = &fd_table[fd];
        debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_select(): got FD " << fd << " events=" <<
               std::hex << do_poll.dp_fds[i].revents << " monitoring=" << devpoll_state[fd].state <<
               " F->read_handler=" << F->read_handler << " F->write_handler=" << F->write_handler);

        /* handle errors */
	if (do_poll.dp_fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
	    debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_select(): devpoll event error: fd " << fd);
	    continue; /* XXX! */
	}

	if (do_poll.dp_fds[i].revents & POLLIN || F->flags.read_pending) {
            if ( (hdl = F->read_handler) != NULL ) {
                debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_select(): Calling read handler on FD " << fd);
                PROF_start(comm_read_handler);
                F->flags.read_pending = 0;
                F->read_handler = NULL;
                hdl(fd, F->read_data);
                PROF_stop(comm_read_handler);
                statCounter.select_fds++;
            } else {
                debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_select(): no read handler for FD " << fd);
                // remove interest since no handler exist for this event.
                commSetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0);
            }
        }

	if (do_poll.dp_fds[i].revents & POLLOUT) {
            if ((hdl = F->write_handler) != NULL) {
                debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_select(): Calling write handler on FD " << fd);
                PROF_start(comm_write_handler);
                F->write_handler = NULL;
                hdl(fd, F->write_data);
                PROF_stop(comm_write_handler);
                statCounter.select_fds++;
            } else {
                debugs(5, DEBUG_DEVPOLL ? 0 : 8, "comm_select(): no write handler for FD " << fd);
                // remove interest since no handler exist for this event.
                commSetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0);
            }
        }
    }

    PROF_stop(comm_handle_ready_fd);

    return COMM_OK;
}

void
comm_quick_poll_required(void)
{
    max_poll_time = 10;
}

#endif /* USE_DEVPOLL */

