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
test-config \
test-add-drive-opts \
test-last-errno \
- test-private-data
+ test-private-data \
+ test-debug-to-file
TESTS = \
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
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 = \
--- /dev/null
+/* 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);
+}
#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[])
{
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);
}
assert (count == 1);
+ /* Closing should implicitly call the close_callback function. */
guestfs_close (g);
+ assert (close_callback_called == 1);
+
exit (EXIT_SUCCESS);
}
: (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) {
/* 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));
/* 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);
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);
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
generator_optgroups.ml \
generator_prepopts.mli \
generator_prepopts.ml \
+ generator_events.ml \
generator_pr.mli \
generator_pr.ml \
generator_docstrings.ml \
[],
"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
-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_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<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, [],
[],
open Generator_optgroups
open Generator_actions
open Generator_structs
+open Generator_events
(* Generate C API. *)
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);
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <assert.h>
#include \"guestfs.h\"
#include \"guestfs-internal.h\"
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
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 (
| 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. *)
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
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, \"<struct guestfs_%s *>\");\n"
+ pr "%s fprintf (trace_fp, \"<struct guestfs_%s *>\");\n"
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 *)
);
- 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. *)
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"
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 " 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"
(* 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";
| 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 " 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 " 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 " 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 " 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";
| 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";
let globals = [
"guestfs_create";
"guestfs_close";
+ "guestfs_delete_event_callback";
"guestfs_first_private";
"guestfs_get_error_handler";
"guestfs_get_out_of_memory_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";
--- /dev/null
+(* 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
src/bindtests.c
src/errnostring.c
src/errnostring_gperf.c
+src/events.c
src/filearch.c
src/guestfs.c
src/inspect.c
actions.c \
appliance.c \
bindtests.c \
+ events.c \
filearch.c \
inspect.c \
launch.c \
/* 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
}
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;
}
--- /dev/null
+/* 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);
+}
/* 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. */
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;
/* 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. */
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 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
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
}
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;
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]);
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)
{
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.
=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
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
=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
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:
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
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);
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 -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;
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) {
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;
}
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;
}
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;
}
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;
}
* 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) {
{
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
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);
}
{
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 -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);
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);
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
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
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;
}
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)
{
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)
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;
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_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);
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;
}
#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);
{
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);
/* 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;
}
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);