Imported Upstream version 3.2.0
[debian/amanda] / common-src / event.c
index 6809ca117c3c61c1f9a364014d72dfce42ea417e..97e7c29552c7e0c7a27bf15aee958020d82b0638 100644 (file)
  * file named AUTHORS, in the root directory of this distribution.
  */
 /*
- * $Id: event.c,v 1.20 2005/10/02 15:31:07 martinea Exp $
+ * $Id: event.c,v 1.24 2006/06/16 10:55:05 martinea Exp $
  *
  * Event handler.  Serializes different kinds of events to allow for
  * a uniform interface, central state storage, and centralized
  * interdependency logic.
+ *
+ * This is a compatibility wrapper over Glib's GMainLoop.  New code should
+ * use Glib's interface directly.
+ *
+ * Each event_handle is associated with a unique GSource, identified by it
+ * event_source_id.
  */
 
-/*#define      EVENT_DEBUG*/
-
-#ifdef EVENT_DEBUG
-#define eventprintf(x)    dbprintf(x)
-#else
-#define eventprintf(x)
-#endif
-
 #include "amanda.h"
+#include "conffile.h"
 #include "event.h"
-#include "queue.h"
+#include "glib-util.h"
+
+/* TODO: use mem chunks to allocate event_handles */
+/* TODO: lock stuff for threading */
+
+/* Write a debugging message if the config variable debug_event
+ * is greater than or equal to i */
+#define event_debug(i, ...) do {       \
+       if ((i) <= debug_event) {       \
+           dbprintf(__VA_ARGS__);      \
+       }                               \
+} while (0)
 
 /*
  * The opaque handle passed back to the caller.  This is typedefed to
 struct event_handle {
     event_fn_t fn;             /* function to call when this fires */
     void *arg;                 /* argument to pass to previous function */
+
     event_type_t type;         /* type of event */
     event_id_t data;           /* type data */
-    time_t lastfired;          /* timestamp of last fired (EV_TIME only) */
-    LIST_ENTRY(event_handle) le; /* queue handle */
-};
 
-/*
- * eventq is a queue of currently active events.
- * cache is a queue of unused handles.  We keep a few around to avoid
- * malloc overhead when doing a lot of register/releases.
- */
-static struct {
-    LIST_HEAD(, event_handle) listhead;
-    int qlength;
-} eventq = {
-    LIST_HEAD_INITIALIZER(eventq.listhead), 0
-}, cache = {
-    LIST_HEAD_INITIALIZER(eventq.listhead), 0
+    GSource *source;           /* Glib event source, if one exists */
+    guint source_id;           /* ID of the glib event source */
+
+    gboolean has_fired;                /* for use by event_wait() */
+    gboolean is_dead;          /* should this event be deleted? */
 };
-#define        eventq_first(q)         LIST_FIRST(&q.listhead)
-#define        eventq_next(eh)         LIST_NEXT(eh, le)
-#define        eventq_add(q, eh)       LIST_INSERT_HEAD(&q.listhead, eh, le);
-#define        eventq_remove(eh)       LIST_REMOVE(eh, le);
 
-/*
- * How many items we can have in the handle cache before we start
- * freeing.
- */
-#define        CACHEDEPTH      10
+/* A list of all extant event_handle objects, used for searching for particular
+ * events and for deleting dead events */
+GSList *all_events;
 
 /*
- * A table of currently set signal handlers.
+ * Utility functions
  */
-static struct sigtabent {
-    event_handle_t *handle;    /* handle for this signal */
-    int score;                 /* number of signals recvd since last checked */
-    void (*oldhandler) P((int));/* old handler (for unsetting) */
-} sigtable[NSIG];
-
-#ifdef EVENT_DEBUG
-static const char *event_type2str P((event_type_t));
-#endif
-#define        fire(eh)        (*(eh)->fn)((eh)->arg)
-static void signal_handler P((int));
-static event_handle_t *gethandle P((void));
-static void puthandle P((event_handle_t *));
+
+static const char *event_type2str(event_type_t type);
+
+/* "Fire" an event handle, by calling its callback function */
+#define        fire(eh) do { \
+       event_debug(1, "firing %p: %s/%jd\n", eh, event_type2str((eh)->type), (eh)->data); \
+       (*(eh)->fn)((eh)->arg); \
+       (eh)->has_fired = TRUE; \
+} while(0)
+
+/* Adapt a Glib callback to an event_handle_t callback; assumes that the
+ * user_ptr for the Glib callback is a pointer to the event_handle_t.  */
+static gboolean 
+event_handle_callback(
+    gpointer user_ptr)
+{
+    event_handle_t *hdl = (event_handle_t *)user_ptr;
+
+    /* if the handle is dead, then don't fire the callback; this means that
+     * we're in the process of freeing the event */
+    if (!hdl->is_dead) {
+       fire(hdl);
+    }
+
+    /* don't ever let GMainLoop destroy GSources */
+    return TRUE;
+}
 
 /*
- * Add a new event.  See the comment in event.h for what the arguments
- * mean.
+ * Public functions
  */
+
 event_handle_t *
-event_register(data, type, fn, arg)
-    event_id_t data;
-    event_type_t type;
-    event_fn_t fn;
-    void *arg;
+event_register(
+    event_id_t data,
+    event_type_t type,
+    event_fn_t fn,
+    void *arg)
 {
     event_handle_t *handle;
+    GIOCondition cond;
 
-    switch (type) {
-    case EV_READFD:
-    case EV_WRITEFD:
+    /* sanity-checking */
+    if ((type == EV_READFD) || (type == EV_WRITEFD)) {
        /* make sure we aren't given a high fd that will overflow a fd_set */
-       assert(data < FD_SETSIZE);
-       break;
-
-    case EV_SIG:
-       /* make sure signals are within range */
-       assert(data < NSIG);
-       /* make sure we don't double-register a signal */
-       assert(sigtable[data].handle == NULL);
-       break;
-
-    case EV_TIME:
-    case EV_WAIT:
-       break;
-
-    case EV_DEAD:
-    default:
-       /* callers can't register EV_DEAD */
-       assert(0);
-       break;
+       if (data >= (int)FD_SETSIZE) {
+           error(_("event_register: Invalid file descriptor %jd"), data);
+           /*NOTREACHED*/
+       }
+    } else if (type == EV_TIME) {
+       if (data <= 0) {
+           error(_("event_register: interval for EV_TIME must be greater than 0; got %jd"), data);
+       }
     }
 
-    handle = gethandle();
+    handle = g_new0(event_handle_t, 1);
     handle->fn = fn;
     handle->arg = arg;
     handle->type = type;
     handle->data = data;
-    handle->lastfired = -1;
-    eventq_add(eventq, handle);
-    eventq.qlength++;
+    handle->is_dead = FALSE;
+
+    event_debug(1, _("event: register: %p->data=%jd, type=%s\n"),
+                   handle, handle->data, event_type2str(handle->type));
+
+    /* add to the list of events */
+    all_events = g_slist_prepend(all_events, (gpointer)handle);
+
+    /* and set up the GSource for this event */
+    switch (type) {
+       case EV_READFD:
+       case EV_WRITEFD:
+           /* create a new source */
+           if (type == EV_READFD) {
+               cond = G_IO_IN | G_IO_HUP | G_IO_ERR;
+           } else {
+               cond = G_IO_OUT | G_IO_ERR;
+           }
+
+           handle->source = new_fdsource(data, cond);
+
+           /* attach it to the default GMainLoop */
+           g_source_attach(handle->source, NULL);
+           handle->source_id = g_source_get_id(handle->source);
+
+           /* And set its callbacks */
+           g_source_set_callback(handle->source, event_handle_callback,
+                                 (gpointer)handle, NULL);
+
+           /* drop our reference to it, so when it's detached, it will be
+            * destroyed. */
+           g_source_unref(handle->source);
+           break;
+
+       case EV_TIME:
+           /* Glib provides a nice shortcut for timeouts.  The *1000 converts
+            * seconds to milliseconds. */
+           handle->source_id = g_timeout_add(data * 1000, event_handle_callback,
+                                             (gpointer)handle);
+
+           /* But it doesn't give us the source directly.. */
+           handle->source = g_main_context_find_source_by_id(NULL, handle->source_id);
+           /* EV_TIME must always be handled after EV_READ */
+           g_source_set_priority(handle->source, 10);
+           break;
 
-    eventprintf(("%s: event: register: %X data=%lu, type=%s\n", debug_prefix_time(NULL), (int)handle,
-                handle->data, event_type2str(handle->type)));
-    return (handle);
+       case EV_WAIT:
+           /* nothing to do -- these are handled independently of GMainLoop */
+           break;
+
+       default:
+           error(_("Unknown event type %s"), event_type2str(type));
+    }
+
+    return handle;
 }
 
 /*
@@ -155,404 +201,182 @@ event_register(data, type, fn, arg)
  * the event.
  */
 void
-event_release(handle)
-    event_handle_t *handle;
+event_release(
+    event_handle_t *handle)
 {
-
     assert(handle != NULL);
 
-    eventprintf(("%s: event: release (mark): %X data=%lu, type=%s\n", debug_prefix_time(NULL),
-       (int)handle, handle->data, event_type2str(handle->type)));
-    assert(handle->type != EV_DEAD);
-
-    /*
-     * For signal events, we need to specially remove then from the
-     * signal event table.
-     */
-    if (handle->type == EV_SIG) {
-       struct sigtabent *se = &sigtable[handle->data];
-
-       assert(se->handle == handle);
-       signal(handle->data, se->oldhandler);
-       se->handle = NULL;
-       se->score = 0;
-    }
-
-    /*
-     * Decrement the qlength now since this is no longer a real
-     * event.
-     */
-    eventq.qlength--;
+    event_debug(1, _("event: release (mark): %p data=%jd, type=%s\n"),
+                   handle, handle->data,
+                   event_type2str(handle->type));
+    assert(!handle->is_dead);
 
-    /*
-     * Mark it as dead and leave it for the loop to remove.
-     */
-    handle->type = EV_DEAD;
+    /* Mark it as dead and leave it for the event_loop to remove */
+    handle->is_dead = TRUE;
 }
 
 /*
  * Fire all EV_WAIT events waiting on the specified id.
  */
 int
-event_wakeup(id)
-    event_id_t id;
+event_wakeup(
+    event_id_t id)
 {
-    event_handle_t *eh;
+    GSList *iter;
+    GSList *tofire = NULL;
     int nwaken = 0;
 
-    eventprintf(("%s: event: wakeup: enter (%lu)\n", debug_prefix_time(NULL), id));
-
-    assert(id >= 0);
+    event_debug(1, _("event: wakeup: enter (%jd)\n"), id);
 
-    for (eh = eventq_first(eventq); eh != NULL; eh = eventq_next(eh)) {
+    /* search for any and all matching events, and record them.  This way
+     * we have determined the whole list of events we'll be firing *before*
+     * we fire any of them. */
+    for (iter = all_events; iter != NULL; iter = g_slist_next(iter)) {
+       event_handle_t *eh = (event_handle_t *)iter->data;
+       if (eh->type == EV_WAIT && eh->data == id && !eh->is_dead) {
+           tofire = g_slist_append(tofire, (gpointer)eh);
+       }
+    }
 
-       if (eh->type == EV_WAIT && eh->data == id) {
-           eventprintf(("%s: event: wakeup: %X id=%lu\n", debug_prefix_time(NULL), (int)eh, id));
+    /* fire them */
+    for (iter = tofire; iter != NULL; iter = g_slist_next(iter)) {
+       event_handle_t *eh = (event_handle_t *)iter->data;
+       if (eh->type == EV_WAIT && eh->data == id && !eh->is_dead) {
+           event_debug(1, _("A: event: wakeup triggering: %p id=%jd\n"), eh, id);
            fire(eh);
            nwaken++;
        }
     }
+
+    /* and free the temporary list */
+    g_slist_free(tofire);
+
     return (nwaken);
 }
 
 
 /*
- * The event loop.  We need to be specially careful here with adds and
- * deletes.  Since adds and deletes will often happen while this is running,
- * we need to make sure we don't end up referencing a dead event handle.
+ * The event loop.
  */
-void
-event_loop(dontblock)
-    const int dontblock;
-{
-#ifdef ASSERTIONS
-    static int entry = 0;
-#endif
-    fd_set readfds, writefds, errfds, werrfds;
-    struct timeval timeout, *tvptr;
-    int ntries, maxfd, rc, interval;
-    time_t curtime;
-    event_handle_t *eh, *nexteh;
-    struct sigtabent *se;
-
-    eventprintf(("%s: event: loop: enter: dontblock=%d, qlength=%d\n", debug_prefix_time(NULL),
-                dontblock, eventq.qlength));
-
-    /*
-     * If we have no events, we have nothing to do
-     */
-    if (eventq.qlength == 0)
-       return;
-
-    /*
-     * We must not be entered twice
-     */
-    assert(++entry == 1);
-
-    ntries = 0;
-
-    /*
-     * Save a copy of the current time once, to reduce syscall load
-     * slightly.
-     */
-    curtime = time(NULL);
-
-    do {
-#ifdef EVENT_DEBUG
-       eventprintf(("%s: event: loop: dontblock=%d, qlength=%d\n", debug_prefix_time(NULL), dontblock,
-           eventq.qlength));
-       for (eh = eventq_first(eventq); eh != NULL; eh = eventq_next(eh)) {
-           eventprintf(("%s: %X: %s data=%lu fn=0x%x arg=0x%x\n", debug_prefix_time(NULL), (int)eh,
-               event_type2str(eh->type), eh->data, (int)eh->fn, (int)eh->arg));
-       }
-#endif
-       /*
-        * Set ourselves up with no timeout initially.
-        */
-       timeout.tv_sec = 0;
-       timeout.tv_usec = 0;
-
-       /*
-        * If we can block, initially set the tvptr to NULL.  If
-        * we come across timeout events in the loop below, they
-        * will set it to an appropriate buffer.  If we don't
-        * see any timeout events, then tvptr will remain NULL
-        * and the select will properly block indefinately.
-        *
-        * If we can't block, set it to point to the timeout buf above.
-        */
-       if (dontblock)
-           tvptr = &timeout;
-       else
-           tvptr = NULL;
-
-       /*
-        * Rebuild the select bitmasks each time.
-        */
-       FD_ZERO(&readfds);
-       FD_ZERO(&writefds);
-       FD_ZERO(&errfds);
-       maxfd = 0;
-
-       /*
-        * Run through each event handle and setup the events.
-        * We save our next pointer early in case we GC some dead
-        * events.
-        */
-       for (eh = eventq_first(eventq); eh != NULL; eh = nexteh) {
-           nexteh = eventq_next(eh);
-
-           switch (eh->type) {
-
-           /*
-            * Read fds just get set into the select bitmask
-            */
-           case EV_READFD:
-               FD_SET(eh->data, &readfds);
-               FD_SET(eh->data, &errfds);
-               maxfd = max(maxfd, eh->data);
-               break;
-
-           /*
-            * Likewise with write fds
-            */
-           case EV_WRITEFD:
-               FD_SET(eh->data, &writefds);
-               FD_SET(eh->data, &errfds);
-               maxfd = max(maxfd, eh->data);
-               break;
-
-           /*
-            * Only set signals that aren't already set to avoid unnecessary
-            * syscall overhead.
-            */
-           case EV_SIG:
-               se = &sigtable[eh->data];
-
-               if (se->handle == eh)
-                   break;
-
-               /* no previous handle */
-               assert(se->handle == NULL);
-               se->handle = eh;
-               se->score = 0;
-               se->oldhandler = signal(eh->data, signal_handler);
-               break;
-
-           /*
-            * Compute the timeout for this select
-            */
-           case EV_TIME:
-               /* if we're not supposed to block, then leave it at 0 */
-               if (dontblock)
-                   break;
-
-               if (eh->lastfired == -1)
-                   eh->lastfired = curtime;
-
-               interval = eh->data - (curtime - eh->lastfired);
-               if (interval < 0)
-                   interval = 0;
-
-               if (tvptr != NULL)
-                   timeout.tv_sec = min(timeout.tv_sec, interval);
-               else {
-                   /* this is the first timeout */
-                   tvptr = &timeout;
-                   timeout.tv_sec = interval;
-               }
-               break;
-
-           /*
-            * Wait events are processed immediately by event_wakeup()
-            */
-           case EV_WAIT:
-               break;
-
-           /*
-            * Prune dead events
-            */
-           case EV_DEAD:
-               eventq_remove(eh);
-               puthandle(eh);
-               break;
-
-           default:
-               assert(0);
-               break;
-           }
-       }
 
-       /*
-        * Let 'er rip
-        */
-       eventprintf(("%s: event: select: dontblock=%d, maxfd=%d, timeout=%ld\n", debug_prefix_time(NULL),
-           dontblock, maxfd, tvptr != NULL ? timeout.tv_sec : -1));
-       rc = select(maxfd + 1, &readfds, &writefds, &errfds, tvptr);
-       eventprintf(("%s: event: select returns %d\n", debug_prefix_time(NULL), rc));
-
-       /*
-        * Select errors can mean many things.  Interrupted events should
-        * not be fatal, since they could be delivered signals which still
-        * need to have their events fired.
-        */
-       if (rc < 0) {
-           if (errno != EINTR) {
-               if (++ntries > 5)
-                   error("select failed: %s", strerror(errno));
-               continue;
-           }
-           /* proceed if errno == EINTR, we may have caught a signal */
-
-           /* contents cannot be trusted */
-           FD_ZERO(&readfds);
-           FD_ZERO(&writefds);
-           FD_ZERO(&errfds);
-       }
+static void event_loop_wait (event_handle_t *, const int);
 
-       /*
-        * Grab the current time again for use in timed events.
-        */
-       curtime = time(NULL);
-
-       /*
-        * We need to copy the errfds into werrfds, so file descriptors
-        * that are being polled for both reading and writing have
-        * both of their poll events 'see' the error.
-        */
-       memcpy(&werrfds, &errfds, sizeof(werrfds));
-
-       /*
-        * Now run through the events and fire the ones that are ready.
-        * Don't handle file descriptor events if the select failed.
-        */
-       for (eh = eventq_first(eventq); eh != NULL; eh = eventq_next(eh)) {
-           switch (eh->type) {
-
-           /*
-            * Read fds: just fire the event if set in the bitmask
-            */
-           case EV_READFD:
-               if (FD_ISSET(eh->data, &readfds) ||
-                   FD_ISSET(eh->data, &errfds)) {
-                   FD_CLR(eh->data, &readfds);
-                   FD_CLR(eh->data, &errfds);
-                   fire(eh);
-               }
-               break;
-
-           /*
-            * Write fds: same as Read fds
-            */
-           case EV_WRITEFD:
-               if (FD_ISSET(eh->data, &writefds) ||
-                   FD_ISSET(eh->data, &werrfds)) {
-                   FD_CLR(eh->data, &writefds);
-                   FD_CLR(eh->data, &werrfds);
-                   fire(eh);
-               }
-               break;
-
-           /*
-            * Signal events: check the score for fires, and run the
-            * event if we got one.
-            */
-           case EV_SIG:
-               se = &sigtable[eh->data];
-               if (se->score > 0) {
-                   assert(se->handle == eh);
-                   se->score = 0;
-                   fire(eh);
-               }
-               break;
-
-           /*
-            * Timed events: check the interval elapsed since last fired,
-            * and set it off if greater or equal to requested interval.
-            */
-           case EV_TIME:
-               if (eh->lastfired == -1)
-                   eh->lastfired = curtime;
-               if (curtime - eh->lastfired >= eh->data) {
-                   eh->lastfired = curtime;
-                   fire(eh);
-               }
-               break;
-
-           /*
-            * Wait events are handled immediately by event_wakeup()
-            * Dead events are handled by the pre-select loop.
-            */
-           case EV_WAIT:
-           case EV_DEAD:
-               break;
-
-           default:
-               assert(0);
-               break;
-           }
-       }
-    } while (!dontblock && eventq.qlength > 0);
+void
+event_loop(
+    int nonblock)
+{
+    event_loop_wait(NULL, nonblock);
+}
 
-    assert(--entry == 0);
+void
+event_wait(
+    event_handle_t *eh)
+{
+    event_loop_wait(eh, 0);
 }
 
-/*
- * Generic signal handler.  Used to count caught signals for the event
- * loop.
+/* Flush out any dead events in all_events.  Be careful that this
+ * isn't called while someone is iterating over all_events.
+ *
+ * @param wait_eh: the event handle we're waiting on, which shouldn't
+ *         be flushed.
  */
 static void
-signal_handler(signo)
-    int signo;
+flush_dead_events(event_handle_t *wait_eh)
 {
+    GSList *iter, *next;
+
+    for (iter = all_events; iter != NULL; iter = next) {
+       event_handle_t *hdl = (event_handle_t *)iter->data;
+       next = g_slist_next(iter);
 
-    assert(signo >= 0 && signo < sizeof(sigtable) / sizeof(sigtable[0]));
-    sigtable[signo].score++;
+       /* (handle the case when wait_eh is dead by simply not deleting
+        * it; the next run of event_loop will take care of it) */
+       if (hdl->is_dead && hdl != wait_eh) {
+           all_events = g_slist_delete_link(all_events, iter);
+           if (hdl->source) g_source_destroy(hdl->source);
+
+           amfree(hdl);
+       }
+    }
 }
 
-/*
- * Return a new handle.  Take from the handle cache if not empty.  Otherwise,
- * alloc a new one.
- */
-static event_handle_t *
-gethandle()
+/* Return TRUE if we have any events outstanding that can be dispatched
+ * by GMainLoop.  Recall EV_WAIT events appear in all_events, but are
+ * not dispatched by GMainLoop.  */
+static gboolean
+any_mainloop_events(void)
 {
-    event_handle_t *eh;
+    GSList *iter;
 
-    if ((eh = eventq_first(cache)) != NULL) {
-       assert(cache.qlength > 0);
-       eventq_remove(eh);
-       cache.qlength--;
-       return (eh);
+    for (iter = all_events; iter != NULL; iter = g_slist_next(iter)) {
+       event_handle_t *hdl = (event_handle_t *)iter->data;
+       if (hdl->type != EV_WAIT)
+           return TRUE;
     }
-    assert(cache.qlength == 0);
-    return (alloc(sizeof(*eh)));
+
+    return FALSE;
 }
 
-/*
- * Free a handle.  If there's space in the handle cache, put it there.
- * Otherwise, free it.
- */
 static void
-puthandle(eh)
-    event_handle_t *eh;
+event_loop_wait(
+    event_handle_t *wait_eh,
+    int nonblock)
 {
+    event_debug(1, _("event: loop: enter: nonblockg=%d, eh=%p\n"), nonblock, wait_eh);
 
-    if (cache.qlength > CACHEDEPTH) {
-       amfree(eh);
-       return;
+    /* If we're waiting for a specific event, then reset its has_fired flag */
+    if (wait_eh) {
+       wait_eh->has_fired = FALSE;
     }
-    eventq_add(cache, eh);
-    cache.qlength++;
+
+    /* Keep looping until there are no events, or until wait_eh has fired */
+    while (1) {
+       /* clean up first, so we don't accidentally check a dead source */
+       flush_dead_events(wait_eh);
+
+       /* if there's nothing to wait for, then don't block, but run an
+        * iteration so that any other users of GMainLoop will get a chance
+        * to run. */
+       if (!any_mainloop_events())
+           break;
+
+       /* Do an interation */
+       g_main_context_iteration(NULL, !nonblock);
+
+       /* If the event we've been waiting for has fired or been released, as
+        * appropriate, we're done.  See the comments for event_wait in event.h
+        * for the skinny on this weird expression. */
+       if (wait_eh && ((wait_eh->type == EV_WAIT && wait_eh->is_dead)
+                    || (wait_eh->type != EV_WAIT && wait_eh->has_fired)))
+           break;
+
+       /* Don't loop if we're not blocking */
+       if (nonblock)
+           break;
+    }
+
+    /* extra cleanup, to keep all_events short, and to delete wait_eh if it
+     * has been released. */
+    flush_dead_events(NULL);
+
+}
+
+GMainLoop *
+default_main_loop(void)
+{
+    static GMainLoop *loop = NULL;
+    if (!loop)
+       loop = g_main_loop_new(NULL, TRUE);
+    return loop;
 }
 
-#ifdef EVENT_DEBUG
 /*
  * Convert an event type into a string
  */
 static const char *
-event_type2str(type)
-    event_type_t type;
+event_type2str(
+    event_type_t type)
 {
     static const struct {
        event_type_t type;
@@ -561,17 +385,207 @@ event_type2str(type)
 #define        X(s)    { s, stringize(s) }
        X(EV_READFD),
        X(EV_WRITEFD),
-       X(EV_SIG),
        X(EV_TIME),
        X(EV_WAIT),
-       X(EV_DEAD),
 #undef X
     };
-    int i;
+    size_t i;
 
-    for (i = 0; i < sizeof(event_types) / sizeof(event_types[0]); i++)
+    for (i = 0; i < (size_t)(sizeof(event_types) / sizeof(event_types[0])); i++)
        if (type == event_types[i].type)
            return (event_types[i].name);
-    return ("BOGUS EVENT TYPE");
+    return (_("BOGUS EVENT TYPE"));
+}
+
+/*
+ * FDSource -- a source for a file descriptor
+ *
+ * We could use Glib's GIOChannel for this, but it adds some buffering
+ * and Unicode functionality that we really don't want.  The custom GSource
+ * is simple enough anyway, and the Glib documentation describes it in prose.
+ */
+
+typedef struct FDSource {
+    GSource source; /* must be the first element in the struct */
+    GPollFD pollfd; /* Our file descriptor */
+} FDSource;
+
+static gboolean
+fdsource_prepare(
+    GSource *source G_GNUC_UNUSED,
+    gint *timeout_)
+{
+    *timeout_ = -1; /* block forever, as far as we're concerned */
+    return FALSE;
+}
+
+static gboolean
+fdsource_check(
+    GSource *source)
+{
+    FDSource *fds = (FDSource *)source;
+
+    /* we need to be dispatched if any interesting events have been received by the FD */
+    return fds->pollfd.events & fds->pollfd.revents;
+}
+
+static gboolean
+fdsource_dispatch(
+    GSource *source G_GNUC_UNUSED,
+    GSourceFunc callback,
+    gpointer user_data)
+{
+    if (callback)
+       return callback(user_data);
+
+    /* Don't automatically detach the event source if there's no callback. */
+    return TRUE;
+}
+
+GSource *
+new_fdsource(gint fd, GIOCondition events)
+{
+    static GSourceFuncs *fdsource_funcs = NULL;
+    GSource *src;
+    FDSource *fds;
+
+    /* initialize these here to avoid a compiler warning */
+    if (!fdsource_funcs) {
+       fdsource_funcs = g_new0(GSourceFuncs, 1);
+       fdsource_funcs->prepare = fdsource_prepare;
+       fdsource_funcs->check = fdsource_check;
+       fdsource_funcs->dispatch = fdsource_dispatch;
+    }
+
+    src = g_source_new(fdsource_funcs, sizeof(FDSource));
+    fds = (FDSource *)src;
+
+    fds->pollfd.fd = fd;
+    fds->pollfd.events = events;
+    g_source_add_poll(src, &fds->pollfd);
+
+    return src;
+}
+
+/*
+ * ChildWatchSource -- a source for a file descriptor
+ *
+ * Newer versions of glib provide equivalent functionality; consider
+ * optionally using that, protected by a GLIB_CHECK_VERSION condition.
+ */
+
+/* Versions before glib-2.4.0 didn't include a child watch source, and versions
+ * before 2.6.0 used unreliable signals.  On these versions, we implement
+ * a "dumb" version of our own invention.  This is dumb in the sense that it
+ * doesn't use SIGCHLD to detect a dead child, preferring to just poll at
+ * exponentially increasing interals.  Writing a smarter implementation runs into
+ * some tricky race conditions and extra machinery.  Since there are few, if any,
+ * users of a glib version this old, such machinery wouldn't get much testing.
+ *
+ * FreeBSD users have also reported problems with the glib child watch source,
+ * so we use the dumb version on FreeBSD, too.
+ */
+
+#if (defined(__FreeBSD__) || GLIB_MAJOR_VERSION < 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION < 6))
+typedef struct ChildWatchSource {
+    GSource source; /* must be the first element in the struct */
+
+    pid_t pid;
+
+    gint dead;
+    gint status;
+
+    gint timeout;
+} ChildWatchSource;
+
+/* this corresponds to rapid checks for about 10 seconds, after which the
+ * waitpid() check occurs every 2 seconds. */
+#define CWS_BASE_TIMEOUT 20
+#define CWS_MULT_TIMEOUT 1.1
+#define CWS_MAX_TIMEOUT 2000
+
+static gboolean
+child_watch_source_prepare(
+    GSource *source,
+    gint *timeout_)
+{
+    ChildWatchSource *cws = (ChildWatchSource *)source;
+
+    *timeout_ = cws->timeout;
+
+    cws->timeout *= CWS_MULT_TIMEOUT;
+    if (cws->timeout > CWS_MAX_TIMEOUT) cws->timeout = CWS_MAX_TIMEOUT;
+
+    return FALSE;
+}
+
+static gboolean
+child_watch_source_check(
+    GSource *source)
+{
+    ChildWatchSource *cws = (ChildWatchSource *)source;
+
+    /* is it dead? */
+    if (!cws->dead && waitpid(cws->pid, &cws->status, WNOHANG) > 0) {
+       cws->dead = TRUE;
+    }
+
+    return cws->dead;
+}
+
+static gboolean
+child_watch_source_dispatch(
+    GSource *source G_GNUC_UNUSED,
+    GSourceFunc callback,
+    gpointer user_data)
+{
+    ChildWatchSource *cws = (ChildWatchSource *)source;
+
+    /* this shouldn't happen, but just in case */
+    if (cws->dead) {
+       if (!callback) {
+           g_warning("child %jd died before callback was registered", (uintmax_t)cws->pid);
+           return FALSE;
+       }
+
+       ((ChildWatchFunc)callback)(cws->pid, cws->status, user_data);
+
+       /* Un-queue this source unconditionally -- the child can't die twice */
+       return FALSE;
+    }
+
+    return TRUE;
 }
-#endif /* EVENT_DEBUG */
+
+GSource *
+new_child_watch_source(pid_t pid)
+{
+    static GSourceFuncs *child_watch_source_funcs = NULL;
+    GSource *src;
+    ChildWatchSource *cws;
+
+    /* initialize these here to avoid a compiler warning */
+    if (!child_watch_source_funcs) {
+       child_watch_source_funcs = g_new0(GSourceFuncs, 1);
+       child_watch_source_funcs->prepare = child_watch_source_prepare;
+       child_watch_source_funcs->check = child_watch_source_check;
+       child_watch_source_funcs->dispatch = child_watch_source_dispatch;
+    }
+
+    src = g_source_new(child_watch_source_funcs, sizeof(ChildWatchSource));
+    cws = (ChildWatchSource *)src;
+
+    cws->pid = pid;
+    cws->dead = FALSE;
+    cws->timeout = CWS_BASE_TIMEOUT;
+
+    return src;
+}
+#else
+/* In more recent versions of glib, we just use the built-in glib source */
+GSource *
+new_child_watch_source(pid_t pid)
+{
+    return g_child_watch_source_new(pid);
+}
+#endif