[Libguestfs] [PATCH for discussion only] New event API (RHBZ#664558).

Richard W.M. Jones
Thu, 10 Mar 2011 15:24:32 -0000

It's not too clear from the patch, so I have also extracted the new
API from <guestfs.h> and the relevant section of the man page.

Rich.

----------------------------------------------------------------------

/* Events. */
#define GUESTFS_EVENT_CLOSE            0x0001
#define GUESTFS_EVENT_SUBPROCESS_QUIT  0x0002
#define GUESTFS_EVENT_LAUNCH_DONE      0x0004
#define GUESTFS_EVENT_PROGRESS         0x0008
#define GUESTFS_EVENT_APPLIANCE        0x0010
#define GUESTFS_EVENT_LIBRARY          0x0020
#define GUESTFS_EVENT_TRACE            0x0040
#define GUESTFS_EVENT_ALL              UINT64_MAX

#ifndef GUESTFS_TYPEDEF_EVENT_CALLBACK
#define GUESTFS_TYPEDEF_EVENT_CALLBACK 1
typedef void (*guestfs_event_callback) (
                        guestfs_h *g,
                        void *opaque,
                        uint64_t event,
                        int event_handle,
                        int flags,
                        const char *buf, size_t buf_len,
                        const uint64_t *array, size_t array_len);
#endif

#define LIBGUESTFS_HAVE_SET_EVENT_CALLBACK 1
int guestfs_set_event_callback (guestfs_h *g,
                                guestfs_event_callback cb,
                                uint64_t event_bitmask,
                                int flags,
                                void *opaque);
#define LIBGUESTFS_HAVE_DELETE_EVENT_CALLBACK 1
void guestfs_delete_event_callback (guestfs_h *g, int event_handle);

----------------------------------------------------------------------

   SETTING CALLBACKS TO HANDLE EVENTS
       Note: This section documents the new-style event mechanism, which you
       should use in new code if possible.  The old functions
       "guestfs_set_log_message_callback",
       "guestfs_set_subprocess_quit_callback",
       "guestfs_set_launch_done_callback",
"guestfs_set_close_callback" and
       "guestfs_set_progress_callback" are no longer documented in
this manual
       page.

       Handles generate events when certain things happen, such as log
       messages being generated, progress messages during long-running
       operations, or the handle being closed.  The API calls described below
       let you register a callback to be called when events happen.  You can
       register multiple callbacks (for the same, different or overlapping
       sets of events), and individually remove callbacks.  If callbacks are
       not removed, then they remain in force until the handle is closed.

       In the current implementation, events are only generated synchronously:
       that means that events (and hence callbacks) can only happen while you
       are in the middle of making another libguestfs call.  The callback is
       called in the same thread.

       Events may contain a payload, usually nothing (void), an array of 64
       bit unsigned integers, or a message buffer.  Payloads are discussed
       later on.

       CLASSES OF EVENTS

       GUESTFS_EVENT_CLOSE (payload type: void)
           The callback function will be called while the handle is being
           closed (synchronously from "guestfs_close").

           Note that libguestfs installs an atexit(3) handler to try to clean
           up handles that are open when the program exits.  This means that
           this callback might be called indirectly from exit(3), which can
           cause unexpected problems in higher-level languages (eg. if your
           HLL interpreter has already been cleaned up by the time this is
           called, and if your callback then jumps into some HLL function).

           If no callback is registered: the handle is closed without any
           callback being invoked.

       GUESTFS_EVENT_SUBPROCESS_QUIT (payload type: void)
           The callback function will be called when the child process quits,
           either asynchronously or if killed by
"guestfs_kill_subprocess".
           (This corresponds to a transition from any state to the CONFIG
           state).

           If no callback is registered: the event is ignored.

       GUESTFS_EVENT_LAUNCH_DONE (payload type: void)
           The callback function will be called when the child process becomes
           ready first time after it has been launched.  (This corresponds to
           a transition from LAUNCHING to the READY state).

           If no callback is registered: the event is ignored.

       GUESTFS_EVENT_PROGRESS (payload type: array of 4 x uint64_t)
           Some long-running operations can generate progress messages.  If
           this callback is registered, then it will be called each time a
           progress message is generated (usually two seconds after the
           operation started, and three times per second thereafter until it
           completes, although the frequency may change in future versions).

           The callback receives in the payload four unsigned 64 bit numbers
           which are (in order): "proc_nr", "serial",
"position", "total".

           The units of "total" are not defined, although for some
operations
           "total" may relate in some way to the amount of data to be
           transferred (eg. in bytes or megabytes), and "position" may
be the
           portion which has been transferred.

           The only defined and stable parts of the API are:

           ?   The callback can display to the user some type of progress bar
               or indicator which shows the ratio of
"position":"total".

           ?   0 <= "position" <= "total"

           ?   If any progress notification is sent during a call, then a
               final progress notification is always sent when
"position"                "total".

               This is to simplify caller code, so callers can easily set the
               progress indicator to "100%" at the end of the
operation,
               without requiring special code to detect this case.

           The callback also receives the procedure number ("proc_nr")
and
           serial number ("serial") of the call.  These are only
useful for
           debugging protocol issues, and the callback can normally ignore
           them.  The callback may want to print these numbers in error
           messages or debugging messages.

           If no callback is registered: progress messages are discarded.

       GUESTFS_EVENT_APPLIANCE (payload type: message buffer)
           The callback function is called whenever a log message is generated
           by qemu, the appliance kernel, guestfsd (daemon), or utility
           programs.

           If the verbose flag ("guestfs_set_verbose") is set before
launch
           ("guestfs_launch") then additional debug messages are
generated.

           If no callback is registered: the messages are discarded unless the
           verbose flag is set in which case they are sent to stderr.  You can
           override the printing of verbose messages to stderr by setting up a
           callback.

       GUESTFS_EVENT_LIBRARY (payload type: message buffer)
           The callback function is called whenever a log message is generated
           by the library part of libguestfs.

           If the verbose flag ("guestfs_set_verbose") is set then
additional
           debug messages are generated.

           If no callback is registered: the messages are discarded unless the
           verbose flag is set in which case they are sent to stderr.  You can
           override the printing of verbose messages to stderr by setting up a
           callback.

       GUESTFS_EVENT_TRACE (payload type: message buffer)
           The callback function is called whenever a trace message is
           generated.  This only applies if the trace flag
           ("guestfs_set_trace") is set.

           If no callback is registered: the messages are discarded unless the
           verbose flag is set in which case they are sent to stderr.  You can
           override the printing of verbose messages to stderr by setting up a
           callback.

       guestfs_set_event_callback

        int guestfs_set_event_callback (guestfs_h *g,
                                        guestfs_event_callback cb,
                                        uint64_t event_bitmask,
                                        int flags,
                                        void *opaque);

       This function registers a callback ("cb") for all event classes
in the
       "event_bitmask".

       For example, to register for all log message events, you could call
       this function with the bitmask
       "GUESTFS_EVENT_APPLIANCE|GUESTFS_EVENT_LIBRARY".  To register a
single
       callback for all possible classes of events, use
"GUESTFS_EVENT_ALL".

       "flags" should always be passed as 0.

       "opaque" is an opaque pointer which is passed to the callback.
You can
       use it for any purpose.

       The return value is the event handle (an integer) which you can use to
       delete the callback (see below).

       If there is an error, this function returns "-1", and sets the
error in
       the handle in the usual way (see "guestfs_last_error" etc.)

       Callbacks remain in effect until they are deleted, or until the handle
       is closed.

       In the case where multiple callbacks are registered for a particular
       event class, all of the callbacks are called.  The order in which
       multiple callbacks are called is not defined.

       guestfs_delete_event_callback

        void guestfs_delete_event_callback (guestfs_h *g, int event_handle);

       Delete a callback that was previously registered.
"event_handle"
       should be the integer that was returned by a previous call to
       "guestfs_set_event_callback" on the same handle.

       guestfs_event_callback

        typedef void (*guestfs_event_callback) (
                         guestfs_h *g,
                         void *opaque,
                         uint64_t event,
                         int event_handle,
                         int flags,
                         const char *buf, size_t buf_len,
                         const uint64_t *array, size_t array_len);

       This is the type of the event callback function that you have to
       provide.

       The basic parameters are: the handle ("g"), the opaque user
pointer
       ("opaque"), the event class (eg.
"GUESTFS_EVENT_PROGRESS"), the event
       handle, and "flags" which in the current API you should ignore.

       The remaining parameters contain the event payload (if any).  Each
       event may contain a payload, which usually relates to the event class,
       but for future proofing your code should be written to handle any
       payload for any event class.

       "buf" and "buf_len" contain a message buffer (if
"buf_len == 0", then
       there is no message buffer).  Note that this message buffer can contain
       arbitrary 8 bit data, including NUL bytes.

       "array" and "array_len" is an array of 64 bit
unsigned integers.  At
       the moment this is only used for progress messages.



--
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming blog: http://rwmj.wordpress.com
Fedora now supports 80 OCaml packages (the OPEN alternative to F#)
http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora
-------------- next part --------------
>From bbc35e61765eb32320b5dfada12b73f46fa03a3c Mon Sep 17 00:00:00 2001
From: Richard W.M. Jones <rjones at redhat.com>
Date: Thu, 10 Mar 2011 12:32:22 +0000
Subject: [PATCH] New event API (RHBZ#664558).

This API allows more than one callback to be registered for each
event, makes it possible to call the API from other languages, and
allows debug and trace messages to be rerouted from stderr.

An older version of this API was discussed on the mailing list here:
https://www.redhat.com/archives/libguestfs/2010-December/msg00081.html
https://www.redhat.com/archives/libguestfs/2011-January/msg00012.html
---
 generator/.depend             |    8 +-
 generator/Makefile.am         |    1 +
 generator/generator_c.ml      |   34 +++++
 generator/generator_events.ml |   40 ++++++
 po/POTFILES.in                |    1 +
 src/Makefile.am               |    1 +
 src/events.c                  |  299 +++++++++++++++++++++++++++++++++++++++++
 src/guestfs-internal.h        |   29 +++--
 src/guestfs.c                 |   60 ++-------
 src/guestfs.pod               |  245 ++++++++++++++++++++++++----------
 src/proto.c                   |   39 +++---
 11 files changed, 609 insertions(+), 148 deletions(-)
 create mode 100644 generator/generator_events.ml
 create mode 100644 src/events.c

diff --git a/generator/.depend b/generator/.depend
index 4ea8040..afd1c1e 100644
--- a/generator/.depend
+++ b/generator/.depend
@@ -21,6 +21,8 @@ generator_optgroups.cmx: generator_types.cmx
generator_actions.cmx
 generator_prepopts.cmi:
 generator_prepopts.cmo: generator_prepopts.cmi
 generator_prepopts.cmx: generator_prepopts.cmi
+generator_events.cmo: generator_utils.cmi
+generator_events.cmx: generator_utils.cmx
 generator_pr.cmi:
 generator_pr.cmo: generator_utils.cmi generator_pr.cmi
 generator_pr.cmx: generator_utils.cmx generator_pr.cmi
@@ -34,10 +36,12 @@ generator_checks.cmx: generator_utils.cmx
generator_types.cmx \
     generator_actions.cmx
 generator_c.cmo: generator_utils.cmi generator_types.cmo \
     generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
-    generator_docstrings.cmo generator_api_versions.cmi generator_actions.cmi
+    generator_events.cmo generator_docstrings.cmo generator_api_versions.cmi \
+    generator_actions.cmi
 generator_c.cmx: generator_utils.cmx generator_types.cmx \
     generator_structs.cmx generator_pr.cmx generator_optgroups.cmx \
-    generator_docstrings.cmx generator_api_versions.cmx generator_actions.cmx
+    generator_events.cmx generator_docstrings.cmx generator_api_versions.cmx \
+    generator_actions.cmx
 generator_xdr.cmo: generator_utils.cmi generator_types.cmo \
     generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
     generator_docstrings.cmo generator_actions.cmi
diff --git a/generator/Makefile.am b/generator/Makefile.am
index 39688c1..112fc69 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -28,6 +28,7 @@ SOURCES = \
 	generator_optgroups.ml \
 	generator_prepopts.mli \
 	generator_prepopts.ml \
+	generator_events.ml \
 	generator_pr.mli \
 	generator_pr.ml \
 	generator_docstrings.ml \
diff --git a/generator/generator_c.ml b/generator/generator_c.ml
index 9b88376..75b679d 100644
--- a/generator/generator_c.ml
+++ b/generator/generator_c.ml
@@ -28,6 +28,7 @@ open Generator_api_versions
 open Generator_optgroups
 open Generator_actions
 open Generator_structs
+open Generator_events

 (* Generate C API. *)

@@ -396,6 +397,39 @@ extern void guestfs_set_out_of_memory_handler (guestfs_h
*g, guestfs_abort_cb);
 extern guestfs_abort_cb guestfs_get_out_of_memory_handler (guestfs_h *g);

 /* Events. */
+";
+
+  List.iter (
+    fun (name, bitmask) ->
+      pr "#define GUESTFS_EVENT_%-16s 0x%04x\n"
+        (String.uppercase name) bitmask
+  ) events;
+  pr "#define GUESTFS_EVENT_%-16s UINT64_MAX\n" "ALL";
+  pr "\n";
+
+  pr "\
+#ifndef GUESTFS_TYPEDEF_EVENT_CALLBACK
+#define GUESTFS_TYPEDEF_EVENT_CALLBACK 1
+typedef void (*guestfs_event_callback) (
+                        guestfs_h *g,
+                        void *opaque,
+                        uint64_t event,
+                        int event_handle,
+                        int flags,
+                        const char *buf, size_t buf_len,
+                        const uint64_t *array, size_t array_len);
+#endif
+
+#define LIBGUESTFS_HAVE_SET_EVENT_CALLBACK 1
+int guestfs_set_event_callback (guestfs_h *g,
+                                guestfs_event_callback cb,
+                                uint64_t event_bitmask,
+                                int flags,
+                                void *opaque);
+#define LIBGUESTFS_HAVE_DELETE_EVENT_CALLBACK 1
+void guestfs_delete_event_callback (guestfs_h *g, int event_handle);
+
+/* Old-style event handling.  In new code use guestfs_set_event_callback. */
 #ifndef GUESTFS_TYPEDEF_LOG_MESSAGE_CB
 #define GUESTFS_TYPEDEF_LOG_MESSAGE_CB 1
 typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *opaque, char *buf,
int len);
diff --git a/generator/generator_events.ml b/generator/generator_events.ml
new file mode 100644
index 0000000..3f45a41
--- /dev/null
+++ b/generator/generator_events.ml
@@ -0,0 +1,40 @@
+(* libguestfs
+ * Copyright (C) 2011 Red Hat Inc.
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *)
+
+(* Please read generator/README first. *)
+
+open Generator_utils
+
+(* NB: DO NOT REORDER THESE, as doing so will change the ABI.  Only
+ * add new event types at the end of the list.
+ *)
+let events = [
+  "close";                              (* close handle *)
+  "subprocess_quit";                    (* subprocess quit *)
+  "launch_done";                        (* launched *)
+
+  "progress";                           (* progress message *)
+
+  (* log messages from various sources *)
+  "appliance";                          (* log messages from
+                                           kernel/guestfsd/appliance tools *)
+  "library";                            (* log messages from library
*)
+  "trace";                              (* call trace messages *)
+]
+
+let events = mapi (fun i name -> name, 1 lsl i) events
diff --git a/po/POTFILES.in b/po/POTFILES.in
index fd8778a..afe4798 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -133,6 +133,7 @@ src/appliance.c
 src/bindtests.c
 src/errnostring.c
 src/errnostring_gperf.c
+src/events.c
 src/filearch.c
 src/guestfs.c
 src/inspect.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 2b9c49b..3e1201d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -127,6 +127,7 @@ libguestfs_la_SOURCES = \
 	actions.c \
 	appliance.c \
 	bindtests.c \
+	events.c \
 	filearch.c \
 	inspect.c \
 	launch.c \
diff --git a/src/events.c b/src/events.c
new file mode 100644
index 0000000..514b3cd
--- /dev/null
+++ b/src/events.c
@@ -0,0 +1,299 @@
+/* libguestfs
+ * Copyright (C) 2011 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+
+#define _BSD_SOURCE /* for mkdtemp, usleep */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "ignore-value.h"
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+
+int
+guestfs_set_event_callback (guestfs_h *g,
+                            guestfs_event_callback cb,
+                            uint64_t event_bitmask,
+                            int flags,
+                            void *opaque)
+{
+  if (flags != 0) {
+    error (g, "flags parameter should be passed as 0 to this
function");
+    return -1;
+  }
+
+  /* We cast size_t to int which is not always safe for large numbers,
+   * and in any case if a program is registering a huge number of
+   * callbacks then we'd want to look at using an alternate data
+   * structure in place of a linear list.
+   */
+  if (g->nr_events >= 1000) {
+    error (g, "too many event callbacks registered");
+    return -1;
+  }
+
+  int event_handle = (int) g->nr_events;
+  g->events +    guestfs_safe_realloc (g, g->events,
+                          (g->nr_events+1) * sizeof (struct event));
+  g->nr_events++;
+
+  g->events[event_handle].event_bitmask = event_bitmask;
+  g->events[event_handle].cb = cb;
+  g->events[event_handle].opaque = opaque;
+  g->events[event_handle].opaque2 = NULL;
+
+  return event_handle;
+}
+
+void
+guestfs_delete_event_callback (guestfs_h *g, int event_handle)
+{
+  if (event_handle < 0 || event_handle >= (int) g->nr_events)
+    return;
+
+  /* Set the event_bitmask to 0, which will ensure that this callback
+   * cannot match any event and therefore cannot be called.
+   */
+  g->events[event_handle].event_bitmask = 0;
+}
+
+/* Functions to generate an event with various payloads. */
+
+void
+guestfs___call_callbacks_void (guestfs_h *g, uint64_t event)
+{
+  size_t i;
+
+  for (i = 0; i < g->nr_events; ++i)
+    if ((g->events[i].event_bitmask & event) != 0)
+      g->events[i].cb (g, g->events[i].opaque, event, i, 0, NULL, 0,
NULL, 0);
+
+  /* All events with payload type void are discarded if no callback
+   * was registered.
+   */
+}
+
+void
+guestfs___call_callbacks_message (guestfs_h *g, uint64_t event,
+                                  const char *buf, size_t buf_len)
+{
+  size_t i, count = 0;
+
+  for (i = 0; i < g->nr_events; ++i)
+    if ((g->events[i].event_bitmask & event) != 0) {
+      g->events[i].cb (g, g->events[i].opaque, event, i, 0,
+                       buf, buf_len, NULL, 0);
+      count++;
+    }
+
+  /* If nothing was registered and we're verbose, then we print the
+   * message on stderr.
+   */
+  if (g->verbose && count == 0)
+    ignore_value (write (STDERR_FILENO, buf, buf_len));
+}
+
+void
+guestfs___call_callbacks_array (guestfs_h *g, uint64_t event,
+                                const uint64_t *array, size_t array_len)
+{
+  size_t i;
+
+  for (i = 0; i < g->nr_events; ++i)
+    if ((g->events[i].event_bitmask & event) != 0)
+      g->events[i].cb (g, g->events[i].opaque, event, i, 0,
+                       NULL, 0, array, array_len);
+
+  /* All events with payload type array are discarded if no callback
+   * was registered.
+   */
+}
+
+/* Emulate old-style callback API.
+ *
+ * There were no event handles, so multiple callbacks per event were
+ * not supported.  Calling the same 'guestfs_set_*_callback' function
+ * would replace the existing event.  Calling it with cb == NULL meant
+ * that the caller wanted to remove the callback.
+ */
+
+static void
+replace_old_style_event_callback (guestfs_h *g,
+                                  guestfs_event_callback cb,
+                                  uint64_t event_bitmask,
+                                  void *opaque,
+                                  void *opaque2)
+{
+  size_t i;
+
+  /* Use 'cb' pointer as a sentinel to replace the existing callback
+   * for this event if one was registered previously.  Else append a
+   * new event.
+   */
+
+  for (i = 0; i < g->nr_events; ++i)
+    if (g->events[i].cb == cb) {
+      if (opaque2 == NULL) {
+        /* opaque2 (the original callback) is NULL, which in the
+         * old-style API meant remove the callback.
+         */
+        guestfs_delete_event_callback (g, i);
+        return;
+      }
+
+      goto replace;
+    }
+
+  if (opaque2 == NULL)
+    return; /* see above */
+
+  /* i == g->nr_events */
+  g->events +    guestfs_safe_realloc (g, g->events,
+                          (g->nr_events+1) * sizeof (struct event));
+  g->nr_events++;
+
+ replace:
+  g->events[i].event_bitmask = event_bitmask;
+  g->events[i].cb = cb;
+  g->events[i].opaque = opaque;
+  g->events[i].opaque2 = opaque2;
+}
+
+static void
+log_message_callback_wrapper (guestfs_h *g,
+                              void *opaque,
+                              uint64_t event,
+                              int event_handle,
+                              int flags,
+                              const char *buf, size_t buf_len,
+                              const uint64_t *array, size_t array_len)
+{
+  guestfs_log_message_cb cb = g->events[event_handle].opaque2;
+  /* Note that the old callback declared the message buffer as
+   * (char *, int).  I sure hope message buffers aren't too large
+   * and that callers aren't writing to them. XXX
+   */
+  cb (g, opaque, (char *) buf, (int) buf_len);
+}
+
+void
+guestfs_set_log_message_callback (guestfs_h *g,
+                                  guestfs_log_message_cb cb, void *opaque)
+{
+  replace_old_style_event_callback (g, log_message_callback_wrapper,
+                                    GUESTFS_EVENT_APPLIANCE,
+                                    opaque, cb);
+}
+
+static void
+subprocess_quit_callback_wrapper (guestfs_h *g,
+                                  void *opaque,
+                                  uint64_t event,
+                                  int event_handle,
+                                  int flags,
+                                  const char *buf, size_t buf_len,
+                                  const uint64_t *array, size_t array_len)
+{
+  guestfs_subprocess_quit_cb cb = g->events[event_handle].opaque2;
+  cb (g, opaque);
+}
+
+void
+guestfs_set_subprocess_quit_callback (guestfs_h *g,
+                                      guestfs_subprocess_quit_cb cb, void
*opaque)
+{
+  replace_old_style_event_callback (g, subprocess_quit_callback_wrapper,
+                                    GUESTFS_EVENT_SUBPROCESS_QUIT,
+                                    opaque, cb);
+}
+
+static void
+launch_done_callback_wrapper (guestfs_h *g,
+                              void *opaque,
+                              uint64_t event,
+                              int event_handle,
+                              int flags,
+                              const char *buf, size_t buf_len,
+                              const uint64_t *array, size_t array_len)
+{
+  guestfs_launch_done_cb cb = g->events[event_handle].opaque2;
+  cb (g, opaque);
+}
+
+void
+guestfs_set_launch_done_callback (guestfs_h *g,
+                                  guestfs_launch_done_cb cb, void *opaque)
+{
+  replace_old_style_event_callback (g, launch_done_callback_wrapper,
+                                    GUESTFS_EVENT_LAUNCH_DONE,
+                                    opaque, cb);
+}
+
+static void
+close_callback_wrapper (guestfs_h *g,
+                        void *opaque,
+                        uint64_t event,
+                        int event_handle,
+                        int flags,
+                        const char *buf, size_t buf_len,
+                        const uint64_t *array, size_t array_len)
+{
+  guestfs_close_cb cb = g->events[event_handle].opaque2;
+  cb (g, opaque);
+}
+
+void
+guestfs_set_close_callback (guestfs_h *g,
+                            guestfs_close_cb cb, void *opaque)
+{
+  replace_old_style_event_callback (g, close_callback_wrapper,
+                                    GUESTFS_EVENT_CLOSE,
+                                    opaque, cb);
+}
+
+static void
+progress_callback_wrapper (guestfs_h *g,
+                           void *opaque,
+                           uint64_t event,
+                           int event_handle,
+                           int flags,
+                           const char *buf, size_t buf_len,
+                           const uint64_t *array, size_t array_len)
+{
+  guestfs_progress_cb cb = g->events[event_handle].opaque2;
+  assert (array_len >= 4);
+  cb (g, opaque, (int) array[0], (int) array[1], array[2], array[3]);
+}
+
+void
+guestfs_set_progress_callback (guestfs_h *g,
+                               guestfs_progress_cb cb, void *opaque)
+{
+  replace_old_style_event_callback (g, progress_callback_wrapper,
+                                    GUESTFS_EVENT_PROGRESS,
+                                    opaque, cb);
+}
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 0eb395b..b4f7f6a 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -87,6 +87,18 @@ enum state { CONFIG, LAUNCHING, READY, BUSY, NO_HANDLE };
 /* Attach method. */
 enum attach_method { ATTACH_METHOD_APPLIANCE = 0, ATTACH_METHOD_UNIX };

+/* Event. */
+struct event {
+  uint64_t event_bitmask;
+  guestfs_event_callback cb;
+  void *opaque;
+
+  /* opaque2 is not exposed through the API, but is used internally to
+   * emulate the old-style callback API.
+   */
+  void *opaque2;
+};
+
 struct guestfs_h
 {
   struct guestfs_h *next;	/* Linked list of open handles. */
@@ -133,16 +145,10 @@ struct guestfs_h
   guestfs_abort_cb           abort_cb;
   guestfs_error_handler_cb   error_cb;
   void *                     error_cb_data;
-  guestfs_log_message_cb     log_message_cb;
-  void *                     log_message_cb_data;
-  guestfs_subprocess_quit_cb subprocess_quit_cb;
-  void *                     subprocess_quit_cb_data;
-  guestfs_launch_done_cb     launch_done_cb;
-  void *                     launch_done_cb_data;
-  guestfs_close_cb           close_cb;
-  void *                     close_cb_data;
-  guestfs_progress_cb        progress_cb;
-  void *                     progress_cb_data;
+
+  /* Events. */
+  struct event *events;
+  size_t nr_events;

   int msg_next_serial;

@@ -289,6 +295,9 @@ extern int guestfs___feature_available (guestfs_h *g, const
char *feature);
 extern void guestfs___free_string_list (char **);
 extern int guestfs___checkpoint_cmdline (guestfs_h *g);
 extern void guestfs___rollback_cmdline (guestfs_h *g, int pos);
+extern void guestfs___call_callbacks_void (guestfs_h *g, uint64_t event);
+extern void guestfs___call_callbacks_message (guestfs_h *g, uint64_t event,
const char *buf, size_t buf_len);
+extern void guestfs___call_callbacks_array (guestfs_h *g, uint64_t event, const
uint64_t *array, size_t array_len);

 #define error(g,...) guestfs_error_errno((g),0,__VA_ARGS__)
 #define perrorf guestfs_perrorf
diff --git a/src/guestfs.c b/src/guestfs.c
index 8b7ab4d..79b170e 100644
--- a/src/guestfs.c
+++ b/src/guestfs.c
@@ -182,24 +182,26 @@ guestfs_close (guestfs_h *g)
   if (g->verbose)
     fprintf (stderr, "closing guestfs handle %p (state %d)\n", g,
g->state);

-  /* Run user close callback before anything else. */
-  if (g->close_cb)
-    g->close_cb (g, g->close_cb_data);
-
-  guestfs___free_inspect_info (g);
-
   /* Try to sync if autosync flag is set. */
   if (g->autosync && g->state == READY)
     guestfs_internal_autosync (g);

-  /* Remove any handlers that might be called back before we kill the
-   * subprocess.
+  /* Run user close callbacks. */
+  guestfs___call_callbacks_void (g, GUESTFS_EVENT_CLOSE);
+
+  /* Remove all other registered callbacks.  They might be called
+   * unnecessarily when we kill the subprocess here, and since we've
+   * already called the close callbacks, we shouldn't call any others.
    */
-  g->log_message_cb = NULL;
+  free (g->events);
+  g->nr_events = 0;
+  g->events = NULL;

   if (g->state != CONFIG)
     guestfs_kill_subprocess (g);

+  guestfs___free_inspect_info (g);
+
   /* Close sockets. */
   if (g->fd[0] >= 0)
     close (g->fd[0]);
@@ -690,46 +692,6 @@ guestfs__get_attach_method (guestfs_h *g)
   return ret;
 }

-void
-guestfs_set_log_message_callback (guestfs_h *g,
-                                  guestfs_log_message_cb cb, void *opaque)
-{
-  g->log_message_cb = cb;
-  g->log_message_cb_data = opaque;
-}
-
-void
-guestfs_set_subprocess_quit_callback (guestfs_h *g,
-                                      guestfs_subprocess_quit_cb cb, void
*opaque)
-{
-  g->subprocess_quit_cb = cb;
-  g->subprocess_quit_cb_data = opaque;
-}
-
-void
-guestfs_set_launch_done_callback (guestfs_h *g,
-                                  guestfs_launch_done_cb cb, void *opaque)
-{
-  g->launch_done_cb = cb;
-  g->launch_done_cb_data = opaque;
-}
-
-void
-guestfs_set_close_callback (guestfs_h *g,
-                            guestfs_close_cb cb, void *opaque)
-{
-  g->close_cb = cb;
-  g->close_cb_data = opaque;
-}
-
-void
-guestfs_set_progress_callback (guestfs_h *g,
-                               guestfs_progress_cb cb, void *opaque)
-{
-  g->progress_cb = cb;
-  g->progress_cb_data = opaque;
-}
-
 /* Note the private data area is allocated lazily, since the vast
  * majority of callers will never use it.  This means g->pda is
  * likely to be NULL.
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 0b3b654..2105773 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -1683,82 +1683,71 @@ For guestfish, see L<guestfish(1)/OPTIONAL
ARGUMENTS>.

 =head2 SETTING CALLBACKS TO HANDLE EVENTS

-The child process generates events in some situations.  Current events
-include: receiving a log message, the child process exits.
-
-Use the C<guestfs_set_*_callback> functions to set a callback for
-different types of events.
-
-Only I<one callback of each type> can be registered for each handle.
-Calling C<guestfs_set_*_callback> again overwrites the previous
-callback of that type.  Cancel all callbacks of this type by calling
-this function with C<cb> set to C<NULL>.
-
-=head2 guestfs_set_log_message_callback
+B<Note:> This section documents the new-style event mechanism, which
+you should use in new code if possible.  The old functions
+C<guestfs_set_log_message_callback>,
+C<guestfs_set_subprocess_quit_callback>,
+C<guestfs_set_launch_done_callback>, C<guestfs_set_close_callback>
and
+C<guestfs_set_progress_callback> are no longer documented in this
+manual page.

- typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *opaque,
-                                         char *buf, int len);
- void guestfs_set_log_message_callback (guestfs_h *g,
-                                        guestfs_log_message_cb cb,
-                                        void *opaque);
+Handles generate events when certain things happen, such as log
+messages being generated, progress messages during long-running
+operations, or the handle being closed.  The API calls described below
+let you register a callback to be called when events happen.  You can
+register multiple callbacks (for the same, different or overlapping
+sets of events), and individually remove callbacks.  If callbacks are
+not removed, then they remain in force until the handle is closed.

-The callback function C<cb> will be called whenever qemu or the guest
-writes anything to the console.
+In the current implementation, events are only generated
+synchronously: that means that events (and hence callbacks) can only
+happen while you are in the middle of making another libguestfs call.
+The callback is called in the same thread.

-Use this function to capture kernel messages and similar.
+Events may contain a payload, usually nothing (void), an array of 64
+bit unsigned integers, or a message buffer.  Payloads are discussed
+later on.

-Normally there is no log message handler, and log messages are just
-discarded.
+=head3 CLASSES OF EVENTS

-=head2 guestfs_set_subprocess_quit_callback
+=over 4

- typedef void (*guestfs_subprocess_quit_cb) (guestfs_h *g, void *opaque);
- void guestfs_set_subprocess_quit_callback (guestfs_h *g,
-                                            guestfs_subprocess_quit_cb cb,
-                                            void *opaque);
+=item GUESTFS_EVENT_CLOSE
+(payload type: void)

-The callback function C<cb> will be called when the child process
-quits, either asynchronously or if killed by
-L</guestfs_kill_subprocess>.  (This corresponds to a transition from
-any state to the CONFIG state).
+The callback function will be called while the handle is being closed
+(synchronously from L</guestfs_close>).

-=head2 guestfs_set_launch_done_callback
+Note that libguestfs installs an L<atexit(3)> handler to try to clean
+up handles that are open when the program exits.  This means that this
+callback might be called indirectly from L<exit(3)>, which can cause
+unexpected problems in higher-level languages (eg. if your HLL
+interpreter has already been cleaned up by the time this is called,
+and if your callback then jumps into some HLL function).

- typedef void (*guestfs_launch_done_cb) (guestfs_h *g, void *opaque);
- void guestfs_set_launch_done_callback (guestfs_h *g,
-                                        guestfs_launch_done_cb cb,
-                                        void *opaque);
+If no callback is registered: the handle is closed without any
+callback being invoked.

-The callback function C<cb> will be called when the child process
-becomes ready first time after it has been launched.  (This
-corresponds to a transition from LAUNCHING to the READY state).
+=item GUESTFS_EVENT_SUBPROCESS_QUIT
+(payload type: void)

-=head2 guestfs_set_close_callback
+The callback function will be called when the child process quits,
+either asynchronously or if killed by L</guestfs_kill_subprocess>.
+(This corresponds to a transition from any state to the CONFIG state).

- typedef void (*guestfs_close_cb) (guestfs_h *g, void *opaque);
- void guestfs_set_close_callback (guestfs_h *g,
-                                  guestfs_close_cb cb,
-                                  void *opaque);
+If no callback is registered: the event is ignored.

-The callback function C<cb> will be called while the handle
-is being closed (synchronously from L</guestfs_close>).
+=item GUESTFS_EVENT_LAUNCH_DONE
+(payload type: void)

-Note that libguestfs installs an L<atexit(3)> handler to try to
-clean up handles that are open when the program exits.  This
-means that this callback might be called indirectly from
-L<exit(3)>, which can cause unexpected problems in higher-level
-languages (eg. if your HLL interpreter has already been cleaned
-up by the time this is called, and if your callback then jumps
-into some HLL function).
+The callback function will be called when the child process becomes
+ready first time after it has been launched.  (This corresponds to a
+transition from LAUNCHING to the READY state).

-=head2 guestfs_set_progress_callback
+If no callback is registered: the event is ignored.

- typedef void (*guestfs_progress_cb) (guestfs_h *g, void *opaque,
-                                      int proc_nr, int serial,
-                                      uint64_t position, uint64_t total);
- void guestfs_set_progress_callback (guestfs_h *g,
-                                     guestfs_progress_cb cb,
-                                     void *opaque);
+=item GUESTFS_EVENT_PROGRESS
+(payload type: array of 4 x uint64_t)

 Some long-running operations can generate progress messages.  If
 this callback is registered, then it will be called each time a
@@ -1766,7 +1755,9 @@ progress message is generated (usually two seconds after
the
 operation started, and three times per second thereafter until
 it completes, although the frequency may change in future versions).

-The callback receives two numbers: C<position> and C<total>.
+The callback receives in the payload four unsigned 64 bit numbers
+which are (in order): C<proc_nr>, C<serial>, C<position>,
C<total>.
+
 The units of C<total> are not defined, although for some
 operations C<total> may relate in some way to the amount of
 data to be transferred (eg. in bytes or megabytes), and
@@ -1796,10 +1787,127 @@ requiring special code to detect this case.

 =back

-The callback also receives the procedure number and serial number of
-the call.  These are only useful for debugging protocol issues, and
-the callback can normally ignore them.  The callback may want to
-print these numbers in error messages or debugging messages.
+The callback also receives the procedure number (C<proc_nr>) and
+serial number (C<serial>) of the call.  These are only useful for
+debugging protocol issues, and the callback can normally ignore them.
+The callback may want to print these numbers in error messages or
+debugging messages.
+
+If no callback is registered: progress messages are discarded.
+
+=item GUESTFS_EVENT_APPLIANCE
+(payload type: message buffer)
+
+The callback function is called whenever a log message is generated by
+qemu, the appliance kernel, guestfsd (daemon), or utility programs.
+
+If the verbose flag (L</guestfs_set_verbose>) is set before launch
+(L</guestfs_launch>) then additional debug messages are generated.
+
+If no callback is registered: the messages are discarded unless the
+verbose flag is set in which case they are sent to stderr.  You can
+override the printing of verbose messages to stderr by setting up a
+callback.
+
+=item GUESTFS_EVENT_LIBRARY
+(payload type: message buffer)
+
+The callback function is called whenever a log message is generated by
+the library part of libguestfs.
+
+If the verbose flag (L</guestfs_set_verbose>) is set then additional
+debug messages are generated.
+
+If no callback is registered: the messages are discarded unless the
+verbose flag is set in which case they are sent to stderr.  You can
+override the printing of verbose messages to stderr by setting up a
+callback.
+
+=item GUESTFS_EVENT_TRACE
+(payload type: message buffer)
+
+The callback function is called whenever a trace message is generated.
+This only applies if the trace flag (L</guestfs_set_trace>) is set.
+
+If no callback is registered: the messages are discarded unless the
+verbose flag is set in which case they are sent to stderr.  You can
+override the printing of verbose messages to stderr by setting up a
+callback.
+
+=back
+
+=head3 guestfs_set_event_callback
+
+ int guestfs_set_event_callback (guestfs_h *g,
+                                 guestfs_event_callback cb,
+                                 uint64_t event_bitmask,
+                                 int flags,
+                                 void *opaque);
+
+This function registers a callback (C<cb>) for all event classes
+in the C<event_bitmask>.
+
+For example, to register for all log message events, you could call
+this function with the bitmask
+C<GUESTFS_EVENT_APPLIANCE|GUESTFS_EVENT_LIBRARY>.  To register a
+single callback for all possible classes of events, use
+C<GUESTFS_EVENT_ALL>.
+
+C<flags> should always be passed as 0.
+
+C<opaque> is an opaque pointer which is passed to the callback.  You
+can use it for any purpose.
+
+The return value is the event handle (an integer) which you can use to
+delete the callback (see below).
+
+If there is an error, this function returns C<-1>, and sets the error
+in the handle in the usual way (see L</guestfs_last_error> etc.)
+
+Callbacks remain in effect until they are deleted, or until the handle
+is closed.
+
+In the case where multiple callbacks are registered for a particular
+event class, all of the callbacks are called.  The order in which
+multiple callbacks are called is not defined.
+
+=head3 guestfs_delete_event_callback
+
+ void guestfs_delete_event_callback (guestfs_h *g, int event_handle);
+
+Delete a callback that was previously registered.  C<event_handle>
+should be the integer that was returned by a previous call to
+C<guestfs_set_event_callback> on the same handle.
+
+=head3 guestfs_event_callback
+
+ typedef void (*guestfs_event_callback) (
+                  guestfs_h *g,
+                  void *opaque,
+                  uint64_t event,
+                  int event_handle,
+                  int flags,
+                  const char *buf, size_t buf_len,
+                  const uint64_t *array, size_t array_len);
+
+This is the type of the event callback function that you have to
+provide.
+
+The basic parameters are: the handle (C<g>), the opaque user pointer
+(C<opaque>), the event class (eg. C<GUESTFS_EVENT_PROGRESS>), the
+event handle, and C<flags> which in the current API you should ignore.
+
+The remaining parameters contain the event payload (if any).  Each
+event may contain a payload, which usually relates to the event class,
+but for future proofing your code should be written to handle any
+payload for any event class.
+
+C<buf> and C<buf_len> contain a message buffer (if C<buf_len ==
0>,
+then there is no message buffer).  Note that this message buffer can
+contain arbitrary 8 bit data, including NUL bytes.
+
+C<array> and C<array_len> is an array of 64 bit unsigned integers.
At
+the moment this is only used for progress messages.

 =head1 PRIVATE DATA AREA

@@ -1833,8 +1941,7 @@ any way.  As far as libguestfs is concerned, it need not
be a valid
 pointer at all.  In particular, libguestfs does I<not> try to free the
 data when the handle is closed.  If the data must be freed, then the
 caller must either free it before calling L</guestfs_close> or must
-set up a close callback to do it (see L</guestfs_set_close_callback>,
-and note that only one callback can be registered for a handle).
+set up a close callback to do it (see L</GUESTFS_EVENT_CLOSE>.

 The private data area is implemented using a hash table, and should be
 reasonably efficient for moderate numbers of keys.
@@ -2100,8 +2207,8 @@ are distinguished by the normal length word being replaced
by
 C<GUESTFS_PROGRESS_FLAG>, followed by a fixed size progress message.

 The library turns them into progress callbacks (see
-C<guestfs_set_progress_callback>) if there is a callback registered,
-or discards them if not.
+L</GUESTFS_EVENT_PROGRESS>) if there is a callback registered, or
+discards them if not.

 The daemon self-limits the frequency of progress messages it sends
 (see C<daemon/proto.c:notify_progress>).  Not all calls generate
diff --git a/src/proto.c b/src/proto.c
index 549734b..6a0fbbf 100644
--- a/src/proto.c
+++ b/src/proto.c
@@ -193,8 +193,7 @@ child_cleanup (guestfs_h *g)
   g->recoverypid = 0;
   memset (&g->launch_t, 0, sizeof g->launch_t);
   g->state = CONFIG;
-  if (g->subprocess_quit_cb)
-    g->subprocess_quit_cb (g, g->subprocess_quit_cb_data);
+  guestfs___call_callbacks_void (g, GUESTFS_EVENT_SUBPROCESS_QUIT);
 }

 static int
@@ -237,13 +236,8 @@ read_log_message_or_eof (guestfs_h *g, int fd, int
error_if_eof)
     return -1;
   }

-  /* In verbose mode, copy all log messages to stderr. */
-  if (g->verbose)
-    ignore_value (write (STDERR_FILENO, buf, n));
-
   /* It's an actual log message, send it upwards if anyone is listening. */
-  if (g->log_message_cb)
-    g->log_message_cb (g, g->log_message_cb_data, buf, n);
+  guestfs___call_callbacks_message (g, GUESTFS_EVENT_APPLIANCE, buf, n);

   return 0;
 }
@@ -293,6 +287,20 @@ really_read_from_socket (guestfs_h *g, int sock, char *buf,
size_t n)
   return (ssize_t) got;
 }

+static void
+send_progress_message (guestfs_h *g, const guestfs_progress *message)
+{
+  uint64_t array[4];
+
+  array[0] = message->proc;
+  array[1] = message->serial;
+  array[2] = message->position;
+  array[3] = message->total;
+
+  guestfs___call_callbacks_array (g, GUESTFS_EVENT_PROGRESS,
+                                  array, sizeof array / sizeof array[0]);
+}
+
 static int
 check_for_daemon_cancellation_or_eof (guestfs_h *g, int fd)
 {
@@ -331,16 +339,14 @@ check_for_daemon_cancellation_or_eof (guestfs_h *g, int
fd)
       return -1;
     }

-    if (g->state == BUSY && g->progress_cb) {
+    if (g->state == BUSY) {
       guestfs_progress message;

       xdrmem_create (&xdr, buf, PROGRESS_MESSAGE_SIZE, XDR_DECODE);
       xdr_guestfs_progress (&xdr, &message);
       xdr_destroy (&xdr);

-      g->progress_cb (g, g->progress_cb_data,
-                      message.proc, message.serial,
-                      message.position, message.total);
+      send_progress_message (g, &message);
     }

     return 0;
@@ -543,8 +549,7 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t
*size_rtn, void **buf_rtn)
                    g->state);
           else {
             g->state = READY;
-            if (g->launch_done_cb)
-              g->launch_done_cb (g, g->launch_done_cb_data);
+            guestfs___call_callbacks_void (g, GUESTFS_EVENT_LAUNCH_DONE);
           }
           return 0;
         }
@@ -614,16 +619,14 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t
*size_rtn, void **buf_rtn)
 #endif

   if (*size_rtn == GUESTFS_PROGRESS_FLAG) {
-    if (g->state == BUSY && g->progress_cb) {
+    if (g->state == BUSY) {
       guestfs_progress message;
       XDR xdr;
       xdrmem_create (&xdr, *buf_rtn, PROGRESS_MESSAGE_SIZE, XDR_DECODE);
       xdr_guestfs_progress (&xdr, &message);
       xdr_destroy (&xdr);

-      g->progress_cb (g, g->progress_cb_data,
-                      message.proc, message.serial,
-                      message.position, message.total);
+      send_progress_message (g, &message);
     }

     free (*buf_rtn);
--
1.7.4