diff options
Diffstat (limited to 'source4/lib/events.c')
-rw-r--r-- | source4/lib/events.c | 372 |
1 files changed, 372 insertions, 0 deletions
diff --git a/source4/lib/events.c b/source4/lib/events.c new file mode 100644 index 0000000000..f95028b731 --- /dev/null +++ b/source4/lib/events.c @@ -0,0 +1,372 @@ +/* + Unix SMB/CIFS implementation. + main select loop and event handling + Copyright (C) Andrew Tridgell 2003 + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +/* + PLEASE READ THIS BEFORE MODIFYING! + + This module is a general abstraction for the main select loop and + event handling. Do not ever put any localised hacks in here, instead + register one of the possible event types and implement that event + somewhere else. + + There are 4 types of event handling that are handled in this module: + + 1) a file descriptor becoming readable or writeable. This is mostly + used for network sockets, but can be used for any type of file + descriptor. You may only register one handler for each file + descriptor/io combination or you will get unpredictable results + (this means that you can have a handler for read events, and a + separate handler for write events, but not two handlers that are + both handling read events) + + 2) a timed event. You can register an event that happens at a + specific time. You can register as many of these as you + like. When they are called the handler can choose to set the time + for the next event. If next_event is not set then the event is removed. + + 3) an event that happens every time through the select loop. These + sorts of events should be very fast, as they will occur a + lot. Mostly used for things like destroying a talloc context or + checking a signal flag. + + 4) an event triggered by a signal. These can be one shot or + repeated. You can have more than one handler registered for a + single signal if you want to. + + To setup a set of events you first need to create a event_context + structure using the function event_context_init(); This returns a + 'struct event_context' that you use in all subsequent calls. + + After that you can add/remove events that you are interested in + using event_add_*() and event_remove_*(). + + Finally, you call event_loop_wait() to block waiting for one of the + events to occor. In normal operation event_loop_wait() will loop + forever, unless you call event_loop_exit() from inside one of your + handler functions. + +*/ + +#include "includes.h" + +/* + create a event_context structure. This must be the first events + call, and all subsequent calls pass this event_context as the first + element. Event handlers also receive this as their first argument. +*/ +struct event_context *event_context_init(void) +{ + struct event_context *ev; + + ev = malloc(sizeof(*ev)); + if (!ev) return NULL; + + /* start off with no events */ + ZERO_STRUCTP(ev); + + return ev; +} + + + +/* + add a fd based event + return False on failure (memory allocation error) +*/ +BOOL event_add_fd(struct event_context *ev, struct fd_event *e) +{ + e = memdup(e, sizeof(*e)); + if (!e) return False; + DLIST_ADD(ev->fd_events, e); + e->ref_count = 1; + if (e->fd > ev->maxfd) { + ev->maxfd = e->fd; + } + return True; +} + + +/* + recalculate the maxfd +*/ +static void calc_maxfd(struct event_context *ev) +{ + struct fd_event *e; + ev->maxfd = 0; + for (e=ev->fd_events; e; e=e->next) { + if (e->ref_count && + e->fd > ev->maxfd) { + ev->maxfd = e->fd; + } + } +} + +/* + remove a fd based event + the event to remove is matched by looking at the handler + function and the file descriptor + return False on failure (event not found) +*/ +BOOL event_remove_fd(struct event_context *ev, struct fd_event *e1) +{ + struct fd_event *e; + for (e=ev->fd_events; e; e=e->next) { + if (e->ref_count && + e->fd == e1->fd && + e->handler == e1->handler) { + e->ref_count--; + /* possibly calculate the new maxfd */ + calc_maxfd(ev); + return True; + } + } + return False; +} + +/* + remove all fd based events that match a specified fd +*/ +void event_remove_fd_all(struct event_context *ev, int fd) +{ + struct fd_event *e; + for (e=ev->fd_events; e; e=e->next) { + if (e->ref_count && e->fd == fd) { + e->ref_count--; + } + } + calc_maxfd(ev); +} + +/* + remove all fd based events that match a specified handler +*/ +void event_remove_fd_all_handler(struct event_context *ev, void *handler) +{ + struct fd_event *e; + for (e=ev->fd_events; e; e=e->next) { + if (e->ref_count && + handler == (void *)e->handler) { + e->ref_count--; + } + } + calc_maxfd(ev); +} + + +/* + add a timed event + return False on failure (memory allocation error) +*/ +BOOL event_add_timed(struct event_context *ev, struct timed_event *e) +{ + e = memdup(e, sizeof(*e)); + if (!e) return False; + e->ref_count = 1; + DLIST_ADD(ev->timed_events, e); + return True; +} + +/* + remove a timed event + the event to remove is matched only on the handler function + return False on failure (event not found) +*/ +BOOL event_remove_timed(struct event_context *ev, struct timed_event *e1) +{ + struct timed_event *e; + for (e=ev->timed_events; e; e=e->next) { + if (e->ref_count && + e->handler == e1->handler) { + e->ref_count--; + return True; + } + } + return False; +} + +/* + add a loop event + return False on failure (memory allocation error) +*/ +BOOL event_add_loop(struct event_context *ev, struct loop_event *e) +{ + e = memdup(e, sizeof(*e)); + if (!e) return False; + e->ref_count = 1; + DLIST_ADD(ev->loop_events, e); + return True; +} + +/* + remove a loop event + the event to remove is matched only on the handler function + return False on failure (memory allocation error) +*/ +BOOL event_remove_loop(struct event_context *ev, struct loop_event *e1) +{ + struct loop_event *e; + for (e=ev->loop_events; e; e=e->next) { + if (e->ref_count && + e->handler == e1->handler) { + e->ref_count--; + return True; + } + } + return False; +} + + +/* + tell the event loop to exit with the specified code +*/ +void event_loop_exit(struct event_context *ev, int code) +{ + ev->exit.exit_now = True; + ev->exit.code = code; +} + +/* + go into an event loop using the events defined in ev this function + will return with the specified code if one of the handlers calls + event_loop_exit() + + also return (with code 0) if all fd events are removed +*/ +int event_loop_wait(struct event_context *ev) +{ + time_t t; + + ZERO_STRUCT(ev->exit); + + t = time(NULL); + + while (ev->fd_events && !ev->exit.exit_now) { + fd_set r_fds, w_fds; + struct fd_event *fe; + struct loop_event *le; + struct timed_event *te; + int selrtn; + struct timeval tval; + + /* the loop events are called on each loop. Be careful to allow the + event to remove itself */ + for (le=ev->loop_events;le;) { + struct loop_event *next = le->next; + if (le->ref_count == 0) { + DLIST_REMOVE(ev->loop_events, le); + free(le); + } else { + le->ref_count++; + le->handler(ev, le, t); + le->ref_count--; + } + le = next; + } + + ZERO_STRUCT(tval); + FD_ZERO(&r_fds); + FD_ZERO(&w_fds); + + /* setup any fd events */ + for (fe=ev->fd_events; fe; ) { + struct fd_event *next = fe->next; + if (fe->ref_count == 0) { + DLIST_REMOVE(ev->fd_events, fe); + free(fe); + } else { + if (fe->flags & EVENT_FD_READ) { + FD_SET(fe->fd, &r_fds); + } + if (fe->flags & EVENT_FD_WRITE) { + FD_SET(fe->fd, &w_fds); + } + } + fe = next; + } + + /* start with a reasonable max timeout */ + tval.tv_sec = 600; + + /* work out the right timeout for all timed events */ + for (te=ev->timed_events;te;te=te->next) { + int timeout = te->next_event - t; + if (timeout < 0) { + timeout = 0; + } + if (te->ref_count && + timeout < tval.tv_sec) { + tval.tv_sec = timeout; + } + } + + + selrtn = sys_select(ev->maxfd+1, &r_fds, &w_fds, NULL, &tval); + + t = time(NULL); + + if (selrtn == -1 && errno == EBADF) { + /* the socket is dead! this should never + happen as the socket should have first been + made readable and that should have removed + the event, so this must be a bug. This is a + fatal error. */ + DEBUG(0,("EBADF on event_loop_wait - exiting\n")); + return -1; + } + + if (selrtn > 0) { + /* at least one file descriptor is ready - check + which ones and call the handler, being careful to allow + the handler to remove itself when called */ + for (fe=ev->fd_events; fe; fe=fe->next) { + uint16 flags = 0; + if (FD_ISSET(fe->fd, &r_fds)) flags |= EVENT_FD_READ; + if (FD_ISSET(fe->fd, &w_fds)) flags |= EVENT_FD_WRITE; + if (fe->ref_count && flags) { + fe->ref_count++; + fe->handler(ev, fe, t, flags); + fe->ref_count--; + } + } + } + + /* call any timed events that are now due */ + for (te=ev->timed_events;te;) { + struct timed_event *next = te->next; + if (te->ref_count == 0) { + DLIST_REMOVE(ev->timed_events, te); + free(te); + } else if (te->next_event <= t) { + te->ref_count++; + te->handler(ev, te, t); + te->ref_count--; + if (te->next_event <= t) { + /* the handler didn't set a time for the + next event - remove the event */ + event_remove_timed(ev, te); + } + } + te = next; + } + + } + + return ev->exit.code; +} |