As discussed previously: https://www.redhat.com/archives/libguestfs/2010-July/msg00003.html https://www.redhat.com/archives/libguestfs/2010-July/msg00024.html To do: (1) Implement progress notifications for many more daemon operations. (2) OCaml bindings to the callback. (3) Perl bindings to the callback. 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
Richard W.M. Jones
2010-Aug-28 12:02 UTC
[Libguestfs] [PATCH 1/4] Implement progress messages in the daemon and library.
-- 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 aacf13308a0fbae58765eca95cbcb5fbe831c53d Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Sat, 28 Aug 2010 10:33:24 +0100 Subject: [PATCH 1/4] 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 | 83 +++++++++++++++++++++++++++++++++++++++++++++--- src/generator.ml | 21 +++++++++++- src/guestfs-internal.h | 2 + src/guestfs.c | 8 +++++ src/guestfs.h | 5 ++- src/guestfs.pod | 52 ++++++++++++++++++++++++++++++ src/proto.c | 37 +++++++++++++++++++-- 8 files changed, 205 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..371ea91 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,12 @@ 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; + /* The daemon communications socket. */ static int sock; @@ -54,8 +61,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 +117,8 @@ 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; /* Decode the message header. */ xdrmem_create (&xdr, buf, len, XDR_DECODE); @@ -160,11 +164,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 +540,69 @@ send_chunk (const guestfs_chunk *chunk) return err; } + +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 (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 to one message per second. */ + if (elapsed_us < 1000000) + return; + + send: + /* We're going to send a message now ... */ + 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..4a7b84a 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -1186,6 +1186,58 @@ 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 one second after the +operation started, and every 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 proportion 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 * + +C<total> will be the same number for each progress message within +a single operation. + +=item * + +0 E<lt>= C<position> E<lt>= C<total> + +=item * + +A final progress notification is always sent when C<position> = C<total>. + +=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. + +Progress callbacks only happen in the BUSY state. + =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..f9b5a33 100644 --- a/src/proto.c +++ b/src/proto.c @@ -373,17 +373,26 @@ 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. */ int guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) { fd_set rset, rset2; +#define PROGRESS_MESSAGE_SIZE 24 +#define message_size() \ + (*size_rtn != GUESTFS_PROGRESS_FLAG ? *size_rtn : PROGRESS_MESSAGE_SIZE) + 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); + again: FD_ZERO (&rset); FD_SET (g->fd[1], &rset); /* Read qemu stdout for log messages & EOF. */ @@ -400,7 +409,7 @@ guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn) */ ssize_t nr = -4; - while (nr < (ssize_t) *size_rtn) { + while (nr < (ssize_t) message_size()) { rset2 = rset; int r = select (max_fd+1, &rset2, NULL, NULL, NULL); if (r == -1) { @@ -463,6 +472,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 +484,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 +535,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. */ + goto again; + } + return 0; } -- 1.7.1
Richard W.M. Jones
2010-Aug-28 12:03 UTC
[Libguestfs] [PATCH 2/4] Add progress messages to copy-size command.
This is an example of how to add progress notifications to one of the daemon commands. Item (1) on the to-do list is to go through the remaining daemon commands and add progress notifications to the easy ones. (Item (94c) is to add it to the hard ones ...) Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones New in Fedora 11: Fedora Windows cross-compiler. Compile Windows programs, test, and build Windows installers. Over 70 libraries supprt'd http://fedoraproject.org/wiki/MinGW http://www.annexia.org/fedora_mingw -------------- next part -------------->From 2d790f65c6591ab751641b59ce27389edde88b64 Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Sat, 28 Aug 2010 11:11:32 +0100 Subject: [PATCH 2/4] 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 W.M. Jones
2010-Aug-28 12:04 UTC
[Libguestfs] [PATCH 3/4] fish: Detect UTF-8 output and open termcap/terminfo database.
-- 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 3bee9619367383bb3b6e3294289a36e176099c3d Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Sat, 28 Aug 2010 12:48:49 +0100 Subject: [PATCH 3/4] 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 W.M. Jones
2010-Aug-28 12:04 UTC
[Libguestfs] [PATCH 4/4] fish: Implement progress bars in guestfish.
ZX81-stylie graphics. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones virt-p2v converts physical machines to virtual machines. Boot with a live CD or over the network (PXE) and turn machines into Xen guests. http://et.redhat.com/~rjones/virt-p2v -------------- next part -------------->From 7151628d09985d4156c2684118beedbf339c953b Mon Sep 17 00:00:00 2001From: Richard Jones <rjones at redhat.com> Date: Sat, 28 Aug 2010 12:49:55 +0100 Subject: [PATCH 4/4] fish: Implement progress bars in guestfish. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The output you get will look something like this:><fs> copy-size /large /large2 2G50% ????????????????????????????????????????????????????? The progress bar is updated once per second, and is not displayed at all for operations which take less than one second. 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 fs:ext4:10G \ -m /dev/sda1 \ fallocate64 /test 2G : \ copy-size /test /test2 2G --- fish/Makefile.am | 1 + fish/fish.c | 20 ++++++++++++ fish/fish.h | 2 + fish/guestfish.pod | 11 +++++++ fish/progress.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ fish/reopen.c | 3 ++ po/POTFILES.in | 1 + 7 files changed, 124 insertions(+), 0 deletions(-) create mode 100644 fish/progress.c diff --git a/fish/Makefile.am b/fish/Makefile.am index 4060f1f..7b1826c 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 \ diff --git a/fish/fish.c b/fish/fish.c index c4ade8c..7d26d4b 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)) diff --git a/fish/fish.h b/fish/fish.h index 8106610..37966c1 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); @@ -73,6 +74,7 @@ extern void free_file_in (char *s); extern char *file_out (const char *arg); extern void extended_help_message (void); extern char *read_key (const char *param); +extern void progress_callback (guestfs_h *g, void *data, int proc_nr, int serial, uint64_t position, uint64_t total); /* in cmds.c (auto-generated) */ extern void list_commands (void); diff --git a/fish/guestfish.pod b/fish/guestfish.pod index cf1140a..162488d 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 diff --git a/fish/progress.c b/fish/progress.c new file mode 100644 index 0000000..2f9d9cb --- /dev/null +++ b/fish/progress.c @@ -0,0 +1,86 @@ +/* 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 <guestfs.h> + +#include "fish.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; + +/* 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 < 8) goto dumb; + + /* Update an existing progress bar just printed? */ + static int last_serial = 0; + if (serial == last_serial) + tputs (UP, 2, putchar); + last_serial = serial; + + double ratio = (double) position / total; + if (ratio < 0) ratio = 0; else if (ratio > 1) ratio = 1; + + int percent = 100.0 * ratio; + printf ("%3d%% ", percent); + + int dots = ratio * (double) (cols - 8); + + if (utf8_mode) { + printf ("\u27e6"); + int i; + for (i = 0; i < dots; ++i) + printf ("\u2593"); + for (i = dots; i < cols-8; ++i) + printf ("\u2591"); + printf ("\u27e7"); + } else { + printf ("["); + int i; + for (i = 0; i < dots; ++i) + printf ("#"); + for (i = dots; i < cols-8; ++i) + printf ("-"); + printf ("]"); + } + putchar ('\n'); + } +} 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/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