commit 51a674414ba5618c98e24d9b6db19ac34afd8f42 Author: mheily Date: Tue Oct 20 00:31:17 2009 +0000 initial import git-svn-id: svn://svn.code.sf.net/p/libkqueue/code/trunk@1 fb4e3144-bc1c-4b72-a658-5bcd248dd7f7 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..31c6378 --- /dev/null +++ b/Makefile @@ -0,0 +1,57 @@ +# +# Copyright (c) 2009 Mark Heily +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +PROGRAM=libkqueue +INSTALL=/usr/bin/install +PREFIX=/usr +DISTFILES=*.c *.h kqueue.2 README Makefile configure os sys +SOURCES=src/$(UNAME)/*.c +CFLAGS=-fPIC -I. -Wall -Werror -fvisibility=hidden +FILTERS=vnode.c timer.c signal.c socket.c user.c + +include config.mk + +build: + gcc $(CFLAGS) -c *.c + ar rcs libkqueue.a *.o + gcc -shared -Wl,-soname,libkqueue.so -o libkqueue.so *.o + +install: + mkdir -p $(PREFIX)/include/kqueue/sys + cp event.h $(PREFIX)/include/kqueue/sys + cp libkqueue.so $(PREFIX)/lib + cp kqueue.2 $(PREFIX)/share/man/man2/kqueue.2 + ln -s kqueue.2 $(PREFIX)/share/man/man2/kevent.2 + +check: + make build CFLAGS="$(CFLAGS) -g -O0 -DKQUEUE_DEBUG -DUNIT_TEST" + gcc -g -O0 $(CFLAGS) test.c libkqueue.a -lpthread -lrt + ./a.out + +check-installed: + gcc -g -O0 -Wall -Werror -I$(PREFIX)/kqueue test.c -lkqueue -lpthread -lrt + ./a.out + +dist: + mkdir $(PROGRAM) + cp -R $(DISTFILES) $(PROGRAM) + tar zcvf $(PROGRAM)-`date +%y%m%d_%H%M`.tar.gz $(PROGRAM) + rm -rf $(PROGRAM) + +clean: + rm -f a.out *.a *.o *.so $(FILTERS) + +distclean: clean + rm -f *.tar.gz config.mk diff --git a/README b/README new file mode 100644 index 0000000..407f94c --- /dev/null +++ b/README @@ -0,0 +1,51 @@ +libkqueue emulates the kqueue(2) kernel event notification mechanism +on platforms which do not provide it natively. Currently, the only +supported platform is Linux and the only tested platform is +Ubuntu x64 with kernel 2.6.28. + + + kevent.flags Implementation Status + + ADD DELETE ENABLE DISABLE DISPATCH ONESHOT CLEAR EOF + +READ/WRITE Y Y Y Y N Y N N +AIO N N N N N N N N +VNODE Y N N N N N N N +PROC NO -- Cannot fully implement on Linux +SIGNAL Y N N N N N N N +NETDEV NO -- Don't know how to implement on Linux +TIMER Y N N N N N N N +USER Totally broken + + kevent.fflags Implementation Status + + Status +READ + NOTE_LOWAT No + EV_EOF No +WRITE + NOTE_LOWAT No + EV_EOF No +VNODE + NOTE_DELETE No + NOTE_WRITE No + NOTE_EXTEND No + NOTE_ATTRIB No + NOTE_LINK No + NOTE_RENAME No + NOTE_REVOKE N/A +USER + NOTE_FFNOP Ignore the input fflags. + NOTE_FFAND Bitwise AND fflags. + NOTE_FFOR Bitwise OR fflags. + NOTE_COPY Copy fflags. + NOTE_FFCTRLMASK Control mask for fflags. + NOTE_FFLAGSMASK User defined flag mask for fflags. + NOTE_TRIGGER Cause the event to be triggered. + + Filter sets kevent.data structure when returning +EV_RECEIPT Yes +kevent.data No + +-- +Mark Heily diff --git a/config.h b/config.h new file mode 100644 index 0000000..ad68a92 --- /dev/null +++ b/config.h @@ -0,0 +1,5 @@ +# AUTOMATICALLY GENERATED -- DO NOT EDIT +#define HAVE_SYS_EPOLL_H +#define HAVE_SYS_INOTIFY_H +#undef HAVE_SYS_SIGNALFD_H +#undef HAVE_SYS_TIMERFD_H diff --git a/configure b/configure new file mode 100755 index 0000000..7cb92d5 --- /dev/null +++ b/configure @@ -0,0 +1,34 @@ +#!/bin/sh + +check_header() { + sym=`echo "HAVE_$1" | tr a-z A-Z | sed 's,[./],_,g'` + if [ -f /usr/include/$1 ] ; then + echo "#define $sym" >> config.h + return 0 + else + echo "#undef $sym" >> config.h + return 1 + fi +} + +echo "Checking operating system type... `uname`" +kernel=`uname -s | tr A-Z a-z` +target=$kernel + +echo "Creating config.h" +echo "# AUTOMATICALLY GENERATED -- DO NOT EDIT" > config.h +if [ $kernel = "linux" ] ; then + check_header sys/epoll.h && ln -s os/linux/socket.c \ + || ln -s os/posix/socket.c + check_header sys/inotify.h && ln -s os/linux/vnode.c \ + || ln -s os/posix/vnode.c + check_header sys/signalfd.h && ln -s os/linux/signal.c \ + || ln -s os/posix/signal.c + check_header sys/timerfd.h && ln -s os/linux/timer.c \ + || ln -s os/posix/timer.c + ln -s os/posix/user.c +fi + +echo "Creating config.mk" +echo "# AUTOMATICALLY GENERATED -- DO NOT EDIT" > config.mk +echo "UNAME=$target" >> config.mk diff --git a/filter.c b/filter.c new file mode 100644 index 0000000..a87942f --- /dev/null +++ b/filter.c @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "private.h" + +extern const struct filter evfilt_read; +extern const struct filter evfilt_write; +extern const struct filter evfilt_signal; +extern const struct filter evfilt_vnode; +extern const struct filter evfilt_timer; +extern const struct filter evfilt_user; + +static int +filter_register(struct kqueue *kq, short filter, const struct filter *src) +{ + struct filter *dst; + u_int filt; + int rv = 0; + + filt = (-1 * filter) - 1; + if (filt >= EVFILT_SYSCOUNT) + return (-1); + + dst = &kq->kq_filt[filt]; + memcpy(dst, src, sizeof(*src)); + dst->kf_kqueue = kq; + KNOTELIST_INIT(&dst->knl); + if (src->kf_init == NULL) { + dbg_puts("filter has no initializer"); + return (-1); + } + + rv = src->kf_init(dst); + if (rv < 0) { + dbg_puts("filter failed to initialize"); + dst->kf_id = 0; /* FIXME: lame magic constant */ + return (-1); + } + + /* Add the filter's event descriptor to the main fdset */ + if (dst->kf_pfd <= 0) { + dbg_printf("FIXME - filter %s did not return a fd to poll!", + filter_name(filter)); + return (-1); + } + FD_SET(dst->kf_pfd, &kq->kq_fds); + if (dst->kf_pfd > kq->kq_nfds) + kq->kq_nfds = dst->kf_pfd; + dbg_printf("fds: added %d (nfds=%d)", dst->kf_pfd, kq->kq_nfds); + dbg_printf("%s registered", filter_name(filter)); + + return (0); +} + +int +filter_register_all(struct kqueue *kq) +{ + int rv; + + FD_ZERO(&kq->kq_fds); + rv = 0; + rv += filter_register(kq, EVFILT_READ, &evfilt_read); + rv += filter_register(kq, EVFILT_WRITE, &evfilt_write); + rv += filter_register(kq, EVFILT_SIGNAL, &evfilt_signal); + rv += filter_register(kq, EVFILT_VNODE, &evfilt_vnode); + rv += filter_register(kq, EVFILT_TIMER, &evfilt_timer); + rv += filter_register(kq, EVFILT_USER, &evfilt_user); + kq->kq_nfds++; + if (rv != 0) { + filter_unregister_all(kq); + return (-1); + } else { + return (0); + } +} + +void +filter_unregister_all(struct kqueue *kq) +{ + struct knote *n1, *n2; + int i; + + for (i = 0; i < EVFILT_SYSCOUNT; i++) { + if (kq->kq_filt[i].kf_id == 0) + continue; + + if (kq->kq_filt[i].kf_destroy != NULL) + kq->kq_filt[i].kf_destroy(&kq->kq_filt[i]); + + /* Destroy all knotes associated with this filter */ + for (n1 = LIST_FIRST(&kq->kq_filt[i].knl); n1 != NULL; n1 = n2) { + n2 = LIST_NEXT(n1, entries); + free(n1); + } + } + memset(&kq->kq_filt[0], 0, sizeof(kq->kq_filt)); +} + +int +filter_socketpair(struct filter *filt) +{ + int sockfd[2]; + + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd) < 0) + return (-1); + + fcntl(sockfd[0], F_SETFL, O_NONBLOCK); + filt->kf_wfd = sockfd[0]; + filt->kf_pfd = sockfd[1]; + return (0); +} + +struct filter * +filter_lookup(struct kqueue *kq, short id) +{ + id = (-1 * id) - 1; + if (id < 0 || id >= EVFILT_SYSCOUNT) { + errno = EINVAL; + return (NULL); + } + if (kq->kq_filt[id].kf_copyin == NULL) { + errno = ENOTSUP; + return (NULL); + } + + return (&kq->kq_filt[id]); +} + +const char * +filter_name(short filt) +{ + const char *fname[EVFILT_SYSCOUNT] = { + "EVFILT_READ", + "EVFILT_WRITE", + "EVFILT_AIO", + "EVFILT_VNODE", + "EVFILT_PROC", + "EVFILT_SIGNAL", + "EVFILT_TIMER", + "EVFILT_NETDEV", + "EVFILT_FS", + "EVFILT_LIO", + "EVFILT_USER" + }; + + if (~filt >= EVFILT_SYSCOUNT) + return "EVFILT_BAD_RANGE"; + else + return fname[~filt]; +} diff --git a/kevent.c b/kevent.c new file mode 100644 index 0000000..eda0dc5 --- /dev/null +++ b/kevent.c @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* To get asprintf(3) */ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/event.h" +#include "private.h" + +const char * +kevent_dump(struct kevent *kev) +{ + char buf[512]; + snprintf(&buf[0], sizeof(buf), "[filter=%d,flags=%d,ident=%u,udata=%p]", + kev->filter, + kev->flags, + (u_int) kev->ident, + kev->udata); + return (strdup(buf)); +} + +static void +kevent_error(struct kevent *dst, const struct kevent *src, int data) +{ + memcpy(dst, src, sizeof(*src)); + dst->data = data; +} + +static int +kevent_copyin(struct kqueue *kq, const struct kevent *src, int nchanges, + struct kevent *eventlist, int nevents) +{ + struct knote *dst; + struct filter *filt; + int kn_alloc,status; + + for (; nchanges > 0; src++, nchanges--) { + + if ((filt = filter_lookup(kq, src->filter)) == NULL) { + status = -EINVAL; + goto err_out; + } + + /* + * Retrieve an existing knote object, or create a new one. + */ + kn_alloc = 0; + dst = knote_lookup(filt, src->ident); + if (dst == NULL) { + if (src->flags & EV_ADD) { + if ((dst = knote_new(filt)) == NULL) { + status = -ENOMEM; + goto err_out; + } + dbg_puts(kevent_dump(&dst->kev)); + kn_alloc = 1; + } else if (src->flags & EV_ENABLE + || src->flags & EV_DISABLE + || src->flags & EV_DELETE) { + status = -ENOENT; + goto err_out; + } else { + /* flags == 0 or no action */ + status = -EINVAL; + goto err_out; + } + } + + if (filt->kf_copyin(filt, dst, src) < 0) { + if (kn_alloc) + knote_free(dst); + status = -EBADMSG; + goto err_out; + } + + /* + * Update the knote flags based on src->flags. + */ + if (src->flags & EV_ENABLE) { + dst->kev.flags |= EV_ENABLE; + dst->kev.flags &= ~EV_DISABLE; + } + if (src->flags & EV_DISABLE) { + dst->kev.flags &= ~EV_ENABLE; + dst->kev.flags |= EV_DISABLE; + } + if (src->flags & EV_DELETE) { + knote_free(dst); + } + if (src->flags & EV_RECEIPT) { + status = 0; + goto err_out; + } + + continue; + +err_out: + if (status != 0 && kn_alloc) + knote_free(dst); + if (nevents > 0) { + kevent_error(eventlist++, src, status); + nevents--; + } else { + return (-1); + } + } + + return (0); +} + +int +kevent(int kqfd, const struct kevent *changelist, int nchanges, + struct kevent *eventlist, int nevents, + const struct timespec *timeout) +{ + struct kqueue *kq; + struct filter *filt; + fd_set fds; + int i, rv, n, nret; + + errno = 0; + + kq = kqueue_lookup(kqfd); + if (kq == NULL) { + dbg_printf("fd lookup failed; fd=%d", kqfd); + errno = EINVAL; + return (-1); + } + + /* + * Process each kevent on the changelist. + */ + if (nchanges) { + kqueue_lock(kq); + rv = kevent_copyin(kq, changelist, nchanges, eventlist, nevents); + kqueue_unlock(kq); + if (rv < 0) + return (-1); + } + + /* Determine if we need to wait for events. */ + if (nevents == 0) + return (0); + if (nevents > MAX_KEVENT) + nevents = MAX_KEVENT; + + /* + * Wait for one or more filters to have events. + */ + fds = kq->kq_fds; +#if BROKEN + n = pselect(kq->kq_nfds, &fds, NULL , NULL, timeout, NULL); +#else + /* BROKEn, but testing vnode crap */ + struct timeval tv; + struct timeval *tvp = &tv; + if (timeout) { + tv.tv_sec = timeout->tv_sec; + tv.tv_usec = timeout->tv_nsec / 1000; + } else { + dbg_puts("null timeout.."); + tvp = NULL; + } + n = select(kq->kq_nfds, &fds, NULL, NULL, tvp); +#endif + if (n < 0) { + if (errno == EINTR) { + dbg_puts("signal caught"); + return (-1); + } + dbg_perror("pselect(2)"); + return (-1); + } + dbg_printf("pselect(2): %d bits set", n); + if (n == 0) + return (0); + + /* + * Process each event and place it on the eventlist + */ + nret = 0; + kqueue_lock(kq); + for (i = 0; (i < EVFILT_SYSCOUNT && n > 0 && nevents > 0); i++) { + dbg_printf("eventlist: n = %d nevents = %d", n, nevents); + filt = &kq->kq_filt[i]; + dbg_printf("pfd[%d] = %d", i, filt->kf_pfd); + if (FD_ISSET(filt->kf_pfd, &fds)) { + dbg_printf("event(s) for filter #%d", i); + rv = filt->kf_copyout(filt, eventlist, nevents); + if (rv < 0) { + kqueue_unlock(kq); + dbg_puts("kevent_copyout failed"); + return (-1); + } + nret += rv; + eventlist += rv; + nevents -= rv; + n--; + } + } + kqueue_unlock(kq); + + return (nret); +} diff --git a/knote.c b/knote.c new file mode 100644 index 0000000..e904d9a --- /dev/null +++ b/knote.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include "private.h" + +/* TODO: These must be called with the kqueue lock held */ + +struct knote * +knote_new(struct filter *filt) +{ + struct knote *dst; + + if ((dst = calloc(1, sizeof(*dst))) == NULL) + return (NULL); + KNOTE_INSERT(&filt->knl, dst); + return (dst); +} + +void +knote_free(struct knote *kn) +{ + dbg_printf("knote_free(): filt=%d, ident=%u", + kn->kev.filter, (u_int) kn->kev.ident); + LIST_REMOVE(kn, entries); + free(kn); +} + +/* TODO: rename to knote_lookup_ident */ +struct knote * +knote_lookup(struct filter *filt, short ident) +{ + struct knote *kn; + LIST_FOREACH(kn, &filt->knl, entries) { + if (ident == kn->kev.ident) + break; + } + return (kn); +} + +struct knote * +knote_lookup_data(struct filter *filt, intptr_t data) +{ + struct knote *kn; + LIST_FOREACH(kn, &filt->knl, entries) { + if (data == kn->kev.data) + break; + } + return (kn); +} diff --git a/kqueue.2 b/kqueue.2 new file mode 100644 index 0000000..6a80875 --- /dev/null +++ b/kqueue.2 @@ -0,0 +1,608 @@ +.\" $FreeBSD: Revision: 197243$ +.\" Copyright (c) 2000 Jonathan Lemon +.\" 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 ``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$ +.\" +.Dd September 15, 2009 +.Dt KQUEUE 2 +.Os +.Sh NAME +.Nm kqueue , +.Nm kevent +.Nd kernel event notification mechanism +.Sh LIBRARY +.Lb libc +.Sh SYNOPSIS +.In sys/types.h +.In sys/event.h +.In sys/time.h +.Ft int +.Fn kqueue "void" +.Ft int +.Fn kevent "int kq" "const struct kevent *changelist" "int nchanges" "struct kevent *eventlist" "int nevents" "const struct timespec *timeout" +.Fn EV_SET "&kev" ident filter flags fflags data udata +.Sh DESCRIPTION +The +.Fn kqueue +system call +provides a generic method of notifying the user when an event +happens or a condition holds, based on the results of small +pieces of kernel code termed filters. +A kevent is identified by the (ident, filter) pair; there may only +be one unique kevent per kqueue. +.Pp +The filter is executed upon the initial registration of a kevent +in order to detect whether a preexisting condition is present, and is also +executed whenever an event is passed to the filter for evaluation. +If the filter determines that the condition should be reported, +then the kevent is placed on the kqueue for the user to retrieve. +.Pp +The filter is also run when the user attempts to retrieve the kevent +from the kqueue. +If the filter indicates that the condition that triggered +the event no longer holds, the kevent is removed from the kqueue and +is not returned. +.Pp +Multiple events which trigger the filter do not result in multiple +kevents being placed on the kqueue; instead, the filter will aggregate +the events into a single struct kevent. +Calling +.Fn close +on a file descriptor will remove any kevents that reference the descriptor. +.Pp +The +.Fn kqueue +system call +creates a new kernel event queue and returns a descriptor. +The queue is not inherited by a child created with +.Xr fork 2 . +However, if +.Xr rfork 2 +is called without the +.Dv RFFDG +flag, then the descriptor table is shared, +which will allow sharing of the kqueue between two processes. +.Pp +The +.Fn kevent +system call +is used to register events with the queue, and return any pending +events to the user. +The +.Fa changelist +argument +is a pointer to an array of +.Va kevent +structures, as defined in +.In sys/event.h . +All changes contained in the +.Fa changelist +are applied before any pending events are read from the queue. +The +.Fa nchanges +argument +gives the size of +.Fa changelist . +The +.Fa eventlist +argument +is a pointer to an array of kevent structures. +The +.Fa nevents +argument +determines the size of +.Fa eventlist . +When +.Fa nevents +is zero, +.Fn kevent +will return immediately even if there is a +.Fa timeout +specified unlike +.Xr select 2 . +If +.Fa timeout +is a non-NULL pointer, it specifies a maximum interval to wait +for an event, which will be interpreted as a struct timespec. +If +.Fa timeout +is a NULL pointer, +.Fn kevent +waits indefinitely. +To effect a poll, the +.Fa timeout +argument should be non-NULL, pointing to a zero-valued +.Va timespec +structure. +The same array may be used for the +.Fa changelist +and +.Fa eventlist . +.Pp +The +.Fn EV_SET +macro is provided for ease of initializing a +kevent structure. +.Pp +The +.Va kevent +structure is defined as: +.Bd -literal +struct kevent { + uintptr_t ident; /* identifier for this event */ + short filter; /* filter for event */ + u_short flags; /* action flags for kqueue */ + u_int fflags; /* filter flag value */ + intptr_t data; /* filter data value */ + void *udata; /* opaque user data identifier */ +}; +.Ed +.Pp +The fields of +.Fa struct kevent +are: +.Bl -tag -width XXXfilter +.It ident +Value used to identify this event. +The exact interpretation is determined by the attached filter, +but often is a file descriptor. +.It filter +Identifies the kernel filter used to process this event. +The pre-defined +system filters are described below. +.It flags +Actions to perform on the event. +.It fflags +Filter-specific flags. +.It data +Filter-specific data value. +.It udata +Opaque user-defined value passed through the kernel unchanged. +.El +.Pp +The +.Va flags +field can contain the following values: +.Bl -tag -width XXXEV_ONESHOT +.It EV_ADD +Adds the event to the kqueue. +Re-adding an existing event +will modify the parameters of the original event, and not result +in a duplicate entry. +Adding an event automatically enables it, +unless overridden by the EV_DISABLE flag. +.It EV_ENABLE +Permit +.Fn kevent +to return the event if it is triggered. +.It EV_DISABLE +Disable the event so +.Fn kevent +will not return it. +The filter itself is not disabled. +.It EV_DISPATCH +Disable the event source immediately after delivery of an event. +See +.Dv EV_DISABLE +above. +.It EV_DELETE +Removes the event from the kqueue. +Events which are attached to +file descriptors are automatically deleted on the last close of +the descriptor. +.It EV_RECEIPT +This flag is useful for making bulk changes to a kqueue without draining +any pending events. +When passed as input, it forces +.Dv EV_ERROR +to always be returned. +When a filter is successfully added the +.Va data +field will be zero. +.It EV_ONESHOT +Causes the event to return only the first occurrence of the filter +being triggered. +After the user retrieves the event from the kqueue, +it is deleted. +.It EV_CLEAR +After the event is retrieved by the user, its state is reset. +This is useful for filters which report state transitions +instead of the current state. +Note that some filters may automatically +set this flag internally. +.It EV_EOF +Filters may set this flag to indicate filter-specific EOF condition. +.It EV_ERROR +See +.Sx RETURN VALUES +below. +.El +.Pp +The predefined system filters are listed below. +Arguments may be passed to and from the filter via the +.Va fflags +and +.Va data +fields in the kevent structure. +.Bl -tag -width EVFILT_SIGNAL +.It EVFILT_READ +Takes a descriptor as the identifier, and returns whenever +there is data available to read. +The behavior of the filter is slightly different depending +on the descriptor type. +.Pp +.Bl -tag -width 2n +.It Sockets +Sockets which have previously been passed to +.Fn listen +return when there is an incoming connection pending. +.Va data +contains the size of the listen backlog. +.Pp +Other socket descriptors return when there is data to be read, +subject to the +.Dv SO_RCVLOWAT +value of the socket buffer. +This may be overridden with a per-filter low water mark at the +time the filter is added by setting the +NOTE_LOWAT +flag in +.Va fflags , +and specifying the new low water mark in +.Va data . +On return, +.Va data +contains the number of bytes of protocol data available to read. +.Pp +If the read direction of the socket has shutdown, then the filter +also sets EV_EOF in +.Va flags , +and returns the socket error (if any) in +.Va fflags . +It is possible for EOF to be returned (indicating the connection is gone) +while there is still data pending in the socket buffer. +.It Vnodes +Returns when the file pointer is not at the end of file. +.Va data +contains the offset from current position to end of file, +and may be negative. +.It "Fifos, Pipes" +Returns when the there is data to read; +.Va data +contains the number of bytes available. +.Pp +When the last writer disconnects, the filter will set EV_EOF in +.Va flags . +This may be cleared by passing in EV_CLEAR, at which point the +filter will resume waiting for data to become available before +returning. +.It "BPF devices" +Returns when the BPF buffer is full, the BPF timeout has expired, or +when the BPF has +.Dq immediate mode +enabled and there is any data to read; +.Va data +contains the number of bytes available. +.El +.It EVFILT_WRITE +Takes a descriptor as the identifier, and returns whenever +it is possible to write to the descriptor. +For sockets, pipes +and fifos, +.Va data +will contain the amount of space remaining in the write buffer. +The filter will set EV_EOF when the reader disconnects, and for +the fifo case, this may be cleared by use of EV_CLEAR. +Note that this filter is not supported for vnodes or BPF devices. +.Pp +For sockets, the low water mark and socket error handling is +identical to the EVFILT_READ case. +.It EVFILT_AIO +The sigevent portion of the AIO request is filled in, with +.Va sigev_notify_kqueue +containing the descriptor of the kqueue that the event should +be attached to, +.Va sigev_value +containing the udata value, and +.Va sigev_notify +set to SIGEV_KEVENT. +When the +.Fn aio_* +system call is made, the event will be registered +with the specified kqueue, and the +.Va ident +argument set to the +.Fa struct aiocb +returned by the +.Fn aio_* +system call. +The filter returns under the same conditions as aio_error. +.It EVFILT_VNODE +Takes a file descriptor as the identifier and the events to watch for in +.Va fflags , +and returns when one or more of the requested events occurs on the descriptor. +The events to monitor are: +.Bl -tag -width XXNOTE_RENAME +.It NOTE_DELETE +The +.Fn unlink +system call +was called on the file referenced by the descriptor. +.It NOTE_WRITE +A write occurred on the file referenced by the descriptor. +.It NOTE_EXTEND +The file referenced by the descriptor was extended. +.It NOTE_ATTRIB +The file referenced by the descriptor had its attributes changed. +.It NOTE_LINK +The link count on the file changed. +.It NOTE_RENAME +The file referenced by the descriptor was renamed. +.It NOTE_REVOKE +Access to the file was revoked via +.Xr revoke 2 +or the underlying file system was unmounted. +.El +.Pp +On return, +.Va fflags +contains the events which triggered the filter. +.It EVFILT_PROC +Takes the process ID to monitor as the identifier and the events to watch for +in +.Va fflags , +and returns when the process performs one or more of the requested events. +If a process can normally see another process, it can attach an event to it. +The events to monitor are: +.Bl -tag -width XXNOTE_TRACKERR +.It NOTE_EXIT +The process has exited. +The exit status will be stored in +.Va data . +.It NOTE_FORK +The process has called +.Fn fork . +.It NOTE_EXEC +The process has executed a new process via +.Xr execve 2 +or similar call. +.It NOTE_TRACK +Follow a process across +.Fn fork +calls. +The parent process will return with NOTE_TRACK set in the +.Va fflags +field, while the child process will return with NOTE_CHILD set in +.Va fflags +and the parent PID in +.Va data . +.It NOTE_TRACKERR +This flag is returned if the system was unable to attach an event to +the child process, usually due to resource limitations. +.El +.Pp +On return, +.Va fflags +contains the events which triggered the filter. +.It EVFILT_SIGNAL +Takes the signal number to monitor as the identifier and returns +when the given signal is delivered to the process. +This coexists with the +.Fn signal +and +.Fn sigaction +facilities, and has a lower precedence. +The filter will record +all attempts to deliver a signal to a process, even if the signal has +been marked as SIG_IGN. +Event notification happens after normal +signal delivery processing. +.Va data +returns the number of times the signal has occurred since the last call to +.Fn kevent . +This filter automatically sets the EV_CLEAR flag internally. +.It EVFILT_TIMER +Establishes an arbitrary timer identified by +.Va ident . +When adding a timer, +.Va data +specifies the timeout period in milliseconds. +The timer will be periodic unless EV_ONESHOT is specified. +On return, +.Va data +contains the number of times the timeout has expired since the last call to +.Fn kevent . +This filter automatically sets the EV_CLEAR flag internally. +There is a system wide limit on the number of timers +which is controlled by the +.Va kern.kq_calloutmax +sysctl. +.It Dv EVFILT_NETDEV +Takes a descriptor to a network interface as the identifier, and the events to watch for in +.Va fflags . +It returns, when one or more of the requested events occur on the descriptor. +The events to monitor are: +.Bl -tag -width XXNOTE_LINKDOWN +.It Dv NOTE_LINKUP +The link is up. +.It Dv NOTE_LINKDOWN +The link is down. +.It Dv NOTE_LINKINV +The link state is invalid. +.El +.Pp +On return, +.Va fflags +contains the events which triggered the filter. +.It Dv EVFILT_USER +Establishes a user event identified by +.Va ident +which is not assosicated with any kernel mechanism but is triggered by +user level code. +The lower 24 bits of the +.Va fflags +may be used for user defined flags and manipulated using the following: +.Bl -tag -width XXNOTE_FFLAGSMASK +.It Dv NOTE_FFNOP +Ignore the input +.Va fflags . +.It Dv NOTE_FFAND +Bitwise AND +.Va fflags . +.It Dv NOTE_FFOR +Bitwise OR +.Va fflags . +.It Dv NOTE_COPY +Copy +.Va fflags . +.It Dv NOTE_FFCTRLMASK +Control mask for +.Va fflags . +.It Dv NOTE_FFLAGSMASK +User defined flag mask for +.Va fflags . +.El +.Pp +A user event is triggered for output with the following: +.Bl -tag -width XXNOTE_FFLAGSMASK +.It Dv NOTE_TRIGGER +Cause the event to be triggered. +.El +.Pp +On return, +.Va fflags +contains the users defined flags in the lower 24 bits. +.El +.Sh RETURN VALUES +The +.Fn kqueue +system call +creates a new kernel event queue and returns a file descriptor. +If there was an error creating the kernel event queue, a value of -1 is +returned and errno set. +.Pp +The +.Fn kevent +system call +returns the number of events placed in the +.Fa eventlist , +up to the value given by +.Fa nevents . +If an error occurs while processing an element of the +.Fa changelist +and there is enough room in the +.Fa eventlist , +then the event will be placed in the +.Fa eventlist +with +.Dv EV_ERROR +set in +.Va flags +and the system error in +.Va data . +Otherwise, +.Dv -1 +will be returned, and +.Dv errno +will be set to indicate the error condition. +If the time limit expires, then +.Fn kevent +returns 0. +.Sh ERRORS +The +.Fn kqueue +system call fails if: +.Bl -tag -width Er +.It Bq Er ENOMEM +The kernel failed to allocate enough memory for the kernel queue. +.It Bq Er EMFILE +The per-process descriptor table is full. +.It Bq Er ENFILE +The system file table is full. +.El +.Pp +The +.Fn kevent +system call fails if: +.Bl -tag -width Er +.It Bq Er EACCES +The process does not have permission to register a filter. +.It Bq Er EFAULT +There was an error reading or writing the +.Va kevent +structure. +.It Bq Er EBADF +The specified descriptor is invalid. +.It Bq Er EINTR +A signal was delivered before the timeout expired and before any +events were placed on the kqueue for return. +.It Bq Er EINVAL +The specified time limit or filter is invalid. +.It Bq Er ENOENT +The event could not be found to be modified or deleted. +.It Bq Er ENOMEM +No memory was available to register the event +or, in the special case of a timer, the maximum number of +timers has been exceeded. +This maximum is configurable via the +.Va kern.kq_calloutmax +sysctl. +.It Bq Er ESRCH +The specified process to attach to does not exist. +.El +.Sh SEE ALSO +.Xr aio_error 2 , +.Xr aio_read 2 , +.Xr aio_return 2 , +.Xr poll 2 , +.Xr read 2 , +.Xr select 2 , +.Xr sigaction 2 , +.Xr write 2 , +.Xr signal 3 +.Sh HISTORY +The +.Fn kqueue +and +.Fn kevent +system calls first appeared in +.Fx 4.1 . +.Sh AUTHORS +The +.Fn kqueue +system and this manual page were written by +.An Jonathan Lemon Aq jlemon@FreeBSD.org . +.Sh BUGS +The +.Dv EVFILT_NETDEV +filter is currently only implemented for devices that use the +.Xr miibus 4 +driver for LINKUP and LINKDOWN operations. +Therefore, it will not work with many non-ethernet devices. +.Pp +The +.Fa timeout +value is limited to 24 hours; longer timeouts will be silently +reinterpreted as 24 hours. diff --git a/kqueue.c b/kqueue.c new file mode 100644 index 0000000..242b18b --- /dev/null +++ b/kqueue.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/event.h" +#include "private.h" + +static LIST_HEAD(,kqueue) kqlist = LIST_HEAD_INITIALIZER(&kqlist); +static pthread_mutex_t kqlist_mtx = PTHREAD_MUTEX_INITIALIZER; +#define kqlist_lock() pthread_mutex_lock(&kqlist_mtx) +#define kqlist_unlock() pthread_mutex_unlock(&kqlist_mtx) + +struct kqueue * +kqueue_lookup(int kq) +{ + struct kqueue *ent = NULL; + + kqlist_lock(); + LIST_FOREACH(ent, &kqlist, entries) { + if (ent->kq_sockfd[1] == kq) + break; + } + kqlist_unlock(); + + return (ent); +} + +static void +kqueue_shutdown(struct kqueue *kq) +{ + dbg_puts("shutdown invoked\n"); + + kqlist_lock(); + LIST_REMOVE(kq, entries); + kqlist_unlock(); + filter_unregister_all(kq); + free(kq); + pthread_exit(NULL); +} + +static void * +kqueue_close_wait(void *arg) +{ + struct kqueue *kq = (struct kqueue *) arg; + struct pollfd fds[1]; + int n; + + /* Block all signals in this thread */ + sigset_t mask; + sigfillset(&mask); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) { + dbg_printf("sigprocmask: %s", strerror(errno)); + abort(); + } + + /* Watch for the close(2) of the kqueue fd */ + fds[0].fd = kq->kq_sockfd[0]; + fds[0].events = POLLIN; + + /* Almost solves a race condition when close(2) is called immediately + after kqueue(2). Also helps prevent signal races. + */ + kqlist_unlock(); + + for (;;) { + n = poll(&fds[0], 1, -1); + if (n == 0) + continue; /* Should never happen */ + if (n < 0) { + if (errno == EINTR) + continue; + dbg_printf("poll(2): %s", strerror(errno)); + abort(); //FIXME + } + } + dbg_puts("kqueue: fd closed"); + + kqueue_shutdown(kq); + + return (NULL); +} + + +void +kqueue_lock(struct kqueue *kq) +{ + dbg_puts("kqueue_lock()"); + pthread_mutex_lock(&kq->kq_mtx); +} + +void +kqueue_unlock(struct kqueue *kq) +{ + dbg_puts("kqueue_unlock()"); + pthread_mutex_unlock(&kq->kq_mtx); +} + +int +kqueue(void) +{ + struct kqueue *kq; + + kq = calloc(1, sizeof(*kq)); + if (kq == NULL) + return (-1); + pthread_mutex_init(&kq->kq_mtx, NULL); + + if (filter_register_all(kq) < 0) + return (-1); + + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, kq->kq_sockfd) < 0) { + free(kq); + return (-1); + } + + kqlist_lock(); + if (pthread_create(&kq->kq_close_tid, NULL, kqueue_close_wait, kq) != 0) { + close(kq->kq_sockfd[0]); + close(kq->kq_sockfd[1]); + free(kq); + return (-1); + } + + kqlist_lock(); + LIST_INSERT_HEAD(&kqlist, kq, entries); + kqlist_unlock(); + + dbg_printf("created kqueue: fd=%d", kq->kq_sockfd[1]); + return (kq->kq_sockfd[1]); +} diff --git a/os/linux/signal.c b/os/linux/signal.c new file mode 100644 index 0000000..bad9052 --- /dev/null +++ b/os/linux/signal.c @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sys/event.h" +#include "../../private.h" + +/* Highest signal number supported. POSIX standard signals are < 32 */ +#define SIGNAL_MAX 32 + +int +evfilt_signal_init(struct filter *filt) +{ + sigemptyset(&filt->kf_sigmask); + filt->kf_pfd = signalfd(-1, &filt->kf_sigmask, 0); + dbg_printf("signalfd = %d", filt->kf_pfd); + if (filt->kf_pfd < 0) + return (-1); + + return (0); +} + +void +evfilt_signal_destroy(struct filter *filt) +{ + close (filt->kf_pfd); +} + +int +evfilt_signal_copyin(struct filter *filt, + struct knote *dst, const struct kevent *src) +{ + int rv; + + if (src->ident >= SIGNAL_MAX) { + dbg_printf("unsupported signal number %u", (u_int) src->ident); + return (-1); + } + + if (src->flags & EV_ADD && KNOTE_EMPTY(dst)) { + memcpy(&dst->kev, src, sizeof(*src)); + dst->kev.flags |= EV_CLEAR; + } + if (src->flags & EV_ADD || src->flags & EV_ENABLE) + sigaddset(&filt->kf_sigmask, src->ident); + if (src->flags & EV_DISABLE || src->flags & EV_DELETE) + sigdelset(&filt->kf_sigmask, src->ident); + + rv = signalfd(filt->kf_pfd, &filt->kf_sigmask, 0); + dbg_printf("signalfd = %d", filt->kf_pfd); + if (rv < 0 || rv != filt->kf_pfd) { + dbg_printf("signalfd(2): %s", strerror(errno)); + return (-1); + } + + return (0); +} + +int +evfilt_signal_copyout(struct filter *filt, + struct kevent *dst, + int nevents) +{ + struct knote *kn; + struct signalfd_siginfo sig[MAX_KEVENT]; + int i; + ssize_t n; + + /* NOTE: This will consume the signals so they will not be delivered + * to the process. This differs from kqueue(2) behavior. */ + n = read(filt->kf_pfd, &sig, nevents * sizeof(sig[0])); + if (n < 0 || n < sizeof(sig[0])) { + dbg_puts("invalid read from signalfd"); + return (-1); + } + n /= sizeof(sig[0]); + + for (i = 0, nevents = 0; i < n; i++) { + /* This is not an error because of this race condition: + * 1. Signal arrives and is queued + * 2. The kevent is deleted via kevent(..., EV_DELETE) + * 3. The event is dequeued from the signalfd + */ + kn = knote_lookup(filt, sig[i].ssi_signo); + if (kn == NULL) + continue; + + dbg_printf("got signal %d", sig[i].ssi_signo); + /* TODO: dst->data should be the number of times the signal occurred */ + dst->ident = sig[i].ssi_signo; + dst->filter = EVFILT_SIGNAL; + dst->udata = kn->kev.udata; + dst->flags = 0; + dst->fflags = 0; + dst->data = 1; + dst++; + nevents++; + } + + return (nevents); +} + +const struct filter evfilt_signal = { + EVFILT_SIGNAL, + evfilt_signal_init, + evfilt_signal_destroy, + evfilt_signal_copyin, + evfilt_signal_copyout, +}; diff --git a/os/linux/socket.c b/os/linux/socket.c new file mode 100644 index 0000000..a01f94a --- /dev/null +++ b/os/linux/socket.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "sys/event.h" +#include "private.h" + +int +evfilt_socket_init(struct filter *filt) +{ + filt->kf_pfd = epoll_create(1); + if (filt->kf_pfd < 0) + return (-1); + + dbg_printf("socket epollfd = %d", filt->kf_pfd); + return (0); +} + +void +evfilt_socket_destroy(struct filter *filt) +{ + close(filt->kf_pfd); +} + +int +evfilt_socket_copyin(struct filter *filt, + struct knote *dst, const struct kevent *src) +{ + struct epoll_event ev; + int op, rv; + + /* Determine which operation to perform */ + if (src->flags & EV_ADD && KNOTE_EMPTY(dst)) { + op = EPOLL_CTL_ADD; + memcpy(&dst->kev, src, sizeof(*src)); + } + if (src->flags & EV_DELETE) + op = EPOLL_CTL_DEL; + if (src->flags & EV_ENABLE || src->flags & EV_DISABLE) + op = EPOLL_CTL_MOD; + // FIXME: probably won't work with EV_ADD | EV_DISABLE + + /* Convert the kevent into an epoll_event */ + if (src->filter == EVFILT_READ) + ev.events = EPOLLIN; + else + ev.events = EPOLLOUT; + if (src->flags & EV_ONESHOT) + ev.events |= EPOLLONESHOT; + if (src->flags & EV_CLEAR) + ev.events |= EPOLLET; + ev.data.fd = src->ident; + + if (src->flags & EV_DISABLE) + ev.events = 0; + + dbg_printf("epoll_ctl(2): epfd=%d, op=%d, fd=%d evts=%d", + filt->kf_pfd, op, (int)src->ident, ev.events); + rv = epoll_ctl(filt->kf_pfd, op, src->ident, &ev); + if (rv < 0) { + dbg_printf("epoll_ctl(2): %s", strerror(errno)); + return (-1); + } + + return (0); +} + +int +evfilt_socket_copyout(struct filter *filt, + struct kevent *dst, + int nevents) +{ + struct epoll_event epevt[MAX_KEVENT]; + struct knote *kn; + int i, nret; + + for (;;) { + nret = epoll_wait(filt->kf_pfd, &epevt[0], nevents, 0); + if (nret < 0) { + if (errno == EINTR) + continue; + dbg_perror("epoll_wait"); + return (-1); + } else { + break; + } + } + + for (i = 0, nevents = 0; i < nret; i++) { + kn = knote_lookup(filt, epevt[i].data.fd); + if (kn != NULL) { + dst->ident = kn->kev.ident; + dst->filter = kn->kev.filter; + dst->udata = kn->kev.udata; + + /* FIXME: this is wrong. See the manpage */ + dst->flags = 0; + dst->fflags = 0; + dst->data = 0; + + nevents++; + dst++; + } + } + + return (nevents); +} + +const struct filter evfilt_read = { + EVFILT_READ, + evfilt_socket_init, + evfilt_socket_destroy, + evfilt_socket_copyin, + evfilt_socket_copyout, +}; + +const struct filter evfilt_write = { + EVFILT_WRITE, + evfilt_socket_init, + evfilt_socket_destroy, + evfilt_socket_copyin, + evfilt_socket_copyout, +}; diff --git a/os/linux/timer.c b/os/linux/timer.c new file mode 100644 index 0000000..bd28f71 --- /dev/null +++ b/os/linux/timer.c @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Linux equivalents to kqueue(2) */ +#include + +#include "sys/event.h" +#include "../../private.h" + +static void timer_convert(struct itimerspec *dst, int src); + +struct evfilt_data { + int dummy; +}; + +/* + * Determine the smallest interval used by active timers. + */ +static int +update_timeres(struct filter *filt) +{ + struct knote *kn; + struct itimerspec tval; + u_int cur = filt->kf_timeres; + + KNOTELIST_FOREACH(kn, &filt->knl) { + if (kn->kev.data < cur) + cur = kn->kev.data; + } + + if (cur == filt->kf_timeres) + return (0); + + dbg_printf("new timer interval = %d", cur); + filt->kf_timeres = cur; + + /* Convert from miliseconds to seconds+nanoseconds */ + timer_convert(&tval, cur); + if (timerfd_settime(filt->kf_pfd, 0, &tval, NULL) < 0) { + dbg_printf("signalfd(2): %s", strerror(errno)); + return (-1); + } + + return (0); +} + +/* Convert milliseconds into seconds+nanoseconds */ +static void +timer_convert(struct itimerspec *dst, int src) +{ + struct timespec now; + time_t x, y; + + /* Set the interval */ + /* XXX-FIXME: this is probably horribly wrong :) */ + x = src / 1000; + y = (src % 1000) * 1000000; + dst->it_interval.tv_sec = x; + dst->it_interval.tv_nsec = y; + + /* Set the initial expiration */ + clock_gettime(CLOCK_MONOTONIC, &now); + dst->it_value.tv_sec = now.tv_sec + x; + dst->it_value.tv_nsec = now.tv_nsec + 7; +} + +int +evfilt_timer_init(struct filter *filt) +{ + filt->kf_pfd = timerfd_create(CLOCK_MONOTONIC, 0); + if (filt->kf_pfd < 0) + return (-1); + + return (0); +} + +void +evfilt_timer_destroy(struct filter *filt) +{ + close (filt->kf_pfd); +} + +int +evfilt_timer_copyin(struct filter *filt, + struct knote *dst, const struct kevent *src) +{ + if (src->flags & EV_ADD && KNOTE_EMPTY(dst)) { + memcpy(&dst->kev, src, sizeof(*src)); + dst->kev.flags |= EV_CLEAR; + } + if (src->flags & EV_ADD || src->flags & EV_ENABLE) { + if (update_timeres(filt) < 0) + return (-1); + } + if (src->flags & EV_DISABLE || src->flags & EV_DELETE) { + // TODO + } + + return (0); +} + +int +evfilt_timer_copyout(struct filter *filt, + struct kevent *dst, + int nevents) +{ + //struct knote *kn; + uint64_t buf; + int i; + ssize_t n; + + n = read(filt->kf_pfd, &buf, sizeof(buf)); + if (n < 0 || n < sizeof(buf)) { + dbg_puts("invalid read from timerfd"); + return (-1); + } + n = 1; // KLUDGE + + //KNOTELIST_FOREACH(kn, &filt->knl) { + + for (i = 0, nevents = 0; i < n; i++) { +#if FIXME + /* Want to have multiple timers, so maybe multiple timerfds... */ + kn = knote_lookup(filt, sig[i].ssi_signo); + if (kn == NULL) + continue; + + /* TODO: dst->data should be the number of times the signal occurred */ + dst->ident = sig[i].ssi_signo; + dst->filter = EVFILT_SIGNAL; + dst->udata = kn->kev.udata; + dst->flags = 0; + dst->fflags = 0; + dst->data = 1; + dst++; + nevents++; +#endif + } + + return (nevents); +} + +const struct filter evfilt_timer = { + EVFILT_TIMER, + evfilt_timer_init, + evfilt_timer_destroy, + evfilt_timer_copyin, + evfilt_timer_copyout, +}; diff --git a/os/linux/vnode.c b/os/linux/vnode.c new file mode 100644 index 0000000..b9fdf6d --- /dev/null +++ b/os/linux/vnode.c @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sys/event.h" +#include "private.h" + +#define INEVT_MASK_DUMP(attrib) \ + if (evt->mask & attrib) \ + fputs(#attrib, stdout); + +static void +inotify_event_dump(struct inotify_event *evt) +{ + fputs("[BEGIN: inotify_event dump]\n", stdout); + fprintf(stdout, " wd = %d\n", evt->wd); + fprintf(stdout, " mask = %o (", evt->mask); + INEVT_MASK_DUMP(IN_ACCESS); + INEVT_MASK_DUMP(IN_MODIFY); + INEVT_MASK_DUMP(IN_ATTRIB); + INEVT_MASK_DUMP(IN_CLOSE_WRITE); + INEVT_MASK_DUMP(IN_CLOSE_NOWRITE); + INEVT_MASK_DUMP(IN_OPEN); + INEVT_MASK_DUMP(IN_MOVED_FROM); + INEVT_MASK_DUMP(IN_MOVED_TO); + INEVT_MASK_DUMP(IN_CREATE); + INEVT_MASK_DUMP(IN_DELETE); + INEVT_MASK_DUMP(IN_DELETE_SELF); + INEVT_MASK_DUMP(IN_MOVE_SELF); + fputs(")\n", stdout); + fputs("[END: inotify_event dump]\n", stdout); + fflush(stdout); +} + +static int +fd_to_path(char *buf, size_t bufsz, int fd) +{ + char path[1024]; //TODO: Maxpathlen, etc. + + if (snprintf(&path[0], sizeof(path), "/proc/%d/fd/%d", getpid(), fd) < 0) + return (-1); + + memset(buf, 0, bufsz); + return (readlink(path, buf, bufsz)); +} + + +int +evfilt_vnode_init(struct filter *filt) +{ + filt->kf_pfd = inotify_init(); + dbg_printf("inotify fd = %d", filt->kf_pfd); + if (filt->kf_pfd < 0) + return (-1); + + return (0); +} + +void +evfilt_vnode_destroy(struct filter *filt) +{ + close(filt->kf_pfd); +} + +int +evfilt_vnode_copyin(struct filter *filt, + struct knote *dst, const struct kevent *src) +{ + char path[1024]; //FIXME: maxpathlen + int rv; + uint32_t mask; + + if (src->flags & EV_DELETE) { + dbg_puts("hi"); + if (inotify_rm_watch(filt->kf_pfd, dst->kev.data) < 0) { + dbg_printf("inotify_rm_watch(2): %s", strerror(errno)); + return (-1); + } else { + return (0); + } + } + + if (src->flags & EV_ADD && KNOTE_EMPTY(dst)) { + memcpy(&dst->kev, src, sizeof(*src)); + if (fd_to_path(&path[0], sizeof(path), src->ident) < 0) + return (-1); + + /* Convert the fflags to the inotify mask */ + mask = 0; + + /* FIXME: buggy inotify will not send IN_DELETE events + if the application has the file opened. +See: http://lists.schmorp.de/pipermail/libev/2008q4/000443.html + Need to proccess this case during copyout. + + Actually it seems that IN_DELETE | IN_DELETE_SELF is only + returned when watching directories; when watching + files, IN_ATTRIB seems to be returned only. + */ + if (src->fflags & NOTE_DELETE) + mask |= IN_ATTRIB | IN_DELETE | IN_DELETE_SELF; + + if (src->flags & EV_ONESHOT) + mask |= IN_ONESHOT; +#if FIXME + if (src->flags & EV_CLEAR) + ev.events |= EPOLLET; +#endif + dbg_printf("inotify_add_watch(2); inofd=%d, mask=%d, path=%s", + filt->kf_pfd, mask, path); + rv = inotify_add_watch(filt->kf_pfd, path, mask); + if (rv < 0) { + dbg_printf("inotify_add_watch(2): %s", strerror(errno)); + return (-1); + } else { + dbg_printf("watch descriptor = %d", rv); + dst->kev.data = rv; + return (0); + } + + } + if (src->flags & EV_ENABLE || src->flags & EV_DISABLE) { + abort(); + //FIXME todo + } + + //REFACTOR this + return (0); +} + +int +evfilt_vnode_copyout(struct filter *filt, + struct kevent *dst, + int nevents) +{ + struct inotify_event inevt[MAX_KEVENT]; + struct knote *kn; + struct stat sb; + ssize_t n; + int i; + + dbg_puts("draining inotify events"); + for (;;) { + n = read(filt->kf_pfd, &inevt[0], nevents * sizeof(inevt[0])); + if (n < 0) { + if (errno == EINTR) + continue; + dbg_perror("read"); + return (-1); + } else { + break; + } + } + dbg_printf("read(2) from inotify wd: %zu bytes", n); + + for (i = 0, nevents = 0; i < n; i++) { + if (inevt[i].wd == 0) + break; + inotify_event_dump(&inevt[i]); + /* FIXME: support variable-length structures.. */ + if (inevt[i].len != 0) + abort(); + + kn = knote_lookup_data(filt, inevt[i].wd); + if (kn != NULL) { + kevent_dump(&kn->kev); + dst->ident = kn->kev.ident; + dst->filter = kn->kev.filter; + dst->udata = kn->kev.udata; + + /* FIXME: this is wrong. See the manpage */ + dst->flags = 0; + dst->fflags = 0; + dst->data = 0; + + /* NOTE: unavoidable filesystem race here */ + if (kn->kev.fflags & EV_DELETE) { + if (fstat(kn->kev.ident, &sb) < 0) { + /* TODO: handle signals */ + dbg_puts("woot!"); + dst->fflags = EV_DELETE; + } else { + dbg_printf("link count = %zu", sb.st_nlink); + if (sb.st_nlink == 0) { + dbg_puts("woot! woot!"); + dst->fflags = EV_DELETE; + } else { + /* FIXME: not delete.. maybe ATTRIB event */ + } + } + } + + nevents++; + dst++; + } else { + dbg_printf("no match for wd # %d", inevt[i].wd); + } + } + + return (nevents); +} + +const struct filter evfilt_vnode = { + EVFILT_VNODE, + evfilt_vnode_init, + evfilt_vnode_destroy, + evfilt_vnode_copyin, + evfilt_vnode_copyout, +}; diff --git a/os/posix/signal.c b/os/posix/signal.c new file mode 100644 index 0000000..6917129 --- /dev/null +++ b/os/posix/signal.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/event.h" +#include "private.h" + +/* Highest signal number supported. POSIX standard signals are < 32 */ +#define SIGNAL_MAX 32 + +int +evfilt_signal_init(struct filter *filt) +{ + return filter_socketpair(filt); +} + +void +evfilt_signal_destroy(struct filter *filt) +{ + close (filt->kf_pfd); +} + +int +evfilt_signal_copyin(struct filter *filt, + struct knote *dst, const struct kevent *src) +{ + //int rv; + + if (src->ident >= SIGNAL_MAX) { + dbg_printf("unsupported signal number %u", (u_int) src->ident); + return (-1); + } + + if (src->flags & EV_ADD && KNOTE_EMPTY(dst)) { + memcpy(&dst->kev, src, sizeof(*src)); + dst->kev.flags |= EV_CLEAR; + } + if (src->flags & EV_ADD || src->flags & EV_ENABLE) + sigaddset(&filt->kf_sigmask, src->ident); + if (src->flags & EV_DISABLE || src->flags & EV_DELETE) + sigdelset(&filt->kf_sigmask, src->ident); + +#if FIXME + /* TODO */ + rv = signalfd(filt->kf_pfd, &filt->kf_sigmask, 0); + dbg_printf("signalfd = %d", filt->kf_pfd); + if (rv < 0 || rv != filt->kf_pfd) { + dbg_printf("signalfd(2): %s", strerror(errno)); + return (-1); + } +#endif + + return (0); +} + +int +evfilt_signal_copyout(struct filter *filt, + struct kevent *dst, + int nevents) +{ + return (-1); +#if TODO + struct knote *kn; + struct signalfd_siginfo sig[MAX_KEVENT]; + int i; + ssize_t n; + + /* NOTE: This will consume the signals so they will not be delivered + * to the process. This differs from kqueue(2) behavior. */ + n = read(filt->kf_pfd, &sig, nevents * sizeof(sig[0])); + if (n < 0 || n < sizeof(sig[0])) { + dbg_puts("invalid read from signalfd"); + return (-1); + } + n /= sizeof(sig[0]); + + for (i = 0, nevents = 0; i < n; i++) { + /* This is not an error because of this race condition: + * 1. Signal arrives and is queued + * 2. The kevent is deleted via kevent(..., EV_DELETE) + * 3. The event is dequeued from the signalfd + */ + kn = knote_lookup(filt, sig[i].ssi_signo); + if (kn == NULL) + continue; + + dbg_printf("got signal %d", sig[i].ssi_signo); + /* TODO: dst->data should be the number of times the signal occurred */ + dst->ident = sig[i].ssi_signo; + dst->filter = EVFILT_SIGNAL; + dst->udata = kn->kev.udata; + dst->flags = 0; + dst->fflags = 0; + dst->data = 1; + dst++; + nevents++; + } + + return (nevents); +#endif +} + +const struct filter evfilt_signal = { + EVFILT_SIGNAL, + evfilt_signal_init, + evfilt_signal_destroy, + evfilt_signal_copyin, + evfilt_signal_copyout, +}; diff --git a/os/posix/timer.c b/os/posix/timer.c new file mode 100644 index 0000000..9385a47 --- /dev/null +++ b/os/posix/timer.c @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/event.h" +#include "private.h" + +static void timer_convert(struct itimerspec *dst, int src); + +struct evfilt_data { + int dummy; +}; + +/* + * Determine the smallest interval used by active timers. + */ +static int +update_timeres(struct filter *filt) +{ + struct knote *kn; + struct itimerspec tval; + u_int cur = filt->kf_timeres; + + KNOTELIST_FOREACH(kn, &filt->knl) { + if (kn->kev.data < cur) + cur = kn->kev.data; + } + + if (cur == filt->kf_timeres) + return (0); + + dbg_printf("new timer interval = %d", cur); + filt->kf_timeres = cur; + + /* Convert from miliseconds to seconds+nanoseconds */ + timer_convert(&tval, cur); + //if (timerfd_settime(filt->kf_pfd, 0, &tval, NULL) < 0) { + // dbg_printf("signalfd(2): %s", strerror(errno)); + // return (-1); + // } + + return (0); +} + +/* Convert milliseconds into seconds+nanoseconds */ +static void +timer_convert(struct itimerspec *dst, int src) +{ + struct timespec now; + time_t x, y; + + /* Set the interval */ + /* XXX-FIXME: this is probably horribly wrong :) */ + x = src / 1000; + y = (src % 1000) * 1000000; + dst->it_interval.tv_sec = x; + dst->it_interval.tv_nsec = y; + + /* Set the initial expiration */ + clock_gettime(CLOCK_MONOTONIC, &now); + dst->it_value.tv_sec = now.tv_sec + x; + dst->it_value.tv_nsec = now.tv_nsec + 7; +} + +int +evfilt_timer_init(struct filter *filt) +{ + return filter_socketpair(filt); +} + +void +evfilt_timer_destroy(struct filter *filt) +{ + close (filt->kf_pfd); +} + +int +evfilt_timer_copyin(struct filter *filt, + struct knote *dst, const struct kevent *src) +{ + if (src->flags & EV_ADD && KNOTE_EMPTY(dst)) { + memcpy(&dst->kev, src, sizeof(*src)); + dst->kev.flags |= EV_CLEAR; + } + if (src->flags & EV_ADD || src->flags & EV_ENABLE) { + if (update_timeres(filt) < 0) + return (-1); + } + if (src->flags & EV_DISABLE || src->flags & EV_DELETE) { + // TODO + } + + return (0); +} + +int +evfilt_timer_copyout(struct filter *filt, + struct kevent *dst, + int nevents) +{ + //struct knote *kn; + uint64_t buf; + int i; + ssize_t n; + + n = read(filt->kf_pfd, &buf, sizeof(buf)); + if (n < 0 || n < sizeof(buf)) { + dbg_puts("invalid read from timerfd"); + return (-1); + } + n = 1; // KLUDGE + + //KNOTELIST_FOREACH(kn, &filt->knl) { + + for (i = 0, nevents = 0; i < n; i++) { +#if FIXME + /* Want to have multiple timers, so maybe multiple timerfds... */ + kn = knote_lookup(filt, sig[i].ssi_signo); + if (kn == NULL) + continue; + + /* TODO: dst->data should be the number of times the signal occurred */ + dst->ident = sig[i].ssi_signo; + dst->filter = EVFILT_SIGNAL; + dst->udata = kn->kev.udata; + dst->flags = 0; + dst->fflags = 0; + dst->data = 1; + dst++; + nevents++; +#endif + } + + return (nevents); +} + +const struct filter evfilt_timer = { + EVFILT_TIMER, + evfilt_timer_init, + evfilt_timer_destroy, + evfilt_timer_copyin, + evfilt_timer_copyout, +}; diff --git a/os/posix/user.c b/os/posix/user.c new file mode 100644 index 0000000..6a92043 --- /dev/null +++ b/os/posix/user.c @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/event.h" +#include "private.h" + +int +evfilt_user_init(struct filter *filt) +{ + return filter_socketpair(filt); +} + +void +evfilt_user_destroy(struct filter *filt) +{ + close(filt->kf_wfd); /* TODO: do this in the parent */ + return; +} + +int +evfilt_user_copyin(struct filter *filt, + struct knote *dst, const struct kevent *src) +{ + /* STUB */ + return (-1); +} + +int +evfilt_user_copyout(struct filter *filt, + struct kevent *dst, + int nevents) +{ + return (0); +} + +const struct filter evfilt_user = { + EVFILT_USER, + evfilt_user_init, + evfilt_user_destroy, + evfilt_user_copyin, + evfilt_user_copyout, +}; diff --git a/private.h b/private.h new file mode 100644 index 0000000..e6f9945 --- /dev/null +++ b/private.h @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _KQUEUE_PRIVATE_H +#define _KQUEUE_PRIVATE_H + +#include +#include +#include "sys/event.h" + +/* Maximum events returnable in a single kevent() call */ +#define MAX_KEVENT 512 + +#ifdef KQUEUE_DEBUG +# define dbg_puts(str) fprintf(stderr, "%s\n", str) +# define dbg_printf(fmt,...) fprintf(stderr, fmt"\n", __VA_ARGS__) +# define dbg_perror(str) fprintf(stderr, "%s: %s\n", str, strerror(errno)) +#else +# define dbg_puts(str) ; +# define dbg_printf(fmt,...) ; +# define dbg_perror(str) ; +#endif + +struct kqueue; +struct kevent; +struct evfilt_data; + +struct knote { + struct kevent kev; + LIST_ENTRY(knote) entries; +}; +LIST_HEAD(knotelist, knote); + +/* TODO: This should be a red-black tree or a heap */ +#define KNOTELIST_INIT(knl) LIST_INIT((knl)) +#define KNOTELIST_FOREACH(ent,knl) LIST_FOREACH((ent),(knl), entries) +#define KNOTE_INSERT(knl, ent) LIST_INSERT_HEAD((knl), (ent), entries) +#define KNOTE_EMPTY(ent) ((ent)->kev.filter == 0) + +struct filter { + int kf_id; + int (*kf_init)(struct filter *); + void (*kf_destroy)(struct filter *); + int (*kf_copyin)(struct filter *, + struct knote *, + const struct kevent *); + int (*kf_copyout)(struct filter *, struct kevent *, int); + int kf_pfd; /* fd to poll(2) for readiness */ + int kf_wfd; /* fd to write when an event occurs */ + u_int kf_timeres; /* timer resolution, in miliseconds */ + sigset_t kf_sigmask; + struct evfilt_data *kf_data; /* filter-specific data */ + struct knotelist knl; + struct kqueue *kf_kqueue; +}; + +struct kqueue { + int kq_sockfd[2]; + pthread_t kq_close_tid; + struct filter kq_filt[EVFILT_SYSCOUNT]; + fd_set kq_fds; + int kq_nfds; + pthread_mutex_t kq_mtx; + LIST_ENTRY(kqueue) entries; +}; + +struct knote * knote_lookup(struct filter *, short); +struct knote * knote_lookup_data(struct filter *filt, intptr_t); +struct knote * knote_new(struct filter *); +void knote_free(struct knote *); + +struct filter * filter_lookup(struct kqueue *, short); +int filter_socketpair(struct filter *); +int filter_register_all(struct kqueue *); +void filter_unregister_all(struct kqueue *); +const char *filter_name(short); + +int kevent_init(struct kqueue *); +const char * kevent_dump(struct kevent *); +int kevent_wait(struct kqueue *kq, + struct kevent *kevent, + int nevents, + const struct timespec *timeout); +void kevent_free(struct kqueue *); + +struct kqueue * kqueue_lookup(int kq); +void kqueue_lock(struct kqueue *kq); +void kqueue_unlock(struct kqueue *kq); + +#endif /* ! _KQUEUE_PRIVATE_H */ diff --git a/sys/event.h b/sys/event.h new file mode 100644 index 0000000..5de6846 --- /dev/null +++ b/sys/event.h @@ -0,0 +1,156 @@ +/*- + * Copyright (c) 2009 Mark Heily + * Copyright (c) 1999,2000,2001 Jonathan Lemon + * 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 SVN Revision 197533$ + */ + +#ifndef _SYS_EVENT_H_ +#define _SYS_EVENT_H_ + +#include +#include +#include +#include + +struct timespec; + + +#define EVFILT_READ (-1) +#define EVFILT_WRITE (-2) +#define EVFILT_AIO (-3) /* attached to aio requests */ +#define EVFILT_VNODE (-4) /* attached to vnodes */ +#define EVFILT_PROC (-5) /* attached to struct proc */ +#define EVFILT_SIGNAL (-6) /* attached to struct proc */ +#define EVFILT_TIMER (-7) /* timers */ +#define EVFILT_NETDEV (-8) /* network devices */ +#define EVFILT_FS (-9) /* filesystem events */ +#define EVFILT_LIO (-10) /* attached to lio requests */ +#define EVFILT_USER (-11) /* User events */ +#define EVFILT_SYSCOUNT 11 + +#define EV_SET(kevp_, a, b, c, d, e, f) do { \ + struct kevent *kevp = (kevp_); \ + (kevp)->ident = (a); \ + (kevp)->filter = (b); \ + (kevp)->flags = (c); \ + (kevp)->fflags = (d); \ + (kevp)->data = (e); \ + (kevp)->udata = (f); \ +} while(0) + +struct kevent { + uintptr_t ident; /* identifier for this event */ + short filter; /* filter for event */ + u_short flags; + u_int fflags; + intptr_t data; + void *udata; /* opaque user data identifier */ +}; + +/* actions */ +#define EV_ADD 0x0001 /* add event to kq (implies enable) */ +#define EV_DELETE 0x0002 /* delete event from kq */ +#define EV_ENABLE 0x0004 /* enable event */ +#define EV_DISABLE 0x0008 /* disable event (not reported) */ + +/* flags */ +#define EV_ONESHOT 0x0010 /* only report one occurrence */ +#define EV_CLEAR 0x0020 /* clear event state after reporting */ +#define EV_RECEIPT 0x0040 /* force EV_ERROR on success, data=0 */ +#define EV_DISPATCH 0x0080 /* disable event after reporting */ + +#define EV_SYSFLAGS 0xF000 /* reserved by system */ +#define EV_FLAG1 0x2000 /* filter-specific flag */ + +/* returned values */ +#define EV_EOF 0x8000 /* EOF detected */ +#define EV_ERROR 0x4000 /* error, data contains errno */ + + /* + * data/hint flags/masks for EVFILT_USER + * + * On input, the top two bits of fflags specifies how the lower twenty four + * bits should be applied to the stored value of fflags. + * + * On output, the top two bits will always be set to NOTE_FFNOP and the + * remaining twenty four bits will contain the stored fflags value. + */ +#define NOTE_FFNOP 0x00000000 /* ignore input fflags */ +#define NOTE_FFAND 0x40000000 /* AND fflags */ +#define NOTE_FFOR 0x80000000 /* OR fflags */ +#define NOTE_FFCOPY 0xc0000000 /* copy fflags */ +#define NOTE_FFCTRLMASK 0xc0000000 /* masks for operations */ +#define NOTE_FFLAGSMASK 0x00ffffff + +#define NOTE_TRIGGER 0x01000000 /* Cause the event to be + triggered for output. */ + +/* + * data/hint flags for EVFILT_{READ|WRITE} + */ +#define NOTE_LOWAT 0x0001 /* low water mark */ + +/* + * data/hint flags for EVFILT_VNODE + */ +#define NOTE_DELETE 0x0001 /* vnode was removed */ +#define NOTE_WRITE 0x0002 /* data contents changed */ +#define NOTE_EXTEND 0x0004 /* size increased */ +#define NOTE_ATTRIB 0x0008 /* attributes changed */ +#define NOTE_LINK 0x0010 /* link count changed */ +#define NOTE_RENAME 0x0020 /* vnode was renamed */ +#define NOTE_REVOKE 0x0040 /* vnode access was revoked */ + +/* + * data/hint flags for EVFILT_PROC + */ +#define NOTE_EXIT 0x80000000 /* process exited */ +#define NOTE_FORK 0x40000000 /* process forked */ +#define NOTE_EXEC 0x20000000 /* process exec'd */ +#define NOTE_PCTRLMASK 0xf0000000 /* mask for hint bits */ +#define NOTE_PDATAMASK 0x000fffff /* mask for pid */ + +/* additional flags for EVFILT_PROC */ +#define NOTE_TRACK 0x00000001 /* follow across forks */ +#define NOTE_TRACKERR 0x00000002 /* could not track child */ +#define NOTE_CHILD 0x00000004 /* am a child process */ + +/* + * data/hint flags for EVFILT_NETDEV + */ +#define NOTE_LINKUP 0x0001 /* link is up */ +#define NOTE_LINKDOWN 0x0002 /* link is down */ +#define NOTE_LINKINV 0x0004 /* link state is invalid */ + +__BEGIN_DECLS +int kqueue(void); +int kevent(int kq, const struct kevent *changelist, int nchanges, + struct kevent *eventlist, int nevents, + const struct timespec *timeout); +__END_DECLS + +#endif /* !_SYS_EVENT_H_ */ + diff --git a/test.c b/test.c new file mode 100644 index 0000000..88c0175 --- /dev/null +++ b/test.c @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2009 Mark Heily + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef UNIT_TEST + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/event.h" + +char *cur_test_id = "undef"; +int kqfd; +int sockfd[2]; +int vnode_fd; + +/* In kevent.c */ +const char * kevent_dump(struct kevent *); + +#define KEV_CMP(kev,_ident,_filter,_flags) do { \ + if (kev.ident != (_ident) || \ + kev.filter != (_filter) || \ + kev.flags != (_flags)) \ + err(1, "kevent mismatch: got %s but expecting [%d,%d,%d]", \ + kevent_dump(&kev),\ + (int)kev.ident, kev.filter, kev.flags);\ +} while (0); + +/* Checks if any events are pending, which is an error. */ +void +test_no_kevents(void) +{ + int nfds; + struct timespec timeo; + struct kevent kev; + + puts("confirming that there are no events pending"); + memset(&timeo, 0, sizeof(timeo)); + nfds = kevent(kqfd, NULL, 0, &kev, 1, &timeo); + if (nfds != 0) { + puts(kevent_dump(&kev)); + errx(1, "%d event(s) pending, but none expected:", nfds); + } +} + + +void +test_begin(const char *func) +{ + static int testnum = 1; + cur_test_id = (char *) func; + printf("\n\nTest %d: %s\n", testnum++, func); +} + +void +success(const char *func) +{ + printf("%-70s %s\n", func, "passed"); +} + +void +test_kqueue(void) +{ + test_begin("kqueue()"); + if ((kqfd = kqueue()) < 0) + err(1, "kqueue()"); + test_no_kevents(); + success("kqueue()"); +} + +void +test_kqueue_close(void) +{ + test_begin("close(kq)"); + if (close(kqfd) < 0) + err(1, "close()"); + success("kqueue_close()"); +} + +void +test_kevent_socket_add(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_ADD)"; + struct kevent kev; + + test_begin(test_id); + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ADD, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(test_id); +} + +void +test_kevent_socket_get(void) +{ + const char *test_id = "kevent(EVFILT_READ) wait"; + char buf[1]; + struct kevent kev; + int nfds; + + test_begin(test_id); + + if (write(sockfd[1], ".", 1) < 1) + err(1, "write(2)"); + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "%s", test_id); + KEV_CMP(kev, sockfd[0], EVFILT_READ, 0); + + /* Drain the read buffer, then make sure there are no more events. */ + puts("draining the read buffer"); + if (read(sockfd[0], &buf[0], 1) < 1) + err(1, "read(2)"); + test_no_kevents(); + + success(test_id); +} + +void +test_kevent_socket_disable(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_DISABLE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DISABLE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + puts("filling the read buffer"); + if (write(sockfd[1], ".", 1) < 1) + err(1, "write(2)"); + test_no_kevents(); + + success(test_id); +} + +void +test_kevent_socket_enable(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_ENABLE)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_ENABLE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "%s", test_id); + KEV_CMP(kev, sockfd[0], EVFILT_READ, 0); + + success(test_id); +} + +void +test_kevent_socket_del(void) +{ + const char *test_id = "kevent(EVFILT_READ, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, sockfd[0], EVFILT_READ, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + puts("filling the read buffer"); + if (write(sockfd[1], ".", 1) < 1) + err(1, "write(2)"); + test_no_kevents(); + + success(test_id); +} + +void +test_kevent_signal_add(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_ADD)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ADD, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(test_id); +} + +void +test_kevent_signal_get(void) +{ + const char *test_id = "kevent(get signal)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "test failed: %s, retval %d", test_id, nfds); + if (kev.ident != SIGUSR1 || + kev.filter != EVFILT_SIGNAL || + kev.flags != 0) + err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)", + test_id, (unsigned int)kev.ident, kev.filter, kev.flags); + //FIXME: test kev->flags, fflags, data + + success(test_id); +} + +void +test_kevent_signal_disable(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_DISABLE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DISABLE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + test_no_kevents(); + + success(test_id); +} + +void +test_kevent_signal_enable(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_ENABLE)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_ENABLE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "test failed: %s, retval %d", test_id, nfds); + if (kev.ident != SIGUSR1 || + kev.filter != EVFILT_SIGNAL || + kev.flags != 0) + err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)", + test_id, (unsigned int)kev.ident, kev.filter, kev.flags); + + success(test_id); +} + +void +test_kevent_signal_del(void) +{ + const char *test_id = "kevent(EVFILT_SIGNAL, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + /* Delete the kevent */ + EV_SET(&kev, SIGUSR1, EVFILT_SIGNAL, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + /* Block SIGUSR1, then send it to ourselves */ + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGUSR1); + if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) + err(1, "sigprocmask"); + if (kill(getpid(), SIGUSR1) < 0) + err(1, "kill"); + + test_no_kevents(); + success(test_id); +} + +void +test_kevent_vnode_add(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, EV_ADD)"; + const char *testfile = "/tmp/kqueue-test.tmp"; + struct kevent kev; + + test_begin(test_id); + + system("touch /tmp/kqueue-test.tmp"); + vnode_fd = open(testfile, O_RDONLY); + if (vnode_fd < 0) + err(1, "open of %s", testfile); + else + printf("vnode_fd = %d\n", vnode_fd); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_ADD, NOTE_DELETE, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + // XXX-causes an event.. + close(vnode_fd); + + success(test_id); +} + +void +test_kevent_vnode_get(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, get)"; + struct kevent kev; + int nfds; + + test_begin(test_id); + + if (unlink("/tmp/kqueue-test.tmp") < 0) + err(1, "unlink"); + + nfds = kevent(kqfd, NULL, 0, &kev, 1, NULL); + if (nfds < 1) + err(1, "%s", test_id); + if (kev.ident != vnode_fd || + kev.filter != EVFILT_VNODE || + kev.fflags != NOTE_DELETE) + err(1, "%s - incorrect event (sig=%u; filt=%d; flags=%d)", + test_id, (unsigned int)kev.ident, kev.filter, kev.flags); + + success(test_id); +} + +void +test_kevent_vnode_del(void) +{ + const char *test_id = "kevent(EVFILT_VNODE, EV_DELETE)"; + struct kevent kev; + + test_begin(test_id); + + EV_SET(&kev, vnode_fd, EVFILT_VNODE, EV_DELETE, 0, 0, &sockfd[0]); + if (kevent(kqfd, &kev, 1, NULL, 0, NULL) < 0) + err(1, "%s", test_id); + + success(test_id); +} + +int +main(int argc, char **argv) +{ + int test_socket = 1; + int test_signal = 0;//XXX-FIXME + int test_vnode = 1; + int test_timer = 1; + + while (argc) { + if (strcmp(argv[0], "--no-socket") == 0) + test_socket = 0; + if (strcmp(argv[0], "--no-timer") == 0) + test_timer = 0; + if (strcmp(argv[0], "--no-signal") == 0) + test_signal = 0; + if (strcmp(argv[0], "--no-vnode") == 0) + test_vnode = 0; + argv++; + argc--; + } + + /* Create a connected pair of full-duplex sockets for testing socket events */ + if (socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd) < 0) + abort(); + + test_kqueue(); + + if (test_socket) { + test_kevent_socket_add(); + test_kevent_socket_get(); + test_kevent_socket_disable(); + test_kevent_socket_enable(); + test_kevent_socket_del(); + } + + if (test_signal) { + test_kevent_signal_add(); + test_kevent_signal_get(); + test_kevent_signal_disable(); + test_kevent_signal_enable(); + test_kevent_signal_del(); + } + + if (test_vnode) { + test_kevent_vnode_add(); +#if ! FIXME + //broken, hangs on epoll of kq->pfd + test_kevent_vnode_get(); +#endif + test_kevent_vnode_del(); + } + + if (test_timer) { + } + + test_kqueue_close(); + + puts("all tests completed."); + return (0); +} + +#else /* UNIT_TEST */ + +void __kqueue_dummy(void) +{ + /* STUB */ +} + +#endif /* ! UNIT_TEST */