/*++ /* NAME /* events 3 /* SUMMARY /* event manager /* SYNOPSIS /* #include /* /* time_t event_time() /* /* void event_loop(delay) /* int delay; /* /* time_t event_request_timer(callback, context, delay) /* void (*callback)(int event, char *context); /* char *context; /* int delay; /* /* int event_cancel_timer(callback, context) /* void (*callback)(char *context); /* char *context; /* /* void event_enable_read(fd, callback, context) /* int fd; /* void (*callback)(int event, char *context); /* char *context; /* /* void event_enable_write(fd, callback, context) /* int fd; /* void (*callback)(int event, char *context); /* char *context; /* /* void event_disable_readwrite(fd) /* int fd; /* /* void event_drain() /* DESCRIPTION /* This module delivers I/O and timer events. /* Multiple I/O streams and timers can be monitored simultaneously. /* Events are delivered via callback routines provided by the /* application. When requesting an event, the application can provide /* private context that is passed back when the callback routine is /* executed. /* /* event_time() returns a cached value of the current time. /* /* event_loop() monitors all I/O channels for which the application has /* expressed interest, and monitors the timer request queue. /* It notifies the application whenever events of interest happen. /* A negative delay value causes the function to pause until something /* happens; a positive delay value causes event_loop() to return when /* the next event happens or when the delay time in seconds is over, /* whatever happens first. A zero delay effectuates a poll. /* /* Note: in order to avoid race conditions, event_loop() cannot /* not be called recursively. /* /* event_request_timer() causes the specified callback function to /* be called with the specified context argument after \fIdelay\fR /* seconds, or as soon as possible thereafter. The delay should /* not be negative. /* The event argument is equal to EVENT_TIME. /* Only one timer request can be active per (callback, context) pair. /* Calling event_request_timer() with an existing (callback, context) /* pair does not schedule a new event, but updates the moment of /* delivery. The result is the absolute time at which the timer is /* scheduled to go off. /* /* event_cancel_timer() cancels the specified (callback, context) request. /* The application is allowed to cancel non-existing requests. The result /* value is the amount of time left before the timer would have gone off, /* or -1 in case of no pending timer. /* /* event_enable_read() (event_enable_write()) enables read (write) events /* on the named I/O channel. It is up to the application to assemble /* partial reads or writes. /* An I/O channel cannot handle more than one request at the /* same time. The application is allowed to enable an event that /* is already enabled (same channel, callback and context). /* /* The manifest constants EVENT_NULL_CONTEXT and EVENT_NULL_TYPE /* provide convenient null values. /* /* The callback routine has the following arguments: /* .IP fd /* The stream on which the event happened. /* .IP event /* An indication of the event type: /* .RS /* .IP EVENT_READ /* read event, /* .IP EVENT_WRITE /* write event, /* .IP EVENT_XCPT /* exception. /* .RE /* .IP context /* Application context given to event_enable_read() (event_enable_write()). /* .PP /* event_disable_readwrite() disables further I/O events on the specified /* I/O channel. The application is allowed to cancel non-existing /* I/O event requests. /* /* event_drain() repeatedly calls event_loop() until no more timer /* events or I/O events are pending. This routine must not be called /* from an event_whatever() callback routine. /* DIAGNOSTICS /* Panics: interface violations. Fatal errors: out of memory, /* system call failure. Warnings: the number of available /* file descriptors is much less than FD_SETSIZE. /* BUGS /* This module is based on event selection. It assumes that the /* event_loop() routine is called frequently. This approach is /* not suitable for applications with compute-bound loops that /* take a significant amount of time. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* System libraries. */ #include "sys_defs.h" #include /* XXX: 44BSD uses bzero() */ #include #include #include #include /* offsetof() */ #include /* bzero() prototype for 44BSD */ #ifdef USE_SYS_SELECT_H #include #endif /* Application-specific. */ #include "mymalloc.h" #include "msg.h" #include "iostuff.h" #include "ring.h" #include "events.h" /* * I/O events. We pre-allocate one data structure per file descriptor. XXX * For now use FD_SETSIZE as defined along with the fd-set type. */ typedef struct EVENT_FDTABLE EVENT_FDTABLE; struct EVENT_FDTABLE { EVENT_NOTIFY_RDWR callback; char *context; }; static fd_set event_rmask; /* enabled read events */ static fd_set event_wmask; /* enabled write events */ static fd_set event_xmask; /* for bad news mostly */ static int event_fdsize; /* number of file descriptors */ static EVENT_FDTABLE *event_fdtable; /* one slot per file descriptor */ static int event_max_fd; /* highest fd number seen */ /* * Timer events. Timer requests are kept sorted, in a circular list. We use * the RING abstraction, so we get to use a couple ugly macros. */ typedef struct EVENT_TIMER EVENT_TIMER; struct EVENT_TIMER { time_t when; /* when event is wanted */ EVENT_NOTIFY_TIME callback; /* callback function */ char *context; /* callback context */ RING ring; /* linkage */ }; static RING event_timer_head; /* timer queue head */ #define RING_TO_TIMER(r) \ ((EVENT_TIMER *) ((char *) (r) - offsetof(EVENT_TIMER, ring))) #define FOREACH_QUEUE_ENTRY(entry, head) \ for (entry = ring_succ(head); entry != (head); entry = ring_succ(entry)) #define FIRST_TIMER(head) \ (ring_succ(head) != (head) ? RING_TO_TIMER(ring_succ(head)) : 0) /* * Other private data structures. */ static time_t event_present; /* cached time of day */ #define EVENT_INIT_NEEDED() (event_present == 0) /* event_init - set up tables and such */ static void event_init(void) { EVENT_FDTABLE *fdp; if (!EVENT_INIT_NEEDED()) msg_panic("event_init: repeated call"); /* * Initialize the file descriptor table. XXX It should be possible to * adjust (or at least extend) the table size on the fly. */ if ((event_fdsize = open_limit(FD_SETSIZE)) < 0) msg_fatal("unable to determine open file limit"); if (event_fdsize < FD_SETSIZE / 2 && event_fdsize < 256) msg_warn("could allocate space for only %d open files", event_fdsize); event_fdtable = (EVENT_FDTABLE *) mymalloc(sizeof(EVENT_FDTABLE) * event_fdsize); for (fdp = event_fdtable; fdp < event_fdtable + event_fdsize; fdp++) { fdp->callback = 0; fdp->context = 0; } /* * Initialize the I/O event request masks. */ FD_ZERO(&event_rmask); FD_ZERO(&event_wmask); FD_ZERO(&event_xmask); /* * Initialize timer stuff. */ ring_init(&event_timer_head); (void) time(&event_present); /* * Avoid an infinite initialization loop. */ if (EVENT_INIT_NEEDED()) msg_panic("event_init: unable to initialize"); } /* event_time - look up cached time of day */ time_t event_time(void) { if (EVENT_INIT_NEEDED()) event_init(); return (event_present); } /* event_drain - loop until all pending events are done */ void event_drain(void) { fd_set zero_mask; if (EVENT_INIT_NEEDED()) return; FD_ZERO(&zero_mask); while (event_timer_head.pred != event_timer_head.succ || memcmp(&zero_mask, &event_xmask, sizeof(zero_mask)) != 0) event_loop(-1); } /* event_enable_read - enable read events */ void event_enable_read(int fd, EVENT_NOTIFY_RDWR callback, char *context) { char *myname = "event_enable_read"; EVENT_FDTABLE *fdp; if (EVENT_INIT_NEEDED()) event_init(); /* * Sanity checks. */ if (fd < 0 || fd >= event_fdsize) msg_panic("%s: bad file descriptor: %d", myname, fd); if (msg_verbose > 2) msg_info("%s: fd %d", myname, fd); /* * Disallow multiple requests on the same file descriptor. Allow * duplicates of the same request. */ fdp = event_fdtable + fd; if (FD_ISSET(fd, &event_xmask)) { if (FD_ISSET(fd, &event_rmask) && fdp->callback == callback && fdp->context == context) return; msg_panic("%s: fd %d: multiple I/O request", myname, fd); } FD_SET(fd, &event_xmask); FD_SET(fd, &event_rmask); fdp->callback = callback; fdp->context = context; if (event_max_fd < fd) event_max_fd = fd; } /* event_enable_write - enable write events */ void event_enable_write(int fd, EVENT_NOTIFY_RDWR callback, char *context) { char *myname = "event_enable_write"; EVENT_FDTABLE *fdp; if (EVENT_INIT_NEEDED()) event_init(); /* * Sanity checks. */ if (fd < 0 || fd >= event_fdsize) msg_panic("%s: bad file descriptor: %d", myname, fd); if (msg_verbose > 2) msg_info("%s: fd %d", myname, fd); /* * Disallow multiple requests on the same file descriptor. Allow * duplicates of the same request. */ fdp = event_fdtable + fd; if (FD_ISSET(fd, &event_xmask)) { if (FD_ISSET(fd, &event_wmask) && fdp->callback == callback && fdp->context == context) return; msg_panic("%s: fd %d: multiple I/O request", myname, fd); } FD_SET(fd, &event_xmask); FD_SET(fd, &event_wmask); fdp->callback = callback; fdp->context = context; if (event_max_fd < fd) event_max_fd = fd; } /* event_disable_readwrite - disable request for read or write events */ void event_disable_readwrite(int fd) { char *myname = "event_disable_readwrite"; EVENT_FDTABLE *fdp; if (EVENT_INIT_NEEDED()) event_init(); /* * Sanity checks. */ if (fd < 0 || fd >= event_fdsize) msg_panic("%s: bad file descriptor: %d", myname, fd); if (msg_verbose > 2) msg_info("%s: fd %d", myname, fd); /* * Don't complain when there is nothing to cancel. The request may have * been canceled from another thread. */ FD_CLR(fd, &event_xmask); FD_CLR(fd, &event_rmask); FD_CLR(fd, &event_wmask); fdp = event_fdtable + fd; fdp->callback = 0; fdp->context = 0; } /* event_request_timer - (re)set timer */ time_t event_request_timer(EVENT_NOTIFY_TIME callback, char *context, int delay) { char *myname = "event_request_timer"; RING *ring; EVENT_TIMER *timer; if (EVENT_INIT_NEEDED()) event_init(); /* * Sanity checks. */ if (delay < 0) msg_panic("%s: invalid delay: %d", myname, delay); /* * Make sure we schedule this event at the right time. */ time(&event_present); /* * See if they are resetting an existing timer request. If so, take the * request away from the timer queue so that it can be inserted at the * right place. */ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { timer = RING_TO_TIMER(ring); if (timer->callback == callback && timer->context == context) { timer->when = event_present + delay; ring_detach(ring); if (msg_verbose > 2) msg_info("%s: reset 0x%lx 0x%lx %d", myname, (long) callback, (long) context, delay); break; } } /* * If not found, schedule a new timer request. */ if (ring == &event_timer_head) { timer = (EVENT_TIMER *) mymalloc(sizeof(EVENT_TIMER)); timer->when = event_present + delay; timer->callback = callback; timer->context = context; if (msg_verbose > 2) msg_info("%s: set 0x%lx 0x%lx %d", myname, (long) callback, (long) context, delay); } /* * Insert the request at the right place. Timer requests are kept sorted * to reduce lookup overhead in the event loop. */ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) if (timer->when < RING_TO_TIMER(ring)->when) break; ring_prepend(ring, &timer->ring); return (timer->when); } /* event_cancel_timer - cancel timer */ int event_cancel_timer(EVENT_NOTIFY_TIME callback, char *context) { char *myname = "event_cancel_timer"; RING *ring; EVENT_TIMER *timer; int time_left = -1; if (EVENT_INIT_NEEDED()) event_init(); /* * See if they are canceling an existing timer request. Do not complain * when the request is not found. It might have been canceled from some * other thread. */ FOREACH_QUEUE_ENTRY(ring, &event_timer_head) { timer = RING_TO_TIMER(ring); if (timer->callback == callback && timer->context == context) { if ((time_left = timer->when - event_present) < 0) time_left = 0; ring_detach(ring); myfree((char *) timer); break; } } if (msg_verbose > 2) msg_info("%s: 0x%lx 0x%lx %d", myname, (long) callback, (long) context, time_left); return (time_left); } /* event_loop - wait for the next event */ void event_loop(int delay) { char *myname = "event_loop"; static int nested; fd_set rmask; fd_set wmask; fd_set xmask; struct timeval tv; struct timeval *tvp; EVENT_TIMER *timer; int fd; EVENT_FDTABLE *fdp; int select_delay; if (EVENT_INIT_NEEDED()) event_init(); /* * Find out when the next timer would go off. Timer requests are sorted. * If any timer is scheduled, adjust the delay appropriately. */ if ((timer = FIRST_TIMER(&event_timer_head)) != 0) { event_present = time((time_t *) 0); if ((select_delay = timer->when - event_present) < 0) { select_delay = 0; } else if (delay >= 0 && select_delay > delay) { select_delay = delay; } } else { select_delay = delay; } if (msg_verbose > 2) msg_info("event_loop: select_delay %d", select_delay); /* * Negative delay means: wait until something happens. Zero delay means: * poll. Positive delay means: wait at most this long. */ if (select_delay < 0) { tvp = 0; } else { tvp = &tv; tv.tv_usec = 0; tv.tv_sec = select_delay; } /* * Pause until the next event happens. When select() has a problem, don't * go into a tight loop. Allow select() to be interrupted due to the * arrival of a signal. */ rmask = event_rmask; wmask = event_wmask; xmask = event_xmask; if (select(event_max_fd + 1, &rmask, &wmask, &xmask, tvp) < 0) { if (errno != EINTR) msg_fatal("event_loop: select: %m"); return; } /* * Before entering the application call-back routines, make sure we * aren't being called from a call-back routine. Doing so would make us * vulnerable to all kinds of race conditions. */ if (nested++ > 0) msg_panic("event_loop: recursive call"); /* * Deliver timer events. Requests are sorted: we can stop when we reach * the future or the list end. Allow the application to update the timer * queue while it is being called back. To this end, we repeatedly pop * the first request off the timer queue before delivering the event to * the application. */ event_present = time((time_t *) 0); while ((timer = FIRST_TIMER(&event_timer_head)) != 0) { if (timer->when > event_present) break; ring_detach(&timer->ring); /* first this */ if (msg_verbose > 2) msg_info("%s: timer 0x%lx 0x%lx", myname, (long) timer->callback, (long) timer->context); timer->callback(EVENT_TIME, timer->context); /* then this */ myfree((char *) timer); } /* * Deliver I/O events. Allow the application to cancel event requests * while it is being called back. To this end, we keep an eye on the * contents of event_xmask, so that we deliver only events that are still * wanted. We do not change the event request masks. It is up to the * application to determine when a read or write is complete. */ for (fd = 0, fdp = event_fdtable; fd <= event_max_fd; fd++, fdp++) { if (FD_ISSET(fd, &event_xmask)) { if (FD_ISSET(fd, &xmask)) { if (msg_verbose > 2) msg_info("%s: exception %d 0x%lx 0x%lx", myname, fd, (long) fdp->callback, (long) fdp->context); fdp->callback(EVENT_XCPT, fdp->context); } else if (FD_ISSET(fd, &wmask)) { if (msg_verbose > 2) msg_info("%s: write %d 0x%lx 0x%lx", myname, fd, (long) fdp->callback, (long) fdp->context); fdp->callback(EVENT_WRITE, fdp->context); } else if (FD_ISSET(fd, &rmask)) { if (msg_verbose > 2) msg_info("%s: read %d 0x%lx 0x%lx", myname, fd, (long) fdp->callback, (long) fdp->context); fdp->callback(EVENT_READ, fdp->context); } } } nested--; } #ifdef TEST /* * Proof-of-concept test program for the event manager. Print "dingdong" * every 5 seconds, while echoing any lines read from stdin. The "dingdong * changes case each time it is printed. */ #include #include #define DELAY 5 /* dingdong - print text every DELAY seconds */ static void dingdong(char *context) { printf("%c", *context); fflush(stdout); *context = (ISUPPER(*context) ? TOLOWER(*context) : TOUPPER(*context)); event_request_timer(dingdong, context, (char *) DELAY); } /* echo - echo text received on stdin */ static void echo(int unused_event, char *unused_context) { char buf[BUFSIZ]; if (fgets(buf, sizeof(buf), stdin) == 0) exit(0); printf("Result: %s", buf); } main(void) { static char text[] = "\rdingdong "; char *cp; for (cp = text; *cp; cp++) event_request_timer(dingdong, cp, (char *) 0); event_enable_read(fileno(stdin), echo, (char *) 0); for (;;) event_loop(-1); } #endif