diff options
| -rw-r--r-- | lib/libc/sys/kqueue.2 | 400 | ||||
| -rw-r--r-- | sys/kern/kern_event.c | 913 | ||||
| -rw-r--r-- | sys/sys/event.h | 165 |
3 files changed, 1478 insertions, 0 deletions
diff --git a/lib/libc/sys/kqueue.2 b/lib/libc/sys/kqueue.2 new file mode 100644 index 000000000000..e0b53d5919d0 --- /dev/null +++ b/lib/libc/sys/kqueue.2 @@ -0,0 +1,400 @@ +.\" 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 April 14, 2000 +.Dt KQUEUE 2 +.Os +.Sh NAME +.Nm kqueue, kevent +.Nd kernel event notification mechanism +.Sh SYNOPSIS +.Fd #include <sys/event.h> +.Ft int +.Fn kqueue "void" +.Ft int +.Fn kevent "int kq" "int nchanges" "struct kevent **changelist" \ +"int nevents" "struct kevent *eventlist" "struct timespec *timeout" +.Sh DESCRIPTION +.Fn kqueue +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. +An kevent is identified by the (ident, filter) pair; there may only +be one unique kevent per kqueue. + +The filter is executed upon the initial registration of a kevent +in order to detect 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. + +The filter is also run when the user attempts to retrieve the kevent +from the kqueue, and if the filter indicates the condition that triggered +the event no longer holds, the kevent is removed from the kqueue and +is not returned. + +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 +.Fn kqueue +creates a new kernel event queue and returns a descriptor. +.Pp +.Fn kevent +is used to register events with the queue, and return any pending +events to the user. +.Fa changelist +is a pointer to an array of pointers to +.Va kevent +structures, as defined in +.Aq Pa event.h . +All changes contained in the +.Fa changelist +are applied before any pending events are read from the queue. +.Fa nchanges +gives the size of +.Fa changelist . +.Fa eventlist +is a pointer to an array of kevent structures. +.Fa nevents +determines the size of +.Fa eventlist . +If +.Fa timeout +is a non-NULL pointer, it specifies a maximum interval to wait +for an event. 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. +.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_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_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 +.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. +.Bl -tag +.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 SO_RCVLOWAT value of the socket buffer. +.Va data +contains the number of bytes in the socket buffer. +.Pp +If the read direction of the socket has shutdown, then the filter +also sets EV_EOF in +.Va flags . +It is possible for EOF to be returned (indicating the connection is gone) +while there is still data pending in the socket buffer. +.El +.Bl -tag +.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. +.El +.Bl -tag +.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. +.El +.El +.Bl -tag +.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. +.El +.Bl -tag +.It EVFILT_AIO +A kevent structure is initialized, with +.Va ident +containing the descriptor of the kqueue that the event should be +attached to. The address of the kevent structure is then placed in the +.Va aio_lio_opcode +field of the AIO request, and the aio_* function is then called. +The event will be registered with the specified kqueue, and the +.Va ident +argument set to the +.Fa struct aiocb +returned by the aio_* function. +The filter returns under the same conditions as aio_error. +.Pp +NOTE: this interface is unstable and subject to change. +.El +.Bl -tag +.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 +.Fn unlink +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. +.El +.Pp +On return, +.Va fflags +contains the events which triggered the filter. +.El +.Bl -tag +.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. +.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 kqueue . +This filter automatically sets the EV_CLEAR flag internally. +.El +.Sh RETURN VALUES +.Fn kevent +returns the number of events placed in the +.Ar eventlist , +up to the value given by +.Ar nevents . +If an error occurs while processing an element of the +.Ar changelist +and there is enough room in the +.Ar eventlist , +then the event will be placed in the +.Ar 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 kevent +function fails if: +.Bl -tag -width Er +.It Bq Er EACCESS +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 ENOMEM +No memory was available to register the event. +.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 signal 3 , +.Xr sigaction 2 , +.Xr write 2 . +.Sh HISTORY +The +.Fn kqueue +and +.Fn kevent +functions first appeared in +.Fx 5.0 . +.Sh AUTHOR +The +.Fn kqueue +system and this manual page were written by +.An Jonathan Lemon Aq jlemon@freebsd.org . diff --git a/sys/kern/kern_event.c b/sys/kern/kern_event.c new file mode 100644 index 000000000000..c22b24878cc6 --- /dev/null +++ b/sys/kern/kern_event.c @@ -0,0 +1,913 @@ +/*- + * Copyright (c) 1999,2000 Jonathan Lemon <jlemon@FreeBSD.org> + * 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/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/proc.h> +#include <sys/malloc.h> +#include <sys/unistd.h> +#include <sys/file.h> +#include <sys/fcntl.h> +#include <sys/select.h> +#include <sys/queue.h> +#include <sys/event.h> +#include <sys/eventvar.h> +#include <sys/poll.h> +#include <sys/protosw.h> +#include <sys/socket.h> +#include <sys/socketvar.h> +#include <sys/stat.h> +#include <sys/sysproto.h> +#include <sys/uio.h> + +#include <vm/vm_zone.h> + +static int filt_nullattach(struct knote *kn); +static int filt_rwtypattach(struct knote *kn); +static int filt_kqattach(struct knote *kn); +static void filt_kqdetach(struct knote *kn); +static int filt_kqueue(struct knote *kn, long hint); +static int filt_procattach(struct knote *kn); +static void filt_procdetach(struct knote *kn); +static int filt_proc(struct knote *kn, long hint); + +static int kqueue_create(struct kqueue **kqp); +static int kqueue_scan(struct file *fp, int maxevents, + struct kevent *ulistp, struct timespec *timeout, + struct proc *p); +static int kqueue_read(struct file *fp, struct uio *uio, + struct ucred *cred, int flags, struct proc *p); +static int kqueue_write(struct file *fp, struct uio *uio, + struct ucred *cred, int flags, struct proc *p); +static int kqueue_ioctl(struct file *fp, u_long com, caddr_t data, + struct proc *p); +static int kqueue_poll(struct file *fp, int events, struct ucred *cred, + struct proc *p); +static int kqueue_stat(struct file *fp, struct stat *st, struct proc *p); +static int kqueue_close(struct file *fp, struct proc *p); +static void kqueue_wakeup(struct kqueue *kq); + +static void knote_attach(struct knote *kn, struct filedesc *fdp); +static void knote_drop(struct knote *kn, struct proc *p); +static void knote_enqueue(struct knote *kn); +static void knote_dequeue(struct knote *kn); +static void knote_init(void); +static struct knote *knote_alloc(void); +static void knote_free(struct knote *kn); + +static vm_zone_t knote_zone; + +#define KNOTE_ACTIVATE(kn) do { \ + kn->kn_status |= KN_ACTIVE; \ + if ((kn->kn_status & (KN_QUEUED | KN_DISABLED)) == 0) \ + knote_enqueue(kn); \ +} while(0) + +#define KN_HASHSIZE 64 /* XXX should be tunable */ +#define KN_HASH(val, mask) (((val) ^ (val >> 8)) & (mask)) + +static struct fileops kqueueops = { + kqueue_read, + kqueue_write, + kqueue_ioctl, + kqueue_poll, + kqueue_stat, + kqueue_close +}; + +extern struct filterops so_rwfiltops[]; +extern struct filterops fifo_rwfiltops[]; +extern struct filterops pipe_rwfiltops[]; +extern struct filterops vn_rwfiltops[]; + +static struct filterops kq_rwfiltops[] = { + { 1, filt_kqattach, filt_kqdetach, filt_kqueue }, + { 1, filt_nullattach, NULL, NULL }, +}; + +extern struct filterops aio_filtops; +extern struct filterops sig_filtops; +extern struct filterops vn_filtops; + +static struct filterops rwtype_filtops = + { 1, filt_rwtypattach, NULL, NULL }; +static struct filterops proc_filtops = + { 0, filt_procattach, filt_procdetach, filt_proc }; + +/* + * XXX + * These must match the order of defines in <sys/file.h> + */ +static struct filterops *rwtypfilt_sw[] = { + NULL, /* 0 */ + vn_rwfiltops, /* DTYPE_VNODE */ + so_rwfiltops, /* DTYPE_SOCKET */ + pipe_rwfiltops, /* DTYPE_PIPE */ + fifo_rwfiltops, /* DTYPE_FIFO */ + kq_rwfiltops, /* DTYPE_KQUEUE */ +}; + +/* + * table for for all system-defined filters. + */ +static struct filterops *sysfilt_ops[] = { + &rwtype_filtops, /* EVFILT_READ */ + &rwtype_filtops, /* EVFILT_WRITE */ + &aio_filtops, /* EVFILT_AIO */ + &vn_filtops, /* EVFILT_VNODE */ + &proc_filtops, /* EVFILT_PROC */ + &sig_filtops, /* EVFILT_SIGNAL */ +}; + +static int +filt_nullattach(struct knote *kn) +{ + return (ENXIO); +} + +/* + * file-type specific attach routine for read/write filters + */ +static int +filt_rwtypattach(struct knote *kn) +{ + struct filterops *fops; + + fops = rwtypfilt_sw[kn->kn_fp->f_type]; + if (fops == NULL) + return (EINVAL); + kn->kn_fop = &fops[~kn->kn_filter]; /* convert to 0-base index */ + return (kn->kn_fop->f_attach(kn)); +} + +static int +filt_kqattach(struct knote *kn) +{ + struct kqueue *kq = (struct kqueue *)kn->kn_fp->f_data; + + SLIST_INSERT_HEAD(&kq->kq_sel.si_note, kn, kn_selnext); + return (0); +} + +static void +filt_kqdetach(struct knote *kn) +{ + struct kqueue *kq = (struct kqueue *)kn->kn_fp->f_data; + + SLIST_REMOVE(&kq->kq_sel.si_note, kn, knote, kn_selnext); +} + +/*ARGSUSED*/ +static int +filt_kqueue(struct knote *kn, long hint) +{ + struct kqueue *kq = (struct kqueue *)kn->kn_fp->f_data; + + kn->kn_data = kq->kq_count; + return (kn->kn_data > 0); +} + +static int +filt_procattach(struct knote *kn) +{ + struct proc *p; + + p = pfind(kn->kn_id); + if (p == NULL) + return (ESRCH); + if (! PRISON_CHECK(curproc, p)) + return (EACCES); + + kn->kn_ptr.p_proc = p; + kn->kn_flags |= EV_CLEAR; /* automatically set */ + + /* + * internal flag indicating registration done by kernel + */ + if (kn->kn_flags & EV_FLAG1) { + kn->kn_data = kn->kn_sdata; /* ppid */ + kn->kn_fflags = NOTE_CHILD; + kn->kn_flags &= ~EV_FLAG1; + } + + /* XXX lock the proc here while adding to the list? */ + SLIST_INSERT_HEAD(&p->p_klist, kn, kn_selnext); + + return (0); +} + +/* + * The knote may be attached to a different process, which may exit, + * leaving nothing for the knote to be attached to. So when the process + * exits, the knote is marked as DETACHED and also flagged as ONESHOT so + * it will be deleted when read out. However, as part of the knote deletion, + * this routine is called, so a check is needed to avoid actually performing + * a detach, because the original process does not exist any more. + */ +static void +filt_procdetach(struct knote *kn) +{ + struct proc *p = kn->kn_ptr.p_proc; + + if (kn->kn_status & KN_DETACHED) + return; + + /* XXX locking? this might modify another process. */ + SLIST_REMOVE(&p->p_klist, kn, knote, kn_selnext); +} + +static int +filt_proc(struct knote *kn, long hint) +{ + u_int event; + + /* + * mask off extra data + */ + event = (u_int)hint & NOTE_PCTRLMASK; + + /* + * if the user is interested in this event, record it. + */ + if (kn->kn_sfflags & event) + kn->kn_fflags |= event; + + /* + * process is gone, so flag the event as finished. + */ + if (event == NOTE_EXIT) { + kn->kn_status |= KN_DETACHED; + kn->kn_flags |= (EV_EOF | EV_ONESHOT); + return (1); + } + + /* + * process forked, and user wants to track the new process, + * so attach a new knote to it, and immediately report an + * event with the parent's pid. + */ + if ((event == NOTE_FORK) && (kn->kn_sfflags & NOTE_TRACK)) { + struct kevent kev; + int error; + + /* + * register knote with new process. + */ + kev.ident = hint & NOTE_PDATAMASK; /* pid */ + kev.filter = kn->kn_filter; + kev.flags = kn->kn_flags | EV_ADD | EV_ENABLE | EV_FLAG1; + kev.fflags = kn->kn_sfflags; + kev.data = kn->kn_id; /* parent */ + error = kqueue_register(kn->kn_kq, &kev, NULL); + if (error) + kn->kn_fflags |= NOTE_TRACKERR; + } + + return (kn->kn_fflags != 0); +} + +static int +kqueue_create(struct kqueue **kqp) +{ + struct kqueue *kq; + + kq = malloc(sizeof(struct kqueue), M_TEMP, M_WAITOK); + if (kq == NULL) + return (EAGAIN); + bzero(kq, sizeof(*kq)); + TAILQ_INIT(&kq->kq_head); + + *kqp = kq; + return (0); +} + +int +kqueue(struct proc *p, struct kqueue_args *uap) +{ + struct filedesc *fdp = p->p_fd; + struct kqueue *kq; + struct file *fp; + int fd, error; + + error = falloc(p, &fp, &fd); + if (error) + return (error); + fp->f_flag = FREAD | FWRITE; + fp->f_type = DTYPE_KQUEUE; + fp->f_ops = &kqueueops; + error = kqueue_create(&kq); + if (error) { + fdp->fd_ofiles[fd] = 0; + ffree(fp); + } else { + fp->f_data = (caddr_t)kq; + p->p_retval[0] = fd; + } + fdp->fd_knlistsize = 0; /* mark this fdesc as having a kq */ + kq->kq_fdp = fdp; + return (error); +} + +#ifndef _SYS_SYSPROTO_H_ +struct kevent_args { + int fd; + int nchanges; + struct kevent **changelist; + int nevents; + struct kevent *eventlist; + struct timespec *timeout; +}; +#endif +int +kevent(struct proc *p, struct kevent_args *uap) +{ + struct filedesc* fdp = p->p_fd; + struct kevent kev; + struct kqueue *kq; + struct file *fp; + struct timespec ts; + int i, n, nerrors, error; + + if (((u_int)uap->fd) >= fdp->fd_nfiles || + (fp = fdp->fd_ofiles[uap->fd]) == NULL || + (fp->f_type != DTYPE_KQUEUE)) + return (EBADF); + + if (uap->timeout != NULL) { + error = copyin((caddr_t)uap->timeout, (caddr_t)&ts, + sizeof(ts)); + if (error) + return error; + uap->timeout = &ts; + } + + kq = (struct kqueue *)fp->f_data; + nerrors = 0; + + while (uap->nchanges > 0) { + n = uap->nchanges > KQ_NEVENTS ? KQ_NEVENTS : uap->nchanges; + error = copyin((caddr_t)uap->changelist, (caddr_t)kq->kq_kevp, + n * sizeof(struct kevent *)); + if (error) + return (error); + for (i = 0; i < n; i++) { + error = copyin((caddr_t)kq->kq_kevp[i], + (caddr_t)&kev, sizeof(kev)); + if (error) + return (error); + kev.flags &= ~EV_SYSFLAGS; + error = kqueue_register(kq, &kev, p); + if (error) { + if (uap->nevents != 0) { + kev.flags = EV_ERROR; + kev.data = error; + (void) copyout((caddr_t)&kev, + (caddr_t)uap->eventlist, + sizeof(kev)); + uap->eventlist++; + uap->nevents--; + nerrors++; + } else { + return (error); + } + } + } + uap->nchanges -= n; + uap->changelist += n; + } + if (nerrors) { + p->p_retval[0] = nerrors; + return (0); + } + + error = kqueue_scan(fp, uap->nevents, uap->eventlist, uap->timeout, p); + return (error); +} + +int +kqueue_register(struct kqueue *kq, struct kevent *kev, struct proc *p) +{ + struct filedesc *fdp = kq->kq_fdp; + struct filterops *fops; + struct file *fp = NULL; + struct knote *kn = NULL; + int s, error = 0; + + if (kev->filter < 0) { + if (kev->filter + EVFILT_SYSCOUNT < 0) + return (EINVAL); + fops = sysfilt_ops[~kev->filter]; /* to 0-base index */ + } else { + /* + * XXX + * filter attach routine is responsible for insuring that + * the identifier can be attached to it. + */ + printf("unknown filter: %d\n", kev->filter); + return (EINVAL); + } + + if (fops->f_isfd) { + /* validate descriptor; ignore invalid descriptors */ + if ((u_int)kev->ident >= fdp->fd_nfiles || + (fp = fdp->fd_ofiles[kev->ident]) == NULL) + return (0); + + if (kev->ident < fdp->fd_knlistsize) { + SLIST_FOREACH(kn, &fdp->fd_knlist[kev->ident], kn_link) + if (kq == kn->kn_kq && + kev->filter == kn->kn_filter) + break; + } + } else { + if (fdp->fd_knhashmask != 0) { + struct klist *list; + + list = &fdp->fd_knhash[ + KN_HASH((u_long)kev->ident, fdp->fd_knhashmask)]; + SLIST_FOREACH(kn, list, kn_link) + if (kev->ident == kn->kn_id && + kq == kn->kn_kq && + kev->filter == kn->kn_filter) + break; + } + } + + if (kn == NULL && ((kev->flags & EV_ADD) == 0)) + goto done; + + /* + * kn now contains the matching knote, or NULL if no match + */ + if (kev->flags & EV_ADD) { + int attach = 0; + + if (kn == NULL) { + kn = knote_alloc(); + if (kn == NULL) + return (ENOMEM); + if (fp != NULL) + fhold(fp); + kn->kn_fp = fp; + kn->kn_kq = kq; + kn->kn_fop = fops; + attach = 1; + } + kn->kn_sfflags = kev->fflags; + kn->kn_sdata = kev->data; + kev->fflags = 0; + kev->data = 0; + kn->kn_kevent = *kev; + + if (attach) { + knote_attach(kn, fdp); + if ((error = fops->f_attach(kn)) != 0) { + knote_drop(kn, p); + goto done; + } + } + s = splhigh(); + if (kn->kn_fop->f_event(kn, 0)) + KNOTE_ACTIVATE(kn); + splx(s); + } else if (kev->flags & EV_DELETE) { + kn->kn_fop->f_detach(kn); + knote_drop(kn, p); + goto done; + } + + if ((kev->flags & EV_DISABLE) && + ((kn->kn_status & KN_DISABLED) == 0)) { + s = splhigh(); + kn->kn_status |= KN_DISABLED; + splx(s); + } + + if ((kev->flags & EV_ENABLE) && (kn->kn_status & KN_DISABLED)) { + s = splhigh(); + kn->kn_status &= ~KN_DISABLED; + if ((kn->kn_status & KN_ACTIVE) && + ((kn->kn_status & KN_QUEUED) == 0)) + knote_enqueue(kn); + splx(s); + } + +done: + return (error); +} + +static int +kqueue_scan(struct file *fp, int maxevents, struct kevent *ulistp, + struct timespec *tsp, struct proc *p) +{ + struct kqueue *kq = (struct kqueue *)fp->f_data; + struct kevent *kevp; + struct timeval atv, rtv, ttv; + struct knote *kn, marker; + int s, count, timeout, nkev = 0, error = 0; + + count = maxevents; + if (count == 0) + goto done; + + if (tsp != NULL) { + TIMESPEC_TO_TIMEVAL(&atv, tsp); + if (itimerfix(&atv)) { + error = EINVAL; + goto done; + } + timeout = atv.tv_sec > 24 * 60 * 60 ? + 24 * 60 * 60 * hz : tvtohz(&atv); + getmicrouptime(&rtv); + timevaladd(&atv, &rtv); + } else { + atv.tv_sec = 0; + timeout = 0; + } + goto start; + +retry: + if (atv.tv_sec) { + getmicrouptime(&rtv); + if (timevalcmp(&rtv, &atv, >=)) + goto done; + ttv = atv; + timevalsub(&ttv, &rtv); + timeout = ttv.tv_sec > 24 * 60 * 60 ? + 24 * 60 * 60 * hz : tvtohz(&ttv); + } + +start: + kevp = kq->kq_kev; + s = splhigh(); + if (kq->kq_count == 0) { + kq->kq_state |= KQ_SLEEP; + error = tsleep(kq, PSOCK | PCATCH, "kqread", timeout); + splx(s); + if (error == 0) + goto retry; + /* don't restart after signals... */ + if (error == ERESTART) + error = EINTR; + else if (error == EWOULDBLOCK) + error = 0; + goto done; + } + + TAILQ_INSERT_TAIL(&kq->kq_head, &marker, kn_tqe); + while (count) { + kn = TAILQ_FIRST(&kq->kq_head); + TAILQ_REMOVE(&kq->kq_head, kn, kn_tqe); + if (kn == &marker) { + splx(s); + if (count == maxevents) + goto retry; + goto done; + } + if (kn->kn_status & KN_DISABLED) { + kn->kn_status &= ~KN_QUEUED; + kq->kq_count--; + continue; + } + if ((kn->kn_flags & EV_ONESHOT) == 0 && + kn->kn_fop->f_event(kn, 0) == 0) { + kn->kn_status &= ~(KN_QUEUED | KN_ACTIVE); + kq->kq_count--; + continue; + } + *kevp = kn->kn_kevent; + kevp++; + nkev++; + if (kn->kn_flags & EV_ONESHOT) { + kn->kn_status &= ~KN_QUEUED; + kq->kq_count--; + splx(s); + kn->kn_fop->f_detach(kn); + knote_drop(kn, p); + s = splhigh(); + } else if (kn->kn_flags & EV_CLEAR) { + kn->kn_data = 0; + kn->kn_fflags = 0; + kn->kn_status &= ~(KN_QUEUED | KN_ACTIVE); + kq->kq_count--; + } else { + TAILQ_INSERT_TAIL(&kq->kq_head, kn, kn_tqe); + } + count--; + if (nkev == KQ_NEVENTS) { + splx(s); + error = copyout((caddr_t)&kq->kq_kev, (caddr_t)ulistp, + sizeof(struct kevent) * nkev); + ulistp += nkev; + nkev = 0; + kevp = kq->kq_kev; + s = splhigh(); + if (error) + break; + } + } + TAILQ_REMOVE(&kq->kq_head, &marker, kn_tqe); + splx(s); +done: + if (nkev != 0) + error = copyout((caddr_t)&kq->kq_kev, (caddr_t)ulistp, + sizeof(struct kevent) * nkev); + p->p_retval[0] = maxevents - count; + return (error); +} + +/* + * XXX + * This could be expanded to call kqueue_scan, if desired. + */ +/*ARGSUSED*/ +static int +kqueue_read(struct file *fp, struct uio *uio, struct ucred *cred, + int flags, struct proc *p) +{ + return (ENXIO); +} + +/*ARGSUSED*/ +static int +kqueue_write(struct file *fp, struct uio *uio, struct ucred *cred, + int flags, struct proc *p) +{ + return (ENXIO); +} + +/*ARGSUSED*/ +static int +kqueue_ioctl(struct file *fp, u_long com, caddr_t data, struct proc *p) +{ + return (ENOTTY); +} + +/*ARGSUSED*/ +static int +kqueue_poll(struct file *fp, int events, struct ucred *cred, struct proc *p) +{ + struct kqueue *kq = (struct kqueue *)fp->f_data; + int revents = 0; + int s = splnet(); + + if (events & (POLLIN | POLLRDNORM)) { + if (kq->kq_count) { + revents |= events & (POLLIN | POLLRDNORM); + } else { + selrecord(p, &kq->kq_sel); + kq->kq_state |= KQ_SEL; + } + } + splx(s); + return (revents); +} + +/*ARGSUSED*/ +static int +kqueue_stat(struct file *fp, struct stat *st, struct proc *p) +{ + struct kqueue *kq = (struct kqueue *)fp->f_data; + + bzero((void *)st, sizeof(*st)); + st->st_size = kq->kq_count; + st->st_blksize = sizeof(struct kevent); + return (0); +} + +/*ARGSUSED*/ +static int +kqueue_close(struct file *fp, struct proc *p) +{ + struct kqueue *kq = (struct kqueue *)fp->f_data; + struct filedesc *fdp = p->p_fd; + struct knote **knp, *kn, *kn0; + int i; + + for (i = 0; i < fdp->fd_knlistsize; i++) { + knp = &SLIST_FIRST(&fdp->fd_knlist[i]); + kn = *knp; + while (kn != NULL) { + kn0 = SLIST_NEXT(kn, kn_link); + if (kq == kn->kn_kq) { + kn->kn_fop->f_detach(kn); + fdrop(kn->kn_fp, p); + knote_free(kn); + *knp = kn0; + } else { + knp = &SLIST_NEXT(kn, kn_link); + } + kn = kn0; + } + } + if (fdp->fd_knhashmask != 0) { + for (i = 0; i < fdp->fd_knhashmask + 1; i++) { + knp = &SLIST_FIRST(&fdp->fd_knhash[i]); + kn = *knp; + while (kn != NULL) { + kn0 = SLIST_NEXT(kn, kn_link); + if (kq == kn->kn_kq) { + kn->kn_fop->f_detach(kn); + /* XXX non-fd release of kn->kn_ptr */ + knote_free(kn); + *knp = kn0; + } else { + knp = &SLIST_NEXT(kn, kn_link); + } + kn = kn0; + } + } + } + free(kq, M_TEMP); + fp->f_data = NULL; + + return (0); +} + +static void +kqueue_wakeup(struct kqueue *kq) +{ + + if (kq->kq_state & KQ_SLEEP) { + kq->kq_state &= ~KQ_SLEEP; + wakeup(kq); + } + if (kq->kq_state & KQ_SEL) { + kq->kq_state &= ~KQ_SEL; + selwakeup(&kq->kq_sel); + } + KNOTE(&kq->kq_sel.si_note, 0); +} + +/* + * walk down a list of knotes, activating them if their event has triggered. + */ +void +knote(struct klist *list, long hint) +{ + struct knote *kn; + + SLIST_FOREACH(kn, list, kn_selnext) + if (kn->kn_fop->f_event(kn, hint)) + KNOTE_ACTIVATE(kn); +} + +/* + * remove all knotes from a specified klist + */ +void +knote_remove(struct proc *p, struct klist *list) +{ + struct knote *kn; + + while ((kn = SLIST_FIRST(list)) != NULL) { + kn->kn_fop->f_detach(kn); + knote_drop(kn, p); + } +} + +/* + * remove all knotes referencing a specified fd + */ +void +knote_fdclose(struct proc *p, int fd) +{ + struct filedesc *fdp = p->p_fd; + struct klist *list = &fdp->fd_knlist[fd]; + + knote_remove(p, list); +} + +static void +knote_attach(struct knote *kn, struct filedesc *fdp) +{ + struct klist *list; + int size; + + if (! kn->kn_fop->f_isfd) { + if (fdp->fd_knhashmask == 0) + fdp->fd_knhash = hashinit(KN_HASHSIZE, M_TEMP, + &fdp->fd_knhashmask); + list = &fdp->fd_knhash[KN_HASH(kn->kn_id, fdp->fd_knhashmask)]; + goto done; + } + + if (fdp->fd_knlistsize <= kn->kn_id) { + size = fdp->fd_knlistsize; + while (size <= kn->kn_id) + size += KQEXTENT; + MALLOC(list, struct klist *, + size * sizeof(struct klist *), M_TEMP, M_WAITOK); + bcopy((caddr_t)fdp->fd_knlist, (caddr_t)list, + fdp->fd_knlistsize * sizeof(struct klist *)); + bzero((caddr_t)list + + fdp->fd_knlistsize * sizeof(struct klist *), + (size - fdp->fd_knlistsize) * sizeof(struct klist *)); + if (fdp->fd_knlist != NULL) + FREE(fdp->fd_knlist, M_TEMP); + fdp->fd_knlistsize = size; + fdp->fd_knlist = list; + } + list = &fdp->fd_knlist[kn->kn_id]; +done: + SLIST_INSERT_HEAD(list, kn, kn_link); + kn->kn_status = 0; +} + +/* + * should be called at spl == 0, since we don't want to hold spl + * while calling fdrop and free. + */ +static void +knote_drop(struct knote *kn, struct proc *p) +{ + struct filedesc *fdp = p->p_fd; + struct klist *list; + + if (kn->kn_fop->f_isfd) + list = &fdp->fd_knlist[kn->kn_id]; + else + list = &fdp->fd_knhash[KN_HASH(kn->kn_id, fdp->fd_knhashmask)]; + + SLIST_REMOVE(list, kn, knote, kn_link); + if (kn->kn_status & KN_QUEUED) + knote_dequeue(kn); + if (kn->kn_fop->f_isfd) + fdrop(kn->kn_fp, p); + knote_free(kn); +} + + +static void +knote_enqueue(struct knote *kn) +{ + struct kqueue *kq = kn->kn_kq; + int s = splhigh(); + + KASSERT((kn->kn_status & KN_QUEUED) == 0, ("knote already queued")); + + TAILQ_INSERT_TAIL(&kq->kq_head, kn, kn_tqe); + kn->kn_status |= KN_QUEUED; + kq->kq_count++; + splx(s); + kqueue_wakeup(kq); +} + +static void +knote_dequeue(struct knote *kn) +{ + struct kqueue *kq = kn->kn_kq; + int s = splhigh(); + + KASSERT(kn->kn_status & KN_QUEUED, ("knote not queued")); + + TAILQ_REMOVE(&kq->kq_head, kn, kn_tqe); + kn->kn_status &= ~KN_QUEUED; + kq->kq_count--; + splx(s); +} + +static void +knote_init(void) +{ + knote_zone = zinit("KNOTE", sizeof(struct knote), 0, 0, 1); +} +SYSINIT(knote, SI_SUB_PSEUDO, SI_ORDER_ANY, knote_init, NULL) + +static struct knote * +knote_alloc(void) +{ + return ((struct knote *)zalloc(knote_zone)); +} + +static void +knote_free(struct knote *kn) +{ + zfree(knote_zone, kn); +} diff --git a/sys/sys/event.h b/sys/sys/event.h new file mode 100644 index 000000000000..366b3476f81b --- /dev/null +++ b/sys/sys/event.h @@ -0,0 +1,165 @@ +/*- + * Copyright (c) 1999,2000 Jonathan Lemon <jlemon@FreeBSD.org> + * 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$ + */ + +#ifndef _SYS_EVENT_H_ +#define _SYS_EVENT_H_ + +#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_SYSCOUNT 6 + +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_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 for EVFILT_VNODE, shared with userspace + */ +#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 */ + +/* + * data/hint flags for EVFILT_PROC, shared with userspace + */ +#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 */ + +/* + * This is currently visible to userland to work around broken + * programs which pull in <sys/proc.h> or <sys/select.h>. + */ +#include <sys/queue.h> +struct knote; +SLIST_HEAD(klist, knote); + +#ifdef _KERNEL + +#define KNOTE(list, hint) if ((list) != NULL) knote(list, hint) + +/* + * Flag indicating hint is a signal. Used by EVFILT_SIGNAL, and also + * shared by EVFILT_PROC (all knotes attached to p->p_klist) + */ +#define NOTE_SIGNAL 0x08000000 + +struct filterops { + int f_isfd; /* true if ident == filedescriptor */ + int (*f_attach) __P((struct knote *kn)); + void (*f_detach) __P((struct knote *kn)); + int (*f_event) __P((struct knote *kn, long hint)); +}; + +struct knote { + SLIST_ENTRY(knote) kn_link; /* for fd */ + SLIST_ENTRY(knote) kn_selnext; /* for struct selinfo */ + TAILQ_ENTRY(knote) kn_tqe; + struct kqueue *kn_kq; /* which queue we are on */ + struct kevent kn_kevent; + int kn_status; + int kn_sfflags; /* saved filter flags */ + intptr_t kn_sdata; /* saved data field */ + union { + struct file *p_fp; /* file data pointer */ + struct proc *p_proc; /* proc pointer */ + } kn_ptr; + struct filterops *kn_fop; +#define KN_ACTIVE 0x01 /* event has been triggered */ +#define KN_QUEUED 0x02 /* event is on queue */ +#define KN_DISABLED 0x04 /* event is disabled */ +#define KN_DETACHED 0x08 /* knote is detached */ + +#define kn_id kn_kevent.ident +#define kn_filter kn_kevent.filter +#define kn_flags kn_kevent.flags +#define kn_fflags kn_kevent.fflags +#define kn_data kn_kevent.data +#define kn_fp kn_ptr.p_fp +}; + +struct proc; + +extern void knote(struct klist *list, long hint); +extern void knote_remove(struct proc *p, struct klist *list); +extern void knote_fdclose(struct proc *p, int fd); +extern int kqueue_register(struct kqueue *kq, + struct kevent *kev, struct proc *p); + +#else /* !_KERNEL */ + +#include <sys/cdefs.h> +struct timespec; + +__BEGIN_DECLS +int kqueue __P((void)); +int kevent __P((int kq, int nchanges, struct kevent **changelist, + int nevents, struct kevent *eventlist, + struct timespec *timeout)); +__END_DECLS + +#endif /* !_KERNEL */ + +#endif /* !_SYS_EVENT_H_ */ |
