/*-
 * Copyright (c) 2005 Hye-Shik Chang
 * Copyright (c) 2000 Doug White
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
 *
 * $FreeBSD$
 */

#include <sys/event.h>

#define MAX_KEVENTS 512

/* Event filters */
EXPCONST(int EVFILT_READ)
EXPCONST(int EVFILT_WRITE)
EXPCONST(int EVFILT_AIO)
EXPCONST(int EVFILT_VNODE)
EXPCONST(int EVFILT_PROC)
EXPCONST(int EVFILT_SIGNAL)

/* Event flags */
EXPCONST(int EV_ADD)
EXPCONST(int EV_DELETE)
EXPCONST(int EV_ENABLE)
EXPCONST(int EV_DISABLE)
EXPCONST(int EV_ONESHOT)
EXPCONST(int EV_CLEAR)
EXPCONST(int EV_SYSFLAGS)
EXPCONST(int EV_FLAG1)
EXPCONST(int EV_EOF)
EXPCONST(int EV_ERROR)

/* Kernel note flags (for VNODE & PROC filter types) */
EXPCONST(int NOTE_DELETE)
EXPCONST(int NOTE_WRITE)
EXPCONST(int NOTE_EXTEND)
EXPCONST(int NOTE_ATTRIB)
EXPCONST(int NOTE_LINK)
EXPCONST(int NOTE_RENAME)

EXPCONST(int NOTE_EXIT)
EXPCONST(int NOTE_FORK)
EXPCONST(int NOTE_EXEC)
EXPCONST(int NOTE_PCTRLMASK)
EXPCONST(int NOTE_PDATAMASK)

EXPCONST(int NOTE_TRACK)
EXPCONST(int NOTE_TRACKERR)
EXPCONST(int NOTE_CHILD)

EXPCONST_IFAVAIL(int NOTE_LINKUP)
EXPCONST_IFAVAIL(int NOTE_LINKDOWN)
EXPCONST_IFAVAIL(int NOTE_LINKINV)

/* Types */
DECLTYPE(KEventType, keventobject)
DECLTYPE(KQueueType, kqueueobject)

static char *keventkwlist[] = {
	"ident", "filter", "flags", "fflags", "data",
	"udata", NULL,
};

/* ---------------------------------------------------------------------- */
/*				keventobject				  */
/* ---------------------------------------------------------------------- */

typedef struct {
	PyObject_HEAD
	struct kevent e;
} keventobject;

static PyTypeObject KEventType;

#define KEvent_Check(v)  ((v)->ob_type == &KEventType)

/* kevent methods */

#define create_blank_kevent() \
		(keventobject *)kevent_new(&KEventType, NULL, NULL)
static PyObject *
kevent_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
	keventobject *ev;

	ev = (keventobject *)type->tp_alloc(type, 0);
	if (ev == NULL)
		return NULL;

	/* defaults */
	ev->e.ident = 0;
	ev->e.filter = EVFILT_READ;
	ev->e.flags = EV_ADD | EV_ENABLE;
	ev->e.fflags = 0;
	ev->e.data = 0;
	ev->e.udata = NULL;

	if (args != NULL &&
	    !PyArg_ParseTupleAndKeywords(args, kw, "i|hhiiO:kevent",
			keventkwlist, &(ev->e.ident), &(ev->e.filter),
			&(ev->e.flags), &(ev->e.fflags), &(ev->e.data),
			&(ev->e.udata))) {
		Py_DECREF(ev);
		return NULL;
	}

	Py_XINCREF((PyObject *)ev->e.udata);
	return (PyObject *)ev;
}

static void
kevent_dealloc(keventobject *self)
{
	Py_XDECREF((PyObject *)self->e.udata);
	self->ob_type->tp_free((PyObject *)self);
}

static int
kevent_traverse(keventobject *self, visitproc visit, void *arg)
{
	if (self->e.udata != NULL)
		Py_VISIT((PyObject *)self->e.udata);
	return 0;
}

#define OFF(x) offsetof(keventobject, x)
static struct PyMemberDef kevent_memberlist[] = {
	{"ident",	T_UINT,		OFF(e.ident),	0,
	 "Value used to identify this event.  The exact interpretation "
	 "is determined by the attached filter, but often is a file "
	 "descriptor."},
	{"filter",	T_SHORT,	OFF(e.filter),	0,
	 "Identifies the kernel filter used to process this event.  The "
	 "pre-defined system filters are described below."},
	{"flags",	T_USHORT,	OFF(e.flags),	0,
	 "Actions to perform on the event."},
	{"fflags",	T_UINT,		OFF(e.fflags),	0,
	 "Filter-specific flags."},
	{"data",	T_INT,		OFF(e.data),	0,
	 "Filter-specific data value."},
	{"udata",	T_OBJECT,	OFF(e.udata),	0,
	 "Opaque user-defined value passed through the kernel unchanged."},
	{NULL}	/* sentinel */
};
#undef OFF

#define F(x) {x, #x},
const static struct KEventFilterRepr {
	short filter;
	const char *name;
} kevent_filter_repr[] = {
	F(EVFILT_READ)		F(EVFILT_WRITE)
	F(EVFILT_AIO)		F(EVFILT_VNODE)
	F(EVFILT_PROC)		F(EVFILT_SIGNAL)
	{ 0, "UNKNOWN" }
};
#undef F

const static struct FlagRepr kevent_flags_repr[] = {
	FLAGREPR(EV_ADD)
	FLAGREPR(EV_DELETE)
	FLAGREPR(EV_ENABLE)
	FLAGREPR(EV_DISABLE)
	FLAGREPR(EV_ONESHOT)
	FLAGREPR(EV_CLEAR)
	FLAGREPR(EV_SYSFLAGS)
	FLAGREPR(EV_FLAG1)
	FLAGREPR(EV_EOF)
	FLAGREPR(EV_ERROR)
	{ 0, }
};

static PyObject *
kevent_repr(keventobject *s)
{
	const struct KEventFilterRepr *rc;
	PyObject *flags, *udata, *r;
	char unknownfilter[15];
	const char *filtername;

	for (rc = kevent_filter_repr; rc->filter; rc++)
		if (rc->filter == s->e.filter) {
			filtername = rc->name;
			break;
		}
	if (rc->filter == 0) {
		sprintf(unknownfilter, "%d", s->e.filter);
		filtername = unknownfilter;
	}

	flags = repr_flag(kevent_flags_repr, s->e.flags);
	if (flags == NULL)
		return NULL;

	if (s->e.udata != NULL)
		udata = PyObject_Repr((PyObject *)s->e.udata);
	else
		udata = PyString_FromString("None");
	if (udata == NULL) {
		Py_DECREF(udata);
		return NULL;
	}

#ifdef __amd64__
#define IDENTTYPE "%ld"
#else
#define IDENTTYPE "%d"
#endif
	r = PyString_FromFormat(
		"<kevent ident=" IDENTTYPE " filter=%s flags=%s fflags=%x "
		"data=%x udata=%s>",
		s->e.ident, filtername, PyString_AS_STRING(flags), s->e.fflags,
		(int)s->e.data, PyString_AS_STRING(udata));
	Py_DECREF(flags);
	Py_DECREF(udata);
	return r;
}

static char kevent_doc[] =
"kevent(ident[, filter[, flags[, fflags[, data[, udata]]]]]):\n"
"this object keeps each kevents to be used in return value and\n"
"argument for kqueue.event().";

static PyTypeObject KEventType = {
	PyObject_HEAD_INIT(NULL)
	tp_name:	"kevent",
	tp_basicsize:	sizeof(keventobject),
	tp_dealloc:	(destructor)kevent_dealloc,
	tp_getattro:	PyObject_GenericGetAttr,
	tp_repr:	(reprfunc)kevent_repr,
	tp_flags:	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
	tp_traverse:	(traverseproc)kevent_traverse,
	tp_members:	kevent_memberlist,
	tp_new:		kevent_new,
	tp_doc:		kevent_doc,
};


/* ---------------------------------------------------------------------- */
/*				kqueueobject				  */
/* ---------------------------------------------------------------------- */

typedef struct {
	PyObject_HEAD
	int fd;
	PyObject *udrefkeep;
} kqueueobject;

static PyTypeObject KQueueType;

#define KQueue_Check(v)	((v)->ob_type == &KQueueType)

/* kqueue methods */

static PyObject *
kqueue_new(PyTypeObject *type, PyObject *args, PyObject *kw)
{
	kqueueobject *kq;

	kq = (kqueueobject *)type->tp_alloc(type, 0);
	if (kq == NULL)
		return NULL;

	if (PyTuple_Size(args) > 0 ||
	    (kw != NULL && PyDict_Size(kw) > 0)) {
		PyErr_BadArgument();
		return NULL;
	}

	kq->fd = kqueue();
	if (kq->fd == -1) {
		Py_DECREF(kq);
		return OSERROR();
	}

	kq->udrefkeep = PyDict_New();
	if (kq->udrefkeep == NULL)
		return NULL;
	return (PyObject *)kq;
}

static void
kqueue_dealloc(kqueueobject *self)
{
	if (self->fd != -1) {
		close(self->fd);
		self->fd = -1;
	}
	Py_XDECREF(self->udrefkeep);
	self->ob_type->tp_free((PyObject *)self);
}

static int
kqueue_traverse(kqueueobject *self, visitproc visit, void *arg)
{
	Py_VISIT(self->udrefkeep);
	return 0;
}

static char kqueue_event_doc[] =
"event(changelist, nevents, timeout)\n"
"is used to register events with the queue, and return any pending\n"
"events to the user.  The `changelist` argument is a list of kevent\n"
"objects or None.  All changes contained in the `changelist` are\n"
"applied before any pending events are read from the queue.\n"
"\n"
"When `nevents` is zero, event() will return immediately even if\n"
"there is a timeout specified unlike select.select().  If timeout\n"
"is zero or positive, it specifies a maximum interval to wait for\n"
"an event.  If timeout is negative, event() waits indefinitely.  To\n"
"effect a poll, the timeout argument should be zero.  The same object\n"
"may be used for the changelist and return value.";

/* Call kevent(2) and do appropriate digestion of lists. */
#define UDATAREFKEY(ev)	(PyString_FromStringAndSize((char *)&(ev), \
			 sizeof(uintptr_t)+sizeof(short)))

static PyObject *
kqueue_event(kqueueobject *self, PyObject *args) 
{
	PyObject *kelist, *output;
	struct kevent *changelist;
	struct kevent *triggered;
	struct timespec totimespec, *tspec;
	int i, haveNumEvents, gotNumEvents;
	int wantNumEvents = 1, timeout = -1;

	if (!PyArg_ParseTuple(args, "O|ii:event", &kelist, &wantNumEvents,
				&timeout))
		return NULL;

	if (PyList_Check(kelist))
		haveNumEvents = PyList_GET_SIZE(kelist);
	else if (kelist == Py_None)
		haveNumEvents = 0;
	else {
		PyErr_SetString(PyExc_TypeError,
			"argument 1 must be list or None");
		return NULL;
	}

	/* If there's no events to process, don't bother. */
	if (haveNumEvents > 0) {
		changelist = PyMem_New(struct kevent, haveNumEvents);
		if (changelist == NULL)
			return PyErr_NoMemory();

		for (i = 0; i < haveNumEvents; i++) {
			PyObject *ei = PyList_GET_ITEM(kelist, i);
			keventobject *ev = (keventobject *)ei;

			if (!KEvent_Check(ei)) {
				PyErr_SetString(PyExc_TypeError,
					"arg 1 must be a list of `kevent` "
					"objects");
				PyMem_Del(changelist);
				return NULL;
			}

			/* copy this kevent into the array */
			memcpy(&(changelist[i]),
				&(ev->e), sizeof(struct kevent));

			if (ev->e.udata != NULL && (ev->e.flags & EV_ADD)) {
				PyErr_SetString(PyExc_ValueError,
					"use `addevent` method to "
					"add an event with udata");
				PyMem_Del(changelist);
				return NULL;
			}

			if (ev->e.flags & EV_DELETE) {
				PyObject *key;
				int r;
				key = UDATAREFKEY(ev->e);
				if (key == NULL) {
					PyMem_Del(changelist);
					return NULL;
				}
				r = PyDict_DelItem(self->udrefkeep,
						   key);
				if (r == -1)
					PyErr_Clear();
				Py_DECREF(key);
			}
		}
	}
	else
		changelist = NULL;

	/* Allocate some space to hold the triggered events */
	triggered = PyMem_New(struct kevent, wantNumEvents);
	if (triggered == NULL) {
		PyMem_Del(changelist);
		return PyErr_NoMemory();
	}

	/* Build timespec for timeout */
	if (timeout >= 0) {
		totimespec.tv_sec = timeout / 1000;
		totimespec.tv_nsec = (timeout % 1000) * 1000000;
		tspec = &totimespec;
	}
	else
		tspec = NULL;

	/* Make the call */
	Py_BEGIN_ALLOW_THREADS
	gotNumEvents = kevent(self->fd, changelist, haveNumEvents,
			      triggered, wantNumEvents, tspec);
	Py_END_ALLOW_THREADS

	/* Don't need the input event list anymore, so get rid of it */
	PyMem_Del(changelist);

	if (gotNumEvents == -1) {
		PyMem_Del(triggered);
		return OSERROR();
	}
	else if (gotNumEvents == 0) {
		/* return empty list */
		PyMem_Del(triggered);
		return PyList_New(0);
	}

	/* Succeeded, got something back; return it in a list */
	output = PyList_New(gotNumEvents);
	if (output == NULL) {
		PyMem_Del(triggered);
		return PyErr_NoMemory();
	}

	for (i = 0; i < gotNumEvents; i++) {
		keventobject *ke = create_blank_kevent();

		if (ke == NULL) {
			PyMem_Del(triggered);
			Py_DECREF(output);
			return NULL;
		}
		else {
			/* copy event data into our struct */
			memmove((void*)&(ke->e), &(triggered[i]),
				sizeof(struct kevent));
			Py_XINCREF((PyObject *)ke->e.udata);
			PyList_SET_ITEM(output, i, (PyObject *)ke);
		}
	}

	PyMem_Del(triggered);
	/* pass back the results */
	return output;
}

static char kqueue_addevent_doc[] =
"addevent(event or ident[, filter[, flags[, fflags[, data[, udata]]]]]):\n"
"is used to register events with the queue.  This function is like\n"
"event() except that it's allowed to register kevent with non-None\n"
"udata object.";

static PyObject *
kqueue_addevent(kqueueobject *self, PyObject *args, PyObject *kw) 
{
	struct kevent change;
	PyObject *key;
	int r;

	if (PyTuple_Size(args) == 1 &&
	    KEvent_Check(PyTuple_GET_ITEM(args, 0))) {
		keventobject *ke = (keventobject *)PyTuple_GET_ITEM(args, 0);
		memcpy(&change, &ke->e, sizeof(change));
	}
	else {
		change.filter = EVFILT_READ;
		change.flags = EV_ADD | EV_ENABLE;
		change.fflags = 0;
		change.data = 0;
		change.udata = NULL;

		if (!PyArg_ParseTupleAndKeywords(args, kw, "i|hhiiO:addevent",
				keventkwlist, &change.ident, &change.filter,
				&change.flags, &change.fflags, &change.data,
				&change.udata))
			return NULL;
		change.flags |= EV_ADD;
	}

	if (change.udata != NULL) {
		key = UDATAREFKEY(change);
		if (key == NULL)
			return NULL;
	}
	else
		key = NULL;

	Py_BEGIN_ALLOW_THREADS
	r = kevent(self->fd, &change, 1, NULL, 0, NULL);
	Py_END_ALLOW_THREADS

	if (r == -1) {
		Py_XDECREF(key);
		return OSERROR();
	}

	if (key != NULL) {
		r = PyDict_SetItem(self->udrefkeep, key,
				   (PyObject *)change.udata);
		Py_DECREF(key);
		if (r == -1)
			return NULL;
	}

	Py_RETURN_NONE;
}

static PyMethodDef kqueue_methods[] = {
	{"event", (PyCFunction)kqueue_event, METH_VARARGS,
	 kqueue_event_doc},
	{"addevent", (PyCFunction)kqueue_addevent, METH_VARARGS|METH_KEYWORDS,
	 kqueue_addevent_doc},
	{NULL, NULL}
};

static char kqueue_doc[] =
"kqueue():\n"
"The kqueue object provides a generic method of notifying the user\n"
"when an event happens or a condition holds, based on the results\n"
"of small pieces of kernel code termed filters.  A kevent is identified\n"
"by the (ident, filter) pair; there may only be one unique kevent\n"
"per kqueue.";

static PyTypeObject KQueueType = {
	PyObject_HEAD_INIT(NULL)
	tp_name:	"kqueue",
	tp_basicsize:	sizeof(kqueueobject),
	tp_dealloc:	(destructor)kqueue_dealloc,
	tp_getattro:	PyObject_GenericGetAttr,
	tp_flags:	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
	tp_traverse:	(traverseproc)kqueue_traverse,
	tp_methods:	kqueue_methods,
	tp_new:		kqueue_new,
	tp_doc:		kqueue_doc,
};


syntax highlighted by Code2HTML, v. 0.9.1