This is an updated and extended version of the original patch: https://www.redhat.com/archives/libguestfs/2010-August/msg00163.html This adds OCaml and Perl bindings (both tested), support for progress bars in virt-resize, and adds progress notifications to a number of the simpler commands. Still to do is to add progress messages to more commands. There are still a few commands which would be fairly easy (eg. download and some t*-out commands). There are many others where implementing progress messages will be quite a lot more complex. Note: I have changed the default frequency of messages. They are now sent after two seconds, and every 1/3rd of a second thereafter. This makes the progress bars smoother and avoids aliasing effects when displaying time left in seconds. Note (2): All known comments about the previous patches have been incorporated. Rich.
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 01/12] Implement progress messages in the daemon and library.
This implements progress notification messages in the daemon, and adds a callback in the library to handle them. No calls are changed so far, so in fact no progress messages can be generated by this commit. For more details, see: https://www.redhat.com/archives/libguestfs/2010-July/msg00003.html https://www.redhat.com/archives/libguestfs/2010-July/msg00024.html --- daemon/daemon.h | 7 +++ daemon/proto.c | 96 +++++++++++++++++++++++++++++++++++++++++++++-- src/generator.ml | 21 ++++++++++- src/guestfs-internal.h | 2 + src/guestfs.c | 8 ++++ src/guestfs.h | 5 ++- src/guestfs.pod | 50 +++++++++++++++++++++++++ src/proto.c | 47 ++++++++++++++++++++++- 8 files changed, 226 insertions(+), 10 deletions(-) diff --git a/daemon/daemon.h b/daemon/daemon.h index 4c1b9b0..03e0d37 100644 --- a/daemon/daemon.h +++ b/daemon/daemon.h @@ -21,6 +21,7 @@ #include <stdio.h> #include <stdarg.h> +#include <stdint.h> #include <errno.h> #include <unistd.h> @@ -161,6 +162,12 @@ extern int send_file_end (int cancel); /* only call this if there is a FileOut parameter */ extern void reply (xdrproc_t xdrp, char *ret); +/* Notify progress to caller. This function is self-rate-limiting so + * you can call it as often as necessary. Actions which call this + * should add 'Progress' note in generator. + */ +extern void notify_progress (uint64_t position, uint64_t total); + /* Helper for functions that need a root filesystem mounted. * NB. Cannot be used for FileIn functions. */ diff --git a/daemon/proto.c b/daemon/proto.c index 628e86c..02ee692 100644 --- a/daemon/proto.c +++ b/daemon/proto.c @@ -26,6 +26,7 @@ #include <errno.h> #include <sys/param.h> /* defines MIN */ #include <sys/select.h> +#include <sys/time.h> #include <rpc/types.h> #include <rpc/xdr.h> @@ -43,6 +44,15 @@ int proc_nr; int serial; +/* Time at which we received the current request. */ +static struct timeval start_t; + +/* Time at which the last progress notification was sent. */ +static struct timeval last_progress_t; + +/* Counts the number of progress notifications sent during this call. */ +static int count_progress; + /* The daemon communications socket. */ static int sock; @@ -54,8 +64,6 @@ main_loop (int _sock) char lenbuf[4]; uint32_t len; struct guestfs_message_header hdr; - struct timeval start_t, end_t; - int64_t start_us, end_us, elapsed_us; sock = _sock; @@ -112,9 +120,9 @@ main_loop (int _sock) } #endif - /* In verbose mode, display the time taken to run each command. */ - if (verbose) - gettimeofday (&start_t, NULL); + gettimeofday (&start_t, NULL); + last_progress_t = start_t; + count_progress = 0; /* Decode the message header. */ xdrmem_create (&xdr, buf, len, XDR_DECODE); @@ -160,11 +168,14 @@ main_loop (int _sock) /* In verbose mode, display the time taken to run each command. */ if (verbose) { + struct timeval end_t; gettimeofday (&end_t, NULL); + int64_t start_us, end_us, elapsed_us; start_us = (int64_t) start_t.tv_sec * 1000000 + start_t.tv_usec; end_us = (int64_t) end_t.tv_sec * 1000000 + end_t.tv_usec; elapsed_us = end_us - start_us; + fprintf (stderr, "proc %d (%s) took %d.%02d seconds\n", proc_nr, proc_nr >= 0 && proc_nr < GUESTFS_PROC_NR_PROCS @@ -533,3 +544,78 @@ send_chunk (const guestfs_chunk *chunk) return err; } + +/* Initial delay before sending notification messages, and + * the period at which we send them thereafter. These times + * are in microseconds. + */ +#define NOTIFICATION_INITIAL_DELAY 2000000 +#define NOTIFICATION_PERIOD 333333 + +void +notify_progress (uint64_t position, uint64_t total) +{ + struct timeval now_t; + gettimeofday (&now_t, NULL); + + /* Always send a notification at 100%. This simplifies callers by + * allowing them to 'finish' the progress bar at 100% without + * needing special code. + */ + if (count_progress > 0 && position == total) + goto send; + + /* Calculate time in microseconds since the last progress message + * was sent out (or since the start of the call). + */ + int64_t last_us, now_us, elapsed_us; + last_us + (int64_t) last_progress_t.tv_sec * 1000000 + last_progress_t.tv_usec; + now_us = (int64_t) now_t.tv_sec * 1000000 + now_t.tv_usec; + elapsed_us = now_us - last_us; + + /* Rate limit. */ + if ((count_progress == 0 && elapsed_us < NOTIFICATION_INITIAL_DELAY) || + (count_progress > 0 && elapsed_us < NOTIFICATION_PERIOD)) + return; + + send: + /* We're going to send a message now ... */ + count_progress++; + last_progress_t = now_t; + + /* Send the header word. */ + XDR xdr; + char buf[128]; + uint32_t i = GUESTFS_PROGRESS_FLAG; + size_t len; + xdrmem_create (&xdr, buf, 4, XDR_ENCODE); + xdr_u_int (&xdr, &i); + xdr_destroy (&xdr); + + if (xwrite (sock, buf, 4) == -1) { + fprintf (stderr, "xwrite failed\n"); + exit (EXIT_FAILURE); + } + + guestfs_progress message = { + .proc = proc_nr, + .serial = serial, + .position = position, + .total = total, + }; + + xdrmem_create (&xdr, buf, sizeof buf, XDR_ENCODE); + if (!xdr_guestfs_progress (&xdr, &message)) { + fprintf (stderr, "xdr_guestfs_progress: failed to encode message\n"); + xdr_destroy (&xdr); + return; + } + len = xdr_getpos (&xdr); + xdr_destroy (&xdr); + + if (xwrite (sock, buf, len) == -1) { + fprintf (stderr, "xwrite failed\n"); + exit (EXIT_FAILURE); + } +} diff --git a/src/generator.ml b/src/generator.ml index c25c871..bbf313a 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -6327,11 +6327,12 @@ and generate_xdr () */ const GUESTFS_PROGRAM = 0x2000F5F5; -const GUESTFS_PROTOCOL_VERSION = 1; +const GUESTFS_PROTOCOL_VERSION = 2; /* These constants must be larger than any possible message length. */ const GUESTFS_LAUNCH_FLAG = 0xf5f55ff5; const GUESTFS_CANCEL_FLAG = 0xffffeeee; +const GUESTFS_PROGRESS_FLAG = 0xffff5555; enum guestfs_message_direction { GUESTFS_DIRECTION_CALL = 0, /* client -> daemon */ @@ -6370,6 +6371,23 @@ struct guestfs_chunk { /* data size is 0 bytes if the transfer has finished successfully */ opaque data<GUESTFS_MAX_CHUNK_SIZE>; }; + +/* Progress notifications. Daemon self-limits these messages to + * at most one per second. The daemon can send these messages + * at any time, and the caller should discard unexpected messages. + * 'position' and 'total' have undefined units; however they may + * have meaning for some calls. + * + * NB. guestfs___recv_from_daemon assumes the XDR-encoded + * structure is 24 bytes long. + */ +struct guestfs_progress { + guestfs_procedure proc; /* @0: GUESTFS_PROC_x */ + unsigned serial; /* @4: message serial number */ + unsigned hyper position; /* @8: 0 <= position <= total */ + unsigned hyper total; /* @16: total size of operation */ + /* @24: size of structure */ +}; " (* Generate the guestfs-structs.h file. *) @@ -6869,6 +6887,7 @@ and generate_linker_script () "guestfs_set_launch_done_callback"; "guestfs_set_log_message_callback"; "guestfs_set_out_of_memory_handler"; + "guestfs_set_progress_callback"; "guestfs_set_subprocess_quit_callback"; (* Unofficial parts of the API: the bindings code use these diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index e37c9c2..32a6c2a 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -122,6 +122,8 @@ struct guestfs_h void * launch_done_cb_data; guestfs_close_cb close_cb; void * close_cb_data; + guestfs_progress_cb progress_cb; + void * progress_cb_data; int msg_next_serial; diff --git a/src/guestfs.c b/src/guestfs.c index eaacd39..206347e 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -645,3 +645,11 @@ guestfs_set_close_callback (guestfs_h *g, 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; +} diff --git a/src/guestfs.h b/src/guestfs.h index 3cff484..ec88f22 100644 --- a/src/guestfs.h +++ b/src/guestfs.h @@ -34,6 +34,8 @@ extern "C" { #endif +#include <stdint.h> + typedef struct guestfs_h guestfs_h; /*--- Connection management ---*/ @@ -57,14 +59,15 @@ typedef void (*guestfs_log_message_cb) (guestfs_h *g, void *data, char *buf, int typedef void (*guestfs_subprocess_quit_cb) (guestfs_h *g, void *data); typedef void (*guestfs_launch_done_cb) (guestfs_h *g, void *data); typedef void (*guestfs_close_cb) (guestfs_h *g, void *data); +typedef void (*guestfs_progress_cb) (guestfs_h *g, void *data, int proc_nr, int serial, uint64_t position, uint64_t total); extern void guestfs_set_log_message_callback (guestfs_h *g, guestfs_log_message_cb cb, void *opaque); extern void guestfs_set_subprocess_quit_callback (guestfs_h *g, guestfs_subprocess_quit_cb cb, void *opaque); extern void guestfs_set_launch_done_callback (guestfs_h *g, guestfs_launch_done_cb cb, void *opaque); extern void guestfs_set_close_callback (guestfs_h *g, guestfs_close_cb cb, void *opaque); +extern void guestfs_set_progress_callback (guestfs_h *g, guestfs_progress_cb cb, void *opaque); /*--- Structures and actions ---*/ -#include <stdint.h> #include <rpc/types.h> #include <rpc/xdr.h> #include <guestfs-structs.h> diff --git a/src/guestfs.pod b/src/guestfs.pod index 590c768..6a956ed 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -1186,6 +1186,56 @@ 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). +=head2 guestfs_set_progress_callback + + 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); + +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 two numbers: C<position> and 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 +C<position> may be the portion which has been transferred. + +The only defined and stable parts of the API are: + +=over 4 + +=item * + +The callback can display to the user some type of progress bar or +indicator which shows the ratio of C<position>:C<total>. + +=item * + +0 E<lt>= C<position> E<lt>= C<total> + +=item * + +If any progress notification is sent during a call, then a final +progress notification is always sent when C<position> = C<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. + +=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. + =head1 BLOCK DEVICE NAMING In the kernel there is now quite a profusion of schemata for naming diff --git a/src/proto.c b/src/proto.c index ad173c6..5d924e8 100644 --- a/src/proto.c +++ b/src/proto.c @@ -373,7 +373,15 @@ guestfs___send_to_daemon (guestfs_h *g, const void *v_buf, size_t n) * * It also checks for EOF (qemu died) and passes that up through the * child_cleanup function above. + * + * Progress notifications are handled transparently by this function. + * If the callback exists, it is called. The caller of this function + * will not see GUESTFS_PROGRESS_FLAG. */ + +/* Size of guestfs_progress message on the wire. */ +#define PROGRESS_MESSAGE_SIZE 24 + int guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) { @@ -400,7 +408,13 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) */ ssize_t nr = -4; - while (nr < (ssize_t) *size_rtn) { + for (;;) { + ssize_t message_size + *size_rtn != GUESTFS_PROGRESS_FLAG ? + *size_rtn : PROGRESS_MESSAGE_SIZE; + if (nr >= message_size) + break; + rset2 = rset; int r = select (max_fd+1, &rset2, NULL, NULL, NULL); if (r == -1) { @@ -450,6 +464,11 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) xdr_uint32_t (&xdr, size_rtn); xdr_destroy (&xdr); + /* *size_rtn changed, recalculate message_size */ + message_size + *size_rtn != GUESTFS_PROGRESS_FLAG ? + *size_rtn : PROGRESS_MESSAGE_SIZE; + if (*size_rtn == GUESTFS_LAUNCH_FLAG) { if (g->state != LAUNCHING) error (g, _("received magic signature from guestfsd, but in state %d"), @@ -463,6 +482,8 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) } else if (*size_rtn == GUESTFS_CANCEL_FLAG) return 0; + else if (*size_rtn == GUESTFS_PROGRESS_FLAG) + /*FALLTHROUGH*/; /* If this happens, it's pretty bad and we've probably lost * synchronization. */ @@ -473,11 +494,11 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) } /* Allocate the complete buffer, size now known. */ - *buf_rtn = safe_malloc (g, *size_rtn); + *buf_rtn = safe_malloc (g, message_size); /*FALLTHROUGH*/ } - size_t sizetoread = *size_rtn - nr; + size_t sizetoread = message_size - nr; if (sizetoread > BUFSIZ) sizetoread = BUFSIZ; r = read (g->sock, (char *) (*buf_rtn) + nr, sizetoread); @@ -524,6 +545,26 @@ 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) { + 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); + } + + free (*buf_rtn); + *buf_rtn = NULL; + + /* Process next message. */ + return guestfs___recv_from_daemon (g, size_rtn, buf_rtn); + } + return 0; } -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 02/12] Add progress messages to copy-size command.
--- daemon/dd.c | 19 +++++++++++++++---- src/generator.ml | 11 ++++++++++- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/daemon/dd.c b/daemon/dd.c index 2bbe855..c004d92 100644 --- a/daemon/dd.c +++ b/daemon/dd.c @@ -73,7 +73,7 @@ do_dd (const char *src, const char *dest) } int -do_copy_size (const char *src, const char *dest, int64_t size) +do_copy_size (const char *src, const char *dest, int64_t ssize) { char *buf; int src_fd, dest_fd; @@ -112,9 +112,19 @@ do_copy_size (const char *src, const char *dest, int64_t size) return -1; } - while (size > 0) { + uint64_t position = 0, size = (uint64_t) ssize; + + while (position < size) { char buf[1024*1024]; - size_t n = size > (int64_t) (sizeof buf) ? sizeof buf : (size_t) size; + + /* Calculate bytes to copy. */ + uint64_t n64 = size - position; + size_t n; + if (n64 > sizeof buf) + n = sizeof buf; + else + n = (size_t) n64; /* safe because of if condition */ + ssize_t r = read (src_fd, buf, n); if (r == -1) { reply_with_perror ("%s: read", src); @@ -136,7 +146,8 @@ do_copy_size (const char *src, const char *dest, int64_t size) return -1; } - size -= r; + position += r; + notify_progress ((uint64_t) position, (uint64_t) size); } if (close (src_fd) == -1) { diff --git a/src/generator.ml b/src/generator.ml index bbf313a..ec93850 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -192,6 +192,7 @@ type flags | NotInDocs (* do not add this function to documentation *) | DeprecatedBy of string (* function is deprecated, use .. instead *) | Optional of string (* function is part of an optional group *) + | Progress (* function can generate progress messages *) and fish_output_t | FishOutputOctal (* for int return, print in octal *) @@ -4875,7 +4876,7 @@ calls to associate logical volumes and volume groups. See also C<guestfs_vgpvuuids>."); - ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [], + ("copy_size", (RErr, [Dev_or_Path "src"; Dev_or_Path "dest"; Int64 "size"]), 227, [Progress], [InitBasicFS, Always, TestOutputBuffer ( [["write"; "/src"; "hello, world"]; ["copy_size"; "/src"; "/dest"; "5"]; @@ -5799,6 +5800,12 @@ let seq_of_test = function | TestLastFail s -> s (* Handling for function flags. *) +let progress_message + "This long-running command can generate progress notification messages +so that the caller can display a progress bar or indicator. +To receive these messages, the caller must register a progress +callback. See L<guestfs(3)/guestfs_set_progress_callback>." + let protocol_limit_warning "Because of the message protocol, there is a transfer limit of somewhere between 2MB and 4MB. See L<guestfs(3)/PROTOCOL LIMITS>." @@ -6133,6 +6140,8 @@ I<The caller must free the strings and the array after use>.\n\n" The size of the returned buffer is written to C<*size_r>. I<The caller must free the returned buffer after use>.\n\n" ); + if List.mem Progress flags then + pr "%s\n\n" progress_message; if List.mem ProtocolLimitWarning flags then pr "%s\n\n" protocol_limit_warning; if List.mem DangerWillRobinson flags then -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 03/12] Add progress messages to fill command.
--- daemon/fill.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/daemon/fill.c b/daemon/fill.c index 807ee39..a3e3d37 100644 --- a/daemon/fill.c +++ b/daemon/fill.c @@ -66,6 +66,7 @@ do_fill (int c, int len, const char *path) return -1; } n += r; + notify_progress ((uint64_t) n, (uint64_t) len_sz); } if (close (fd) == -1) { -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 04/12] Add progress messages to fill-pattern command.
--- daemon/fill.c | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/daemon/fill.c b/daemon/fill.c index a3e3d37..8686226 100644 --- a/daemon/fill.c +++ b/daemon/fill.c @@ -113,6 +113,7 @@ do_fill_pattern (const char *pattern, int len, const char *path) return -1; } n += wrlen; + notify_progress ((uint64_t) n, (uint64_t) len_sz); } if (close (fd) == -1) { -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 05/12] Add progress messages to zero command.
--- daemon/zero.c | 4 +++- 1 files changed, 3 insertions(+), 1 deletions(-) diff --git a/daemon/zero.c b/daemon/zero.c index df4ebd6..43089e0 100644 --- a/daemon/zero.c +++ b/daemon/zero.c @@ -42,12 +42,14 @@ do_zero (const char *device) memset (buf, 0, sizeof buf); - for (i = 0; i < 32; ++i) + for (i = 0; i < 32; ++i) { if (write (fd, buf, sizeof buf) != sizeof buf) { reply_with_perror ("write: %s", device); close (fd); return -1; } + notify_progress ((uint64_t) i, 32); + } if (close (fd) == -1) { reply_with_perror ("close: %s", device); -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 06/12] Add progress messages to zero-device command.
--- daemon/zero.c | 21 ++++++++++++++++----- 1 files changed, 16 insertions(+), 5 deletions(-) diff --git a/daemon/zero.c b/daemon/zero.c index 43089e0..8fbd963 100644 --- a/daemon/zero.c +++ b/daemon/zero.c @@ -62,9 +62,10 @@ do_zero (const char *device) int do_zero_device (const char *device) { - int64_t size = do_blockdev_getsize64 (device); - if (size == -1) + int64_t ssize = do_blockdev_getsize64 (device); + if (ssize == -1) return -1; + uint64_t size = (uint64_t) ssize; int fd = open (device, O_WRONLY); if (fd == -1) { @@ -75,8 +76,16 @@ do_zero_device (const char *device) char buf[1024*1024]; memset (buf, 0, sizeof buf); - while (size > 0) { - size_t n = (size_t) size > sizeof buf ? sizeof buf : (size_t) size; + uint64_t pos = 0; + + while (pos < size) { + uint64_t n64 = size - pos; + size_t n; + if (n64 > sizeof buf) + n = sizeof buf; + else + n = (size_t) n64; /* safe because of if condition */ + ssize_t r = write (fd, buf, n); if (r == -1) { reply_with_perror ("write: %s (with %" PRId64 " bytes left to write)", @@ -84,7 +93,9 @@ do_zero_device (const char *device) close (fd); return -1; } - size -= r; + + pos += r; + notify_progress (pos, size); } if (close (fd) == -1) { -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 07/12] fish: Detect UTF-8 output and open termcap/terminfo database.
Provide a generic mechanism within guestfish to detect if output if UTF-8 and to open the termcap (or terminfo) database for the current terminal type. --- fish/fish.c | 35 +++++++++++++++++++++++++++++++++++ fish/fish.h | 2 ++ 2 files changed, 37 insertions(+), 0 deletions(-) diff --git a/fish/fish.c b/fish/fish.c index c535e06..c4ade8c 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -30,6 +30,7 @@ #include <sys/types.h> #include <sys/wait.h> #include <locale.h> +#include <langinfo.h> #include <termios.h> #ifdef HAVE_LIBREADLINE @@ -69,6 +70,7 @@ struct mp { char *mountpoint; }; +static void set_up_terminal (void); static char add_drives (struct drv *drv, char next_drive); static void prepare_drives (struct drv *drv); static void mount_mps (struct mp *mp); @@ -96,6 +98,8 @@ int command_num = 0; int keys_from_stdin = 0; const char *libvirt_uri = NULL; int inspector = 0; +int utf8_mode = 0; +int have_terminfo = 0; static void __attribute__((noreturn)) usage (int status) @@ -159,6 +163,8 @@ main (int argc, char *argv[]) bindtextdomain (PACKAGE, LOCALEBASEDIR); textdomain (PACKAGE); + set_up_terminal (); + enum { HELP_OPTION = CHAR_MAX + 1 }; static const char *options = "a:c:d:Df:h::im:nN:rv?Vx"; @@ -509,6 +515,35 @@ main (int argc, char *argv[]) exit (EXIT_SUCCESS); } +/* The <term.h> header file which defines this has "issues". */ +extern int tgetent (char *, const char *); + +static void +set_up_terminal (void) +{ + /* http://www.cl.cam.ac.uk/~mgk25/unicode.html#activate */ + utf8_mode = STREQ (nl_langinfo (CODESET), "UTF-8"); + + char *term = getenv ("TERM"); + if (term == NULL) { + //fprintf (stderr, _("guestfish: TERM (terminal type) not defined.\n")); + return; + } + + int r = tgetent (NULL, term); + if (r == -1) { + fprintf (stderr, _("guestfish: could not access termcap or terminfo database.\n")); + return; + } + if (r == 0) { + fprintf (stderr, _("guestfish: terminal type \"%s\" not defined.\n"), + term); + return; + } + + have_terminfo = 1; +} + void pod2text (const char *name, const char *shortdesc, const char *str) { diff --git a/fish/fish.h b/fish/fish.h index 660b8ee..8106610 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -53,6 +53,8 @@ extern int read_only; extern int quit; extern int verbose; extern int command_num; +extern int utf8_mode; +extern int have_terminfo; extern const char *libvirt_uri; extern int issue_command (const char *cmd, char *argv[], const char *pipe); extern void pod2text (const char *name, const char *shortdesc, const char *body); -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 08/12] fish: Implement progress bars in guestfish.
The output you get will look something like this:><fs> copy-size /large /large2 2G50% ????????????????????????????????????????????????????? 00:30 The progress bar is updated 3 times per second, and is not displayed at all for operations which take less than two seconds. You can disable progress bars by using the flag --no-progress-bars, and you can enable progress bars in non-interactive sessions with the flag --progress-bars. A good way to test this is to use the following command: guestfish --progress-bars \ -N disk:10G \ zero-device /dev/sda (adjust "10G" to get different lengths of time). --- fish/Makefile.am | 3 +- fish/fish.c | 22 ++++++ fish/fish.h | 5 ++ fish/guestfish.pod | 36 +++++++++ fish/progress.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fish/reopen.c | 3 + fish/rmsd.h | 67 +++++++++++++++++ po/POTFILES.in | 1 + 8 files changed, 339 insertions(+), 1 deletions(-) create mode 100644 fish/progress.c create mode 100644 fish/rmsd.h diff --git a/fish/Makefile.am b/fish/Makefile.am index 4060f1f..e1e8833 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -49,6 +49,7 @@ guestfish_SOURCES = \ man.c \ more.c \ prep.c \ + progress.c \ rc.c \ reopen.c \ supported.c \ @@ -72,7 +73,7 @@ guestfish_CFLAGS = \ guestfish_LDADD = \ $(LIBVIRT_LIBS) $(LIBXML2_LIBS) \ - $(top_builddir)/src/libguestfs.la $(LIBREADLINE) + $(top_builddir)/src/libguestfs.la $(LIBREADLINE) -lm # Make libguestfs use the convenience library. noinst_LTLIBRARIES = librc_protocol.la diff --git a/fish/fish.c b/fish/fish.c index c4ade8c..9696545 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -85,6 +85,8 @@ static void cleanup_readline (void); static void add_history_line (const char *); #endif +static int override_progress_bars = -1; + /* Currently open libguestfs handle. */ guestfs_h *g; @@ -100,6 +102,7 @@ const char *libvirt_uri = NULL; int inspector = 0; int utf8_mode = 0; int have_terminfo = 0; +int progress_bars = 0; static void __attribute__((noreturn)) usage (int status) @@ -137,6 +140,8 @@ usage (int status) " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" " -n|--no-sync Don't autosync\n" " -N|--new type Create prepared disk (test1.img, ...)\n" + " --progress-bars Enable progress bars even when not interactive\n" + " --no-progress-bars Disable progress bars\n" " --remote[=pid] Send commands to remote %s\n" " -r|--ro Mount read-only\n" " --selinux Enable SELinux support\n" @@ -182,6 +187,8 @@ main (int argc, char *argv[]) { "new", 1, 0, 'N' }, { "no-dest-paths", 0, 0, 'D' }, { "no-sync", 0, 0, 'n' }, + { "progress-bars", 0, 0, 0 }, + { "no-progress-bars", 0, 0, 0 }, { "remote", 2, 0, 0 }, { "ro", 0, 0, 'r' }, { "selinux", 0, 0, 0 }, @@ -267,6 +274,10 @@ main (int argc, char *argv[]) guestfs_set_selinux (g, 1); } else if (STREQ (long_options[option_index].name, "keys-from-stdin")) { keys_from_stdin = 1; + } else if (STREQ (long_options[option_index].name, "progress-bars")) { + override_progress_bars = 1; + } else if (STREQ (long_options[option_index].name, "no-progress-bars")) { + override_progress_bars = 0; } else { fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), program_name, long_options[option_index].name, option_index); @@ -500,6 +511,15 @@ main (int argc, char *argv[]) } } + /* Decide if we display progress bars. */ + progress_bars + override_progress_bars >= 0 + ? override_progress_bars + : (optind >= argc && isatty (0)); + + if (progress_bars) + guestfs_set_progress_callback (g, progress_callback, NULL); + /* Interactive, shell script, or command(s) on the command line? */ if (optind >= argc) { if (isatty (0)) @@ -963,6 +983,8 @@ issue_command (const char *cmd, char *argv[], const char *pipecmd) int pid = 0; int i, r; + reset_progress_bar (); + /* This counts the commands issued, starting at 1. */ command_num++; diff --git a/fish/fish.h b/fish/fish.h index 8106610..be357f5 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -55,6 +55,7 @@ extern int verbose; extern int command_num; extern int utf8_mode; extern int have_terminfo; +extern int progress_bars; extern const char *libvirt_uri; extern int issue_command (const char *cmd, char *argv[], const char *pipe); extern void pod2text (const char *name, const char *shortdesc, const char *body); @@ -122,6 +123,10 @@ extern prep_data *create_prepared_file (const char *type_string, extern void prepare_drive (const char *filename, prep_data *data, const char *device); +/* 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); + /* in rc.c (remote control) */ extern void rc_listen (void) __attribute__((noreturn)); extern int rc_remote (int pid, const char *cmd, int argc, char *argv[], diff --git a/fish/guestfish.pod b/fish/guestfish.pod index cf1140a..3b9aa20 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -232,6 +232,17 @@ alternative to the I<-a> option: whereas I<-a> adds an existing disk, I<-N> creates a preformatted disk with a filesystem and adds it. See L</PREPARED DISK IMAGES> below. +=item B<--progress-bars> + +Enable progress bars, even when guestfish is used non-interactively. + +Progress bars are enabled by default when guestfish is used as an +interactive shell. + +=item B<--no-progress-bars> + +Disable progress bars. + =item B<--remote[=pid]> Send remote commands to C<$GUESTFISH_PID> or C<pid>. See section @@ -729,6 +740,31 @@ Create a blank 200MB disk: guestfish -N disk:200M +=head1 PROGRESS BARS + +Some (not all) long-running commands send progress notification +messages as they are running. Guestfish turns these messages into +progress bars. + +When a command that supports progress bars takes longer than two +seconds to run, and if progress bars are enabled, then you will see +one appearing below the command: + + ><fs> copy-size /large-file /another-file 2048M + / 10% [#####-----------------------------------------] 00:30 + +The spinner on the left hand side moves round once for every progress +notification received from the backend. This is a (reasonably) golden +assurance that the command is "doing something" even if the progress +bar is not moving, because the command is able to send the progress +notifications. When the bar reaches 100% and the command finishes, +the spinner disappears. + +Progress bars are enabled by default when guestfish is used +interactively. You can enable them even for non-interactive modes +using I<--progress-bars>, and you can disable them completely using +I<--no-progress-bars>. + =head1 GUESTFISH COMMANDS The commands in this section are guestfish convenience commands, in diff --git a/fish/progress.c b/fish/progress.c new file mode 100644 index 0000000..1468989 --- /dev/null +++ b/fish/progress.c @@ -0,0 +1,203 @@ +/* guestfish - the filesystem interactive shell + * Copyright (C) 2010 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <inttypes.h> +#include <math.h> + +#include <guestfs.h> + +#include "fish.h" +#include "rmsd.h" + +/* Include these last since they redefine symbols such as 'lines' + * which seriously breaks other headers. + */ +#include <term.h> +#include <curses.h> + +/* Provided by termcap or terminfo emulation, but not defined + * in any header file. + */ +extern const char *UP; + +static char +spinner (int count) +{ + return "/-\\|"[count&3]; +} + +static double start; /* start time of command */ +static int count; /* number of progress notifications per cmd */ +static struct rmsd rmsd; /* running mean and standard deviation */ + +/* This function is called just before we issue any command. */ +void +reset_progress_bar (void) +{ + /* The time at which this command was issued. */ + struct timeval start_t; + gettimeofday (&start_t, NULL); + + start = start_t.tv_sec + start_t.tv_usec / 1000000.; + + count = 0; + + rmsd_init (&rmsd); +} + +/* Return remaining time estimate (in seconds) for current call. + * + * This returns the running mean estimate of remaining time, but if + * the latest estimate of total time is greater than two s.d.'s from + * the running mean then we don't print anything because we're not + * confident that the estimate is meaningful. (Returned value is <0.0 + * when nothing should be printed). + */ +static double +estimate_remaining_time (double ratio) +{ + if (ratio <= 0.) + return -1.0; + + struct timeval now_t; + gettimeofday (&now_t, NULL); + + double now = now_t.tv_sec + now_t.tv_usec / 1000000.; + /* We've done 'ratio' of the work in 'now - start' seconds. */ + double time_passed = now - start; + + double total_time = time_passed / ratio; + + /* Add total_time to running mean and s.d. and then see if our + * estimate of total time is meaningful. + */ + rmsd_add_sample (&rmsd, total_time); + + double mean = rmsd_get_mean (&rmsd); + double sd = rmsd_get_standard_deviation (&rmsd); + if (fabs (total_time - mean) >= 2.0*sd) + return -1.0; + + /* Don't return early estimates. */ + if (time_passed < 3.0) + return -1.0; + + return total_time - time_passed; +} + +/* The overhead is how much we subtract before we get to the progress + * bar itself. + * + * / 100% [########---------------] xx:xx + * | | | | | + * | | | | time (5 cols) + * | | | | + * | | open paren + close paren + space (3 cols) + * | | + * | percentage and space (5 cols) + * | + * spinner and space (2 cols) + * + * Total = 2 + 5 + 3 + 5 = 15 + */ +#define COLS_OVERHEAD 15 + +/* 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) +{ + if (have_terminfo == 0) { + dumb: + printf ("%" PRIu64 "/%" PRIu64 "\n", position, total); + } else { + int cols = tgetnum ((char *) "co"); + if (cols < 32) goto dumb; + + /* Update an existing progress bar just printed? */ + if (count > 0) + tputs (UP, 2, putchar); + count++; + + double ratio = (double) position / total; + if (ratio < 0) ratio = 0; else if (ratio > 1) ratio = 1; + + if (ratio < 1) { + int percent = 100.0 * ratio; + printf ("%c%3d%% ", spinner (count), percent); + } else { + fputs (" 100% ", stdout); + } + + int dots = ratio * (double) (cols - COLS_OVERHEAD); + + const char *s_open, *s_dot, *s_dash, *s_close; + if (utf8_mode) { + s_open = "\u27e6"; + s_dot = "\u2593"; + s_dash = "\u2591"; + s_close = "\u27e7"; + } else { + s_open = "["; + s_dot = "#"; + s_dash = "-"; + s_close = "]"; + } + + fputs (s_open, stdout); + int i; + for (i = 0; i < dots; ++i) + fputs (s_dot, stdout); + for (i = dots; i < cols - COLS_OVERHEAD; ++i) + fputs (s_dash, stdout); + fputs (s_close, stdout); + fputc (' ', stdout); + + /* Time estimate. */ + double estimate = estimate_remaining_time (ratio); + if (estimate >= 100.0 * 60.0 * 60.0 /* >= 100 hours */) { + /* Display hours<h> */ + estimate /= 60. * 60.; + int hh = floor (estimate); + printf (">%dh", hh); + } else if (estimate >= 100.0 * 60.0 /* >= 100 minutes */) { + /* Display hours<h>minutes */ + estimate /= 60. * 60.; + int hh = floor (estimate); + double ignore; + int mm = floor (modf (estimate, &ignore) * 60.); + printf ("%02dh%02d", hh, mm); + } else if (estimate >= 0.0) { + /* Display minutes:seconds */ + estimate /= 60.; + int mm = floor (estimate); + double ignore; + int ss = floor (modf (estimate, &ignore) * 60.); + printf ("%02d:%02d", mm, ss); + } + else /* < 0 means estimate was not meaningful */ + fputs ("--:--", stdout); + + fputc ('\n', stdout); + } +} diff --git a/fish/reopen.c b/fish/reopen.c index 2dfc8db..9e19018 100644 --- a/fish/reopen.c +++ b/fish/reopen.c @@ -66,6 +66,9 @@ do_reopen (const char *cmd, int argc, char *argv[]) if (p) guestfs_set_path (g2, p); + if (progress_bars) + guestfs_set_progress_callback (g2, progress_callback, NULL); + /* Close the original handle. */ guestfs_close (g); g = g2; diff --git a/fish/rmsd.h b/fish/rmsd.h new file mode 100644 index 0000000..d4335bd --- /dev/null +++ b/fish/rmsd.h @@ -0,0 +1,67 @@ +/* libguestfs - guestfish shell + * Copyright (C) 2010 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. + */ + +#ifndef FISH_RMSD_H +#define FISH_RMSD_H + +/* Compute the running mean and standard deviation from the + * series of estimated values. + * + * Method: + * http://en.wikipedia.org/wiki/Standard_deviation#Rapid_calculation_methods + * Checked in a test program against answers given by Wolfram Alpha. + */ +struct rmsd { + double a; /* mean */ + double i; /* number of samples */ + double q; +}; + +static void +rmsd_init (struct rmsd *r) +{ + r->a = 0; + r->i = 1; + r->q = 0; +} + +static void +rmsd_add_sample (struct rmsd *r, double x) +{ + double a_next, q_next; + + a_next = r->a + (x - r->a) / r->i; + q_next = r->q + (x - r->a) * (x - a_next); + r->a = a_next; + r->q = q_next; + r->i += 1.0; +} + +static double +rmsd_get_mean (const struct rmsd *r) +{ + return r->a; +} + +static double +rmsd_get_standard_deviation (const struct rmsd *r) +{ + return sqrt (r->q / (r->i - 1.0)); +} + +#endif /* FISH_RMSD_H */ diff --git a/po/POTFILES.in b/po/POTFILES.in index e5fb857..3faa1fb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -81,6 +81,7 @@ fish/lcd.c fish/man.c fish/more.c fish/prep.c +fish/progress.c fish/rc.c fish/reopen.c fish/supported.c -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 09/12] Implement private data area.
The private data area is a hash table which is associated with libguestfs handles, that C callers may use to store arbitrary data for the lifetime of the handle. Later the OCaml bindings will use this in order to implement callbacks. --- src/generator.ml | 2 + src/guestfs-internal.h | 3 ++ src/guestfs.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ src/guestfs.h | 4 ++ src/guestfs.pod | 38 ++++++++++++++++++++++ 5 files changed, 128 insertions(+), 0 deletions(-) diff --git a/src/generator.ml b/src/generator.ml index ec93850..5580f1a 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -6890,12 +6890,14 @@ and generate_linker_script () "guestfs_close"; "guestfs_get_error_handler"; "guestfs_get_out_of_memory_handler"; + "guestfs_get_private"; "guestfs_last_error"; "guestfs_set_close_callback"; "guestfs_set_error_handler"; "guestfs_set_launch_done_callback"; "guestfs_set_log_message_callback"; "guestfs_set_out_of_memory_handler"; + "guestfs_set_private"; "guestfs_set_progress_callback"; "guestfs_set_subprocess_quit_callback"; diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 32a6c2a..ed82fde 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -132,6 +132,9 @@ struct guestfs_h */ struct inspect_fs *fses; size_t nr_fses; + + /* Private data area. */ + struct hash_table *pda; }; /* Per-filesystem data stored for inspect_os. */ diff --git a/src/guestfs.c b/src/guestfs.c index 206347e..b958689 100644 --- a/src/guestfs.c +++ b/src/guestfs.c @@ -34,6 +34,7 @@ #include <sys/select.h> #include <dirent.h> #include <signal.h> +#include <assert.h> #include <rpc/types.h> #include <rpc/xdr.h> @@ -63,6 +64,8 @@ #include "c-ctype.h" #include "glthread/lock.h" +#include "hash.h" +#include "hash-pjw.h" #include "ignore-value.h" #include "guestfs.h" @@ -244,6 +247,8 @@ guestfs_close (guestfs_h *g) } gl_lock_unlock (handles_lock); + if (g->pda) + hash_free (g->pda); free (g->last_error); free (g->path); free (g->qemu); @@ -653,3 +658,79 @@ guestfs_set_progress_callback (guestfs_h *g, 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. + */ +struct pda_entry { + char *key; /* key */ + void *data; /* opaque user data pointer */ +}; + +static size_t +hasher (void const *x, size_t table_size) +{ + struct pda_entry const *p = x; + return hash_pjw (p->key, table_size); +} + +static bool +comparator (void const *x, void const *y) +{ + struct pda_entry const *a = x; + struct pda_entry const *b = y; + return STREQ (a->key, b->key); +} + +static void +freer (void *x) +{ + if (x) { + struct pda_entry *p = x; + free (p->key); + free (p); + } +} + +void +guestfs_set_private (guestfs_h *g, const char *key, void *data) +{ + if (g->pda == NULL) { + g->pda = hash_initialize (16, NULL, hasher, comparator, freer); + if (g->pda == NULL) + g->abort_cb (); + } + + struct pda_entry *new_entry = safe_malloc (g, sizeof *new_entry); + new_entry->key = safe_strdup (g, key); + new_entry->data = data; + + struct pda_entry *old_entry = hash_delete (g->pda, new_entry); + freer (old_entry); + + struct pda_entry *entry = hash_insert (g->pda, new_entry); + if (entry == NULL) + g->abort_cb (); + assert (entry == new_entry); +} + +static inline char * +bad_cast (char const *s) +{ + return (char *) s; +} + +void * +guestfs_get_private (guestfs_h *g, const char *key) +{ + if (g->pda == NULL) + return NULL; /* no keys have been set */ + + const struct pda_entry k = { .key = bad_cast (key) }; + struct pda_entry *entry = hash_lookup (g->pda, &k); + if (entry) + return entry->data; + else + return NULL; +} diff --git a/src/guestfs.h b/src/guestfs.h index ec88f22..0bfad4f 100644 --- a/src/guestfs.h +++ b/src/guestfs.h @@ -67,6 +67,10 @@ extern void guestfs_set_launch_done_callback (guestfs_h *g, guestfs_launch_done_ extern void guestfs_set_close_callback (guestfs_h *g, guestfs_close_cb cb, void *opaque); extern void guestfs_set_progress_callback (guestfs_h *g, guestfs_progress_cb cb, void *opaque); +/*--- Private data area ---*/ +extern void guestfs_set_private (guestfs_h *g, const char *key, void *data); +extern void *guestfs_get_private (guestfs_h *g, const char *key); + /*--- Structures and actions ---*/ #include <rpc/types.h> #include <rpc/xdr.h> diff --git a/src/guestfs.pod b/src/guestfs.pod index 6a956ed..455cd89 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -1236,6 +1236,44 @@ 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. +=head1 PRIVATE DATA AREA + +You can attach named pieces of private data to the libguestfs handle, +and fetch them by name for the lifetime of the handle. This is called +the private data area and is only available from the C API. + +To attach a named piece of data, use the following call: + + void guestfs_set_private (guestfs_h *g, const char *key, void *data); + +C<key> is the name to associate with this data, and C<data> is an +arbitrary pointer (which can be C<NULL>). Any previous item with the +same name is overwritten. + +You can use any C<key> you want, but names beginning with an +underscore character are reserved for internal libguestfs purposes +(for implementing language bindings). It is recommended to prefix the +name with some unique string to avoid collisions with other users. + +To retrieve the pointer, use: + + void *guestfs_get_private (guestfs_h *g, const char *key); + +This function returns C<NULL> if either no data is found associated +with C<key>, or if the user previously set the C<key>'s C<data> +pointer to C<NULL>. + +Libguestfs does not try to look at or interpret the C<data> pointer in +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). + +The private data area is implemented using a hash table, and should be +reasonably efficient for moderate numbers of keys. + =head1 BLOCK DEVICE NAMING In the kernel there is now quite a profusion of schemata for naming -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 10/12] ocaml: bindings to progress callback.
--- ocaml/guestfs_c.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/generator.ml | 29 ++++++++++++++++ 2 files changed, 120 insertions(+), 2 deletions(-) diff --git a/ocaml/guestfs_c.c b/ocaml/guestfs_c.c index 71f416a..7648ba9 100644 --- a/ocaml/guestfs_c.c +++ b/ocaml/guestfs_c.c @@ -1,5 +1,5 @@ /* libguestfs - * Copyright (C) 2009 Red Hat Inc. + * Copyright (C) 2009-2010 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 @@ -30,9 +30,14 @@ #include <caml/fail.h> #include <caml/memory.h> #include <caml/mlvalues.h> +#include <caml/printexc.h> +#include <caml/signals.h> #include "guestfs_c.h" +static void clear_progress_callback (guestfs_h *g); +static void progress_callback (guestfs_h *g, void *data, int proc_nr, int serial, uint64_t position, uint64_t total); + /* This macro was added in OCaml 3.10. Backport for earlier versions. */ #ifndef CAMLreturnT #define CAMLreturnT(type, result) do{ \ @@ -45,13 +50,18 @@ /* These prototypes are solely to quiet gcc warning. */ CAMLprim value ocaml_guestfs_create (void); CAMLprim value ocaml_guestfs_close (value gv); +CAMLprim value ocaml_guestfs_set_progress_callback (value gv, value closure); +CAMLprim value ocaml_guestfs_clear_progress_callback (value gv); /* Allocate handles and deal with finalization. */ static void guestfs_finalize (value gv) { guestfs_h *g = Guestfs_val (gv); - if (g) guestfs_close (g); + if (g) { + clear_progress_callback (g); + guestfs_close (g); + } } static struct custom_operations guestfs_custom_operations = { @@ -162,3 +172,82 @@ ocaml_guestfs_free_strings (char **argv) free (argv[i]); free (argv); } + +#define PROGRESS_ROOT_KEY "_ocaml_progress_root" + +/* Guestfs.set_progress_callback */ +CAMLprim value +ocaml_guestfs_set_progress_callback (value gv, value closure) +{ + CAMLparam2 (gv, closure); + + guestfs_h *g = Guestfs_val (gv); + clear_progress_callback (g); + + value *root = guestfs_safe_malloc (g, sizeof *root); + *root = closure; + + /* XXX This global root is generational, but we cannot rely on every + * user having the OCaml 3.11 version which supports this. + */ + caml_register_global_root (root); + + guestfs_set_private (g, PROGRESS_ROOT_KEY, root); + + guestfs_set_progress_callback (g, progress_callback, root); + + CAMLreturn (Val_unit); +} + +/* Guestfs.clear_progress_callback */ +CAMLprim value +ocaml_guestfs_clear_progress_callback (value gv) +{ + CAMLparam1 (gv); + + guestfs_h *g = Guestfs_val (gv); + clear_progress_callback (g); + + CAMLreturn (Val_unit); +} + +static void +clear_progress_callback (guestfs_h *g) +{ + guestfs_set_progress_callback (g, NULL, NULL); + + value *root = guestfs_get_private (g, PROGRESS_ROOT_KEY); + if (root) { + caml_remove_global_root (root); + free (root); + guestfs_set_private (g, PROGRESS_ROOT_KEY, NULL); + } +} + +static void +progress_callback (guestfs_h *g, void *root, + int proc_nr, int serial, uint64_t position, uint64_t total) +{ + CAMLparam0 (); + CAMLlocal5 (proc_nrv, serialv, positionv, totalv, rv); + + proc_nrv = Val_int (proc_nr); + serialv = Val_int (serial); + positionv = caml_copy_int64 (position); + totalv = caml_copy_int64 (total); + + value args[4] = { proc_nrv, serialv, positionv, totalv }; + + caml_leave_blocking_section (); + rv = caml_callbackN_exn (*(value*)root, 4, args); + caml_enter_blocking_section (); + + /* Callbacks shouldn't throw exceptions. There's not much we can do + * except to print it. + */ + if (Is_exception_result (rv)) + fprintf (stderr, "libguestfs: uncaught OCaml exception in progress callback: %s", + caml_format_exception (Extract_exception (rv))); + + CAMLreturn0; +} diff --git a/src/generator.ml b/src/generator.ml index 5580f1a..69ea477 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -8930,6 +8930,28 @@ val close : t -> unit unreferenced, but callers can call this in order to provide predictable cleanup. *) +type progress_cb = int -> int -> int64 -> int64 -> unit + +val set_progress_callback : t -> progress_cb -> unit +(** [set_progress_callback g f] sets [f] as the progress callback function. + For some long-running functions, [f] will be called repeatedly + during the function with progress updates. + + The callback is [f proc_nr serial position total]. See + the description of [guestfs_set_progress_callback] in guestfs(3) + for the meaning of these four numbers. + + Note that if the closure captures a reference to the handle, + this reference will prevent the handle from being + automatically closed by the garbage collector. There are + three ways to avoid this: be careful not to capture the handle + in the closure, or use a weak reference, or call + {!Guestfs.clear_progress_callback} to remove the reference. *) + +val clear_progress_callback : t -> unit +(** [clear_progress_callback g] removes any progress callback function + associated with the handle. See {!Guestfs.set_progress_callback}. *) + "; generate_ocaml_structure_decls (); @@ -8954,6 +8976,13 @@ exception Handle_closed of string external create : unit -> t = \"ocaml_guestfs_create\" external close : t -> unit = \"ocaml_guestfs_close\" +type progress_cb = int -> int -> int64 -> int64 -> unit + +external set_progress_callback : t -> progress_cb -> unit + = \"ocaml_guestfs_set_progress_callback\" +external clear_progress_callback : t -> unit + = \"ocaml_guestfs_clear_progress_callback\" + (* Give the exceptions names, so they can be raised from the C code. *) let () Callback.register_exception \"ocaml_guestfs_error\" (Error \"\"); -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 11/12] perl: bindings to progress callback.
--- src/generator.ml | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/generator.ml b/src/generator.ml index 69ea477..d5ebe23 100755 --- a/src/generator.ml +++ b/src/generator.ml @@ -9430,6 +9430,46 @@ XS_unpack_charPtrPtr (SV *arg) { return ret; } +#define PROGRESS_KEY \"_perl_progress_cb\" + +static void +_clear_progress_callback (guestfs_h *g) +{ + guestfs_set_progress_callback (g, NULL, NULL); + SV *cb = guestfs_get_private (g, PROGRESS_KEY); + if (cb) { + guestfs_set_private (g, PROGRESS_KEY, NULL); + SvREFCNT_dec (cb); + } +} + +/* http://www.perlmonks.org/?node=338857 */ +static void +_progress_callback (guestfs_h *g, void *cb, + int proc_nr, int serial, uint64_t position, uint64_t total) +{ + dSP; + ENTER; + SAVETMPS; + PUSHMARK (SP); + XPUSHs (sv_2mortal (newSViv (proc_nr))); + XPUSHs (sv_2mortal (newSViv (serial))); + XPUSHs (sv_2mortal (my_newSVull (position))); + XPUSHs (sv_2mortal (my_newSVull (total))); + PUTBACK; + call_sv ((SV *) cb, G_VOID | G_DISCARD | G_EVAL); + FREETMPS; + LEAVE; +} + +static void +_close_handle (guestfs_h *g) +{ + assert (g != NULL); + _clear_progress_callback (g); + guestfs_close (g); +} + MODULE = Sys::Guestfs PACKAGE = Sys::Guestfs PROTOTYPES: ENABLE @@ -9457,19 +9497,34 @@ DESTROY (sv) SV **svp = hv_fetch (hv, \"_g\", 2, 0); if (svp != NULL) { guestfs_h *g = (guestfs_h *) SvIV (*svp); - assert (g != NULL); - guestfs_close (g); + _close_handle (g); } void close (g) guestfs_h *g; PPCODE: - guestfs_close (g); + _close_handle (g); /* Avoid double-free in DESTROY method. */ HV *hv = (HV *) SvRV (ST(0)); (void) hv_delete (hv, \"_g\", 2, G_DISCARD); +void +set_progress_callback (g, cb) + guestfs_h *g; + SV *cb; + PPCODE: + _clear_progress_callback (g); + SvREFCNT_inc (cb); + guestfs_set_private (g, PROGRESS_KEY, cb); + guestfs_set_progress_callback (g, _progress_callback, cb); + +void +clear_progress_callback (g) + guestfs_h *g; + PPCODE: + _clear_progress_callback (g); + "; List.iter ( @@ -9843,6 +9898,25 @@ C<close> the program must not call any method (including C<close>) on the handle (but the implicit call to C<DESTROY> that happens when the final reference is cleaned up is OK). +=item $h->set_progress_callback (\\&cb); + +Set the progress notification callback for this handle +to the Perl closure C<cb>. + +C<cb> will be called whenever a long-running operation +generates a progress notification message. The 4 parameters +to the function are: C<proc_nr>, C<serial>, C<position> +and C<total>. + +You should carefully read the documentation for +L<guestfs(3)/guestfs_set_progress_callback> before using +this function. + +=item $h->clear_progress_callback (); + +This removes any progress callback function associated with +the handle. + =cut " max_proc_nr; -- 1.7.1
Richard Jones
2010-Aug-31 20:11 UTC
[Libguestfs] [PATCH 12/12] resize: Add progress bar to virt-resize.
With this commit you will see a plain progress bar during the lengthy copy operations, similar to below: Summary of changes: /dev/sda1: partition will be left alone /dev/sda2: partition will be resized from 7.5G to 9.5G /dev/sda2: content will be expanded using the 'pvresize' method Copying /dev/sda1 ... [############################################################################] Copying /dev/sda2 ... [########################################------------------------------------] --- tools/virt-resize | 29 +++++++++++++++++++++++------ 1 files changed, 23 insertions(+), 6 deletions(-) diff --git a/tools/virt-resize b/tools/virt-resize index a70492e..28f51af 100755 --- a/tools/virt-resize +++ b/tools/virt-resize @@ -33,6 +33,8 @@ $Data::Dumper::Sortkeys = 1; die __"virt-resize: sorry this program does not work on a 32 bit host\n" if ~1 == 4294967294; +$| = 1; + =encoding utf8 =head1 NAME @@ -583,6 +585,7 @@ sub launch_guestfs $g->set_trace (1) if $debug; $g->add_drive_ro ($infile); $g->add_drive ($outfile); + $g->set_progress_callback (\&progress_callback) unless $quiet; $g->launch (); } @@ -1122,16 +1125,11 @@ sub copy_data } if (!$quiet && !$debug) { - local $| = 1; - print __x("Copying {p} ...", p => $part); + print __x("Copying {p} ...\n", p => $part); } $g->copy_size ($part, $target, $newsize < $oldsize ? $newsize : $oldsize); - - if (!$quiet && !$debug) { - print " ", __"done", "\n"; - } } } } @@ -1330,6 +1328,25 @@ sub canonicalize $_; } +# Not as sophisticated as the guestfish progress bar, because +# I intend to use an external library for this at some point (XXX). +sub progress_callback +{ + my $proc_nr = shift; + my $serial = shift; + my $position = shift; + my $total = shift; + + my $ratio = $position / $total; + if ($ratio < 0) { $ratio = 0 } + elsif ($ratio > 1) { $ratio = 1 } + + my $dots = int ($ratio * 76); + + print "[", "#"x$dots, "-"x(76-$dots), "]\r"; + print "\n" if $ratio == 1; +} + =head1 NOTES =head2 "Partition 1 does not end on cylinder boundary." -- 1.7.1
Richard W.M. Jones
2010-Sep-01 08:58 UTC
[Libguestfs] [PATCH 03/12] Add progress messages to fill command.
On Tue, Aug 31, 2010 at 09:11:09PM +0100, Richard Jones wrote:> --- > daemon/fill.c | 1 + > 1 files changed, 1 insertions(+), 0 deletions(-) > > diff --git a/daemon/fill.c b/daemon/fill.c > index 807ee39..a3e3d37 100644 > --- a/daemon/fill.c > +++ b/daemon/fill.c > @@ -66,6 +66,7 @@ do_fill (int c, int len, const char *path) > return -1; > } > n += r; > + notify_progress ((uint64_t) n, (uint64_t) len_sz); > } > > if (close (fd) == -1) { > --In these patches I forgot to add the Progress flag to the generator ... I'll fix that in my repo. Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://et.redhat.com/~rjones/libguestfs/ See what it can do: http://et.redhat.com/~rjones/libguestfs/recipes.html
Possibly Parallel Threads
- [PATCH 0/3] protocol: Abstract out socket operations.
- [PATCH 0/4] Small refactorings of the protocol layer.
- [PATCH 1/2] gobject: Use generator_built macro to ensure generated files are rebuilt properly.
- [PATCH] Fix verbose packet dumping functions.
- Re: guestfs_launch gets stuck