New event API (RHBZ#664558).
authorRichard W.M. Jones <rjones@redhat.com>
Thu, 10 Mar 2011 12:32:22 +0000 (12:32 +0000)
committerRichard W.M. Jones <rjones@redhat.com>
Tue, 15 Mar 2011 12:16:50 +0000 (12:16 +0000)
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 [nearly all] log, 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

This also updates guestfish to use the new API for its progress bars.

23 files changed:
.gitignore
capitests/Makefile.am
capitests/test-debug-to-file.c [new file with mode: 0644]
capitests/test-private-data.c
fish/fish.c
fish/fish.h
fish/progress.c
fish/reopen.c
generator/.depend
generator/Makefile.am
generator/generator_actions.ml
generator/generator_c.ml
generator/generator_events.ml [new file with mode: 0644]
po/POTFILES.in
src/Makefile.am
src/appliance.c
src/events.c [new file with mode: 0644]
src/guestfs-internal.h
src/guestfs.c
src/guestfs.pod
src/inspect.c
src/launch.c
src/proto.c

index 7df10b2..d3b3f3e 100644 (file)
@@ -10,11 +10,13 @@ appliance/supermin.d
 autom4te.cache
 *.bak
 bindtests.tmp
 autom4te.cache
 *.bak
 bindtests.tmp
+capitests/test.log
 capitests/test-add-drive-opts
 capitests/test-add-libvirt-dom
 capitests/test-command
 capitests/test-config
 capitests/test-create-handle
 capitests/test-add-drive-opts
 capitests/test-add-libvirt-dom
 capitests/test-command
 capitests/test-config
 capitests/test-create-handle
+capitests/test-debug-to-file
 capitests/test-just-header
 capitests/test-last-errno
 capitests/test-private-data
 capitests/test-just-header
 capitests/test-last-errno
 capitests/test-private-data
index 83e62c8..89d29e0 100644 (file)
@@ -31,7 +31,8 @@ check_PROGRAMS = \
        test-config \
        test-add-drive-opts \
        test-last-errno \
        test-config \
        test-add-drive-opts \
        test-last-errno \
-       test-private-data
+       test-private-data \
+       test-debug-to-file
 
 TESTS = \
        tests \
 
 TESTS = \
        tests \
@@ -40,7 +41,8 @@ TESTS = \
        test-config \
        test-add-drive-opts \
        test-last-errno \
        test-config \
        test-add-drive-opts \
        test-last-errno \
-       test-private-data
+       test-private-data \
+       test-debug-to-file
 
 # The API behind this test is not baked yet.
 #if HAVE_LIBVIRT
 
 # The API behind this test is not baked yet.
 #if HAVE_LIBVIRT
@@ -112,6 +114,13 @@ test_private_data_CFLAGS = \
 test_private_data_LDADD = \
        $(top_builddir)/src/libguestfs.la
 
 test_private_data_LDADD = \
        $(top_builddir)/src/libguestfs.la
 
+test_debug_to_file_SOURCES = test-debug-to-file.c
+test_debug_to_file_CFLAGS = \
+       -I$(top_srcdir)/src -I$(top_builddir)/src \
+       $(WARN_CFLAGS) $(WERROR_CFLAGS)
+test_debug_to_file_LDADD = \
+       $(top_builddir)/src/libguestfs.la
+
 #if HAVE_LIBVIRT
 #test_add_libvirt_dom_SOURCES = test-add-libvirt-dom.c
 #test_add_libvirt_dom_CFLAGS = \
 #if HAVE_LIBVIRT
 #test_add_libvirt_dom_SOURCES = test-add-libvirt-dom.c
 #test_add_libvirt_dom_CFLAGS = \
diff --git a/capitests/test-debug-to-file.c b/capitests/test-debug-to-file.c
new file mode 100644 (file)
index 0000000..6d1b619
--- /dev/null
@@ -0,0 +1,89 @@
+/* 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.
+ */
+
+/* Test that we can use the new event API to capture all debugging
+ * messages to a file.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "guestfs.h"
+
+static void
+debug_to_file (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)
+{
+  FILE *fp = opaque;
+
+  fwrite (buf, 1, buf_len, fp);
+}
+
+int
+main (int argc, char *argv[])
+{
+  guestfs_h *g;
+  const char *filename = "test.log";
+  FILE *debugfp;
+
+  debugfp = fopen (filename, "w");
+  if (debugfp == NULL) {
+    perror (filename);
+    exit (EXIT_FAILURE);
+  }
+
+  g = guestfs_create ();
+  if (g == NULL) {
+    fprintf (stderr, "failed to create handle\n");
+    exit (EXIT_FAILURE);
+  }
+
+  if (guestfs_set_event_callback
+      (g, debug_to_file,
+       GUESTFS_EVENT_LIBRARY|GUESTFS_EVENT_APPLIANCE|GUESTFS_EVENT_TRACE,
+       0, debugfp) == -1)
+    exit (EXIT_FAILURE);
+
+  if (guestfs_set_verbose (g, 1) == -1)
+    exit (EXIT_FAILURE);
+
+  if (guestfs_set_trace (g, 1) == -1)
+    exit (EXIT_FAILURE);
+
+  if (guestfs_add_drive_opts (g, "/dev/null",
+                              GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw",
+                              GUESTFS_ADD_DRIVE_OPTS_READONLY, 1,
+                              -1) == -1)
+    exit (EXIT_FAILURE);
+
+  if (guestfs_launch (g) == -1)
+    exit (EXIT_FAILURE);
+
+  guestfs_close (g);
+
+  exit (EXIT_SUCCESS);
+}
index fad88b5..f2ff647 100644 (file)
 
 #define PREFIX "test_"
 
 
 #define PREFIX "test_"
 
+static size_t close_callback_called = 0;
+
+/* This callback deletes all test keys in the handle. */
+static void
+close_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)
+{
+  const char *key;
+  void *data;
+
+  close_callback_called++;
+
+ again:
+  data = guestfs_first_private (g, &key);
+  while (data != NULL) {
+    if (strncmp (key, PREFIX, strlen (PREFIX)) == 0) {
+      guestfs_set_private (g, key, NULL);
+      goto again;
+    }
+    data = guestfs_next_private (g, &key);
+  }
+}
+
 int
 main (int argc, char *argv[])
 {
 int
 main (int argc, char *argv[])
 {
@@ -44,6 +72,10 @@ main (int argc, char *argv[])
     exit (EXIT_FAILURE);
   }
 
     exit (EXIT_FAILURE);
   }
 
+  if (guestfs_set_event_callback (g, close_callback, GUESTFS_EVENT_CLOSE,
+                                  0, NULL) == -1)
+    exit (EXIT_FAILURE);
+
   guestfs_set_private (g, PREFIX "a", (void *) 1);
   guestfs_set_private (g, PREFIX "b", (void *) 2);
   guestfs_set_private (g, PREFIX "c", (void *) 3);
   guestfs_set_private (g, PREFIX "a", (void *) 1);
   guestfs_set_private (g, PREFIX "b", (void *) 2);
   guestfs_set_private (g, PREFIX "c", (void *) 3);
@@ -79,7 +111,10 @@ main (int argc, char *argv[])
   }
   assert (count == 1);
 
   }
   assert (count == 1);
 
+  /* Closing should implicitly call the close_callback function. */
   guestfs_close (g);
 
   guestfs_close (g);
 
+  assert (close_callback_called == 1);
+
   exit (EXIT_SUCCESS);
 }
   exit (EXIT_SUCCESS);
 }
index b62c098..3ed200c 100644 (file)
@@ -499,7 +499,8 @@ main (int argc, char *argv[])
     : (optind >= argc && isatty (0));
 
   if (progress_bars)
     : (optind >= argc && isatty (0));
 
   if (progress_bars)
-    guestfs_set_progress_callback (g, progress_callback, NULL);
+    guestfs_set_event_callback (g, progress_callback,
+                                GUESTFS_EVENT_PROGRESS, 0, NULL);
 
   /* Interactive, shell script, or command(s) on the command line? */
   if (optind >= argc) {
 
   /* Interactive, shell script, or command(s) on the command line? */
   if (optind >= argc) {
index da0c6a7..114e8a8 100644 (file)
@@ -146,7 +146,7 @@ extern int vg_lv_parse (const char *device, char **vg, char **lv);
 
 /* in progress.c */
 extern void reset_progress_bar (void);
 
 /* in progress.c */
 extern void reset_progress_bar (void);
-extern void progress_callback (guestfs_h *g, void *data, int proc_nr, int serial, uint64_t position, uint64_t total);
+extern void progress_callback (guestfs_h *g, void *data, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len);
 
 /* in rc.c (remote control) */
 extern void rc_listen (void) __attribute__((noreturn));
 
 /* in rc.c (remote control) */
 extern void rc_listen (void) __attribute__((noreturn));
index 27dfbec..6a89ae0 100644 (file)
@@ -167,9 +167,18 @@ estimate_remaining_time (double ratio)
 /* Callback which displays a progress bar. */
 void
 progress_callback (guestfs_h *g, void *data,
 /* Callback which displays a progress bar. */
 void
 progress_callback (guestfs_h *g, void *data,
-                   int proc_nr, int serial,
-                   uint64_t position, uint64_t total)
+                   uint64_t event, int event_handle, int flags,
+                   const char *buf, size_t buf_len,
+                   const uint64_t *array, size_t array_len)
 {
 {
+  if (array_len < 4)
+    return;
+
+  /*uint64_t proc_nr = array[0];*/
+  /*uint64_t serial = array[1];*/
+  uint64_t position = array[2];
+  uint64_t total = array[3];
+
   if (have_terminfo == 0) {
   dumb:
     printf ("%" PRIu64 "/%" PRIu64 "\n", position, total);
   if (have_terminfo == 0) {
   dumb:
     printf ("%" PRIu64 "/%" PRIu64 "\n", position, total);
index b076982..67a845c 100644 (file)
@@ -67,7 +67,8 @@ run_reopen (const char *cmd, size_t argc, char *argv[])
     guestfs_set_path (g2, p);
 
   if (progress_bars)
     guestfs_set_path (g2, p);
 
   if (progress_bars)
-    guestfs_set_progress_callback (g2, progress_callback, NULL);
+    guestfs_set_event_callback (g2, progress_callback,
+                                GUESTFS_EVENT_PROGRESS, 0, NULL);
 
   /* Close the original handle. */
   guestfs_close (g);
 
   /* Close the original handle. */
   guestfs_close (g);
index 4ea8040..d96d4f3 100644 (file)
@@ -34,10 +34,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_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_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
 generator_xdr.cmo: generator_utils.cmi generator_types.cmo \
     generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \
     generator_docstrings.cmo generator_actions.cmi
index 39688c1..112fc69 100644 (file)
@@ -28,6 +28,7 @@ SOURCES = \
        generator_optgroups.ml \
        generator_prepopts.mli \
        generator_prepopts.ml \
        generator_optgroups.ml \
        generator_prepopts.mli \
        generator_prepopts.ml \
+       generator_events.ml \
        generator_pr.mli \
        generator_pr.ml \
        generator_docstrings.ml \
        generator_pr.mli \
        generator_pr.ml \
        generator_docstrings.ml \
index ca2dfe3..e085fb6 100644 (file)
@@ -300,10 +300,14 @@ Get the autosync flag.");
    [],
    "set verbose mode",
    "\
    [],
    "set verbose mode",
    "\
-If C<verbose> is true, this turns on verbose messages (to C<stderr>).
+If C<verbose> is true, this turns on verbose messages.
 
 Verbose messages are disabled unless the environment variable
 
 Verbose messages are disabled unless the environment variable
-C<LIBGUESTFS_DEBUG> is defined and set to C<1>.");
+C<LIBGUESTFS_DEBUG> is defined and set to C<1>.
+
+Verbose messages are normally sent to C<stderr>, unless you
+register a callback to send them somewhere else (see
+C<guestfs_set_event_callback>).");
 
   ("get_verbose", (RBool "verbose", [], []), -1, [],
    [],
 
   ("get_verbose", (RBool "verbose", [], []), -1, [],
    [],
@@ -469,19 +473,19 @@ see L<guestfs(3)>.");
        ["get_trace"]])],
    "enable or disable command traces",
    "\
        ["get_trace"]])],
    "enable or disable command traces",
    "\
-If the command trace flag is set to 1, then commands are
-printed on stderr before they are executed in a format
-which is very similar to the one used by guestfish.  In
-other words, you can run a program with this enabled, and
-you will get out a script which you can feed to guestfish
-to perform the same set of actions.
+If the command trace flag is set to 1, then libguestfs
+calls, parameters and return values are traced.
 
 If you want to trace C API calls into libguestfs (and
 other libraries) then possibly a better way is to use
 the external ltrace(1) command.
 
 Command traces are disabled unless the environment variable
 
 If you want to trace C API calls into libguestfs (and
 other libraries) then possibly a better way is to use
 the external ltrace(1) command.
 
 Command traces are disabled unless the environment variable
-C<LIBGUESTFS_TRACE> is defined and set to C<1>.");
+C<LIBGUESTFS_TRACE> is defined and set to C<1>.
+
+Trace messages are normally sent to C<stderr>, unless you
+register a callback to send them somewhere else (see
+C<guestfs_set_event_callback>).");
 
   ("get_trace", (RBool "trace", [], []), -1, [],
    [],
 
   ("get_trace", (RBool "trace", [], []), -1, [],
    [],
index 656e752..aee2d77 100644 (file)
@@ -28,6 +28,7 @@ open Generator_api_versions
 open Generator_optgroups
 open Generator_actions
 open Generator_structs
 open Generator_optgroups
 open Generator_actions
 open Generator_structs
+open Generator_events
 
 (* Generate C API. *)
 
 
 (* 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. */
 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);
 #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);
@@ -584,6 +618,7 @@ and generate_client_actions () =
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <assert.h>
 
 #include \"guestfs.h\"
 #include \"guestfs-internal.h\"
 
 #include \"guestfs.h\"
 #include \"guestfs-internal.h\"
@@ -639,6 +674,42 @@ check_state (guestfs_h *g, const char *caller)
   return 0;
 }
 
   return 0;
 }
 
+/* Convenience wrapper for tracing. */
+static FILE *
+trace_open (guestfs_h *g)
+{
+  assert (g->trace_fp == NULL);
+  g->trace_buf = NULL;
+  g->trace_len = 0;
+  g->trace_fp = open_memstream (&g->trace_buf, &g->trace_len);
+  if (g->trace_fp)
+    return g->trace_fp;
+  else
+    return stderr;
+}
+
+static void
+trace_send_line (guestfs_h *g)
+{
+  char *buf;
+  size_t len;
+
+  if (g->trace_fp) {
+    fclose (g->trace_fp);
+    g->trace_fp = NULL;
+
+    /* The callback might invoke other libguestfs calls, so keep
+     * a copy of the pointer to the buffer and length.
+     */
+    buf = g->trace_buf;
+    len = g->trace_len;
+    g->trace_buf = NULL;
+    guestfs___call_callbacks_message (g, GUESTFS_EVENT_TRACE, buf, len);
+
+    free (buf);
+  }
+}
+
 ";
 
   (* Generate code to check String-like parameters are not passed in
 ";
 
   (* Generate code to check String-like parameters are not passed in
@@ -743,8 +814,9 @@ check_state (guestfs_h *g, const char *caller)
       pr "\n"
     );
 
       pr "\n"
     );
 
-    pr "    fprintf (stderr, \"%%s: %%s: %%s\",\n";
-    pr "             \"libguestfs\", \"trace\", \"%s\");\n" shortname;
+    pr "    trace_fp = trace_open (g);\n";
+
+    pr "    fprintf (trace_fp, \"%%s\", \"%s\");\n" shortname;
 
     (* Required arguments. *)
     List.iter (
 
     (* Required arguments. *)
     List.iter (
@@ -756,33 +828,33 @@ check_state (guestfs_h *g, const char *caller)
       | FileIn n
       | FileOut n ->
           (* guestfish doesn't support string escaping, so neither do we *)
       | FileIn n
       | FileOut n ->
           (* guestfish doesn't support string escaping, so neither do we *)
-          pr "    fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n
+          pr "    fprintf (trace_fp, \" \\\"%%s\\\"\", %s);\n" n
       | Key n ->
           (* don't print keys *)
       | Key n ->
           (* don't print keys *)
-          pr "    fprintf (stderr, \" \\\"***\\\"\");\n"
+          pr "    fprintf (trace_fp, \" \\\"***\\\"\");\n"
       | OptString n ->                 (* string option *)
       | OptString n ->                 (* string option *)
-          pr "    if (%s) fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n n;
-          pr "    else fprintf (stderr, \" null\");\n"
+          pr "    if (%s) fprintf (trace_fp, \" \\\"%%s\\\"\", %s);\n" n n;
+          pr "    else fprintf (trace_fp, \" null\");\n"
       | StringList n
       | DeviceList n ->                        (* string list *)
       | StringList n
       | DeviceList n ->                        (* string list *)
-          pr "    fputc (' ', stderr);\n";
-          pr "    fputc ('\"', stderr);\n";
+          pr "    fputc (' ', trace_fp);\n";
+          pr "    fputc ('\"', trace_fp);\n";
           pr "    for (i = 0; %s[i]; ++i) {\n" n;
           pr "    for (i = 0; %s[i]; ++i) {\n" n;
-          pr "      if (i > 0) fputc (' ', stderr);\n";
-          pr "      fputs (%s[i], stderr);\n" n;
+          pr "      if (i > 0) fputc (' ', trace_fp);\n";
+          pr "      fputs (%s[i], trace_fp);\n" n;
           pr "    }\n";
           pr "    }\n";
-          pr "    fputc ('\"', stderr);\n";
+          pr "    fputc ('\"', trace_fp);\n";
       | Bool n ->                      (* boolean *)
       | Bool n ->                      (* boolean *)
-          pr "    fputs (%s ? \" true\" : \" false\", stderr);\n" n
+          pr "    fputs (%s ? \" true\" : \" false\", trace_fp);\n" n
       | Int n ->                       (* int *)
       | Int n ->                       (* int *)
-          pr "    fprintf (stderr, \" %%d\", %s);\n" n
+          pr "    fprintf (trace_fp, \" %%d\", %s);\n" n
       | Int64 n ->
       | Int64 n ->
-          pr "    fprintf (stderr, \" %%\" PRIi64, %s);\n" n
+          pr "    fprintf (trace_fp, \" %%\" PRIi64, %s);\n" n
       | BufferIn n ->                   (* RHBZ#646822 *)
       | BufferIn n ->                   (* RHBZ#646822 *)
-          pr "    fputc (' ', stderr);\n";
-          pr "    guestfs___print_BufferIn (stderr, %s, %s_size);\n" n n
+          pr "    fputc (' ', trace_fp);\n";
+          pr "    guestfs___print_BufferIn (trace_fp, %s, %s_size);\n" n n
       | Pointer (t, n) ->
       | Pointer (t, n) ->
-          pr "    fprintf (stderr, \" (%s)%%p\", %s);\n" t n
+          pr "    fprintf (trace_fp, \" (%s)%%p\", %s);\n" t n
     ) args;
 
     (* Optional arguments. *)
     ) args;
 
     (* Optional arguments. *)
@@ -795,18 +867,18 @@ check_state (guestfs_h *g, const char *caller)
           uc_shortname uc_n;
         (match argt with
          | String n ->
           uc_shortname uc_n;
         (match argt with
          | String n ->
-             pr "      fprintf (stderr, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s);\n" n n
+             pr "      fprintf (trace_fp, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s);\n" n n
          | Bool n ->
          | Bool n ->
-             pr "      fprintf (stderr, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s ? \"true\" : \"false\");\n" n n
+             pr "      fprintf (trace_fp, \" \\\"%%s:%%s\\\"\", \"%s\", optargs->%s ? \"true\" : \"false\");\n" n n
          | Int n ->
          | Int n ->
-             pr "      fprintf (stderr, \" \\\"%%s:%%d\\\"\", \"%s\", optargs->%s);\n" n n
+             pr "      fprintf (trace_fp, \" \\\"%%s:%%d\\\"\", \"%s\", optargs->%s);\n" n n
          | Int64 n ->
          | Int64 n ->
-             pr "      fprintf (stderr, \" \\\"%%s:%%\" PRIi64 \"\\\"\", \"%s\", optargs->%s);\n" n n
+             pr "      fprintf (trace_fp, \" \\\"%%s:%%\" PRIi64 \"\\\"\", \"%s\", optargs->%s);\n" n n
          | _ -> assert false
         );
     ) optargs;
 
          | _ -> assert false
         );
     ) optargs;
 
-    pr "    fputc ('\\n', stderr);\n";
+    pr "    trace_send_line (g);\n";
     pr "  }\n";
     pr "\n";
   in
     pr "  }\n";
     pr "\n";
   in
@@ -825,67 +897,53 @@ check_state (guestfs_h *g, const char *caller)
       pr "\n"
     );
 
       pr "\n"
     );
 
-    pr "%s  fprintf (stderr, \"%%s: %%s: %%s = \",\n" indent;
-    pr "%s           \"libguestfs\", \"trace\", \"%s\");\n"
-      indent shortname;
+    pr "%s  trace_fp = trace_open (g);\n" indent;
+
+    pr "%s  fprintf (trace_fp, \"%%s = \", \"%s\");\n" indent shortname;
 
     (match ret with
      | RErr | RInt _ | RBool _ ->
 
     (match ret with
      | RErr | RInt _ | RBool _ ->
-         pr "%s  fprintf (stderr, \"%%d\", %s);\n" indent rv
+         pr "%s  fprintf (trace_fp, \"%%d\", %s);\n" indent rv
      | RInt64 _ ->
      | RInt64 _ ->
-         pr "%s  fprintf (stderr, \"%%\" PRIi64, %s);\n" indent rv
+         pr "%s  fprintf (trace_fp, \"%%\" PRIi64, %s);\n" indent rv
      | RConstString _ | RString _ ->
      | RConstString _ | RString _ ->
-         pr "%s  fprintf (stderr, \"\\\"%%s\\\"\", %s);\n" indent rv
+         pr "%s  fprintf (trace_fp, \"\\\"%%s\\\"\", %s);\n" indent rv
      | RConstOptString _ ->
      | RConstOptString _ ->
-         pr "%s  fprintf (stderr, \"\\\"%%s\\\"\", %s != NULL ? %s : \"NULL\");\n"
+         pr "%s  fprintf (trace_fp, \"\\\"%%s\\\"\", %s != NULL ? %s : \"NULL\");\n"
            indent rv rv
      | RBufferOut _ ->
            indent rv rv
      | RBufferOut _ ->
-         pr "%s  guestfs___print_BufferOut (stderr, %s, *size_r);\n" indent rv
+         pr "%s  guestfs___print_BufferOut (trace_fp, %s, *size_r);\n" indent rv
      | RStringList _ | RHashtable _ ->
      | RStringList _ | RHashtable _ ->
-         pr "%s  fputs (\"[\\\"\", stderr);\n" indent;
+         pr "%s  fputs (\"[\\\"\", trace_fp);\n" indent;
          pr "%s  for (i = 0; %s[i]; ++i) {\n" indent rv;
          pr "%s  for (i = 0; %s[i]; ++i) {\n" indent rv;
-         pr "%s    if (i > 0) fputs (\"\\\", \\\"\", stderr);\n" indent;
-         pr "%s    fputs (%s[i], stderr);\n" indent rv;
+         pr "%s    if (i > 0) fputs (\"\\\", \\\"\", trace_fp);\n" indent;
+         pr "%s    fputs (%s[i], trace_fp);\n" indent rv;
          pr "%s  }\n" indent;
          pr "%s  }\n" indent;
-         pr "%s  fputs (\"\\\"]\", stderr);\n" indent;
+         pr "%s  fputs (\"\\\"]\", trace_fp);\n" indent;
      | RStruct (_, typ) ->
          (* XXX There is code generated for guestfish for printing
           * these structures.  We need to make it generally available
           * for all callers
           *)
      | RStruct (_, typ) ->
          (* XXX There is code generated for guestfish for printing
           * these structures.  We need to make it generally available
           * for all callers
           *)
-         pr "%s  fprintf (stderr, \"<struct guestfs_%s *>\");\n"
+         pr "%s  fprintf (trace_fp, \"<struct guestfs_%s *>\");\n"
            indent typ (* XXX *)
      | RStructList (_, typ) ->
            indent typ (* XXX *)
      | RStructList (_, typ) ->
-         pr "%s  fprintf (stderr, \"<struct guestfs_%s_list *>\");\n"
+         pr "%s  fprintf (trace_fp, \"<struct guestfs_%s_list *>\");\n"
            indent typ (* XXX *)
     );
            indent typ (* XXX *)
     );
-    pr "%s  fputc ('\\n', stderr);\n" indent;
+    pr "%s  trace_send_line (g);\n" indent;
     pr "%s}\n" indent;
     pr "\n";
   in
 
     pr "%s}\n" indent;
     pr "\n";
   in
 
-  let trace_return_error ?(indent = 2) shortname (ret, _, _) =
+  let trace_return_error ?(indent = 2) shortname (ret, _, _) errcode =
     let indent = spaces indent in
 
     pr "%sif (trace_flag)\n" indent;
 
     let indent = spaces indent in
 
     pr "%sif (trace_flag)\n" indent;
 
-    pr "%s  fprintf (stderr, \"%%s: %%s: %%s = %%s (error)\\n\",\n" indent;
-    pr "%s           \"libguestfs\", \"trace\", \"%s\", "
-      indent shortname;
-
-    (match ret with
-     | RErr | RInt _ | RBool _
-     | RInt64 _ ->
-         pr "\"-1\""
-     | RConstString _ | RString _
-     | RConstOptString _
-     | RBufferOut _
-     | RStringList _ | RHashtable _
-     | RStruct _
-     | RStructList _ ->
-         pr "\"NULL\""
-    );
-    pr ");\n"
+    pr "%s  guestfs___trace (g, \"%%s = %%s (error)\",\n" indent;
+    pr "%s                   \"%s\", \"%s\");\n"
+      indent shortname (string_of_errcode errcode)
   in
 
   (* For non-daemon functions, generate a wrapper around each function. *)
   in
 
   (* For non-daemon functions, generate a wrapper around each function. *)
@@ -901,6 +959,7 @@ check_state (guestfs_h *g, const char *caller)
           shortname style;
       pr "{\n";
       pr "  int trace_flag = g->trace;\n";
           shortname style;
       pr "{\n";
       pr "  int trace_flag = g->trace;\n";
+      pr "  FILE *trace_fp;\n";
       (match ret with
        | RErr | RInt _ | RBool _ ->
            pr "  int r;\n"
       (match ret with
        | RErr | RInt _ | RBool _ ->
            pr "  int r;\n"
@@ -932,7 +991,7 @@ check_state (guestfs_h *g, const char *caller)
            pr "  if (r != %s) {\n" (string_of_errcode errcode);
            trace_return ~indent:4 shortname style "r";
            pr "  } else {\n";
            pr "  if (r != %s) {\n" (string_of_errcode errcode);
            trace_return ~indent:4 shortname style "r";
            pr "  } else {\n";
-           trace_return_error ~indent:4 shortname style;
+           trace_return_error ~indent:4 shortname style errcode;
            pr "  }\n";
        | `CannotReturnError ->
            trace_return shortname style "r";
            pr "  }\n";
        | `CannotReturnError ->
            trace_return shortname style "r";
@@ -985,6 +1044,7 @@ check_state (guestfs_h *g, const char *caller)
       pr "  int serial;\n";
       pr "  int r;\n";
       pr "  int trace_flag = g->trace;\n";
       pr "  int serial;\n";
       pr "  int r;\n";
       pr "  int trace_flag = g->trace;\n";
+      pr "  FILE *trace_fp;\n";
       (match ret with
        | RErr | RInt _ | RBool _ ->
            pr "  int ret_v;\n"
       (match ret with
        | RErr | RInt _ | RBool _ ->
            pr "  int ret_v;\n"
@@ -1030,7 +1090,7 @@ check_state (guestfs_h *g, const char *caller)
 
       (* Check we are in the right state for sending a request. *)
       pr "  if (check_state (g, \"%s\") == -1) {\n" shortname;
 
       (* Check we are in the right state for sending a request. *)
       pr "  if (check_state (g, \"%s\") == -1) {\n" shortname;
-      trace_return_error ~indent:4 shortname style;
+      trace_return_error ~indent:4 shortname style errcode;
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "  guestfs___set_busy (g);\n";
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "  guestfs___set_busy (g);\n";
@@ -1061,7 +1121,7 @@ check_state (guestfs_h *g, const char *caller)
           | BufferIn n ->
               pr "  /* Just catch grossly large sizes. XDR encoding will make this precise. */\n";
               pr "  if (%s_size >= GUESTFS_MESSAGE_MAX) {\n" n;
           | BufferIn n ->
               pr "  /* Just catch grossly large sizes. XDR encoding will make this precise. */\n";
               pr "  if (%s_size >= GUESTFS_MESSAGE_MAX) {\n" n;
-              trace_return_error ~indent:4 shortname style;
+              trace_return_error ~indent:4 shortname style errcode;
               pr "    error (g, \"%%s: size of input buffer too large\", \"%s\");\n"
                 shortname;
               pr "    guestfs___end_busy (g);\n";
               pr "    error (g, \"%%s: size of input buffer too large\", \"%s\");\n"
                 shortname;
               pr "    guestfs___end_busy (g);\n";
@@ -1103,7 +1163,7 @@ check_state (guestfs_h *g, const char *caller)
       );
       pr "  if (serial == -1) {\n";
       pr "    guestfs___end_busy (g);\n";
       );
       pr "  if (serial == -1) {\n";
       pr "    guestfs___end_busy (g);\n";
-      trace_return_error ~indent:4 shortname style;
+      trace_return_error ~indent:4 shortname style errcode;
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "\n";
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "\n";
@@ -1116,7 +1176,7 @@ check_state (guestfs_h *g, const char *caller)
             pr "  r = guestfs___send_file (g, %s);\n" n;
             pr "  if (r == -1) {\n";
             pr "    guestfs___end_busy (g);\n";
             pr "  r = guestfs___send_file (g, %s);\n" n;
             pr "  if (r == -1) {\n";
             pr "    guestfs___end_busy (g);\n";
-            trace_return_error ~indent:4 shortname style;
+            trace_return_error ~indent:4 shortname style errcode;
             pr "    return %s;\n" (string_of_errcode errcode);
             pr "  }\n";
             pr "  if (r == -2) /* daemon cancelled */\n";
             pr "    return %s;\n" (string_of_errcode errcode);
             pr "  }\n";
             pr "  if (r == -2) /* daemon cancelled */\n";
@@ -1141,7 +1201,7 @@ check_state (guestfs_h *g, const char *caller)
 
       pr "  if (r == -1) {\n";
       pr "    guestfs___end_busy (g);\n";
 
       pr "  if (r == -1) {\n";
       pr "    guestfs___end_busy (g);\n";
-      trace_return_error ~indent:4 shortname style;
+      trace_return_error ~indent:4 shortname style errcode;
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "\n";
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "\n";
@@ -1149,13 +1209,13 @@ check_state (guestfs_h *g, const char *caller)
       pr "  if (check_reply_header (g, &hdr, GUESTFS_PROC_%s, serial) == -1) {\n"
         (String.uppercase shortname);
       pr "    guestfs___end_busy (g);\n";
       pr "  if (check_reply_header (g, &hdr, GUESTFS_PROC_%s, serial) == -1) {\n"
         (String.uppercase shortname);
       pr "    guestfs___end_busy (g);\n";
-      trace_return_error ~indent:4 shortname style;
+      trace_return_error ~indent:4 shortname style errcode;
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "\n";
 
       pr "  if (hdr.status == GUESTFS_STATUS_ERROR) {\n";
       pr "    return %s;\n" (string_of_errcode errcode);
       pr "  }\n";
       pr "\n";
 
       pr "  if (hdr.status == GUESTFS_STATUS_ERROR) {\n";
-      trace_return_error ~indent:4 shortname style;
+      trace_return_error ~indent:4 shortname style errcode;
       pr "    int errnum = 0;\n";
       pr "    if (err.errno_string[0] != '\\0')\n";
       pr "      errnum = guestfs___string_to_errno (err.errno_string);\n";
       pr "    int errnum = 0;\n";
       pr "    if (err.errno_string[0] != '\\0')\n";
       pr "      errnum = guestfs___string_to_errno (err.errno_string);\n";
@@ -1179,7 +1239,7 @@ check_state (guestfs_h *g, const char *caller)
         | FileOut n ->
             pr "  if (guestfs___recv_file (g, %s) == -1) {\n" n;
             pr "    guestfs___end_busy (g);\n";
         | FileOut n ->
             pr "  if (guestfs___recv_file (g, %s) == -1) {\n" n;
             pr "    guestfs___end_busy (g);\n";
-            trace_return_error ~indent:4 shortname style;
+            trace_return_error ~indent:4 shortname style errcode;
             pr "    return %s;\n" (string_of_errcode errcode);
             pr "  }\n";
             pr "\n";
             pr "    return %s;\n" (string_of_errcode errcode);
             pr "  }\n";
             pr "\n";
@@ -1363,6 +1423,7 @@ and generate_linker_script () =
   let globals = [
     "guestfs_create";
     "guestfs_close";
   let globals = [
     "guestfs_create";
     "guestfs_close";
+    "guestfs_delete_event_callback";
     "guestfs_first_private";
     "guestfs_get_error_handler";
     "guestfs_get_out_of_memory_handler";
     "guestfs_first_private";
     "guestfs_get_error_handler";
     "guestfs_get_out_of_memory_handler";
@@ -1372,6 +1433,7 @@ and generate_linker_script () =
     "guestfs_next_private";
     "guestfs_set_close_callback";
     "guestfs_set_error_handler";
     "guestfs_next_private";
     "guestfs_set_close_callback";
     "guestfs_set_error_handler";
+    "guestfs_set_event_callback";
     "guestfs_set_launch_done_callback";
     "guestfs_set_log_message_callback";
     "guestfs_set_out_of_memory_handler";
     "guestfs_set_launch_done_callback";
     "guestfs_set_log_message_callback";
     "guestfs_set_out_of_memory_handler";
diff --git a/generator/generator_events.ml b/generator/generator_events.ml
new file mode 100644 (file)
index 0000000..54557c3
--- /dev/null
@@ -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
+                                           qemu / kernel / guestfsd / tools *)
+  "library";                            (* log messages from library *)
+  "trace";                              (* call trace messages *)
+]
+
+let events = mapi (fun i name -> name, 1 lsl i) events
index fd8778a..afe4798 100644 (file)
@@ -133,6 +133,7 @@ src/appliance.c
 src/bindtests.c
 src/errnostring.c
 src/errnostring_gperf.c
 src/bindtests.c
 src/errnostring.c
 src/errnostring_gperf.c
+src/events.c
 src/filearch.c
 src/guestfs.c
 src/inspect.c
 src/filearch.c
 src/guestfs.c
 src/inspect.c
index 2b9c49b..3e1201d 100644 (file)
@@ -127,6 +127,7 @@ libguestfs_la_SOURCES = \
        actions.c \
        appliance.c \
        bindtests.c \
        actions.c \
        appliance.c \
        bindtests.c \
+       events.c \
        filearch.c \
        inspect.c \
        launch.c \
        filearch.c \
        inspect.c \
        launch.c \
index 99bb21f..5683882 100644 (file)
@@ -1,5 +1,5 @@
 /* libguestfs
 /* libguestfs
- * Copyright (C) 2010 Red Hat Inc.
+ * Copyright (C) 2010-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
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -241,14 +241,14 @@ calculate_supermin_checksum (guestfs_h *g, const char *supermin_path)
   }
 
   if (pclose (pp) == -1) {
   }
 
   if (pclose (pp) == -1) {
-    perror ("pclose");
+    warning (g, "pclose: %m");
     return NULL;
   }
 
   len = strlen (checksum);
 
   if (len < 16) {               /* sanity check */
     return NULL;
   }
 
   len = strlen (checksum);
 
   if (len < 16) {               /* sanity check */
-    fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n");
+    warning (g, "febootstrap-supermin-helper -f checksum returned a short string");
     return NULL;
   }
 
     return NULL;
   }
 
diff --git a/src/events.c b/src/events.c
new file mode 100644 (file)
index 0000000..159862a
--- /dev/null
@@ -0,0 +1,323 @@
+/* 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 or tracing, then we
+   * print the message on stderr.  This essentially emulates the
+   * behaviour of the old-style handlers, while allowing callers to
+   * override print-on-stderr simply by registering a callback.
+   */
+  if (count == 0 && (g->verbose || event == GUESTFS_EVENT_TRACE)) {
+    const char *prefix = "libguestfs: ";
+    const char *trace = "trace: ";
+    const char *nl = "\n";
+
+    /* APPLIANCE =>  <buf>
+     * LIBRARY =>    libguestfs: <buf>\n
+     * TRACE =>      libguestfs: trace: <buf>\n  (RHBZ#673479)
+     */
+
+    if (event != GUESTFS_EVENT_APPLIANCE)
+      ignore_value (write (STDERR_FILENO, prefix, strlen (prefix)));
+
+    if (event == GUESTFS_EVENT_TRACE)
+      ignore_value (write (STDERR_FILENO, trace, strlen (trace)));
+
+    ignore_value (write (STDERR_FILENO, buf, buf_len));
+
+    /* Messages from the appliance already contain \n characters, others
+     * need this to be appended.
+     */
+    if (event != GUESTFS_EVENT_APPLIANCE)
+      ignore_value (write (STDERR_FILENO, nl, strlen (nl)));
+  }
+}
+
+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);
+}
index 297bed0..7223a88 100644 (file)
@@ -87,6 +87,18 @@ enum state { CONFIG, LAUNCHING, READY, BUSY, NO_HANDLE };
 /* Attach method. */
 enum attach_method { ATTACH_METHOD_APPLIANCE = 0, ATTACH_METHOD_UNIX };
 
 /* 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. */
 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_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;
 
 
   int msg_next_serial;
 
@@ -155,6 +161,11 @@ struct guestfs_h
   /* Private data area. */
   struct hash_table *pda;
   struct pda_entry *pda_next;
   /* Private data area. */
   struct hash_table *pda;
   struct pda_entry *pda_next;
+
+  /* Used by src/actions.c:trace_* functions. */
+  FILE *trace_fp;
+  char *trace_buf;
+  size_t trace_len;
 };
 
 /* Per-filesystem data stored for inspect_os. */
 };
 
 /* Per-filesystem data stored for inspect_os. */
@@ -264,6 +275,12 @@ extern char *guestfs_safe_strndup (guestfs_h *g, const char *str, size_t n);
 extern void *guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size);
 extern char *guestfs_safe_asprintf (guestfs_h *g, const char *fs, ...)
   __attribute__((format (printf,2,3)));
 extern void *guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size);
 extern char *guestfs_safe_asprintf (guestfs_h *g, const char *fs, ...)
   __attribute__((format (printf,2,3)));
+extern void guestfs___warning (guestfs_h *g, const char *fs, ...)
+  __attribute__((format (printf,2,3)));
+extern void guestfs___debug (guestfs_h *g, const char *fs, ...)
+  __attribute__((format (printf,2,3)));
+extern void guestfs___trace (guestfs_h *g, const char *fs, ...)
+  __attribute__((format (printf,2,3)));
 extern const char *guestfs___persistent_tmpdir (void);
 extern void guestfs___print_timestamped_argv (guestfs_h *g, const char *argv[]);
 extern void guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...);
 extern const char *guestfs___persistent_tmpdir (void);
 extern void guestfs___print_timestamped_argv (guestfs_h *g, const char *argv[]);
 extern void guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...);
@@ -290,9 +307,15 @@ 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___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
 
 #define error(g,...) guestfs_error_errno((g),0,__VA_ARGS__)
 #define perrorf guestfs_perrorf
+#define warning(g,...) guestfs___warning((g),__VA_ARGS__)
+#define debug(g,...) \
+  do { if ((g)->verbose) guestfs___debug ((g),__VA_ARGS__); } while (0)
 #define safe_calloc guestfs_safe_calloc
 #define safe_malloc guestfs_safe_malloc
 #define safe_realloc guestfs_safe_realloc
 #define safe_calloc guestfs_safe_calloc
 #define safe_malloc guestfs_safe_malloc
 #define safe_realloc guestfs_safe_realloc
index 97762ca..776214e 100644 (file)
@@ -132,7 +132,7 @@ guestfs_create (void)
   str = getenv ("LIBGUESTFS_MEMSIZE");
   if (str) {
     if (sscanf (str, "%d", &g->memsize) != 1 || g->memsize <= 256) {
   str = getenv ("LIBGUESTFS_MEMSIZE");
   if (str) {
     if (sscanf (str, "%d", &g->memsize) != 1 || g->memsize <= 256) {
-      fprintf (stderr, "libguestfs: non-numeric or too small value for LIBGUESTFS_MEMSIZE\n");
+      warning (g, "non-numeric or too small value for LIBGUESTFS_MEMSIZE");
       goto error;
     }
   } else
       goto error;
     }
   } else
@@ -153,8 +153,7 @@ guestfs_create (void)
   }
   gl_lock_unlock (handles_lock);
 
   }
   gl_lock_unlock (handles_lock);
 
-  if (g->verbose)
-    fprintf (stderr, "new guestfs handle %p\n", g);
+  debug (g, "new guestfs handle %p", g);
 
   return g;
 
 
   return g;
 
@@ -174,32 +173,33 @@ guestfs_close (guestfs_h *g)
   guestfs_h *gg;
 
   if (g->state == NO_HANDLE) {
   guestfs_h *gg;
 
   if (g->state == NO_HANDLE) {
-    /* Not safe to call 'error' here, so ... */
+    /* Not safe to call ANY callbacks here, so ... */
     fprintf (stderr, _("guestfs_close: called twice on the same handle\n"));
     return;
   }
 
     fprintf (stderr, _("guestfs_close: called twice on the same handle\n"));
     return;
   }
 
-  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);
+  debug (g, "closing guestfs handle %p (state %d)", g, g->state);
 
   /* Try to sync if autosync flag is set. */
   if (g->autosync && g->state == READY)
     guestfs_internal_autosync (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.
-   */
-  g->log_message_cb = NULL;
-
+  /* Kill the qemu subprocess. */
   if (g->state != CONFIG)
     guestfs_kill_subprocess (g);
 
   if (g->state != CONFIG)
     guestfs_kill_subprocess (g);
 
+  /* Run user close callbacks. */
+  guestfs___call_callbacks_void (g, GUESTFS_EVENT_CLOSE);
+
+  /* Remove all other registered callbacks.  Since we've already
+   * called the close callbacks, we shouldn't call any others.
+   */
+  free (g->events);
+  g->nr_events = 0;
+  g->events = NULL;
+
+  guestfs___free_inspect_info (g);
+
   /* Close sockets. */
   if (g->fd[0] >= 0)
     close (g->fd[0]);
   /* Close sockets. */
   if (g->fd[0] >= 0)
     close (g->fd[0]);
@@ -282,6 +282,79 @@ set_last_error (guestfs_h *g, int errnum, const char *msg)
   g->last_errnum = errnum;
 }
 
   g->last_errnum = errnum;
 }
 
+/* Warning are printed unconditionally.  We try to make these rare.
+ * Generally speaking, a warning should either be an error, or if it's
+ * not important for end users then it should be a debug message.
+ */
+void
+guestfs___warning (guestfs_h *g, const char *fs, ...)
+{
+  va_list args;
+  char *msg, *msg2;
+  int len;
+
+  va_start (args, fs);
+  len = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (len < 0) return;
+
+  len = asprintf (&msg2, _("warning: %s"), msg);
+  free (msg);
+
+  if (len < 0) return;
+
+  guestfs___call_callbacks_message (g, GUESTFS_EVENT_LIBRARY, msg2, len);
+
+  free (msg2);
+}
+
+/* Debug messages. */
+void
+guestfs___debug (guestfs_h *g, const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  int len;
+
+  /* The cpp macro "debug" has already checked that g->verbose is true
+   * before calling this function, but we check it again just in case
+   * anyone calls this function directly.
+   */
+  if (!g->verbose)
+    return;
+
+  va_start (args, fs);
+  len = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (len < 0) return;
+
+  guestfs___call_callbacks_message (g, GUESTFS_EVENT_LIBRARY, msg, len);
+}
+
+/* Call trace messages.  These are enabled by setting g->trace, and
+ * calls to this function should only happen from the generated code
+ * in src/actions.c
+ */
+void
+guestfs___trace (guestfs_h *g, const char *fs, ...)
+{
+  va_list args;
+  char *msg;
+  int len;
+
+  va_start (args, fs);
+  len = vasprintf (&msg, fs, args);
+  va_end (args);
+
+  if (len < 0) return;
+
+  guestfs___call_callbacks_message (g, GUESTFS_EVENT_TRACE, msg, len);
+
+  free (msg);
+}
+
 static void
 default_error_cb (guestfs_h *g, void *data, const char *msg)
 {
 static void
 default_error_cb (guestfs_h *g, void *data, const char *msg)
 {
@@ -690,46 +763,6 @@ guestfs__get_attach_method (guestfs_h *g)
   return ret;
 }
 
   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.
 /* 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.
index b0a408d..5984d2c 100644 (file)
@@ -1683,82 +1683,71 @@ For guestfish, see L<guestfish(1)/OPTIONAL ARGUMENTS>.
 
 =head2 SETTING CALLBACKS TO HANDLE EVENTS
 
 
 =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 generic event mechanism introduced
+in libguestfs 1.10, 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
 
 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).
 
 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
 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,168 @@ requiring special code to detect this case.
 
 =back
 
 
 =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 sent to stderr.  You
+can override the printing of trace 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.
+
+=head3 EXAMPLE: CAPTURING LOG MESSAGES
+
+One motivation for the generic event API was to allow GUI programs to
+capture debug and other messages.  In libguestfs E<le> 1.8 these were
+sent unconditionally to C<stderr>.
+
+Events associated with log messages are: C<GUESTFS_EVENT_LIBRARY>,
+C<GUESTFS_EVENT_APPLIANCE> and C<GUESTFS_EVENT_TRACE>.  (Note that
+error messages are not events; you must capture error messages
+separately).
+
+Programs have to set up a callback to capture the classes of events of
+interest:
+
+ int eh =
+   guestfs_set_event_callback
+     (g, message_callback,
+      GUESTFS_EVENT_LIBRARY|GUESTFS_EVENT_APPLIANCE|
+      GUESTFS_EVENT_TRACE,
+      0, NULL) == -1)
+ if (eh == -1) {
+   // handle error in the usual way
+ }
+
+The callback can then direct messages to the appropriate place.  In
+this example, messages are directed to syslog:
+
+ static void
+ message_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)
+ {
+   const int priority = LOG_USER|LOG_INFO;
+   if (buf_len > 0)
+     syslog (priority, "event 0x%lx: %s", event, buf);
+ }
 
 =head1 PRIVATE DATA AREA
 
 
 =head1 PRIVATE DATA AREA
 
@@ -1834,8 +1983,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
 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>).
 
 To walk over all entries, use these two functions:
 
 
 To walk over all entries, use these two functions:
 
@@ -2193,8 +2341,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_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
 
 The daemon self-limits the frequency of progress messages it sends
 (see C<daemon/proto.c:notify_progress>).  Not all calls generate
index 99097ee..7cf18c3 100644 (file)
@@ -258,10 +258,9 @@ check_for_filesystem_on (guestfs_h *g, const char *device,
 
   int is_swap = vfs_type && STREQ (vfs_type, "swap");
 
 
   int is_swap = vfs_type && STREQ (vfs_type, "swap");
 
-  if (g->verbose)
-    fprintf (stderr, "check_for_filesystem_on: %s %d %d (%s)\n",
-             device, is_block, is_partnum,
-             vfs_type ? vfs_type : "failed to get vfs type");
+  debug (g, "check_for_filesystem_on: %s %d %d (%s)",
+         device, is_block, is_partnum,
+         vfs_type ? vfs_type : "failed to get vfs type");
 
   if (is_swap) {
     free (vfs_type);
 
   if (is_swap) {
     free (vfs_type);
@@ -1307,8 +1306,7 @@ add_fstab_entry (guestfs_h *g, struct inspect_fs *fs,
   fs->fstab[n-1].device = device;
   fs->fstab[n-1].mountpoint = mountpoint;
 
   fs->fstab[n-1].device = device;
   fs->fstab[n-1].mountpoint = mountpoint;
 
-  if (g->verbose)
-    fprintf (stderr, "fstab: device=%s mountpoint=%s\n", device, mountpoint);
+  debug (g, "fstab: device=%s mountpoint=%s", device, mountpoint);
 
   return 0;
 }
 
   return 0;
 }
@@ -1414,8 +1412,7 @@ check_windows_root (guestfs_h *g, struct inspect_fs *fs)
     return -1;
   }
 
     return -1;
   }
 
-  if (g->verbose)
-    fprintf (stderr, "windows %%SYSTEMROOT%% = %s", systemroot);
+  debug (g, "windows %%SYSTEMROOT%% = %s", systemroot);
 
   /* Freed by guestfs___free_inspect_info. */
   fs->windows_systemroot = systemroot;
 
   /* Freed by guestfs___free_inspect_info. */
   fs->windows_systemroot = systemroot;
@@ -2219,8 +2216,7 @@ list_applications_rpm (guestfs_h *g, struct inspect_fs *fs)
 
   snprintf (cmd, cmd_len, DB_DUMP " -p '%s'", tmpfile);
 
 
   snprintf (cmd, cmd_len, DB_DUMP " -p '%s'", tmpfile);
 
-  if (g->verbose)
-    fprintf (stderr, "list_applications_rpm: %s\n", cmd);
+  debug (g, "list_applications_rpm: %s", cmd);
 
   pp = popen (cmd, "r");
   if (pp == NULL) {
 
   pp = popen (cmd, "r");
   if (pp == NULL) {
@@ -2940,7 +2936,7 @@ guestfs___match (guestfs_h *g, const char *str, const pcre *re)
     return 0;
   if (r != 1) {
     /* Internal error -- should not happen. */
     return 0;
   if (r != 1) {
     /* Internal error -- should not happen. */
-    fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
+    warning (g, "%s: %s: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
              __FILE__, __func__, r, str);
     return 0;
   }
              __FILE__, __func__, r, str);
     return 0;
   }
@@ -2963,7 +2959,7 @@ guestfs___match1 (guestfs_h *g, const char *str, const pcre *re)
     return NULL;
   if (r != 2) {
     /* Internal error -- should not happen. */
     return NULL;
   if (r != 2) {
     /* Internal error -- should not happen. */
-    fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
+    warning (g, "%s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"",
              __FILE__, __func__, r, str);
     return NULL;
   }
              __FILE__, __func__, r, str);
     return NULL;
   }
@@ -2984,7 +2980,7 @@ guestfs___match2 (guestfs_h *g, const char *str, const pcre *re,
     return 0;
   if (r != 3) {
     /* Internal error -- should not happen. */
     return 0;
   if (r != 3) {
     /* Internal error -- should not happen. */
-    fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
+    warning (g, "%s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"",
              __FILE__, __func__, r, str);
     return 0;
   }
              __FILE__, __func__, r, str);
     return 0;
   }
@@ -3008,7 +3004,7 @@ guestfs___match3 (guestfs_h *g, const char *str, const pcre *re,
     return 0;
   if (r != 4) {
     /* Internal error -- should not happen. */
     return 0;
   if (r != 4) {
     /* Internal error -- should not happen. */
-    fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n",
+    warning (g, "%s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"",
              __FILE__, __func__, r, str);
     return 0;
   }
              __FILE__, __func__, r, str);
     return 0;
   }
index 8de2857..261136d 100644 (file)
@@ -373,7 +373,7 @@ guestfs__launch (guestfs_h *g)
    * want. (RHBZ#610880).
    */
   if (chmod (g->tmpdir, 0755) == -1)
    * want. (RHBZ#610880).
    */
   if (chmod (g->tmpdir, 0755) == -1)
-    fprintf (stderr, "chmod: %s: %m (ignored)\n", g->tmpdir);
+    warning (g, "chmod: %s: %m (ignored)", g->tmpdir);
 
   /* Launch the appliance or attach to an existing daemon. */
   switch (g->attach_method) {
 
   /* Launch the appliance or attach to an existing daemon. */
   switch (g->attach_method) {
@@ -918,27 +918,40 @@ guestfs___print_timestamped_argv (guestfs_h *g, const char * argv[])
 {
   int i = 0;
   int needs_quote;
 {
   int i = 0;
   int needs_quote;
+  char *buf = NULL;
+  size_t len;
+  FILE *fp;
+
+  fp = open_memstream (&buf, &len);
+  if (fp == NULL) {
+    warning (g, "open_memstream: %m");
+    return;
+  }
 
   struct timeval tv;
   gettimeofday (&tv, NULL);
 
   struct timeval tv;
   gettimeofday (&tv, NULL);
-  fprintf (stderr, "[%05" PRIi64 "ms] ", timeval_diff (&g->launch_t, &tv));
+  fprintf (fp, "[%05" PRIi64 "ms] ", timeval_diff (&g->launch_t, &tv));
 
   while (argv[i]) {
     if (argv[i][0] == '-') /* -option starts a new line */
 
   while (argv[i]) {
     if (argv[i][0] == '-') /* -option starts a new line */
-      fprintf (stderr, " \\\n   ");
+      fprintf (fp, " \\\n   ");
 
 
-    if (i > 0) fputc (' ', stderr);
+    if (i > 0) fputc (' ', fp);
 
     /* Does it need shell quoting?  This only deals with simple cases. */
     needs_quote = strcspn (argv[i], " ") != strlen (argv[i]);
 
 
     /* Does it need shell quoting?  This only deals with simple cases. */
     needs_quote = strcspn (argv[i], " ") != strlen (argv[i]);
 
-    if (needs_quote) fputc ('\'', stderr);
-    fprintf (stderr, "%s", argv[i]);
-    if (needs_quote) fputc ('\'', stderr);
+    if (needs_quote) fputc ('\'', fp);
+    fprintf (fp, "%s", argv[i]);
+    if (needs_quote) fputc ('\'', fp);
     i++;
   }
 
     i++;
   }
 
-  fputc ('\n', stderr);
+  fclose (fp);
+
+  debug (g, "%s", buf);
+
+  free (buf);
 }
 
 void
 }
 
 void
@@ -957,8 +970,7 @@ guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...)
 
   gettimeofday (&tv, NULL);
 
 
   gettimeofday (&tv, NULL);
 
-  fprintf (stderr, "[%05" PRIi64 "ms] %s\n",
-           timeval_diff (&g->launch_t, &tv), msg);
+  debug (g, "[%05" PRIi64 "ms] %s", timeval_diff (&g->launch_t, &tv), msg);
 
   free (msg);
 }
 
   free (msg);
 }
@@ -1064,8 +1076,7 @@ is_openable (guestfs_h *g, const char *path, int flags)
 {
   int fd = open (path, flags);
   if (fd == -1) {
 {
   int fd = open (path, flags);
   if (fd == -1) {
-    if (g->verbose)
-      perror (path);
+    debug (g, "is_openable: %s: %m", path);
     return 0;
   }
   close (fd);
     return 0;
   }
   close (fd);
@@ -1094,8 +1105,7 @@ guestfs__kill_subprocess (guestfs_h *g)
     return -1;
   }
 
     return -1;
   }
 
-  if (g->verbose)
-    fprintf (stderr, "sending SIGTERM to process %d\n", g->pid);
+  debug (g, "sending SIGTERM to process %d", g->pid);
 
   if (g->pid > 0) kill (g->pid, SIGTERM);
   if (g->recoverypid > 0) kill (g->recoverypid, 9);
 
   if (g->pid > 0) kill (g->pid, SIGTERM);
   if (g->recoverypid > 0) kill (g->recoverypid, 9);
index 549734b..fb582cf 100644 (file)
@@ -176,8 +176,7 @@ guestfs___end_busy (guestfs_h *g)
 static void
 child_cleanup (guestfs_h *g)
 {
 static void
 child_cleanup (guestfs_h *g)
 {
-  if (g->verbose)
-    fprintf (stderr, "child_cleanup: %p: child process died\n", g);
+  debug (g, "child_cleanup: %p: child process died", g);
 
   /*if (g->pid > 0) kill (g->pid, SIGTERM);*/
   if (g->recoverypid > 0) kill (g->recoverypid, 9);
 
   /*if (g->pid > 0) kill (g->pid, SIGTERM);*/
   if (g->recoverypid > 0) kill (g->recoverypid, 9);
@@ -193,8 +192,7 @@ child_cleanup (guestfs_h *g)
   g->recoverypid = 0;
   memset (&g->launch_t, 0, sizeof g->launch_t);
   g->state = CONFIG;
   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
 }
 
 static int
@@ -204,10 +202,8 @@ read_log_message_or_eof (guestfs_h *g, int fd, int error_if_eof)
   int n;
 
 #if 0
   int n;
 
 #if 0
-  if (g->verbose)
-    fprintf (stderr,
-             "read_log_message_or_eof: %p g->state = %d, fd = %d\n",
-             g, g->state, fd);
+  debug (g, "read_log_message_or_eof: %p g->state = %d, fd = %d",
+         g, g->state, fd);
 #endif
 
   /* QEMU's console emulates a 16550A serial port.  The real 16550A
 #endif
 
   /* QEMU's console emulates a 16550A serial port.  The real 16550A
@@ -237,13 +233,8 @@ read_log_message_or_eof (guestfs_h *g, int fd, int error_if_eof)
     return -1;
   }
 
     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. */
   /* 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;
 }
 
   return 0;
 }
@@ -293,6 +284,20 @@ really_read_from_socket (guestfs_h *g, int sock, char *buf, size_t n)
   return (ssize_t) got;
 }
 
   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)
 {
 static int
 check_for_daemon_cancellation_or_eof (guestfs_h *g, int fd)
 {
@@ -301,10 +306,8 @@ check_for_daemon_cancellation_or_eof (guestfs_h *g, int fd)
   uint32_t flag;
   XDR xdr;
 
   uint32_t flag;
   XDR xdr;
 
-  if (g->verbose)
-    fprintf (stderr,
-             "check_for_daemon_cancellation_or_eof: %p g->state = %d, fd = %d\n",
-             g, g->state, fd);
+  debug (g, "check_for_daemon_cancellation_or_eof: %p g->state = %d, fd = %d",
+         g, g->state, fd);
 
   n = really_read_from_socket (g, fd, buf, 4);
   if (n == -1)
 
   n = really_read_from_socket (g, fd, buf, 4);
   if (n == -1)
@@ -331,16 +334,14 @@ check_for_daemon_cancellation_or_eof (guestfs_h *g, int fd)
       return -1;
     }
 
       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);
 
       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;
     }
 
     return 0;
@@ -374,9 +375,7 @@ guestfs___send_to_daemon (guestfs_h *g, const void *v_buf, size_t n)
   fd_set rset, rset2;
   fd_set wset, wset2;
 
   fd_set rset, rset2;
   fd_set wset, wset2;
 
-  if (g->verbose)
-    fprintf (stderr,
-             "send_to_daemon: %p g->state = %d, n = %zu\n", g, g->state, n);
+  debug (g, "send_to_daemon: %p g->state = %d, n = %zu", g, g->state, n);
 
   FD_ZERO (&rset);
   FD_ZERO (&wset);
 
   FD_ZERO (&rset);
   FD_ZERO (&wset);
@@ -454,10 +453,8 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn)
 {
   fd_set rset, rset2;
 
 {
   fd_set rset, rset2;
 
-  if (g->verbose)
-    fprintf (stderr,
-             "recv_from_daemon: %p g->state = %d, size_rtn = %p, buf_rtn = %p\n",
-             g, g->state, size_rtn, buf_rtn);
+  debug (g, "recv_from_daemon: %p g->state = %d, size_rtn = %p, buf_rtn = %p",
+         g, g->state, size_rtn, buf_rtn);
 
   FD_ZERO (&rset);
 
 
   FD_ZERO (&rset);
 
@@ -543,8 +540,7 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn)
                    g->state);
           else {
             g->state = READY;
                    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;
         }
           }
           return 0;
         }
@@ -614,16 +610,14 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn)
 #endif
 
   if (*size_rtn == GUESTFS_PROGRESS_FLAG) {
 #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);
 
       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);
     }
 
     free (*buf_rtn);
@@ -646,9 +640,7 @@ guestfs___accept_from_daemon (guestfs_h *g)
 {
   fd_set rset, rset2;
 
 {
   fd_set rset, rset2;
 
-  if (g->verbose)
-    fprintf (stderr,
-             "accept_from_daemon: %p g->state = %d\n", g, g->state);
+  debug (g, "accept_from_daemon: %p g->state = %d", g, g->state);
 
   FD_ZERO (&rset);
 
 
   FD_ZERO (&rset);
 
@@ -908,8 +900,7 @@ send_file_chunk (guestfs_h *g, int cancel, const char *buf, size_t buflen)
 
   /* Did the daemon send a cancellation message? */
   if (r == -2) {
 
   /* Did the daemon send a cancellation message? */
   if (r == -2) {
-    if (g->verbose)
-      fprintf (stderr, "got daemon cancellation\n");
+    debug (g, "got daemon cancellation");
     return -2;
   }
 
     return -2;
   }
 
@@ -1030,9 +1021,8 @@ guestfs___recv_file (guestfs_h *g, const char *filename)
   char fbuf[4];
   uint32_t flag = GUESTFS_CANCEL_FLAG;
 
   char fbuf[4];
   uint32_t flag = GUESTFS_CANCEL_FLAG;
 
-  if (g->verbose)
-    fprintf (stderr, "%s: waiting for daemon to acknowledge cancellation\n",
-             __func__);
+  debug (g, "%s: waiting for daemon to acknowledge cancellation",
+         __func__);
 
   xdrmem_create (&xdr, fbuf, sizeof fbuf, XDR_ENCODE);
   xdr_uint32_t (&xdr, &flag);
 
   xdrmem_create (&xdr, fbuf, sizeof fbuf, XDR_ENCODE);
   xdr_uint32_t (&xdr, &flag);