Richard W.M. Jones
2011-Mar-10 15:24 UTC
[Libguestfs] [PATCH for discussion only] New event API (RHBZ#664558).
It's not too clear from the patch, so I have also extracted the new API from <guestfs.h> and the relevant section of the man page. Rich. ---------------------------------------------------------------------- /* Events. */ #define GUESTFS_EVENT_CLOSE 0x0001 #define GUESTFS_EVENT_SUBPROCESS_QUIT 0x0002 #define GUESTFS_EVENT_LAUNCH_DONE 0x0004 #define GUESTFS_EVENT_PROGRESS 0x0008 #define GUESTFS_EVENT_APPLIANCE 0x0010 #define GUESTFS_EVENT_LIBRARY 0x0020 #define GUESTFS_EVENT_TRACE 0x0040 #define GUESTFS_EVENT_ALL UINT64_MAX #ifndef GUESTFS_TYPEDEF_EVENT_CALLBACK #define GUESTFS_TYPEDEF_EVENT_CALLBACK 1 typedef void (*guestfs_event_callback) ( guestfs_h *g, void *opaque, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len); #endif #define LIBGUESTFS_HAVE_SET_EVENT_CALLBACK 1 int guestfs_set_event_callback (guestfs_h *g, guestfs_event_callback cb, uint64_t event_bitmask, int flags, void *opaque); #define LIBGUESTFS_HAVE_DELETE_EVENT_CALLBACK 1 void guestfs_delete_event_callback (guestfs_h *g, int event_handle); ---------------------------------------------------------------------- SETTING CALLBACKS TO HANDLE EVENTS Note: This section documents the new-style event mechanism, which you should use in new code if possible. The old functions "guestfs_set_log_message_callback", "guestfs_set_subprocess_quit_callback", "guestfs_set_launch_done_callback", "guestfs_set_close_callback" and "guestfs_set_progress_callback" are no longer documented in this manual page. Handles generate events when certain things happen, such as log messages being generated, progress messages during long-running operations, or the handle being closed. The API calls described below let you register a callback to be called when events happen. You can register multiple callbacks (for the same, different or overlapping sets of events), and individually remove callbacks. If callbacks are not removed, then they remain in force until the handle is closed. In the current implementation, events are only generated synchronously: that means that events (and hence callbacks) can only happen while you are in the middle of making another libguestfs call. The callback is called in the same thread. Events may contain a payload, usually nothing (void), an array of 64 bit unsigned integers, or a message buffer. Payloads are discussed later on. CLASSES OF EVENTS GUESTFS_EVENT_CLOSE (payload type: void) The callback function will be called while the handle is being closed (synchronously from "guestfs_close"). Note that libguestfs installs an atexit(3) handler to try to clean up handles that are open when the program exits. This means that this callback might be called indirectly from exit(3), which can cause unexpected problems in higher-level languages (eg. if your HLL interpreter has already been cleaned up by the time this is called, and if your callback then jumps into some HLL function). If no callback is registered: the handle is closed without any callback being invoked. GUESTFS_EVENT_SUBPROCESS_QUIT (payload type: void) The callback function will be called when the child process quits, either asynchronously or if killed by "guestfs_kill_subprocess". (This corresponds to a transition from any state to the CONFIG state). If no callback is registered: the event is ignored. GUESTFS_EVENT_LAUNCH_DONE (payload type: void) The callback function will be called when the child process becomes ready first time after it has been launched. (This corresponds to a transition from LAUNCHING to the READY state). If no callback is registered: the event is ignored. GUESTFS_EVENT_PROGRESS (payload type: array of 4 x uint64_t) Some long-running operations can generate progress messages. If this callback is registered, then it will be called each time a progress message is generated (usually two seconds after the operation started, and three times per second thereafter until it completes, although the frequency may change in future versions). The callback receives in the payload four unsigned 64 bit numbers which are (in order): "proc_nr", "serial", "position", "total". The units of "total" are not defined, although for some operations "total" may relate in some way to the amount of data to be transferred (eg. in bytes or megabytes), and "position" may be the portion which has been transferred. The only defined and stable parts of the API are: ? The callback can display to the user some type of progress bar or indicator which shows the ratio of "position":"total". ? 0 <= "position" <= "total" ? If any progress notification is sent during a call, then a final progress notification is always sent when "position" "total". This is to simplify caller code, so callers can easily set the progress indicator to "100%" at the end of the operation, without requiring special code to detect this case. The callback also receives the procedure number ("proc_nr") and serial number ("serial") of the call. These are only useful for debugging protocol issues, and the callback can normally ignore them. The callback may want to print these numbers in error messages or debugging messages. If no callback is registered: progress messages are discarded. GUESTFS_EVENT_APPLIANCE (payload type: message buffer) The callback function is called whenever a log message is generated by qemu, the appliance kernel, guestfsd (daemon), or utility programs. If the verbose flag ("guestfs_set_verbose") is set before launch ("guestfs_launch") then additional debug messages are generated. If no callback is registered: the messages are discarded unless the verbose flag is set in which case they are sent to stderr. You can override the printing of verbose messages to stderr by setting up a callback. GUESTFS_EVENT_LIBRARY (payload type: message buffer) The callback function is called whenever a log message is generated by the library part of libguestfs. If the verbose flag ("guestfs_set_verbose") is set then additional debug messages are generated. If no callback is registered: the messages are discarded unless the verbose flag is set in which case they are sent to stderr. You can override the printing of verbose messages to stderr by setting up a callback. GUESTFS_EVENT_TRACE (payload type: message buffer) The callback function is called whenever a trace message is generated. This only applies if the trace flag ("guestfs_set_trace") is set. If no callback is registered: the messages are discarded unless the verbose flag is set in which case they are sent to stderr. You can override the printing of verbose messages to stderr by setting up a callback. guestfs_set_event_callback int guestfs_set_event_callback (guestfs_h *g, guestfs_event_callback cb, uint64_t event_bitmask, int flags, void *opaque); This function registers a callback ("cb") for all event classes in the "event_bitmask". For example, to register for all log message events, you could call this function with the bitmask "GUESTFS_EVENT_APPLIANCE|GUESTFS_EVENT_LIBRARY". To register a single callback for all possible classes of events, use "GUESTFS_EVENT_ALL". "flags" should always be passed as 0. "opaque" is an opaque pointer which is passed to the callback. You can use it for any purpose. The return value is the event handle (an integer) which you can use to delete the callback (see below). If there is an error, this function returns "-1", and sets the error in the handle in the usual way (see "guestfs_last_error" etc.) Callbacks remain in effect until they are deleted, or until the handle is closed. In the case where multiple callbacks are registered for a particular event class, all of the callbacks are called. The order in which multiple callbacks are called is not defined. guestfs_delete_event_callback void guestfs_delete_event_callback (guestfs_h *g, int event_handle); Delete a callback that was previously registered. "event_handle" should be the integer that was returned by a previous call to "guestfs_set_event_callback" on the same handle. guestfs_event_callback typedef void (*guestfs_event_callback) ( guestfs_h *g, void *opaque, uint64_t event, int event_handle, int flags, const char *buf, size_t buf_len, const uint64_t *array, size_t array_len); This is the type of the event callback function that you have to provide. The basic parameters are: the handle ("g"), the opaque user pointer ("opaque"), the event class (eg. "GUESTFS_EVENT_PROGRESS"), the event handle, and "flags" which in the current API you should ignore. The remaining parameters contain the event payload (if any). Each event may contain a payload, which usually relates to the event class, but for future proofing your code should be written to handle any payload for any event class. "buf" and "buf_len" contain a message buffer (if "buf_len == 0", then there is no message buffer). Note that this message buffer can contain arbitrary 8 bit data, including NUL bytes. "array" and "array_len" is an array of 64 bit unsigned integers. At the moment this is only used for progress messages. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming blog: http://rwmj.wordpress.com Fedora now supports 80 OCaml packages (the OPEN alternative to F#) http://cocan.org/getting_started_with_ocaml_on_red_hat_and_fedora -------------- next part -------------->From bbc35e61765eb32320b5dfada12b73f46fa03a3c Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Thu, 10 Mar 2011 12:32:22 +0000 Subject: [PATCH] New event API (RHBZ#664558). This API allows more than one callback to be registered for each event, makes it possible to call the API from other languages, and allows debug and trace messages to be rerouted from stderr. An older version of this API was discussed on the mailing list here: https://www.redhat.com/archives/libguestfs/2010-December/msg00081.html https://www.redhat.com/archives/libguestfs/2011-January/msg00012.html --- generator/.depend | 8 +- generator/Makefile.am | 1 + generator/generator_c.ml | 34 +++++ generator/generator_events.ml | 40 ++++++ po/POTFILES.in | 1 + src/Makefile.am | 1 + src/events.c | 299 +++++++++++++++++++++++++++++++++++++++++ src/guestfs-internal.h | 29 +++-- src/guestfs.c | 60 ++------- src/guestfs.pod | 245 ++++++++++++++++++++++++---------- src/proto.c | 39 +++--- 11 files changed, 609 insertions(+), 148 deletions(-) create mode 100644 generator/generator_events.ml create mode 100644 src/events.c diff --git a/generator/.depend b/generator/.depend index 4ea8040..afd1c1e 100644 --- a/generator/.depend +++ b/generator/.depend @@ -21,6 +21,8 @@ generator_optgroups.cmx: generator_types.cmx generator_actions.cmx generator_prepopts.cmi: generator_prepopts.cmo: generator_prepopts.cmi generator_prepopts.cmx: generator_prepopts.cmi +generator_events.cmo: generator_utils.cmi +generator_events.cmx: generator_utils.cmx generator_pr.cmi: generator_pr.cmo: generator_utils.cmi generator_pr.cmi generator_pr.cmx: generator_utils.cmx generator_pr.cmi @@ -34,10 +36,12 @@ generator_checks.cmx: generator_utils.cmx generator_types.cmx \ generator_actions.cmx generator_c.cmo: generator_utils.cmi generator_types.cmo \ generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \ - generator_docstrings.cmo generator_api_versions.cmi generator_actions.cmi + generator_events.cmo generator_docstrings.cmo generator_api_versions.cmi \ + generator_actions.cmi generator_c.cmx: generator_utils.cmx generator_types.cmx \ generator_structs.cmx generator_pr.cmx generator_optgroups.cmx \ - generator_docstrings.cmx generator_api_versions.cmx generator_actions.cmx + generator_events.cmx generator_docstrings.cmx generator_api_versions.cmx \ + generator_actions.cmx generator_xdr.cmo: generator_utils.cmi generator_types.cmo \ generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \ generator_docstrings.cmo generator_actions.cmi diff --git a/generator/Makefile.am b/generator/Makefile.am index 39688c1..112fc69 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -28,6 +28,7 @@ SOURCES = \ generator_optgroups.ml \ generator_prepopts.mli \ generator_prepopts.ml \ + generator_events.ml \ generator_pr.mli \ generator_pr.ml \ generator_docstrings.ml \ diff --git a/generator/generator_c.ml b/generator/generator_c.ml index 9b88376..75b679d 100644 --- a/generator/generator_c.ml +++ b/generator/generator_c.ml @@ -28,6 +28,7 @@ open Generator_api_versions open Generator_optgroups open Generator_actions open Generator_structs +open Generator_events (* Generate C API. *) @@ -396,6 +397,39 @@ extern void guestfs_set_out_of_memory_handler (guestfs_h *g, guestfs_abort_cb); extern guestfs_abort_cb guestfs_get_out_of_memory_handler (guestfs_h *g); /* Events. */ +"; + + List.iter ( + fun (name, bitmask) -> + pr "#define GUESTFS_EVENT_%-16s 0x%04x\n" + (String.uppercase name) bitmask + ) events; + pr "#define GUESTFS_EVENT_%-16s UINT64_MAX\n" "ALL"; + pr "\n"; + + pr "\ +#ifndef GUESTFS_TYPEDEF_EVENT_CALLBACK +#define GUESTFS_TYPEDEF_EVENT_CALLBACK 1 +typedef void (*guestfs_event_callback) ( + guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len); +#endif + +#define LIBGUESTFS_HAVE_SET_EVENT_CALLBACK 1 +int guestfs_set_event_callback (guestfs_h *g, + guestfs_event_callback cb, + uint64_t event_bitmask, + int flags, + void *opaque); +#define LIBGUESTFS_HAVE_DELETE_EVENT_CALLBACK 1 +void guestfs_delete_event_callback (guestfs_h *g, int event_handle); + +/* Old-style event handling. In new code use guestfs_set_event_callback. */ #ifndef GUESTFS_TYPEDEF_LOG_MESSAGE_CB #define GUESTFS_TYPEDEF_LOG_MESSAGE_CB 1 typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *opaque, char *buf, int len); diff --git a/generator/generator_events.ml b/generator/generator_events.ml new file mode 100644 index 0000000..3f45a41 --- /dev/null +++ b/generator/generator_events.ml @@ -0,0 +1,40 @@ +(* libguestfs + * Copyright (C) 2011 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + *) + +(* Please read generator/README first. *) + +open Generator_utils + +(* NB: DO NOT REORDER THESE, as doing so will change the ABI. Only + * add new event types at the end of the list. + *) +let events = [ + "close"; (* close handle *) + "subprocess_quit"; (* subprocess quit *) + "launch_done"; (* launched *) + + "progress"; (* progress message *) + + (* log messages from various sources *) + "appliance"; (* log messages from + kernel/guestfsd/appliance tools *) + "library"; (* log messages from library *) + "trace"; (* call trace messages *) +] + +let events = mapi (fun i name -> name, 1 lsl i) events diff --git a/po/POTFILES.in b/po/POTFILES.in index fd8778a..afe4798 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -133,6 +133,7 @@ src/appliance.c src/bindtests.c src/errnostring.c src/errnostring_gperf.c +src/events.c src/filearch.c src/guestfs.c src/inspect.c diff --git a/src/Makefile.am b/src/Makefile.am index 2b9c49b..3e1201d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -127,6 +127,7 @@ libguestfs_la_SOURCES = \ actions.c \ appliance.c \ bindtests.c \ + events.c \ filearch.c \ inspect.c \ launch.c \ diff --git a/src/events.c b/src/events.c new file mode 100644 index 0000000..514b3cd --- /dev/null +++ b/src/events.c @@ -0,0 +1,299 @@ +/* libguestfs + * Copyright (C) 2011 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <config.h> + +#define _BSD_SOURCE /* for mkdtemp, usleep */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <inttypes.h> +#include <unistd.h> +#include <assert.h> + +#include "ignore-value.h" + +#include "guestfs.h" +#include "guestfs-internal.h" + +int +guestfs_set_event_callback (guestfs_h *g, + guestfs_event_callback cb, + uint64_t event_bitmask, + int flags, + void *opaque) +{ + if (flags != 0) { + error (g, "flags parameter should be passed as 0 to this function"); + return -1; + } + + /* We cast size_t to int which is not always safe for large numbers, + * and in any case if a program is registering a huge number of + * callbacks then we'd want to look at using an alternate data + * structure in place of a linear list. + */ + if (g->nr_events >= 1000) { + error (g, "too many event callbacks registered"); + return -1; + } + + int event_handle = (int) g->nr_events; + g->events + guestfs_safe_realloc (g, g->events, + (g->nr_events+1) * sizeof (struct event)); + g->nr_events++; + + g->events[event_handle].event_bitmask = event_bitmask; + g->events[event_handle].cb = cb; + g->events[event_handle].opaque = opaque; + g->events[event_handle].opaque2 = NULL; + + return event_handle; +} + +void +guestfs_delete_event_callback (guestfs_h *g, int event_handle) +{ + if (event_handle < 0 || event_handle >= (int) g->nr_events) + return; + + /* Set the event_bitmask to 0, which will ensure that this callback + * cannot match any event and therefore cannot be called. + */ + g->events[event_handle].event_bitmask = 0; +} + +/* Functions to generate an event with various payloads. */ + +void +guestfs___call_callbacks_void (guestfs_h *g, uint64_t event) +{ + size_t i; + + for (i = 0; i < g->nr_events; ++i) + if ((g->events[i].event_bitmask & event) != 0) + g->events[i].cb (g, g->events[i].opaque, event, i, 0, NULL, 0, NULL, 0); + + /* All events with payload type void are discarded if no callback + * was registered. + */ +} + +void +guestfs___call_callbacks_message (guestfs_h *g, uint64_t event, + const char *buf, size_t buf_len) +{ + size_t i, count = 0; + + for (i = 0; i < g->nr_events; ++i) + if ((g->events[i].event_bitmask & event) != 0) { + g->events[i].cb (g, g->events[i].opaque, event, i, 0, + buf, buf_len, NULL, 0); + count++; + } + + /* If nothing was registered and we're verbose, then we print the + * message on stderr. + */ + if (g->verbose && count == 0) + ignore_value (write (STDERR_FILENO, buf, buf_len)); +} + +void +guestfs___call_callbacks_array (guestfs_h *g, uint64_t event, + const uint64_t *array, size_t array_len) +{ + size_t i; + + for (i = 0; i < g->nr_events; ++i) + if ((g->events[i].event_bitmask & event) != 0) + g->events[i].cb (g, g->events[i].opaque, event, i, 0, + NULL, 0, array, array_len); + + /* All events with payload type array are discarded if no callback + * was registered. + */ +} + +/* Emulate old-style callback API. + * + * There were no event handles, so multiple callbacks per event were + * not supported. Calling the same 'guestfs_set_*_callback' function + * would replace the existing event. Calling it with cb == NULL meant + * that the caller wanted to remove the callback. + */ + +static void +replace_old_style_event_callback (guestfs_h *g, + guestfs_event_callback cb, + uint64_t event_bitmask, + void *opaque, + void *opaque2) +{ + size_t i; + + /* Use 'cb' pointer as a sentinel to replace the existing callback + * for this event if one was registered previously. Else append a + * new event. + */ + + for (i = 0; i < g->nr_events; ++i) + if (g->events[i].cb == cb) { + if (opaque2 == NULL) { + /* opaque2 (the original callback) is NULL, which in the + * old-style API meant remove the callback. + */ + guestfs_delete_event_callback (g, i); + return; + } + + goto replace; + } + + if (opaque2 == NULL) + return; /* see above */ + + /* i == g->nr_events */ + g->events + guestfs_safe_realloc (g, g->events, + (g->nr_events+1) * sizeof (struct event)); + g->nr_events++; + + replace: + g->events[i].event_bitmask = event_bitmask; + g->events[i].cb = cb; + g->events[i].opaque = opaque; + g->events[i].opaque2 = opaque2; +} + +static void +log_message_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_log_message_cb cb = g->events[event_handle].opaque2; + /* Note that the old callback declared the message buffer as + * (char *, int). I sure hope message buffers aren't too large + * and that callers aren't writing to them. XXX + */ + cb (g, opaque, (char *) buf, (int) buf_len); +} + +void +guestfs_set_log_message_callback (guestfs_h *g, + guestfs_log_message_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, log_message_callback_wrapper, + GUESTFS_EVENT_APPLIANCE, + opaque, cb); +} + +static void +subprocess_quit_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_subprocess_quit_cb cb = g->events[event_handle].opaque2; + cb (g, opaque); +} + +void +guestfs_set_subprocess_quit_callback (guestfs_h *g, + guestfs_subprocess_quit_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, subprocess_quit_callback_wrapper, + GUESTFS_EVENT_SUBPROCESS_QUIT, + opaque, cb); +} + +static void +launch_done_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_launch_done_cb cb = g->events[event_handle].opaque2; + cb (g, opaque); +} + +void +guestfs_set_launch_done_callback (guestfs_h *g, + guestfs_launch_done_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, launch_done_callback_wrapper, + GUESTFS_EVENT_LAUNCH_DONE, + opaque, cb); +} + +static void +close_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_close_cb cb = g->events[event_handle].opaque2; + cb (g, opaque); +} + +void +guestfs_set_close_callback (guestfs_h *g, + guestfs_close_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, close_callback_wrapper, + GUESTFS_EVENT_CLOSE, + opaque, cb); +} + +static void +progress_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_progress_cb cb = g->events[event_handle].opaque2; + assert (array_len >= 4); + cb (g, opaque, (int) array[0], (int) array[1], array[2], array[3]); +} + +void +guestfs_set_progress_callback (guestfs_h *g, + guestfs_progress_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, progress_callback_wrapper, + GUESTFS_EVENT_PROGRESS, + opaque, cb); +} diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 0eb395b..b4f7f6a 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -87,6 +87,18 @@ enum state { CONFIG, LAUNCHING, READY, BUSY, NO_HANDLE }; /* Attach method. */ enum attach_method { ATTACH_METHOD_APPLIANCE = 0, ATTACH_METHOD_UNIX }; +/* Event. */ +struct event { + uint64_t event_bitmask; + guestfs_event_callback cb; + void *opaque; + + /* opaque2 is not exposed through the API, but is used internally to + * emulate the old-style callback API. + */ + void *opaque2; +}; + struct guestfs_h { struct guestfs_h *next; /* Linked list of open handles. */ @@ -133,16 +145,10 @@ struct guestfs_h guestfs_abort_cb abort_cb; guestfs_error_handler_cb error_cb; void * error_cb_data; - guestfs_log_message_cb log_message_cb; - void * log_message_cb_data; - guestfs_subprocess_quit_cb subprocess_quit_cb; - void * subprocess_quit_cb_data; - guestfs_launch_done_cb launch_done_cb; - void * launch_done_cb_data; - guestfs_close_cb close_cb; - void * close_cb_data; - guestfs_progress_cb progress_cb; - void * progress_cb_data; + + /* Events. */ + struct event *events; + size_t nr_events; int msg_next_serial; @@ -289,6 +295,9 @@ extern int guestfs___feature_available (guestfs_h *g, const char *feature); extern void guestfs___free_string_list (char **); extern int guestfs___checkpoint_cmdline (guestfs_h *g); extern void guestfs___rollback_cmdline (guestfs_h *g, int pos); +extern void guestfs___call_callbacks_void (guestfs_h *g, uint64_t event); +extern void guestfs___call_callbacks_message (guestfs_h *g, uint64_t event, const char *buf, size_t buf_len); +extern void guestfs___call_callbacks_array (guestfs_h *g, uint64_t event, const uint64_t *array, size_t array_len); #define error(g,...) guestfs_error_errno((g),0,__VA_ARGS__) #define perrorf guestfs_perrorf diff --git a/src/guestfs.c b/src/guestfs.c index 8b7ab4d..79b170e 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -182,24 +182,26 @@ guestfs_close (guestfs_h *g) if (g->verbose) fprintf (stderr, "closing guestfs handle %p (state %d)\n", g, g->state); - /* Run user close callback before anything else. */ - if (g->close_cb) - g->close_cb (g, g->close_cb_data); - - guestfs___free_inspect_info (g); - /* Try to sync if autosync flag is set. */ if (g->autosync && g->state == READY) guestfs_internal_autosync (g); - /* Remove any handlers that might be called back before we kill the - * subprocess. + /* Run user close callbacks. */ + guestfs___call_callbacks_void (g, GUESTFS_EVENT_CLOSE); + + /* Remove all other registered callbacks. They might be called + * unnecessarily when we kill the subprocess here, and since we've + * already called the close callbacks, we shouldn't call any others. */ - g->log_message_cb = NULL; + free (g->events); + g->nr_events = 0; + g->events = NULL; if (g->state != CONFIG) guestfs_kill_subprocess (g); + guestfs___free_inspect_info (g); + /* Close sockets. */ if (g->fd[0] >= 0) close (g->fd[0]); @@ -690,46 +692,6 @@ guestfs__get_attach_method (guestfs_h *g) return ret; } -void -guestfs_set_log_message_callback (guestfs_h *g, - guestfs_log_message_cb cb, void *opaque) -{ - g->log_message_cb = cb; - g->log_message_cb_data = opaque; -} - -void -guestfs_set_subprocess_quit_callback (guestfs_h *g, - guestfs_subprocess_quit_cb cb, void *opaque) -{ - g->subprocess_quit_cb = cb; - g->subprocess_quit_cb_data = opaque; -} - -void -guestfs_set_launch_done_callback (guestfs_h *g, - guestfs_launch_done_cb cb, void *opaque) -{ - g->launch_done_cb = cb; - g->launch_done_cb_data = opaque; -} - -void -guestfs_set_close_callback (guestfs_h *g, - guestfs_close_cb cb, void *opaque) -{ - g->close_cb = cb; - g->close_cb_data = opaque; -} - -void -guestfs_set_progress_callback (guestfs_h *g, - guestfs_progress_cb cb, void *opaque) -{ - g->progress_cb = cb; - g->progress_cb_data = opaque; -} - /* Note the private data area is allocated lazily, since the vast * majority of callers will never use it. This means g->pda is * likely to be NULL. diff --git a/src/guestfs.pod b/src/guestfs.pod index 0b3b654..2105773 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -1683,82 +1683,71 @@ For guestfish, see L<guestfish(1)/OPTIONAL ARGUMENTS>. =head2 SETTING CALLBACKS TO HANDLE EVENTS -The child process generates events in some situations. Current events -include: receiving a log message, the child process exits. - -Use the C<guestfs_set_*_callback> functions to set a callback for -different types of events. - -Only I<one callback of each type> can be registered for each handle. -Calling C<guestfs_set_*_callback> again overwrites the previous -callback of that type. Cancel all callbacks of this type by calling -this function with C<cb> set to C<NULL>. - -=head2 guestfs_set_log_message_callback +B<Note:> This section documents the new-style event mechanism, which +you should use in new code if possible. The old functions +C<guestfs_set_log_message_callback>, +C<guestfs_set_subprocess_quit_callback>, +C<guestfs_set_launch_done_callback>, C<guestfs_set_close_callback> and +C<guestfs_set_progress_callback> are no longer documented in this +manual page. - typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *opaque, - char *buf, int len); - void guestfs_set_log_message_callback (guestfs_h *g, - guestfs_log_message_cb cb, - void *opaque); +Handles generate events when certain things happen, such as log +messages being generated, progress messages during long-running +operations, or the handle being closed. The API calls described below +let you register a callback to be called when events happen. You can +register multiple callbacks (for the same, different or overlapping +sets of events), and individually remove callbacks. If callbacks are +not removed, then they remain in force until the handle is closed. -The callback function C<cb> will be called whenever qemu or the guest -writes anything to the console. +In the current implementation, events are only generated +synchronously: that means that events (and hence callbacks) can only +happen while you are in the middle of making another libguestfs call. +The callback is called in the same thread. -Use this function to capture kernel messages and similar. +Events may contain a payload, usually nothing (void), an array of 64 +bit unsigned integers, or a message buffer. Payloads are discussed +later on. -Normally there is no log message handler, and log messages are just -discarded. +=head3 CLASSES OF EVENTS -=head2 guestfs_set_subprocess_quit_callback +=over 4 - typedef void (*guestfs_subprocess_quit_cb) (guestfs_h *g, void *opaque); - void guestfs_set_subprocess_quit_callback (guestfs_h *g, - guestfs_subprocess_quit_cb cb, - void *opaque); +=item GUESTFS_EVENT_CLOSE +(payload type: void) -The callback function C<cb> will be called when the child process -quits, either asynchronously or if killed by -L</guestfs_kill_subprocess>. (This corresponds to a transition from -any state to the CONFIG state). +The callback function will be called while the handle is being closed +(synchronously from L</guestfs_close>). -=head2 guestfs_set_launch_done_callback +Note that libguestfs installs an L<atexit(3)> handler to try to clean +up handles that are open when the program exits. This means that this +callback might be called indirectly from L<exit(3)>, which can cause +unexpected problems in higher-level languages (eg. if your HLL +interpreter has already been cleaned up by the time this is called, +and if your callback then jumps into some HLL function). - typedef void (*guestfs_launch_done_cb) (guestfs_h *g, void *opaque); - void guestfs_set_launch_done_callback (guestfs_h *g, - guestfs_launch_done_cb cb, - void *opaque); +If no callback is registered: the handle is closed without any +callback being invoked. -The callback function C<cb> will be called when the child process -becomes ready first time after it has been launched. (This -corresponds to a transition from LAUNCHING to the READY state). +=item GUESTFS_EVENT_SUBPROCESS_QUIT +(payload type: void) -=head2 guestfs_set_close_callback +The callback function will be called when the child process quits, +either asynchronously or if killed by L</guestfs_kill_subprocess>. +(This corresponds to a transition from any state to the CONFIG state). - typedef void (*guestfs_close_cb) (guestfs_h *g, void *opaque); - void guestfs_set_close_callback (guestfs_h *g, - guestfs_close_cb cb, - void *opaque); +If no callback is registered: the event is ignored. -The callback function C<cb> will be called while the handle -is being closed (synchronously from L</guestfs_close>). +=item GUESTFS_EVENT_LAUNCH_DONE +(payload type: void) -Note that libguestfs installs an L<atexit(3)> handler to try to -clean up handles that are open when the program exits. This -means that this callback might be called indirectly from -L<exit(3)>, which can cause unexpected problems in higher-level -languages (eg. if your HLL interpreter has already been cleaned -up by the time this is called, and if your callback then jumps -into some HLL function). +The callback function will be called when the child process becomes +ready first time after it has been launched. (This corresponds to a +transition from LAUNCHING to the READY state). -=head2 guestfs_set_progress_callback +If no callback is registered: the event is ignored. - typedef void (*guestfs_progress_cb) (guestfs_h *g, void *opaque, - int proc_nr, int serial, - uint64_t position, uint64_t total); - void guestfs_set_progress_callback (guestfs_h *g, - guestfs_progress_cb cb, - void *opaque); +=item GUESTFS_EVENT_PROGRESS +(payload type: array of 4 x uint64_t) Some long-running operations can generate progress messages. If this callback is registered, then it will be called each time a @@ -1766,7 +1755,9 @@ progress message is generated (usually two seconds after the operation started, and three times per second thereafter until it completes, although the frequency may change in future versions). -The callback receives two numbers: C<position> and C<total>. +The callback receives in the payload four unsigned 64 bit numbers +which are (in order): C<proc_nr>, C<serial>, C<position>, C<total>. + The units of C<total> are not defined, although for some operations C<total> may relate in some way to the amount of data to be transferred (eg. in bytes or megabytes), and @@ -1796,10 +1787,127 @@ requiring special code to detect this case. =back -The callback also receives the procedure number and serial number of -the call. These are only useful for debugging protocol issues, and -the callback can normally ignore them. The callback may want to -print these numbers in error messages or debugging messages. +The callback also receives the procedure number (C<proc_nr>) and +serial number (C<serial>) of the call. These are only useful for +debugging protocol issues, and the callback can normally ignore them. +The callback may want to print these numbers in error messages or +debugging messages. + +If no callback is registered: progress messages are discarded. + +=item GUESTFS_EVENT_APPLIANCE +(payload type: message buffer) + +The callback function is called whenever a log message is generated by +qemu, the appliance kernel, guestfsd (daemon), or utility programs. + +If the verbose flag (L</guestfs_set_verbose>) is set before launch +(L</guestfs_launch>) then additional debug messages are generated. + +If no callback is registered: the messages are discarded unless the +verbose flag is set in which case they are sent to stderr. You can +override the printing of verbose messages to stderr by setting up a +callback. + +=item GUESTFS_EVENT_LIBRARY +(payload type: message buffer) + +The callback function is called whenever a log message is generated by +the library part of libguestfs. + +If the verbose flag (L</guestfs_set_verbose>) is set then additional +debug messages are generated. + +If no callback is registered: the messages are discarded unless the +verbose flag is set in which case they are sent to stderr. You can +override the printing of verbose messages to stderr by setting up a +callback. + +=item GUESTFS_EVENT_TRACE +(payload type: message buffer) + +The callback function is called whenever a trace message is generated. +This only applies if the trace flag (L</guestfs_set_trace>) is set. + +If no callback is registered: the messages are discarded unless the +verbose flag is set in which case they are sent to stderr. You can +override the printing of verbose messages to stderr by setting up a +callback. + +=back + +=head3 guestfs_set_event_callback + + int guestfs_set_event_callback (guestfs_h *g, + guestfs_event_callback cb, + uint64_t event_bitmask, + int flags, + void *opaque); + +This function registers a callback (C<cb>) for all event classes +in the C<event_bitmask>. + +For example, to register for all log message events, you could call +this function with the bitmask +C<GUESTFS_EVENT_APPLIANCE|GUESTFS_EVENT_LIBRARY>. To register a +single callback for all possible classes of events, use +C<GUESTFS_EVENT_ALL>. + +C<flags> should always be passed as 0. + +C<opaque> is an opaque pointer which is passed to the callback. You +can use it for any purpose. + +The return value is the event handle (an integer) which you can use to +delete the callback (see below). + +If there is an error, this function returns C<-1>, and sets the error +in the handle in the usual way (see L</guestfs_last_error> etc.) + +Callbacks remain in effect until they are deleted, or until the handle +is closed. + +In the case where multiple callbacks are registered for a particular +event class, all of the callbacks are called. The order in which +multiple callbacks are called is not defined. + +=head3 guestfs_delete_event_callback + + void guestfs_delete_event_callback (guestfs_h *g, int event_handle); + +Delete a callback that was previously registered. C<event_handle> +should be the integer that was returned by a previous call to +C<guestfs_set_event_callback> on the same handle. + +=head3 guestfs_event_callback + + typedef void (*guestfs_event_callback) ( + guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len); + +This is the type of the event callback function that you have to +provide. + +The basic parameters are: the handle (C<g>), the opaque user pointer +(C<opaque>), the event class (eg. C<GUESTFS_EVENT_PROGRESS>), the +event handle, and C<flags> which in the current API you should ignore. + +The remaining parameters contain the event payload (if any). Each +event may contain a payload, which usually relates to the event class, +but for future proofing your code should be written to handle any +payload for any event class. + +C<buf> and C<buf_len> contain a message buffer (if C<buf_len == 0>, +then there is no message buffer). Note that this message buffer can +contain arbitrary 8 bit data, including NUL bytes. + +C<array> and C<array_len> is an array of 64 bit unsigned integers. At +the moment this is only used for progress messages. =head1 PRIVATE DATA AREA @@ -1833,8 +1941,7 @@ any way. As far as libguestfs is concerned, it need not be a valid pointer at all. In particular, libguestfs does I<not> try to free the data when the handle is closed. If the data must be freed, then the caller must either free it before calling L</guestfs_close> or must -set up a close callback to do it (see L</guestfs_set_close_callback>, -and note that only one callback can be registered for a handle). +set up a close callback to do it (see L</GUESTFS_EVENT_CLOSE>. The private data area is implemented using a hash table, and should be reasonably efficient for moderate numbers of keys. @@ -2100,8 +2207,8 @@ are distinguished by the normal length word being replaced by C<GUESTFS_PROGRESS_FLAG>, followed by a fixed size progress message. The library turns them into progress callbacks (see -C<guestfs_set_progress_callback>) if there is a callback registered, -or discards them if not. +L</GUESTFS_EVENT_PROGRESS>) if there is a callback registered, or +discards them if not. The daemon self-limits the frequency of progress messages it sends (see C<daemon/proto.c:notify_progress>). Not all calls generate diff --git a/src/proto.c b/src/proto.c index 549734b..6a0fbbf 100644 --- a/src/proto.c +++ b/src/proto.c @@ -193,8 +193,7 @@ child_cleanup (guestfs_h *g) g->recoverypid = 0; memset (&g->launch_t, 0, sizeof g->launch_t); g->state = CONFIG; - if (g->subprocess_quit_cb) - g->subprocess_quit_cb (g, g->subprocess_quit_cb_data); + guestfs___call_callbacks_void (g, GUESTFS_EVENT_SUBPROCESS_QUIT); } static int @@ -237,13 +236,8 @@ read_log_message_or_eof (guestfs_h *g, int fd, int error_if_eof) return -1; } - /* In verbose mode, copy all log messages to stderr. */ - if (g->verbose) - ignore_value (write (STDERR_FILENO, buf, n)); - /* It's an actual log message, send it upwards if anyone is listening. */ - if (g->log_message_cb) - g->log_message_cb (g, g->log_message_cb_data, buf, n); + guestfs___call_callbacks_message (g, GUESTFS_EVENT_APPLIANCE, buf, n); return 0; } @@ -293,6 +287,20 @@ really_read_from_socket (guestfs_h *g, int sock, char *buf, size_t n) return (ssize_t) got; } +static void +send_progress_message (guestfs_h *g, const guestfs_progress *message) +{ + uint64_t array[4]; + + array[0] = message->proc; + array[1] = message->serial; + array[2] = message->position; + array[3] = message->total; + + guestfs___call_callbacks_array (g, GUESTFS_EVENT_PROGRESS, + array, sizeof array / sizeof array[0]); +} + static int check_for_daemon_cancellation_or_eof (guestfs_h *g, int fd) { @@ -331,16 +339,14 @@ check_for_daemon_cancellation_or_eof (guestfs_h *g, int fd) return -1; } - if (g->state == BUSY && g->progress_cb) { + if (g->state == BUSY) { guestfs_progress message; xdrmem_create (&xdr, buf, PROGRESS_MESSAGE_SIZE, XDR_DECODE); xdr_guestfs_progress (&xdr, &message); xdr_destroy (&xdr); - g->progress_cb (g, g->progress_cb_data, - message.proc, message.serial, - message.position, message.total); + send_progress_message (g, &message); } return 0; @@ -543,8 +549,7 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) g->state); else { g->state = READY; - if (g->launch_done_cb) - g->launch_done_cb (g, g->launch_done_cb_data); + guestfs___call_callbacks_void (g, GUESTFS_EVENT_LAUNCH_DONE); } return 0; } @@ -614,16 +619,14 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) #endif if (*size_rtn == GUESTFS_PROGRESS_FLAG) { - if (g->state == BUSY && g->progress_cb) { + if (g->state == BUSY) { guestfs_progress message; XDR xdr; xdrmem_create (&xdr, *buf_rtn, PROGRESS_MESSAGE_SIZE, XDR_DECODE); xdr_guestfs_progress (&xdr, &message); xdr_destroy (&xdr); - g->progress_cb (g, g->progress_cb_data, - message.proc, message.serial, - message.position, message.total); + send_progress_message (g, &message); } free (*buf_rtn); -- 1.7.4
Richard W.M. Jones
2011-Mar-11 21:47 UTC
[Libguestfs] [PATCH v2 for discussion only] New event API (RHBZ#664558).
API is still the same as in the previous message. This just continues with the implementation. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-top is 'top' for virtual machines. Tiny program with many powerful monitoring features, net stats, disk stats, logging, etc. http://et.redhat.com/~rjones/virt-top -------------- next part -------------->From 33f6b4b03628ab73fc17cbc67213f81e9d1b0da9 Mon Sep 17 00:00:00 2001From: Richard W.M. Jones <rjones at redhat.com> Date: Thu, 10 Mar 2011 12:32:22 +0000 Subject: [PATCH] New event API (RHBZ#664558). This API allows more than one callback to be registered for each event, makes it possible to call the API from other languages, and allows [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 ++++++++++++ fish/fish.c | 3 +- fish/fish.h | 2 +- fish/progress.c | 13 ++- fish/reopen.c | 3 +- generator/.depend | 8 +- generator/Makefile.am | 1 + generator/generator_actions.ml | 22 ++-- generator/generator_c.ml | 189 +++++++++++++++++-------- generator/generator_events.ml | 40 ++++++ po/POTFILES.in | 1 + src/Makefile.am | 1 + src/appliance.c | 6 +- src/events.c | 301 ++++++++++++++++++++++++++++++++++++++++ src/guestfs-internal.h | 43 +++++-- src/guestfs.c | 154 +++++++++++++-------- src/guestfs.pod | 244 +++++++++++++++++++++++--------- src/inspect.c | 24 ++-- src/launch.c | 38 +++-- src/proto.c | 76 +++++------ 22 files changed, 984 insertions(+), 289 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 1511c4a..28bd352 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*.img diff --git a/capitests/Makefile.am b/capitests/Makefile.am index 542c4fb..d1acec7 100644 --- a/capitests/Makefile.am +++ b/capitests/Makefile.am @@ -30,7 +30,8 @@ check_PROGRAMS = \ test-create-handle \ test-config \ test-add-drive-opts \ - test-last-errno + test-last-errno \ + test-debug-to-file TESTS = \ tests \ @@ -38,7 +39,8 @@ TESTS = \ test-create-handle \ test-config \ test-add-drive-opts \ - test-last-errno + test-last-errno \ + test-debug-to-file # The API behind this test is not baked yet. #if HAVE_LIBVIRT @@ -103,6 +105,13 @@ test_last_errno_CFLAGS = \ test_last_errno_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 <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); +} 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..afd1c1e 100644 --- a/generator/.depend +++ b/generator/.depend @@ -21,6 +21,8 @@ generator_optgroups.cmx: generator_types.cmx generator_actions.cmx generator_prepopts.cmi: generator_prepopts.cmo: generator_prepopts.cmi generator_prepopts.cmx: generator_prepopts.cmi +generator_events.cmo: generator_utils.cmi +generator_events.cmx: generator_utils.cmx generator_pr.cmi: generator_pr.cmo: generator_utils.cmi generator_pr.cmi generator_pr.cmx: generator_utils.cmx generator_pr.cmi @@ -34,10 +36,12 @@ generator_checks.cmx: generator_utils.cmx generator_types.cmx \ generator_actions.cmx generator_c.cmo: generator_utils.cmi generator_types.cmo \ generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \ - generator_docstrings.cmo generator_api_versions.cmi generator_actions.cmi + generator_events.cmo generator_docstrings.cmo generator_api_versions.cmi \ + generator_actions.cmi generator_c.cmx: generator_utils.cmx generator_types.cmx \ generator_structs.cmx generator_pr.cmx generator_optgroups.cmx \ - generator_docstrings.cmx generator_api_versions.cmx generator_actions.cmx + generator_events.cmx generator_docstrings.cmx generator_api_versions.cmx \ + generator_actions.cmx generator_xdr.cmo: generator_utils.cmi generator_types.cmo \ generator_structs.cmi generator_pr.cmi generator_optgroups.cmo \ generator_docstrings.cmo generator_actions.cmi diff --git a/generator/Makefile.am b/generator/Makefile.am index 39688c1..112fc69 100644 --- a/generator/Makefile.am +++ b/generator/Makefile.am @@ -28,6 +28,7 @@ SOURCES = \ generator_optgroups.ml \ generator_prepopts.mli \ generator_prepopts.ml \ + generator_events.ml \ generator_pr.mli \ generator_pr.ml \ generator_docstrings.ml \ diff --git a/generator/generator_actions.ml b/generator/generator_actions.ml index ca2dfe3..fa2f6f5 100644 --- a/generator/generator_actions.ml +++ b/generator/generator_actions.ml @@ -300,10 +300,14 @@ Get the autosync flag."); [], "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_error_callback>)."); ("get_verbose", (RBool "verbose", [], []), -1, [], [], @@ -469,19 +473,19 @@ see L<guestfs(3)>."); ["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_error_callback>)."); ("get_trace", (RBool "trace", [], []), -1, [], [], diff --git a/generator/generator_c.ml b/generator/generator_c.ml index 9b88376..6fc1776 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); @@ -580,6 +614,7 @@ and generate_client_actions () #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> +#include <assert.h> #include \"guestfs.h\" #include \"guestfs-internal.h\" @@ -635,6 +670,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 @@ -739,7 +810,9 @@ check_state (guestfs_h *g, const char *caller) pr "\n" ); - pr " fprintf (stderr, \"%%s: %%s: %%s\",\n"; + pr " trace_fp = trace_open (g);\n"; + + pr " fprintf (trace_fp, \"%%s: %%s: %%s\",\n"; pr " \"libguestfs\", \"trace\", \"%s\");\n" shortname; (* Required arguments. *) @@ -752,33 +825,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. *) @@ -791,18 +864,19 @@ 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 " fputc ('\\n', trace_fp);\n"; + pr " trace_send_line (g);\n"; pr " }\n"; pr "\n"; in @@ -821,67 +895,56 @@ check_state (guestfs_h *g, const char *caller) pr "\n" ); - pr "%s fprintf (stderr, \"%%s: %%s: %%s = \",\n" indent; + pr "%s trace_fp = trace_open (g);\n" indent; + + pr "%s fprintf (trace_fp, \"%%s: %%s: %%s = \",\n" indent; pr "%s \"libguestfs\", \"trace\", \"%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 fputc ('\\n', trace_fp);\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: %%s = %%s (error)\\n\",\n" indent; + pr "%s \"libguestfs\", \"trace\", \"%s\", \"%s\");\n" + indent shortname (string_of_errcode errcode) in (* For non-daemon functions, generate a wrapper around each function. *) @@ -897,6 +960,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" @@ -928,7 +992,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"; @@ -981,6 +1045,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" @@ -1026,7 +1091,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"; @@ -1057,7 +1122,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"; @@ -1099,7 +1164,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"; @@ -1112,7 +1177,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"; @@ -1137,7 +1202,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"; @@ -1145,13 +1210,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"; @@ -1175,7 +1240,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"; @@ -1359,6 +1424,7 @@ and generate_linker_script () let globals = [ "guestfs_create"; "guestfs_close"; + "guestfs_delete_event_callback"; "guestfs_get_error_handler"; "guestfs_get_out_of_memory_handler"; "guestfs_get_private"; @@ -1366,6 +1432,7 @@ and generate_linker_script () "guestfs_last_error"; "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 ef724be..0bcda89 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..d3e3b71 --- /dev/null +++ b/src/events.c @@ -0,0 +1,301 @@ +/* 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)) + ignore_value (write (STDERR_FILENO, buf, buf_len)); +} + +void +guestfs___call_callbacks_array (guestfs_h *g, uint64_t event, + const uint64_t *array, size_t array_len) +{ + size_t i; + + for (i = 0; i < g->nr_events; ++i) + if ((g->events[i].event_bitmask & event) != 0) + g->events[i].cb (g, g->events[i].opaque, event, i, 0, + NULL, 0, array, array_len); + + /* All events with payload type array are discarded if no callback + * was registered. + */ +} + +/* Emulate old-style callback API. + * + * There were no event handles, so multiple callbacks per event were + * not supported. Calling the same 'guestfs_set_*_callback' function + * would replace the existing event. Calling it with cb == NULL meant + * that the caller wanted to remove the callback. + */ + +static void +replace_old_style_event_callback (guestfs_h *g, + guestfs_event_callback cb, + uint64_t event_bitmask, + void *opaque, + void *opaque2) +{ + size_t i; + + /* Use 'cb' pointer as a sentinel to replace the existing callback + * for this event if one was registered previously. Else append a + * new event. + */ + + for (i = 0; i < g->nr_events; ++i) + if (g->events[i].cb == cb) { + if (opaque2 == NULL) { + /* opaque2 (the original callback) is NULL, which in the + * old-style API meant remove the callback. + */ + guestfs_delete_event_callback (g, i); + return; + } + + goto replace; + } + + if (opaque2 == NULL) + return; /* see above */ + + /* i == g->nr_events */ + g->events + guestfs_safe_realloc (g, g->events, + (g->nr_events+1) * sizeof (struct event)); + g->nr_events++; + + replace: + g->events[i].event_bitmask = event_bitmask; + g->events[i].cb = cb; + g->events[i].opaque = opaque; + g->events[i].opaque2 = opaque2; +} + +static void +log_message_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_log_message_cb cb = g->events[event_handle].opaque2; + /* Note that the old callback declared the message buffer as + * (char *, int). I sure hope message buffers aren't too large + * and that callers aren't writing to them. XXX + */ + cb (g, opaque, (char *) buf, (int) buf_len); +} + +void +guestfs_set_log_message_callback (guestfs_h *g, + guestfs_log_message_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, log_message_callback_wrapper, + GUESTFS_EVENT_APPLIANCE, + opaque, cb); +} + +static void +subprocess_quit_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_subprocess_quit_cb cb = g->events[event_handle].opaque2; + cb (g, opaque); +} + +void +guestfs_set_subprocess_quit_callback (guestfs_h *g, + guestfs_subprocess_quit_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, subprocess_quit_callback_wrapper, + GUESTFS_EVENT_SUBPROCESS_QUIT, + opaque, cb); +} + +static void +launch_done_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_launch_done_cb cb = g->events[event_handle].opaque2; + cb (g, opaque); +} + +void +guestfs_set_launch_done_callback (guestfs_h *g, + guestfs_launch_done_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, launch_done_callback_wrapper, + GUESTFS_EVENT_LAUNCH_DONE, + opaque, cb); +} + +static void +close_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_close_cb cb = g->events[event_handle].opaque2; + cb (g, opaque); +} + +void +guestfs_set_close_callback (guestfs_h *g, + guestfs_close_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, close_callback_wrapper, + GUESTFS_EVENT_CLOSE, + opaque, cb); +} + +static void +progress_callback_wrapper (guestfs_h *g, + void *opaque, + uint64_t event, + int event_handle, + int flags, + const char *buf, size_t buf_len, + const uint64_t *array, size_t array_len) +{ + guestfs_progress_cb cb = g->events[event_handle].opaque2; + assert (array_len >= 4); + cb (g, opaque, (int) array[0], (int) array[1], array[2], array[3]); +} + +void +guestfs_set_progress_callback (guestfs_h *g, + guestfs_progress_cb cb, void *opaque) +{ + replace_old_style_event_callback (g, progress_callback_wrapper, + GUESTFS_EVENT_PROGRESS, + opaque, cb); +} diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 0eb395b..077c5f9 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; @@ -154,6 +160,11 @@ struct guestfs_h /* Private data area. */ struct hash_table *pda; + + /* Used by src/actions.c:trace_* functions. */ + FILE *trace_fp; + char *trace_buf; + size_t trace_len; }; /* Per-filesystem data stored for inspect_os. */ @@ -263,6 +274,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, ...); @@ -289,9 +306,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 8b7ab4d..89d46b9 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,86 @@ 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, "libguestfs: warning: %s\n", 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, *msg2; + 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; + + len = asprintf (&msg2, "libguestfs: %s\n", msg); + free (msg); + + if (len < 0) return; + + guestfs___call_callbacks_message (g, GUESTFS_EVENT_LIBRARY, msg2, len); + + free (msg2); +} + +/* 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 +770,6 @@ guestfs__get_attach_method (guestfs_h *g) return ret; } -void -guestfs_set_log_message_callback (guestfs_h *g, - guestfs_log_message_cb cb, void *opaque) -{ - g->log_message_cb = cb; - g->log_message_cb_data = opaque; -} - -void -guestfs_set_subprocess_quit_callback (guestfs_h *g, - guestfs_subprocess_quit_cb cb, void *opaque) -{ - g->subprocess_quit_cb = cb; - g->subprocess_quit_cb_data = opaque; -} - -void -guestfs_set_launch_done_callback (guestfs_h *g, - guestfs_launch_done_cb cb, void *opaque) -{ - g->launch_done_cb = cb; - g->launch_done_cb_data = opaque; -} - -void -guestfs_set_close_callback (guestfs_h *g, - guestfs_close_cb cb, void *opaque) -{ - g->close_cb = cb; - g->close_cb_data = opaque; -} - -void -guestfs_set_progress_callback (guestfs_h *g, - guestfs_progress_cb cb, void *opaque) -{ - g->progress_cb = cb; - g->progress_cb_data = opaque; -} - /* Note the private data area is allocated lazily, since the vast * majority of callers will never use it. This means g->pda is * likely to be NULL. diff --git a/src/guestfs.pod b/src/guestfs.pod index 0b3b654..3601321 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -1683,82 +1683,71 @@ For guestfish, see L<guestfish(1)/OPTIONAL ARGUMENTS>. =head2 SETTING CALLBACKS TO HANDLE EVENTS -The child process generates events in some situations. Current events -include: receiving a log message, the child process exits. - -Use the C<guestfs_set_*_callback> functions to set a callback for -different types of events. - -Only I<one callback of each type> can be registered for each handle. -Calling C<guestfs_set_*_callback> again overwrites the previous -callback of that type. Cancel all callbacks of this type by calling -this function with C<cb> set to C<NULL>. - -=head2 guestfs_set_log_message_callback +B<Note:> This section documents the new-style event mechanism, which +you should use in new code if possible. The old functions +C<guestfs_set_log_message_callback>, +C<guestfs_set_subprocess_quit_callback>, +C<guestfs_set_launch_done_callback>, C<guestfs_set_close_callback> and +C<guestfs_set_progress_callback> are no longer documented in this +manual page. - typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *opaque, - char *buf, int len); - void guestfs_set_log_message_callback (guestfs_h *g, - guestfs_log_message_cb cb, - void *opaque); +Handles generate events when certain things happen, such as log +messages being generated, progress messages during long-running +operations, or the handle being closed. The API calls described below +let you register a callback to be called when events happen. You can +register multiple callbacks (for the same, different or overlapping +sets of events), and individually remove callbacks. If callbacks are +not removed, then they remain in force until the handle is closed. -The callback function C<cb> will be called whenever qemu or the guest -writes anything to the console. +In the current implementation, events are only generated +synchronously: that means that events (and hence callbacks) can only +happen while you are in the middle of making another libguestfs call. +The callback is called in the same thread. -Use this function to capture kernel messages and similar. +Events may contain a payload, usually nothing (void), an array of 64 +bit unsigned integers, or a message buffer. Payloads are discussed +later on. -Normally there is no log message handler, and log messages are just -discarded. +=head3 CLASSES OF EVENTS -=head2 guestfs_set_subprocess_quit_callback +=over 4 - typedef void (*guestfs_subprocess_quit_cb) (guestfs_h *g, void *opaque); - void guestfs_set_subprocess_quit_callback (guestfs_h *g, - guestfs_subprocess_quit_cb cb, - void *opaque); +=item GUESTFS_EVENT_CLOSE +(payload type: void) -The callback function C<cb> will be called when the child process -quits, either asynchronously or if killed by -L</guestfs_kill_subprocess>. (This corresponds to a transition from -any state to the CONFIG state). +The callback function will be called while the handle is being closed +(synchronously from L</guestfs_close>). -=head2 guestfs_set_launch_done_callback +Note that libguestfs installs an L<atexit(3)> handler to try to clean +up handles that are open when the program exits. This means that this +callback might be called indirectly from L<exit(3)>, which can cause +unexpected problems in higher-level languages (eg. if your HLL +interpreter has already been cleaned up by the time this is called, +and if your callback then jumps into some HLL function). - typedef void (*guestfs_launch_done_cb) (guestfs_h *g, void *opaque); - void guestfs_set_launch_done_callback (guestfs_h *g, - guestfs_launch_done_cb cb, - void *opaque); +If no callback is registered: the handle is closed without any +callback being invoked. -The callback function C<cb> will be called when the child process -becomes ready first time after it has been launched. (This -corresponds to a transition from LAUNCHING to the READY state). +=item GUESTFS_EVENT_SUBPROCESS_QUIT +(payload type: void) -=head2 guestfs_set_close_callback +The callback function will be called when the child process quits, +either asynchronously or if killed by L</guestfs_kill_subprocess>. +(This corresponds to a transition from any state to the CONFIG state). - typedef void (*guestfs_close_cb) (guestfs_h *g, void *opaque); - void guestfs_set_close_callback (guestfs_h *g, - guestfs_close_cb cb, - void *opaque); +If no callback is registered: the event is ignored. -The callback function C<cb> will be called while the handle -is being closed (synchronously from L</guestfs_close>). +=item GUESTFS_EVENT_LAUNCH_DONE +(payload type: void) -Note that libguestfs installs an L<atexit(3)> handler to try to -clean up handles that are open when the program exits. This -means that this callback might be called indirectly from -L<exit(3)>, which can cause unexpected problems in higher-level -languages (eg. if your HLL interpreter has already been cleaned -up by the time this is called, and if your callback then jumps -into some HLL function). +The callback function will be called when the child process becomes +ready first time after it has been launched. (This corresponds to a +transition from LAUNCHING to the READY state). -=head2 guestfs_set_progress_callback +If no callback is registered: the event is ignored. - typedef void (*guestfs_progress_cb) (guestfs_h *g, void *opaque, - int proc_nr, int serial, - uint64_t position, uint64_t total); - void guestfs_set_progress_callback (guestfs_h *g, - guestfs_progress_cb cb, - void *opaque); +=item GUESTFS_EVENT_PROGRESS +(payload type: array of 4 x uint64_t) Some long-running operations can generate progress messages. If this callback is registered, then it will be called each time a @@ -1766,7 +1755,9 @@ progress message is generated (usually two seconds after the operation started, and three times per second thereafter until it completes, although the frequency may change in future versions). -The callback receives two numbers: C<position> and C<total>. +The callback receives in the payload four unsigned 64 bit numbers +which are (in order): C<proc_nr>, C<serial>, C<position>, C<total>. + The units of C<total> are not defined, although for some operations C<total> may relate in some way to the amount of data to be transferred (eg. in bytes or megabytes), and @@ -1796,10 +1787,126 @@ requiring special code to detect this case. =back -The callback also receives the procedure number and serial number of -the call. These are only useful for debugging protocol issues, and -the callback can normally ignore them. The callback may want to -print these numbers in error messages or debugging messages. +The callback also receives the procedure number (C<proc_nr>) and +serial number (C<serial>) of the call. These are only useful for +debugging protocol issues, and the callback can normally ignore them. +The callback may want to print these numbers in error messages or +debugging messages. + +If no callback is registered: progress messages are discarded. + +=item GUESTFS_EVENT_APPLIANCE +(payload type: message buffer) + +The callback function is called whenever a log message is generated by +qemu, the appliance kernel, guestfsd (daemon), or utility programs. + +If the verbose flag (L</guestfs_set_verbose>) is set before launch +(L</guestfs_launch>) then additional debug messages are generated. + +If no callback is registered: the messages are discarded unless the +verbose flag is set in which case they are sent to stderr. You can +override the printing of verbose messages to stderr by setting up a +callback. + +=item GUESTFS_EVENT_LIBRARY +(payload type: message buffer) + +The callback function is called whenever a log message is generated by +the library part of libguestfs. + +If the verbose flag (L</guestfs_set_verbose>) is set then additional +debug messages are generated. + +If no callback is registered: the messages are discarded unless the +verbose flag is set in which case they are sent to stderr. You can +override the printing of verbose messages to stderr by setting up a +callback. + +=item GUESTFS_EVENT_TRACE +(payload type: message buffer) + +The callback function is called whenever a trace message is generated. +This only applies if the trace flag (L</guestfs_set_trace>) is set. + +If no callback is registered: the messages are 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. =head1 PRIVATE DATA AREA @@ -1833,8 +1940,7 @@ any way. As far as libguestfs is concerned, it need not be a valid pointer at all. In particular, libguestfs does I<not> try to free the data when the handle is closed. If the data must be freed, then the caller must either free it before calling L</guestfs_close> or must -set up a close callback to do it (see L</guestfs_set_close_callback>, -and note that only one callback can be registered for a handle). +set up a close callback to do it (see L</GUESTFS_EVENT_CLOSE>. The private data area is implemented using a hash table, and should be reasonably efficient for moderate numbers of keys. @@ -2100,8 +2206,8 @@ are distinguished by the normal length word being replaced by C<GUESTFS_PROGRESS_FLAG>, followed by a fixed size progress message. The library turns them into progress callbacks (see -C<guestfs_set_progress_callback>) if there is a callback registered, -or discards them if not. +L</GUESTFS_EVENT_PROGRESS>) if there is a callback registered, or +discards them if not. The daemon self-limits the frequency of progress messages it sends (see C<daemon/proto.c:notify_progress>). Not all calls generate diff --git a/src/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 e50985d..d2ed948 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.7.4
Seemingly Similar Threads
- [PATCH 2/5] threads: Acquire and release the lock around each public guestfs_* API.
- Callbacks, log messages etc.
- Re: [PATCH] Rust bindings: Implement Event features
- [PATCH] tests/qemu: Add program for tracing and analyzing boot times.
- [PATCH] New APIs: Model libvirt authentication events through the API.