/*  Poller_kqueue - FreeBSD 4.1 kernel queue wrapper class
 *  Copyright (C) 2000 Michael R. Elkins <me@sigpipe.org>
 *
 *  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-1307  USA
 */

#if HAVE_KQUEUE

#include <stdlib.h>
#include <string.h>
#include "Poller_kqueue.h"
#include "dprint.h"

/* returns 0 on success, or E* if an error occurs */
int Poller_kqueue::init(void)
{
	// initialize a kernel queue for use by the class
	mKernelQueue = kqueue();
	if(mKernelQueue == -1)
		return errno;
	// initialize everything to zero
	mNumChanges = 0;
	mMaxChanges = 0;
	mNumResults = 0;
	mChanges = 0;
	mResults = 0;
	mNumFds = 0;
	mMaxFds = 0;
	mCurResult = 0;

	Poller::init();

	return 0;	// success
}

void Poller_kqueue::shutdown(void)
{
	/* kernel queue events are automatically deleted when the socket is
	 * closed, so we don't need to do anything special.
	 */
	if(mChanges != 0) {
		free(mChanges);
		mChanges = 0;
	}
	if(mResults != 0) {
		free(mResults);
		mResults = 0;
	}
	mNumResults = 0;
	mNumChanges = 0;
	mMaxChanges = 0;
	mNumFds = 0;

	Poller::shutdown();
}

int Poller_kqueue::add(int fd, Client *client, short eventmask)
{
	if(mNumFds == mMaxFds) {
		mMaxFds += 128;
		/* note that we allocate 2* the amount since each event
		 * is returned separately
		 */
		mResults = (struct kevent*)realloc(mResults, 2 * sizeof(struct kevent) * mMaxFds);
	}
	mNumFds++;
	return setMaskInternal(fd, eventmask, client);
}

int Poller_kqueue::del(int fd)
{
	if (mNumFds == 0) { 
		return EINVAL;	// oops, one too many ::del() calls
	}

	if (mNumChanges + 2 > mMaxChanges) {
		mMaxChanges += 128;
		mChanges = (struct kevent*)realloc(mChanges, sizeof(struct kevent) * mMaxChanges);
	}

	struct kevent *ke;

	ke = &mChanges[mNumChanges++];
	ke->ident = fd;
	ke->filter = EVFILT_READ;
	ke->flags = EV_DELETE;

	ke = &mChanges[mNumChanges++];
	ke->ident = fd;
	ke->filter = EVFILT_WRITE;
	ke->flags = EV_DELETE;

	mNumFds--;

	return 0;
}

int Poller_kqueue::setMaskInternal(int fd, short eventmask, Client *client)
{
	struct kevent *ke;

	if(mNumChanges + 2 > mMaxChanges) {
		mMaxChanges += 128;
		mChanges=(struct kevent*)realloc(mChanges,sizeof(struct kevent) * mMaxChanges);
	}

	if (eventmask & POLLIN) {
		ke = &mChanges[mNumChanges++];
		memset(ke, 0, sizeof(struct kevent));
		ke->ident = fd;
		ke->filter = EVFILT_READ;
		ke->flags = EV_ADD | EV_ENABLE;
		if (client)
			ke->udata = client;
	}

	if (eventmask & POLLOUT) {
		//printf("Poller_kqueue::setMaskInternal : setting write on fd %d\n", fd);

		ke = &mChanges[mNumChanges++];
		memset(ke, 0, sizeof(struct kevent));
		ke->ident = fd;
		ke->filter = EVFILT_WRITE;
		ke->flags = EV_ADD | EV_ENABLE;
		if (client)
			ke->udata = client;
	}

	return 0;
}

int Poller_kqueue::clearMaskInternal(int fd, short eventmask)
{
	struct kevent *ke;

	if(mNumChanges + 2 > mMaxChanges) {
		mMaxChanges += 128;
		mChanges=(struct kevent*)realloc(mChanges,sizeof(struct kevent) * mMaxChanges);
	}

	if (!(eventmask & POLLIN)) {
		ke = &mChanges[mNumChanges++];
		memset(ke, 0, sizeof(struct kevent));
		ke->ident = fd;
		ke->filter = EVFILT_READ;
		ke->flags = EV_DELETE;
	}

	if (!(eventmask & POLLOUT)) {

		ke = &mChanges[mNumChanges++];
		memset(ke, 0, sizeof(struct kevent));
		ke->ident = fd;
		ke->filter = EVFILT_WRITE;
		ke->flags = EV_DELETE;
	}

	return 0;
}

int Poller_kqueue::setMask(int fd, short eventmask)
{
	int n;

	n = setMaskInternal(fd, eventmask, 0);
	if (n)
		return n;

	/* clear the unset bit(s).  this might not actually be set
	 * but to avoid keeping state we just attempt to unset it
	 * anyway
	 */
	n = clearMaskInternal(fd, eventmask);
	if (n)
		return n;

	return 0;
}

int Poller_kqueue::orMask(int fd, short eventmask)
{
	return setMaskInternal(fd, eventmask, 0);
}

int Poller_kqueue::andMask(int fd, short eventmask)
{
	return clearMaskInternal(fd, eventmask);
}

int Poller_kqueue::waitForEvents(int timeout_millisec)
{
	struct timespec to;

	if (timeout_millisec >= 0) {
		to.tv_sec = timeout_millisec / 1000;
		to.tv_nsec = (timeout_millisec % 1000) * 1000000;	// nanosec
	}

/*	printf("Poller_kqueue::waitForEvents : %d changes\n",
		mNumChanges);
	printf("Poller_kqueue::waitForEvents : %d fds\n",
		mNumFds);

	for(int i=0; i < mNumChanges; i++) {
		printf("Poller_kqueue::waitForEvents : fd=%d filt=%d flags=%d\n",
			mChanges[i].ident,
			mChanges[i].filter,
			mChanges[i].flags);
	}
*/

	mNumResults = kevent(mKernelQueue, mChanges, mNumChanges,
			mResults, 2*mMaxFds,
			(timeout_millisec >= 0) ? &to : (struct timespec *) 0);

	mCurResult = 0;
	mNumChanges = 0;	// reset
	if(mNumResults == -1) {
		int err = errno;
		DPRINT(("Poller_kqueue::waitForEvents : kevent : %s (errno %d)\n",
			strerror(err), err));
		return err;
	}
/*	printf("Poller_kqueue::waitForEvents : %d pending events\n",
		mNumResults);
*/
	if(mNumResults == 0)
		return EWOULDBLOCK;
	return 0;
}

int Poller_kqueue::getNextEvent(PollEvent *e)
{
	if(mCurResult == mNumResults)
		return EWOULDBLOCK;	// no more events
	struct kevent *ke = &mResults[mCurResult++];
	memset(e, 0, sizeof(struct PollEvent));
	e->fd = ke->ident;
	if (ke->filter == EVFILT_READ)
		e->revents = POLLIN;
	else if (ke->filter == EVFILT_WRITE)
		e->revents = POLLOUT;
	else
		e->revents = POLLERR;	// huh, what's this?
	e->client = (Client*)ke->udata;
	return 0;
}

int Poller_kqueue::waitAndDispatchEvents(int timeout_millisec)
{
	struct PollEvent pe;

	if(waitForEvents(timeout_millisec))
		return EWOULDBLOCK;
	while(getNextEvent(&pe) == 0) {
		Client *client = pe.client;
		if(client)
			client->notifyPollEvent(&pe);
	}
	return 0;
}

#endif /* HAVE_KQUEUE */


syntax highlighted by Code2HTML, v. 0.9.1