From aa6567fe6475d3230c7c745a7ca208735af0c331 Mon Sep 17 00:00:00 2001 From: Niels Provos Date: Tue, 9 Apr 2002 15:14:06 +0000 Subject: [PATCH] Initial revision svn:r2 --- acconfig.h | 55 ++++++ configure.in | 117 ++++++++++++ event.3 | 268 ++++++++++++++++++++++++++ event.c | 454 ++++++++++++++++++++++++++++++++++++++++++++ event.h | 138 ++++++++++++++ install-sh | 238 +++++++++++++++++++++++ kqueue.c | 300 +++++++++++++++++++++++++++++ sample/event-test.c | 94 +++++++++ sample/time-test.c | 59 ++++++ select.c | 213 +++++++++++++++++++++ test/test-eof.c | 69 +++++++ test/test-weof.c | 69 +++++++ 12 files changed, 2074 insertions(+) create mode 100644 acconfig.h create mode 100644 configure.in create mode 100644 event.3 create mode 100644 event.c create mode 100644 event.h create mode 100755 install-sh create mode 100644 kqueue.c create mode 100644 sample/event-test.c create mode 100644 sample/time-test.c create mode 100644 select.c create mode 100644 test/test-eof.c create mode 100644 test/test-weof.c diff --git a/acconfig.h b/acconfig.h new file mode 100644 index 00000000..440d54a8 --- /dev/null +++ b/acconfig.h @@ -0,0 +1,55 @@ +/* Define if kqueue works correctly with pipes */ +#undef HAVE_WORKING_KQUEUE + +/* Define to `unsigned long long' if doesn't define. */ +#undef u_int64_t + +/* Define to `unsigned int' if doesn't define. */ +#undef u_int32_t + +/* Define to `unsigned short' if doesn't define. */ +#undef u_int16_t + +/* Define to `unsigned char' if doesn't define. */ +#undef u_int8_t + +/* Define if timeradd is defined in */ +#undef HAVE_TIMERADD +#ifndef HAVE_TIMERADD +#define timeradd(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \ + if ((vvp)->tv_usec >= 1000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_usec -= 1000000; \ + } \ + } while (0) +#define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (0) +#endif /* !HAVE_TIMERADD */ + +/* Define if TAILQ_FOREACH is defined in */ +#undef HAVE_TAILQFOREACH +#ifndef HAVE_TAILQFOREACH +#define TAILQ_FIRST(head) ((head)->tqh_first) +#define TAILQ_END(head) NULL +#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) +#define TAILQ_FOREACH(var, head, field) \ + for((var) = TAILQ_FIRST(head); \ + (var) != TAILQ_END(head); \ + (var) = TAILQ_NEXT(var, field)) +#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ + (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ + (elm)->field.tqe_next = (listelm); \ + *(listelm)->field.tqe_prev = (elm); \ + (listelm)->field.tqe_prev = &(elm)->field.tqe_next; \ +} while (0) +#endif /* TAILQ_FOREACH */ diff --git a/configure.in b/configure.in new file mode 100644 index 00000000..fdeb3dde --- /dev/null +++ b/configure.in @@ -0,0 +1,117 @@ +dnl configure.in for libevent +dnl Dug Song +AC_INIT(event.c) + +AC_CONFIG_HEADER(config.h) + +dnl Initialize prefix. +if test "$prefix" = "NONE"; then + prefix="/usr/local" +fi + +dnl Checks for programs. +AC_PROG_CC +AC_PROG_RANLIB +AC_PROG_INSTALL +AC_PROG_LN_S + +dnl Checks for libraries. + +dnl Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS(unistd.h sys/time.h sys/queue.h sys/event.h) +if test "x$ac_cv_header_sys_queue_h" = "xyes"; then + AC_MSG_CHECKING(for TAILQ_FOREACH in sys/queue.h) + AC_EGREP_CPP(yes, +[ +#include +#ifdef TAILQ_FOREACH + yes +#endif +], [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_TAILQFOREACH) ], AC_MSG_RESULT(no) + ) +fi + +if test "x$ac_cv_header_sys_time_h" = "xyes"; then + AC_MSG_CHECKING(for timeradd in sys/time.h) + AC_EGREP_CPP(yes, +[ +#include +#ifdef timeradd + yes +#endif +], [ AC_DEFINE(HAVE_TIMERADD) + AC_MSG_RESULT(yes)] ,AC_MSG_RESULT(no) +) +fi + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_HEADER_TIME + +dnl Checks for library functions. +AC_CHECK_FUNCS(gettimeofday) + +AC_SUBST(EVSRCS) +AC_CHECK_FUNCS(select, [haveselect=yes], ) +if test "x$haveselect" = "xyes" ; then + EVSRCS="select.c" +fi + +havekqueue=no +if test "x$ac_cv_header_sys_event_h" = "xyes"; then + AC_CHECK_FUNCS(kqueue, [havekqueue=yes], ) + if test "x$havekqueue" = "xyes" ; then + AC_MSG_CHECKING(for working kqueue) + AC_TRY_RUN( +#include +#include +#include +#include +#include +#include + +int +main(int argc, char **argv) +{ + int kq; + int n; + int fd[[2]]; + struct kevent ev; + struct timespec ts; + char buf[[8000]]; + + if (pipe(fd) == -1) + exit(1); + if (fcntl(fd[[1]], F_SETFL, O_NONBLOCK) == -1) + exit(1); + + while ((n = write(fd[[1]], buf, sizeof(buf))) == sizeof(buf)) + ; + + if ((kq = kqueue()) == -1) + exit(1); + + ev.ident = fd[[1]]; + ev.filter = EVFILT_WRITE; + ev.flags = EV_ADD | EV_ENABLE; + n = kevent(kq, &ev, 1, NULL, 0, NULL); + if (n == -1) + exit(1); + + read(fd[[0]], buf, sizeof(buf)); + + ts.tv_sec = 0; + ts.tv_nsec = 0; + n = kevent(kq, NULL, 0, &ev, 1, &ts); + if (n == -1 || n == 0) + exit(1); + + exit(0); +}, [AC_MSG_RESULT(yes) + AC_DEFINE(HAVE_WORKING_KQUEUE) + EVSRCS="$EVSRCS kqueue.c"], AC_MSG_RESULT(no), AC_MSG_RESULT(no)) + fi +fi + +AC_OUTPUT(Makefile test/Makefile) diff --git a/event.3 b/event.3 new file mode 100644 index 00000000..287dd7f1 --- /dev/null +++ b/event.3 @@ -0,0 +1,268 @@ +.\" $OpenBSD: timeout.9,v 1.11 2000/10/12 18:06:03 aaron Exp $ +.\" +.\" Copyright (c) 2000 Artur Grabowski +.\" 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. +.\" 3. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" 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 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. +.\" +.Dd June 23, 1996 +.Dt EVENT 3 +.Os +.Sh NAME +.Nm event_init , +.Nm event_dispatch , +.Nm event_loop , +.Nm event_set , +.Nm event_add , +.Nm event_del , +.Nm event_pending , +.Nm event_initalized , +.Nm timeout_set , +.Nm timeout_add , +.Nm timeout_del +.Nm timeout_pending , +.Nm timeout_initalized , +.Nd execute a function when a specific event occurs +.Sh SYNOPSIS +.Fd #include +.Ft void +.Fn "event_init" +.Ft int +.Fn "event_dispatch" +.Ft int +.Fn "event_loop" "int flags" +.Ft void +.Fn "event_set" "struct event *ev" "int fd" "short event" "void (*fn)(int, short, void *)" "void *arg" +.Ft int +.Fn "event_add" "struct event *ev" "struct timeval *tv" +.Ft int +.Fn "event_del" "struct event *ev" +.Ft int +.Fn "event_pending" "struct event *ev" "short event" "struct timeval *tv" +.Ft int +.Fn "event_initialized" "struct event *ev" +.Ft void +.Fn "timeout_set" "struct event *ev" "void (*fn)(int, short, void *)" "void *arg" +.Ft void +.Fn "timeout_add" "struct event *ev" "struct timeval *" +.Ft void +.Fn "timeout_del" "struct event *ev" +.Ft int +.Fn "timeout_pending" "struct event *ev" "struct timeval *tv" +.Ft int +.Fn "timeout_initialized" "struct event *ev" +.Ft int +.Fa (*event_sigcb)(void) ; +.Ft int +.Fa event_gotsig ; +.Sh DESCRIPTION +The +.Nm event +API provides a mechanism to execute a function when a specific event +on a file descriptor occurs or after at a given time has passed. +.Pp +The +.Nm event +API needs to be initalized with +.Fn event_init +before it can be used. +.Pp +In order to process events, an application needs to call +.Fn event_dispatch . +This functions only returns on error, and should replace the event core +of the application program. +.Pp +In order to avoid races in signal handlers, the +.Nm event +API provides two variables: +.Va event_sigcb +and +.Va event_gotsig . +A signal handler +sets +.Va event_gotsig +to indicate that a signal has been received. +The application sets +.Va event_sigcb +to a callback function. After the signal handler sets +.Va event_gotsig , +.Nm event_dispatch +will execute the callback function to process received signals. The +callback returns 1 when no events are registered any more. It can +return -1 to indicate an error to the +.Nm event +library, causing +.Fn event_dispatch +to terminate with +.Va errno +set to +.Er EINTR. +.Pp +The +.Nm event_loop +function provides an interface for single pass execution of pending +events. The flags +.Va EVLOOP_ONCE +and +.Va EVLOOP_NONBLOCK +are recognized. +.Pp +It is the responsibility of the caller to provide these functions with +pre-allocated event structures. +.Pp +The function +.Fn event_set +prepares the event structure +.Fa ev +to be used in future calls to +.Fn event_add +and +.Fn event_del . +The event will be prepared to call the function specified by the +.Fa fn +argument with an +.Fa int +argument indicating the file descriptor, with a +.Fa short +argument indicating the type of event, and a +.Fa void * +argument given in the +.Fa arg +argument. +The +.Fa fd +indicates the file descriptor that should be monitored for events. +The events can be either +.Va EV_READ, +.Va EV_WRITE, +or both. +Indicating that an application can read or write from the file descriptor +respectively without blocking. +.Pp +The function +.Fa fn +will be called with the file descriptor that triggered the event and +the type of event which will be either +.Va EV_TIMEOUT , +.Va EV_READ , +or +.Va EV_WRITE . +.Pp +Once initialized, the +.Fa ev +structure can be used in repeatedly in +.Fn event_add +and +.Fn event_del +and does not need to be reinitialized unless you wish to +change the function called and/or the argument to it. +.Pp +The function +.Fn event_add +schedules the execution of the +.Fa ev +event when the event specified in +.Fn event_set +occurs or in at least the time specified in the +.Fa tv . +If +.Fa tv +is NULL, no timeout occurs and the function will only be called +if a matching event occurs on the file descriptor. +The event in the +.Fa ev +argument must be already initialized by +.Fn event_set +and may not be used in calls to +.Fn event_set +until it has timed out or been removed with +.Fn event_del . +If the event in the +.Fa ev +argument has already a scheduled timeout, the old timeout will be +replaced by the new one. +.Pp +The function +.Fn event_del +will cancel the event in the argument +.Fa ev . +If the event has already executed or has never been added +the call will have no effect. +.Pp +The +.Fn event_pending +function can be used to check if the event specified by +.Fa event +is pending to run. +If +.Va EV_TIMEOUT +was specified and +.Fa tv +is not +.Va NULL , +the expiration time of the event will be returned in +.Fa tv . +.Pp +The +.Fn event_initialized +macro can be used to check if an event has been initialized. +.Pp +The functions +.Fn timeout_set , +.Fn timeout_add , +.Fn timeout_del , +.Fn timeout_initialized , +and +.Fn timeout_pending +are abbreviations for common situations where only a timeout is required. +The file descriptor passed will be 0, and the event type will be +.Va EV_TIMEOUT . +.Pp +It is possible to disable support for +.Va kqueue +but setting the environment variable +.Va EVENT_NOKQUEUE . +.Pp +.Sh RETURN VALUES +Upon successful completion +.Fn event_add +and +.Fn event_del +return 0. +Otherwise, -1 is returned and the global variable errno is +set to indicate the error. +.Sh SEE ALSO +.Xr timeout 9 , +.Xr select 2 , +.Xr kqueue 2 +.Sh HISTORY +The +.Nm event +API manpage is based on the +.Xr timeout 9 +manpage by Artur Grabowski. +.Sh AUTHORS +The +.Nm event +library was written by Niels Provos. diff --git a/event.c b/event.c new file mode 100644 index 00000000..b4272af4 --- /dev/null +++ b/event.c @@ -0,0 +1,454 @@ +/* + * Copyright 2000-2002 Niels Provos + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_LOG +#include "log.h" +#else +#define LOG_DBG(x) +#define log_error(x) perror(x) +#endif + +#include "event.h" + +#ifdef HAVE_SELECT +extern struct eventop selectops; +#endif +#ifdef HAVE_WORKING_KQUEUE +extern struct eventop kqops; +#endif + +/* In order of preference */ +struct eventop *eventops[] = { +#ifdef HAVE_WORKING_KQUEUE + &kqops, +#endif +#ifdef HAVE_SELECT + &selectops, +#endif + NULL +}; + +struct eventop *evsel; +void *evbase; + +/* Handle signals */ +int (*event_sigcb)(void); /* Signal callback when gotsig is set */ +int event_gotsig; /* Set in signal handler */ + +/* Prototypes */ +void event_queue_insert(struct event *, int); +void event_queue_remove(struct event *, int); + +static RB_HEAD(event_tree, event) timetree; +static struct event_list activequeue; +struct event_list eventqueue; +static struct timeval event_tv; + +static int +compare(struct event *a, struct event *b) +{ + if (timercmp(&a->ev_timeout, &b->ev_timeout, <)) + return (-1); + else if (timercmp(&a->ev_timeout, &b->ev_timeout, >)) + return (1); + return (0); +} + +RB_PROTOTYPE(event_tree, event, ev_timeout_node, compare); + +RB_GENERATE(event_tree, event, ev_timeout_node, compare); + + +void +event_init(void) +{ + int i; + + event_sigcb = NULL; + event_gotsig = 0; + gettimeofday(&event_tv, NULL); + + RB_INIT(&timetree); + TAILQ_INIT(&eventqueue); + TAILQ_INIT(&activequeue); + + evbase = NULL; + for (i = 0; eventops[i] && !evbase; i++) { + evsel = eventops[i]; + + evbase = evsel->init(); + } + +#if defined(USE_LOG) && defined(USE_DEBUG) + log_to(stderr); + log_debug_cmd(LOG_MISC, 80); +#endif +} + +int +event_haveevents(void) +{ + return (RB_ROOT(&timetree) || TAILQ_FIRST(&eventqueue) || + TAILQ_FIRST(&activequeue)); +} + +void +event_process_active(void) +{ + struct event *ev; + + for (ev = TAILQ_FIRST(&activequeue); ev; + ev = TAILQ_FIRST(&activequeue)) { + event_queue_remove(ev, EVLIST_ACTIVE); + + (*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg); + } +} + +int +event_dispatch(void) +{ + return (event_loop(0)); +} + +int +event_loop(int flags) +{ + struct timeval tv; + int res, done; + + /* Calculate the initial events that we are waiting for */ + if (evsel->recalc(evbase, 0) == -1) + return (-1); + + done = 0; + while (!done) { + while (event_gotsig) { + event_gotsig = 0; + if (event_sigcb) { + res = (*event_sigcb)(); + if (res == -1) { + errno = EINTR; + return (-1); + } + } + } + + /* Check if time is running backwards */ + gettimeofday(&tv, NULL); + if (timercmp(&tv, &event_tv, <)) { + struct timeval off; + LOG_DBG((LOG_MIST, 10, + "%s: time is running backwards, corrected", + __FUNCTION__)); + + timersub(&event_tv, &tv, &off); + timeout_correct(&off); + } + event_tv = tv; + + if (!(flags & EVLOOP_NONBLOCK)) + timeout_next(&tv); + else + timerclear(&tv); + + /* If we have no events, we just exit */ + if (!event_haveevents()) + return (1); + + res = evsel->dispatch(evbase, &tv); + + if (res == -1) + return (-1); + + timeout_process(); + + if (TAILQ_FIRST(&activequeue)) { + event_process_active(); + if (flags & EVLOOP_ONCE) + done = 1; + } else if (flags & EVLOOP_NONBLOCK) + done = 1; + + if (evsel->recalc(evbase, 0) == -1) + return (-1); + } + + return (0); +} + +void +event_set(struct event *ev, int fd, short events, + void (*callback)(int, short, void *), void *arg) +{ + ev->ev_callback = callback; + ev->ev_arg = arg; + ev->ev_fd = fd; + ev->ev_events = events; + ev->ev_flags = EVLIST_INIT; +} + +/* + * Checks if a specific event is pending or scheduled. + */ + +int +event_pending(struct event *ev, short event, struct timeval *tv) +{ + int flags = 0; + + if (ev->ev_flags & EVLIST_INSERTED) + flags |= (ev->ev_events & (EV_READ|EV_WRITE)); + if (ev->ev_flags & EVLIST_ACTIVE) + flags |= ev->ev_res; + if (ev->ev_flags & EVLIST_TIMEOUT) + flags |= EV_TIMEOUT; + + event &= (EV_TIMEOUT|EV_READ|EV_WRITE); + + /* See if there is a timeout that we should report */ + if (tv != NULL && (flags & event & EV_TIMEOUT)) + *tv = ev->ev_timeout; + + return (flags & event); +} + +int +event_add(struct event *ev, struct timeval *tv) +{ + LOG_DBG((LOG_MISC, 55, + "event_add: event: %p, %s%s%scall %p", + ev, + ev->ev_events & EV_READ ? "EV_READ " : " ", + ev->ev_events & EV_WRITE ? "EV_WRITE " : " ", + tv ? "EV_TIMEOUT " : " ", + ev->ev_callback)); + + assert(!(ev->ev_flags & ~EVLIST_ALL)); + + if (tv != NULL) { + struct timeval now; + + gettimeofday(&now, NULL); + timeradd(&now, tv, &ev->ev_timeout); + + LOG_DBG((LOG_MISC, 55, + "event_add: timeout in %d seconds, call %p", + tv->tv_sec, ev->ev_callback)); + if (ev->ev_flags & EVLIST_TIMEOUT) + event_queue_remove(ev, EVLIST_TIMEOUT); + + event_queue_insert(ev, EVLIST_TIMEOUT); + } + + if ((ev->ev_events & (EV_READ|EV_WRITE)) && + !(ev->ev_flags & EVLIST_INSERTED)) { + event_queue_insert(ev, EVLIST_INSERTED); + + return (evsel->add(evbase, ev)); + } + + return (0); +} + +int +event_del(struct event *ev) +{ + LOG_DBG((LOG_MISC, 80, "event_del: %p, callback %p", + ev, ev->ev_callback)); + + assert(!(ev->ev_flags & ~EVLIST_ALL)); + + if (ev->ev_flags & EVLIST_TIMEOUT) + event_queue_remove(ev, EVLIST_TIMEOUT); + + if (ev->ev_flags & EVLIST_ACTIVE) + event_queue_remove(ev, EVLIST_ACTIVE); + + if (ev->ev_flags & EVLIST_INSERTED) { + event_queue_remove(ev, EVLIST_INSERTED); + return (evsel->del(evbase, ev)); + } + + return (0); +} + +void +event_active(struct event *ev, int res) +{ + ev->ev_res = res; + event_queue_insert(ev, EVLIST_ACTIVE); +} + +int +timeout_next(struct timeval *tv) +{ + struct timeval now; + struct event *ev; + + if ((ev = RB_MIN(event_tree, &timetree)) == NULL) { + timerclear(tv); + tv->tv_sec = TIMEOUT_DEFAULT; + return (0); + } + + if (gettimeofday(&now, NULL) == -1) + return (-1); + + if (timercmp(&ev->ev_timeout, &now, <=)) { + timerclear(tv); + return (0); + } + + timersub(&ev->ev_timeout, &now, tv); + + LOG_DBG((LOG_MISC, 60, "timeout_next: in %d seconds", tv->tv_sec)); + return (0); +} + +void +timeout_correct(struct timeval *off) +{ + struct event *ev; + + /* We can modify the key element of the node without destroying + * the key, beause we apply it to all in the right order. + */ + RB_FOREACH(ev, event_tree, &timetree) + timersub(&ev->ev_timeout, off, &ev->ev_timeout); +} + +void +timeout_process(void) +{ + struct timeval now; + struct event *ev, *next; + + gettimeofday(&now, NULL); + + for (ev = RB_MIN(event_tree, &timetree); ev; ev = next) { + if (timercmp(&ev->ev_timeout, &now, >)) + break; + next = RB_NEXT(event_tree, &timetree, ev); + + event_queue_remove(ev, EVLIST_TIMEOUT); + + /* delete this event from the I/O queues */ + event_del(ev); + + LOG_DBG((LOG_MISC, 60, "timeout_process: call %p", + ev->ev_callback)); + event_active(ev, EV_TIMEOUT); + } +} + +void +timeout_insert(struct event *ev) +{ + struct event *tmp; + + tmp = RB_FIND(event_tree, &timetree, ev); + + if (tmp != NULL) { + struct timeval tv; + struct timeval add = {0,1}; + + /* Find unique time */ + tv = ev->ev_timeout; + do { + timeradd(&tv, &add, &tv); + tmp = RB_NEXT(event_tree, &timetree, tmp); + } while (tmp != NULL && timercmp(&tmp->ev_timeout, &tv, ==)); + + ev->ev_timeout = tv; + } + + tmp = RB_INSERT(event_tree, &timetree, ev); + assert(tmp == NULL); +} + +void +event_queue_remove(struct event *ev, int queue) +{ + if (!(ev->ev_flags & queue)) + errx(1, "%s: %p(fd %d) not on queue %x", __FUNCTION__, + ev, ev->ev_fd, queue); + + ev->ev_flags &= ~queue; + switch (queue) { + case EVLIST_ACTIVE: + TAILQ_REMOVE(&activequeue, ev, ev_active_next); + break; + case EVLIST_TIMEOUT: + RB_REMOVE(event_tree, &timetree, ev); + break; + case EVLIST_INSERTED: + TAILQ_REMOVE(&eventqueue, ev, ev_next); + break; + default: + errx(1, "%s: unknown queue %x", __FUNCTION__, queue); + } +} + +void +event_queue_insert(struct event *ev, int queue) +{ + if (ev->ev_flags & queue) + errx(1, "%s: %p(fd %d) already on queue %x", __FUNCTION__, + ev, ev->ev_fd, queue); + + ev->ev_flags |= queue; + switch (queue) { + case EVLIST_ACTIVE: + TAILQ_INSERT_TAIL(&activequeue, ev, ev_active_next); + break; + case EVLIST_TIMEOUT: + timeout_insert(ev); + break; + case EVLIST_INSERTED: + TAILQ_INSERT_TAIL(&eventqueue, ev, ev_next); + break; + default: + errx(1, "%s: unknown queue %x", __FUNCTION__, queue); + } +} diff --git a/event.h b/event.h new file mode 100644 index 00000000..cdc8d2aa --- /dev/null +++ b/event.h @@ -0,0 +1,138 @@ +/* + * Copyright 2000-2002 Niels Provos + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + */ +#ifndef _EVENT_H_ +#define _EVENT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#define EVLIST_TIMEOUT 0x01 +#define EVLIST_INSERTED 0x02 +#define EVLIST_ACTIVE 0x08 +#define EVLIST_INIT 0x80 + +/* EVLIST_X_ Private space: 0x1000-0xf000 */ +#define EVLIST_ALL (0xf000 | 0x8f) + +#define EV_TIMEOUT 0x01 +#define EV_READ 0x02 +#define EV_WRITE 0x04 + +/* Fix so that ppl dont have to run with */ +#ifndef TAILQ_ENTRY +#define _EVENT_DEFINED_TQENTRY +#define TAILQ_ENTRY(type) \ +struct { \ + struct type *tqe_next; /* next element */ \ + struct type **tqe_prev; /* address of previous next element */ \ +} +#endif /* !TAILQ_ENTRY */ +#ifndef RB_ENTRY +#define _EVENT_DEFINED_RBENTRY +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} +#endif /* !RB_ENTRY */ + +struct event { + TAILQ_ENTRY (event) ev_next; + TAILQ_ENTRY (event) ev_active_next; + RB_ENTRY (event) ev_timeout_node; + + int ev_fd; + short ev_events; + + struct timeval ev_timeout; + + void (*ev_callback)(int, short, void *arg); + void *ev_arg; + + int ev_res; /* result passed to event callback */ + int ev_flags; +}; + +#ifdef _EVENT_DEFINED_TQENTRY +#undef TAILQ_ENTRY +#undef _EVENT_DEFINED_TQENTRY +#else +TAILQ_HEAD (event_list, event); +#endif /* _EVENT_DEFINED_TQENTRY */ +#ifdef _EVENT_DEFINED_RBENTRY +#undef RB_ENTRY +#undef _EVENT_DEFINED_RBENTRY +#endif /* _EVENT_DEFINED_RBENTRY */ + +struct eventop { + char *name; + void *(*init)(void); + int (*add)(void *, struct event *); + int (*del)(void *, struct event *); + int (*recalc)(void *, int); + int (*dispatch)(void *, struct timeval *); +}; + +#define TIMEOUT_DEFAULT 5 + +void event_init(void); +int event_dispatch(void); + +#define EVLOOP_ONCE 0x01 +#define EVLOOP_NONBLOCK 0x02 +int event_loop(int); + +int timeout_next(struct timeval *); +void timeout_correct(struct timeval *); +void timeout_process(void); + +#define timeout_add(ev, tv) event_add(ev, tv) +#define timeout_set(ev, cb, arg) event_set(ev, -1, 0, cb, arg) +#define timeout_del(ev) event_del(ev) +#define timeout_pending(ev, tv) event_pending(ev, EV_TIMEOUT, tv) +#define timeout_initialized(ev) ((ev)->ev_flags & EVLIST_INIT) + +void event_set(struct event *, int, short, void (*)(int, short, void *), void *); +int event_add(struct event *, struct timeval *); +int event_del(struct event *); + void event_active(struct event *, int); + +int event_pending(struct event *, short, struct timeval *); + +#define event_initialized(ev) ((ev)->ev_flags & EVLIST_INIT) + +#ifdef __cplusplus +} +#endif + +#endif /* _EVENT_H_ */ diff --git a/install-sh b/install-sh new file mode 100755 index 00000000..89fc9b09 --- /dev/null +++ b/install-sh @@ -0,0 +1,238 @@ +#! /bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# `make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" +mkdirprog="${MKDIRPROG-mkdir}" + +tranformbasename="" +transform_arg="" +instcmd="$mvprog" +chmodcmd="$chmodprog 0755" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" +dir_arg="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -d) dir_arg=true + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + -t=*) transformarg=`echo $1 | sed 's/-t=//'` + shift + continue;; + + -b=*) transformbasename=`echo $1 | sed 's/-b=//'` + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + # this colon is to work around a 386BSD /bin/sh bug + : + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +else + true +fi + +if [ x"$dir_arg" != x ]; then + dst=$src + src="" + + if [ -d $dst ]; then + instcmd=: + else + instcmd=mkdir + fi +else + +# Waiting for this to be detected by the "$instcmd $src $dsttmp" command +# might cause directories to be created, which would be especially bad +# if $src (and thus $dsttmp) contains '*'. + + if [ -f $src -o -d $src ] + then + true + else + echo "install: $src does not exist" + exit 1 + fi + + if [ x"$dst" = x ] + then + echo "install: no destination specified" + exit 1 + else + true + fi + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + + if [ -d $dst ] + then + dst="$dst"/`basename $src` + else + true + fi +fi + +## this sed command emulates the dirname command +dstdir=`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'` + +# Make sure that the destination directory exists. +# this part is taken from Noah Friedman's mkinstalldirs script + +# Skip lots of stat calls in the usual case. +if [ ! -d "$dstdir" ]; then +defaultIFS=' +' +IFS="${IFS-${defaultIFS}}" + +oIFS="${IFS}" +# Some sh's can't handle IFS=/ for some reason. +IFS='%' +set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'` +IFS="${oIFS}" + +pathcomp='' + +while [ $# -ne 0 ] ; do + pathcomp="${pathcomp}${1}" + shift + + if [ ! -d "${pathcomp}" ] ; + then + $mkdirprog "${pathcomp}" + else + true + fi + + pathcomp="${pathcomp}/" +done +fi + +if [ x"$dir_arg" != x ] +then + $doit $instcmd $dst && + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst; else true ; fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst; else true ; fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dst; else true ; fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst; else true ; fi +else + +# If we're going to rename the final executable, determine the name now. + + if [ x"$transformarg" = x ] + then + dstfile=`basename $dst` + else + dstfile=`basename $dst $transformbasename | + sed $transformarg`$transformbasename + fi + +# don't allow the sed command to completely eliminate the filename + + if [ x"$dstfile" = x ] + then + dstfile=`basename $dst` + else + true + fi + +# Make a temp file name in the proper directory. + + dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + + $doit $instcmd $src $dsttmp && + + trap "rm -f ${dsttmp}" 0 && + +# and set any options; do chmod last to preserve setuid bits + +# If any of these fail, we abort the whole thing. If we want to +# ignore errors from any of these, just make sure not to ignore +# errors from the above "$doit $instcmd $src $dsttmp" command. + + if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; else true;fi && + if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; else true;fi && + if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; else true;fi && + if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; else true;fi && + +# Now rename the file to the real destination. + + $doit $rmcmd -f $dstdir/$dstfile && + $doit $mvcmd $dsttmp $dstdir/$dstfile + +fi && + + +exit 0 diff --git a/kqueue.c b/kqueue.c new file mode 100644 index 00000000..594fe16d --- /dev/null +++ b/kqueue.c @@ -0,0 +1,300 @@ +/* + * Copyright 2000-2002 Niels Provos + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_LOG +#include "log.h" +#else +#define LOG_DBG(x) +#define log_error(x) perror(x) +#endif + +#include "event.h" + +extern struct event_list timequeue; +extern struct event_list eventqueue; +extern struct event_list addqueue; + +#define EVLIST_X_KQINKERNEL 0x1000 + +#define NEVENT 64 + +struct kqop { + struct kevent *changes; + int nchanges; + struct kevent *events; + int nevents; + int kq; +} kqop; + +void *kq_init (void); +int kq_add (void *, struct event *); +int kq_del (void *, struct event *); +int kq_recalc (void *, int); +int kq_dispatch (void *, struct timeval *); + +struct eventop kqops = { + "kqueue", + kq_init, + kq_add, + kq_del, + kq_recalc, + kq_dispatch +}; + +void * +kq_init(void) +{ + int kq; + + /* Disable kqueue when this environment variable is set */ + if (getenv("EVENT_NOKQUEUE")) + return (NULL); + + memset(&kqop, 0, sizeof(kqop)); + + /* Initalize the kernel queue */ + + if ((kq = kqueue()) == -1) { + log_error("kqueue"); + return (NULL); + } + + kqop.kq = kq; + + /* Initalize fields */ + kqop.changes = malloc(NEVENT * sizeof(struct kevent)); + if (kqop.changes == NULL) + return (NULL); + kqop.events = malloc(NEVENT * sizeof(struct kevent)); + if (kqop.events == NULL) { + free (kqop.changes); + return (NULL); + } + kqop.nevents = NEVENT; + + return (&kqop); +} + +int +kq_recalc(void *arg, int max) +{ + return (0); +} + +int +kq_insert(struct kqop *kqop, struct kevent *kev) +{ + int nevents = kqop->nevents; + + if (kqop->nchanges == nevents) { + struct kevent *newchange; + struct kevent *newresult; + + nevents *= 2; + + newchange = realloc(kqop->changes, + nevents * sizeof(struct kevent)); + if (newchange == NULL) { + log_error(__FUNCTION__": malloc"); + return (-1); + } + kqop->changes = newchange; + + newresult = realloc(kqop->changes, + nevents * sizeof(struct kevent)); + + /* + * If we fail, we don't have to worry about freeing, + * the next realloc will pick it up. + */ + if (newresult == NULL) { + log_error(__FUNCTION__": malloc"); + return (-1); + } + kqop->events = newchange; + + kqop->nevents = nevents; + } + + memcpy(&kqop->changes[kqop->nchanges++], kev, sizeof(struct kevent)); + + LOG_DBG((LOG_MISC, 70, __FUNCTION__": fd %d %s%s", + kev->ident, + kev->filter == EVFILT_READ ? "EVFILT_READ" : "EVFILT_WRITE", + kev->flags == EV_DELETE ? " (del)" : "")); + + return (0); +} + +int +kq_dispatch(void *arg, struct timeval *tv) +{ + struct kqop *kqop = arg; + struct kevent *changes = kqop->changes; + struct kevent *events = kqop->events; + struct event *ev; + struct timespec ts; + int i, res; + + TIMEVAL_TO_TIMESPEC(tv, &ts); + + res = kevent(kqop->kq, changes, kqop->nchanges, + events, kqop->nevents, &ts); + kqop->nchanges = 0; + if (res == -1) { + if (errno != EINTR) { + log_error("kevent"); + return (-1); + } + + return (0); + } + + LOG_DBG((LOG_MISC, 80, __FUNCTION__": kevent reports %d", res)); + + for (i = 0; i < res; i++) { + int which = 0; + + if (events[i].flags & EV_ERROR) { + /* + * Error messages that can happen, when a delete fails. + * EBADF happens when the file discriptor has been + * closed, + * ENOENT when the file discriptor was closed and + * then reopened. + * An error is also indicated when a callback deletes + * an event we are still processing. In that case + * the data field is set to ENOENT. + */ + if (events[i].data == EBADF || + events[i].data == ENOENT) + continue; + return (-1); + } + + ev = events[i].udata; + + if (events[i].filter == EVFILT_READ) { + which |= EV_READ; + } else if (events[i].filter == EVFILT_WRITE) { + which |= EV_WRITE; + } + + if (which) { + ev->ev_flags &= ~EVLIST_X_KQINKERNEL; + event_del(ev); + event_active(ev, which); + } + } + + return (0); +} + + +int +kq_add(void *arg, struct event *ev) +{ + struct kqop *kqop = arg; + struct kevent kev; + + if (ev->ev_events & EV_READ) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_READ; + kev.flags = EV_ADD | EV_ONESHOT; + kev.udata = ev; + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags |= EVLIST_X_KQINKERNEL; + } + + if (ev->ev_events & EV_WRITE) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_WRITE; + kev.flags = EV_ADD | EV_ONESHOT; + kev.udata = ev; + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags |= EVLIST_X_KQINKERNEL; + } + + return (0); +} + +int +kq_del(void *arg, struct event *ev) +{ + struct kqop *kqop = arg; + struct kevent kev; + + if (!(ev->ev_flags & EVLIST_X_KQINKERNEL)) + return (0); + + if (ev->ev_events & EV_READ) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_READ; + kev.flags = EV_DELETE; + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags &= ~EVLIST_X_KQINKERNEL; + } + + if (ev->ev_events & EV_WRITE) { + memset(&kev, 0, sizeof(kev)); + kev.ident = ev->ev_fd; + kev.filter = EVFILT_WRITE; + kev.flags = EV_DELETE; + + if (kq_insert(kqop, &kev) == -1) + return (-1); + + ev->ev_flags &= ~EVLIST_X_KQINKERNEL; + } + + return (0); +} diff --git a/sample/event-test.c b/sample/event-test.c new file mode 100644 index 00000000..f4cfa11b --- /dev/null +++ b/sample/event-test.c @@ -0,0 +1,94 @@ +/* + * Compile with: + * cc -I/usr/local/include -o event-test event-test.c -L/usr/local/lib -levent + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +void +fifo_read(int fd, short event, void *arg) +{ + char buf[255]; + int len; + struct event *ev = arg; + + /* Reschedule this event */ + event_add(ev, NULL); + + fprintf(stderr, "fifo_read called with fd: %d, event: %d, arg: %p\n", + fd, event, arg); + + len = read(fd, buf, sizeof(buf) - 1); + if (len == -1) { + perror("read"); + return; + } else if (len == 0) { + fprintf(stderr, "Connection closed\n"); + return; + } + + buf[len] = '\0'; + fprintf(stdout, "Read: %s\n", buf); +} + +int +main (int argc, char **argv) +{ + struct stat st; + char *fifo = "event.fifo"; + int socket; + struct event evfifo; + + if (lstat (fifo, &st) == 0) { + if ((st.st_mode & S_IFMT) == S_IFREG) { + errno = EEXIST; + perror("lstat"); + exit (1); + } + } + + unlink (fifo); + if (mkfifo (fifo, 0600) == -1) { + perror("mkfifo"); + exit (1); + } + + /* Linux pipes are broken, we need O_RDWR instead of O_RDONLY */ +#ifdef __linux + socket = open (fifo, O_RDWR | O_NONBLOCK, 0); +#else + socket = open (fifo, O_RDONLY | O_NONBLOCK, 0); +#endif + + if (socket == -1) { + perror("open"); + exit (1); + } + + fprintf(stderr, "Write data to %s\n", fifo); + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + event_set(&evfifo, socket, EV_READ, fifo_read, &evfifo); + + /* Add it to the active events, without a timeout */ + event_add(&evfifo, NULL); + + event_dispatch(); + + return (0); +} + diff --git a/sample/time-test.c b/sample/time-test.c new file mode 100644 index 00000000..4f42f006 --- /dev/null +++ b/sample/time-test.c @@ -0,0 +1,59 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int lasttime; + +void +timeout_cb(int fd, short event, void *arg) +{ + struct timeval tv; + struct event *timeout = arg; + int newtime = time(NULL); + + printf("%s: called at %d: %d\n", __FUNCTION__, newtime, + newtime - lasttime); + lasttime = newtime; + + timerclear(&tv); + tv.tv_sec = 2; + event_add(timeout, &tv); +} + +int +main (int argc, char **argv) +{ + struct event timeout; + struct timeval tv; + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + timeout_set(&timeout, timeout_cb, &timeout); + + timerclear(&tv); + tv.tv_sec = 2; + event_add(&timeout, &tv); + + lasttime = time(NULL); + + event_dispatch(); + + return (0); +} + diff --git a/select.c b/select.c new file mode 100644 index 00000000..b1329886 --- /dev/null +++ b/select.c @@ -0,0 +1,213 @@ +/* + * Copyright 2000-2002 Niels Provos + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Niels Provos. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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. + */ +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_LOG +#include "log.h" +#else +#define LOG_DBG(x) +#define log_error(x) perror(x) +#endif + +#include "event.h" + +extern struct event_list timequeue; +extern struct event_list eventqueue; +extern struct event_list addqueue; + +#ifndef howmany +#define howmany(x, y) (((x)+((y)-1))/(y)) +#endif + +struct selectop { + int event_fds; /* Highest fd in fd set */ + int event_fdsz; + fd_set *event_readset; + fd_set *event_writeset; +} sop; + +void *select_init (void); +int select_add (void *, struct event *); +int select_del (void *, struct event *); +int select_recalc (void *, int); +int select_dispatch (void *, struct timeval *); + +struct eventop selectops = { + "select", + select_init, + select_add, + select_del, + select_recalc, + select_dispatch +}; + +void * +select_init(void) +{ + memset(&sop, 0, sizeof(sop)); + + return (&sop); +} + +/* + * Called with the highest fd that we know about. If it is 0, completely + * recalculate everything. + */ + +int +select_recalc(void *arg, int max) +{ + struct selectop *sop = arg; + fd_set *readset, *writeset; + struct event *ev; + int fdsz; + + if (sop->event_fds < max) + sop->event_fds = max; + + if (!sop->event_fds) { + TAILQ_FOREACH(ev, &eventqueue, ev_next) + if (ev->ev_fd > sop->event_fds) + sop->event_fds = ev->ev_fd; + } + + fdsz = howmany(sop->event_fds + 1, NFDBITS) * sizeof(fd_mask); + if (fdsz > sop->event_fdsz) { + if ((readset = realloc(sop->event_readset, fdsz)) == NULL) { + log_error("malloc"); + return (-1); + } + + if ((writeset = realloc(sop->event_writeset, fdsz)) == NULL) { + log_error("malloc"); + free(readset); + return (-1); + } + + memset((char *)readset + sop->event_fdsz, 0, + fdsz - sop->event_fdsz); + memset((char *)writeset + sop->event_fdsz, 0, + fdsz - sop->event_fdsz); + + sop->event_readset = readset; + sop->event_writeset = writeset; + sop->event_fdsz = fdsz; + } + + return (0); +} + +int +select_dispatch(void *arg, struct timeval *tv) +{ + int maxfd, res; + struct event *ev, *next; + struct selectop *sop = arg; + + memset(sop->event_readset, 0, sop->event_fdsz); + memset(sop->event_writeset, 0, sop->event_fdsz); + + TAILQ_FOREACH(ev, &eventqueue, ev_next) { + if (ev->ev_events & EV_WRITE) + FD_SET(ev->ev_fd, sop->event_writeset); + if (ev->ev_events & EV_READ) + FD_SET(ev->ev_fd, sop->event_readset); + } + + + if ((res = select(sop->event_fds + 1, sop->event_readset, + sop->event_writeset, NULL, tv)) == -1) { + if (errno != EINTR) { + log_error("select"); + return (-1); + } + + return (0); + } + + LOG_DBG((LOG_MISC, 80, __FUNCTION__": select reports %d", + res)); + + maxfd = 0; + for (ev = TAILQ_FIRST(&eventqueue); ev != NULL; ev = next) { + next = TAILQ_NEXT(ev, ev_next); + + res = 0; + if (FD_ISSET(ev->ev_fd, sop->event_readset)) + res |= EV_READ; + if (FD_ISSET(ev->ev_fd, sop->event_writeset)) + res |= EV_WRITE; + res &= ev->ev_events; + + if (res) { + event_del(ev); + event_active(ev, res); + } else if (ev->ev_fd > maxfd) + maxfd = ev->ev_fd; + } + + sop->event_fds = maxfd; + + return (0); +} + +int +select_add(void *arg, struct event *ev) +{ + struct selectop *sop = arg; + + /* + * Keep track of the highest fd, so that we can calculate the size + * of the fd_sets for select(2) + */ + if (sop->event_fds < ev->ev_fd) + sop->event_fds = ev->ev_fd; + + return (0); +} + +/* + * Nothing to be done here. + */ + +int +select_del(void *arg, struct event *ev) +{ + return (0); +} diff --git a/test/test-eof.c b/test/test-eof.c new file mode 100644 index 00000000..a1c1eeb7 --- /dev/null +++ b/test/test-eof.c @@ -0,0 +1,69 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int test_okay = 1; +int called = 0; + +void +read_cb(int fd, short event, void *arg) +{ + char buf[256]; + int len; + + len = read(fd, buf, sizeof(buf)); + + printf("%s: read %d%s\n", __FUNCTION__, + len, len ? "" : " - means EOF"); + + if (len) { + if (!called) + event_add(arg, NULL); + } else if (called == 1) + test_okay = 0; + + called++; +} + +int +main (int argc, char **argv) +{ + struct event ev; + char *test = "test string"; + int pair[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + + write(pair[0], test, strlen(test)+1); + shutdown(pair[0], SHUT_WR); + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + event_set(&ev, pair[1], EV_READ, read_cb, &ev); + + event_add(&ev, NULL); + + event_dispatch(); + + return (test_okay); +} + diff --git a/test/test-weof.c b/test/test-weof.c new file mode 100644 index 00000000..637063ea --- /dev/null +++ b/test/test-weof.c @@ -0,0 +1,69 @@ +/* + * Compile with: + * cc -I/usr/local/include -o time-test time-test.c -L/usr/local/lib -levent + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +int pair[2]; +int test_okay = 1; +int called = 0; + +void +write_cb(int fd, short event, void *arg) +{ + char *test = "test string"; + int len; + + len = write(fd, test, strlen(test) + 1); + + printf("%s: write %d%s\n", __FUNCTION__, + len, len ? "" : " - means EOF"); + + if (len > 0) { + if (!called) + event_add(arg, NULL); + close(pair[0]); + } else if (called == 1): + test_okay = 0; + + called++; +} + +int +main (int argc, char **argv) +{ + struct event ev; + + if (signal(SIGPIPE, SIG_IGN) == SIG_IGN) + return (1); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == -1) + return (1); + + /* Initalize the event library */ + event_init(); + + /* Initalize one event */ + event_set(&ev, pair[1], EV_WRITE, write_cb, &ev); + + event_add(&ev, NULL); + + event_dispatch(); + + return (test_okay); +} + -- 2.40.0