From 4e0cf4dbf8a8a96288f70114fdc3939da0aa7ad1 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" 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 [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. --- .gitignore | 2 + capitests/Makefile.am | 13 +- capitests/test-debug-to-file.c | 89 ++++++++++++ capitests/test-private-data.c | 35 +++++ fish/fish.c | 3 +- fish/fish.h | 2 +- fish/progress.c | 13 +- fish/reopen.c | 3 +- generator/.depend | 6 +- generator/Makefile.am | 1 + generator/generator_actions.ml | 22 +-- generator/generator_c.ml | 190 ++++++++++++++++-------- generator/generator_events.ml | 40 +++++ po/POTFILES.in | 1 + src/Makefile.am | 1 + src/appliance.c | 6 +- src/events.c | 323 +++++++++++++++++++++++++++++++++++++++++ src/guestfs-internal.h | 43 ++++-- src/guestfs.c | 147 +++++++++++-------- src/guestfs.pod | 286 +++++++++++++++++++++++++++--------- src/inspect.c | 24 ++- src/launch.c | 38 +++-- src/proto.c | 76 +++++----- 23 files changed, 1072 insertions(+), 292 deletions(-) create mode 100644 capitests/test-debug-to-file.c create mode 100644 generator/generator_events.ml create mode 100644 src/events.c diff --git a/.gitignore b/.gitignore index 7df10b2..d3b3f3e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,11 +10,13 @@ appliance/supermin.d 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-debug-to-file capitests/test-just-header capitests/test-last-errno capitests/test-private-data diff --git a/capitests/Makefile.am b/capitests/Makefile.am index 83e62c8..89d29e0 100644 --- a/capitests/Makefile.am +++ b/capitests/Makefile.am @@ -31,7 +31,8 @@ check_PROGRAMS = \ test-config \ test-add-drive-opts \ test-last-errno \ - test-private-data + test-private-data \ + test-debug-to-file TESTS = \ tests \ @@ -40,7 +41,8 @@ TESTS = \ 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 @@ -112,6 +114,13 @@ test_private_data_CFLAGS = \ 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 = \ diff --git a/capitests/test-debug-to-file.c b/capitests/test-debug-to-file.c new file mode 100644 index 0000000..6d1b619 --- /dev/null +++ b/capitests/test-debug-to-file.c @@ -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 + +#include +#include +#include +#include + +#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); +} diff --git a/capitests/test-private-data.c b/capitests/test-private-data.c index fad88b5..f2ff647 100644 --- a/capitests/test-private-data.c +++ b/capitests/test-private-data.c @@ -30,6 +30,34 @@ #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[]) { @@ -44,6 +72,10 @@ main (int argc, char *argv[]) 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); @@ -79,7 +111,10 @@ main (int argc, char *argv[]) } assert (count == 1); + /* Closing should implicitly call the close_callback function. */ guestfs_close (g); + assert (close_callback_called == 1); + exit (EXIT_SUCCESS); } diff --git a/fish/fish.c b/fish/fish.c index b62c098..3ed200c 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -499,7 +499,8 @@ main (int argc, char *argv[]) : (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) { diff --git a/fish/fish.h b/fish/fish.h index da0c6a7..114e8a8 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -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); -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)); diff --git a/fish/progress.c b/fish/progress.c index 27dfbec..6a89ae0 100644 --- a/fish/progress.c +++ b/fish/progress.c @@ -167,9 +167,18 @@ estimate_remaining_time (double ratio) /* 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); diff --git a/fish/reopen.c b/fish/reopen.c index b076982..67a845c 100644 --- a/fish/reopen.c +++ b/fish/reopen.c @@ -67,7 +67,8 @@ run_reopen (const char *cmd, size_t argc, char *argv[]) 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); diff --git a/generator/.depend b/generator/.depend index 4ea8040..d96d4f3 100644 --- a/generator/.depend +++ b/generator/.depend @@ -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_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_actions.ml b/generator/generator_actions.ml index ca2dfe3..e085fb6 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -300,10 +300,14 @@ Get the autosync flag."); [], "set verbose mode", "\ -If C is true, this turns on verbose messages (to C). +If C is true, this turns on verbose messages. Verbose messages are disabled unless the environment variable -C is defined and set to C<1>."); +C is defined and set to C<1>. + +Verbose messages are normally sent to C, unless you +register a callback to send them somewhere else (see +C)."); ("get_verbose", (RBool "verbose", [], []), -1, [], [], @@ -469,19 +473,19 @@ see L."); ["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 -C is defined and set to C<1>."); +C is defined and set to C<1>. + +Trace messages are normally sent to C, unless you +register a callback to send them somewhere else (see +C)."); ("get_trace", (RBool "trace", [], []), -1, [], [], diff --git a/generator/generator_c.ml b/generator/generator_c.ml index 656e752..aee2d77 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); @@ -584,6 +618,7 @@ and generate_client_actions () = #include #include #include +#include #include \"guestfs.h\" #include \"guestfs-internal.h\" @@ -639,6 +674,42 @@ check_state (guestfs_h *g, const char *caller) 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 @@ -743,8 +814,9 @@ check_state (guestfs_h *g, const char *caller) 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 ( @@ -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 *) - pr " fprintf (stderr, \" \\\"%%s\\\"\", %s);\n" n + pr " fprintf (trace_fp, \" \\\"%%s\\\"\", %s);\n" n | Key n -> (* don't print keys *) - pr " fprintf (stderr, \" \\\"***\\\"\");\n" + pr " fprintf (trace_fp, \" \\\"***\\\"\");\n" | 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 *) - 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 " 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 " fputc ('\"', stderr);\n"; + pr " fputc ('\"', trace_fp);\n"; | Bool n -> (* boolean *) - pr " fputs (%s ? \" true\" : \" false\", stderr);\n" n + pr " fputs (%s ? \" true\" : \" false\", trace_fp);\n" n | Int n -> (* int *) - pr " fprintf (stderr, \" %%d\", %s);\n" n + pr " fprintf (trace_fp, \" %%d\", %s);\n" n | Int64 n -> - pr " fprintf (stderr, \" %%\" PRIi64, %s);\n" n + pr " fprintf (trace_fp, \" %%\" PRIi64, %s);\n" n | 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) -> - pr " fprintf (stderr, \" (%s)%%p\", %s);\n" t n + pr " fprintf (trace_fp, \" (%s)%%p\", %s);\n" t n ) args; (* Optional arguments. *) @@ -795,18 +867,18 @@ check_state (guestfs_h *g, const char *caller) 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 -> - 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 -> - 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 -> - 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; - pr " fputc ('\\n', stderr);\n"; + pr " trace_send_line (g);\n"; pr " }\n"; pr "\n"; in @@ -825,67 +897,53 @@ check_state (guestfs_h *g, const char *caller) 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 _ -> - pr "%s fprintf (stderr, \"%%d\", %s);\n" indent rv + pr "%s fprintf (trace_fp, \"%%d\", %s);\n" indent rv | RInt64 _ -> - pr "%s fprintf (stderr, \"%%\" PRIi64, %s);\n" indent rv + pr "%s fprintf (trace_fp, \"%%\" PRIi64, %s);\n" indent rv | RConstString _ | RString _ -> - pr "%s fprintf (stderr, \"\\\"%%s\\\"\", %s);\n" indent rv + pr "%s fprintf (trace_fp, \"\\\"%%s\\\"\", %s);\n" indent rv | 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 _ -> - 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 _ -> - 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 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 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 *) - pr "%s fprintf (stderr, \"\");\n" + pr "%s fprintf (trace_fp, \"\");\n" indent typ (* XXX *) | RStructList (_, typ) -> - pr "%s fprintf (stderr, \"\");\n" + pr "%s fprintf (trace_fp, \"\");\n" 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 - 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; - 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. *) @@ -901,6 +959,7 @@ check_state (guestfs_h *g, const char *caller) 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" @@ -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"; - trace_return_error ~indent:4 shortname style; + trace_return_error ~indent:4 shortname style errcode; 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 " FILE *trace_fp;\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; - 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"; @@ -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; - 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"; @@ -1103,7 +1163,7 @@ check_state (guestfs_h *g, const char *caller) ); 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"; @@ -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"; - 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"; @@ -1141,7 +1201,7 @@ check_state (guestfs_h *g, const char *caller) 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"; @@ -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"; - 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"; - 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"; @@ -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"; - 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"; @@ -1363,6 +1423,7 @@ and generate_linker_script () = let globals = [ "guestfs_create"; "guestfs_close"; + "guestfs_delete_event_callback"; "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_set_event_callback"; "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 index 0000000..54557c3 --- /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 + qemu / kernel / guestfsd / 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/appliance.c b/src/appliance.c index 99bb21f..5683882 100644 --- a/src/appliance.c +++ b/src/appliance.c @@ -1,5 +1,5 @@ /* 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 @@ -241,14 +241,14 @@ calculate_supermin_checksum (guestfs_h *g, const char *supermin_path) } if (pclose (pp) == -1) { - perror ("pclose"); + warning (g, "pclose: %m"); 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; } diff --git a/src/events.c b/src/events.c new file mode 100644 index 0000000..159862a --- /dev/null +++ b/src/events.c @@ -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 + +#define _BSD_SOURCE /* for mkdtemp, usleep */ + +#include +#include +#include +#include +#include +#include + +#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 => + * LIBRARY => libguestfs: \n + * TRACE => libguestfs: trace: \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); +} diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 297bed0..7223a88 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; @@ -155,6 +161,11 @@ struct guestfs_h /* 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. */ @@ -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___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, ...); @@ -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___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 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 diff --git a/src/guestfs.c b/src/guestfs.c index 97762ca..776214e 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -132,7 +132,7 @@ guestfs_create (void) 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 @@ -153,8 +153,7 @@ guestfs_create (void) } 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; @@ -174,32 +173,33 @@ guestfs_close (guestfs_h *g) 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; } - 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); - /* 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); + /* 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]); @@ -282,6 +282,79 @@ set_last_error (guestfs_h *g, int errnum, const char *msg) 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) { @@ -690,46 +763,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 b0a408d..5984d2c 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -1683,82 +1683,71 @@ For guestfish, see L. =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 functions to set a callback for -different types of events. - -Only I can be registered for each handle. -Calling C again overwrites the previous -callback of that type. Cancel all callbacks of this type by calling -this function with C set to C. - -=head2 guestfs_set_log_message_callback +B 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, +C, +C, C and +C 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 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 will be called when the child process -quits, either asynchronously or if killed by -L. (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). -=head2 guestfs_set_launch_done_callback +Note that libguestfs installs an L 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, 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 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. +(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 will be called while the handle -is being closed (synchronously from L). +=item GUESTFS_EVENT_LAUNCH_DONE +(payload type: void) -Note that libguestfs installs an L 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, 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 and C. +The callback receives in the payload four unsigned 64 bit numbers +which are (in order): C, C, C, C. + The units of C are not defined, although for some operations C 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 -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) and +serial number (C) 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) is set before launch +(L) 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) 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) 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) for all event classes +in the C. + +For example, to register for all log message events, you could call +this function with the bitmask +C. To register a +single callback for all possible classes of events, use +C. + +C should always be passed as 0. + +C 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 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 +should be the integer that was returned by a previous call to +C 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), the opaque user pointer +(C), the event class (eg. C), the +event handle, and C 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 and C contain a message buffer (if C, +then there is no message buffer). Note that this message buffer can +contain arbitrary 8 bit data, including NUL bytes. + +C and C 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 1.8 these were +sent unconditionally to C. + +Events associated with log messages are: C, +C and C. (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 @@ -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 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 or must -set up a close callback to do it (see L, -and note that only one callback can be registered for a handle). +set up a close callback to do it (see L). To walk over all entries, use these two functions: @@ -2193,8 +2341,8 @@ are distinguished by the normal length word being replaced by C, followed by a fixed size progress message. The library turns them into progress callbacks (see -C) if there is a callback registered, -or discards them if not. +L) if there is a callback registered, or +discards them if not. The daemon self-limits the frequency of progress messages it sends (see C). Not all calls generate diff --git a/src/inspect.c b/src/inspect.c index 99097ee..7cf18c3 100644 --- a/src/inspect.c +++ b/src/inspect.c @@ -258,10 +258,9 @@ check_for_filesystem_on (guestfs_h *g, const char *device, 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); @@ -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; - if (g->verbose) - fprintf (stderr, "fstab: device=%s mountpoint=%s\n", device, mountpoint); + debug (g, "fstab: device=%s mountpoint=%s", device, mountpoint); return 0; } @@ -1414,8 +1412,7 @@ check_windows_root (guestfs_h *g, struct inspect_fs *fs) 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; @@ -2219,8 +2216,7 @@ list_applications_rpm (guestfs_h *g, struct inspect_fs *fs) 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) { @@ -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. */ - 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; } @@ -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. */ - 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; } @@ -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. */ - 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; } @@ -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. */ - 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; } diff --git a/src/launch.c b/src/launch.c index 8de2857..261136d 100644 --- a/src/launch.c +++ b/src/launch.c @@ -373,7 +373,7 @@ guestfs__launch (guestfs_h *g) * 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) { @@ -918,27 +918,40 @@ guestfs___print_timestamped_argv (guestfs_h *g, const char * argv[]) { 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); - 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 */ - 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]); - 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++; } - fputc ('\n', stderr); + fclose (fp); + + debug (g, "%s", buf); + + free (buf); } void @@ -957,8 +970,7 @@ guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...) 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); } @@ -1064,8 +1076,7 @@ is_openable (guestfs_h *g, const char *path, int flags) { int fd = open (path, flags); if (fd == -1) { - if (g->verbose) - perror (path); + debug (g, "is_openable: %s: %m", path); return 0; } close (fd); @@ -1094,8 +1105,7 @@ guestfs__kill_subprocess (guestfs_h *g) 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); diff --git a/src/proto.c b/src/proto.c index 549734b..fb582cf 100644 --- a/src/proto.c +++ b/src/proto.c @@ -176,8 +176,7 @@ guestfs___end_busy (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); @@ -193,8 +192,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 @@ -204,10 +202,8 @@ read_log_message_or_eof (guestfs_h *g, int fd, int error_if_eof) 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 @@ -237,13 +233,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 +284,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) { @@ -301,10 +306,8 @@ check_for_daemon_cancellation_or_eof (guestfs_h *g, int fd) 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) @@ -331,16 +334,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; @@ -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; - 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); @@ -454,10 +453,8 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) { 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); @@ -543,8 +540,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 +610,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); @@ -646,9 +640,7 @@ guestfs___accept_from_daemon (guestfs_h *g) { 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); @@ -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) { - if (g->verbose) - fprintf (stderr, "got daemon cancellation\n"); + debug (g, "got daemon cancellation"); return -2; } @@ -1030,9 +1021,8 @@ guestfs___recv_file (guestfs_h *g, const char *filename) 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); -- 1.8.3.1