[Libguestfs] [PATCH v2 1/2] New tool: virt-tail.
Pino Toscano
ptoscano at redhat.com
Mon Oct 3 13:46:06 UTC 2016
On Monday, 3 October 2016 13:27:13 CEST Richard W.M. Jones wrote:
> This follows (tails) a log file within a guest, rather like
> the regular 'tail -f' command. For example:
>
> virt-tail -d guest /var/log/messages
> ---
> .gitignore | 3 +
> bash/Makefile.am | 4 +-
> bash/virt-alignment-scan | 6 +
> cat/Makefile.am | 47 ++++-
> cat/tail.c | 498 +++++++++++++++++++++++++++++++++++++++++++++++
> cat/test-docs.sh | 1 +
> cat/virt-cat.pod | 3 +
> cat/virt-log.pod | 8 +-
> cat/virt-tail.pod | 253 ++++++++++++++++++++++++
> docs/guestfs-hacking.pod | 4 +-
> fish/guestfish.pod | 1 +
> src/guestfs.pod | 1 +
> tools/virt-win-reg | 1 +
> website/index.html.in | 1 +
> 14 files changed, 820 insertions(+), 11 deletions(-)
> create mode 100644 cat/tail.c
> create mode 100644 cat/virt-tail.pod
>
> diff --git a/.gitignore b/.gitignore
> index 3d3bf0d..c4d6eda 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -69,6 +69,7 @@ Makefile.in
> /bash/virt-resize
> /bash/virt-sysprep
> /bash/virt-sparsify
> +/bash/virt-tail
> /bash/virt-tar-in
> /bash/virt-tar-out
> /build-aux/.gitignore
> @@ -111,6 +112,8 @@ Makefile.in
> /cat/virt-log.1
> /cat/virt-ls
> /cat/virt-ls.1
> +/cat/virt-tail
> +/cat/virt-tail.1
> /ChangeLog
> /compile
> /config.cache
> diff --git a/bash/Makefile.am b/bash/Makefile.am
> index 9a51847..65505ef 100644
> --- a/bash/Makefile.am
> +++ b/bash/Makefile.am
> @@ -48,6 +48,7 @@ symlinks = \
> virt-resize \
> virt-sparsify \
> virt-sysprep \
> + virt-tail \
> virt-tar-in \
> virt-tar-out
>
> @@ -70,7 +71,8 @@ virt-builder virt-cat virt-customize virt-df virt-dib virt-diff \
> virt-edit virt-filesystems virt-format virt-get-kernel virt-inspector \
> virt-log virt-ls \
> virt-p2v-make-disk virt-p2v-make-kickstart virt-p2v-make-kiwi \
> -virt-resize virt-sparsify virt-sysprep:
> +virt-resize virt-sparsify virt-sysprep \
> +virt-tail:
> rm -f $@
> $(LN_S) virt-alignment-scan $@
>
> diff --git a/bash/virt-alignment-scan b/bash/virt-alignment-scan
> index 055bad1..80f6e51 100644
> --- a/bash/virt-alignment-scan
> +++ b/bash/virt-alignment-scan
> @@ -204,3 +204,9 @@ _virt_sysprep ()
> _guestfs_virttools "virt-sysprep" 0
> } &&
> complete -o default -F _virt_sysprep virt-sysprep
> +
> +_virt_tail ()
> +{
> + _guestfs_virttools "virt-tail" 1
> +} &&
> +complete -o default -F _virt_tail virt-tail
> diff --git a/cat/Makefile.am b/cat/Makefile.am
> index 796e808..02a8064 100644
> --- a/cat/Makefile.am
> +++ b/cat/Makefile.am
> @@ -1,4 +1,4 @@
> -# libguestfs virt-cat, virt-filesystems, virt-log and virt-ls.
> +# libguestfs virt-cat, virt-filesystems, virt-log, virt-ls and virt-tail.
> # Copyright (C) 2010-2016 Red Hat Inc.
> #
> # This program is free software; you can redistribute it and/or modify
> @@ -26,9 +26,10 @@ EXTRA_DIST = \
> test-virt-log.sh \
> virt-log.pod \
> test-virt-ls.sh \
> - virt-ls.pod
> + virt-ls.pod \
> + virt-tail.pod
>
> -bin_PROGRAMS = virt-cat virt-filesystems virt-log virt-ls
> +bin_PROGRAMS = virt-cat virt-filesystems virt-log virt-ls virt-tail
>
> SHARED_SOURCE_FILES = \
> ../fish/windows.h \
> @@ -132,14 +133,39 @@ virt_ls_LDADD = \
> $(LTLIBINTL) \
> ../gnulib/lib/libgnu.la
>
> +virt_tail_SOURCES = \
> + $(SHARED_SOURCE_FILES) \
> + tail.c
> +
> +virt_tail_CPPFLAGS = \
> + -DGUESTFS_WARN_DEPRECATED=1 \
> + -DLOCALEBASEDIR=\""$(datadir)/locale"\" \
> + -I$(top_srcdir)/src -I$(top_builddir)/src \
> + -I$(top_srcdir)/fish \
> + -I$(srcdir)/../gnulib/lib -I../gnulib/lib
> +
> +virt_tail_CFLAGS = \
> + $(WARN_CFLAGS) $(WERROR_CFLAGS) \
> + $(LIBXML2_CFLAGS)
> +
> +virt_tail_LDADD = \
> + $(top_builddir)/src/libutils.la \
> + $(top_builddir)/src/libguestfs.la \
> + $(top_builddir)/fish/libfishcommon.la \
> + $(LIBXML2_LIBS) \
> + $(LIBVIRT_LIBS) \
> + $(LTLIBINTL) \
> + ../gnulib/lib/libgnu.la
> +
> # Manual pages and HTML files for the website.
> -man_MANS = virt-cat.1 virt-filesystems.1 virt-log.1 virt-ls.1
> +man_MANS = virt-cat.1 virt-filesystems.1 virt-log.1 virt-ls.1 virt-tail.1
>
> noinst_DATA = \
> $(top_builddir)/website/virt-cat.1.html \
> $(top_builddir)/website/virt-filesystems.1.html \
> $(top_builddir)/website/virt-log.1.html \
> - $(top_builddir)/website/virt-ls.1.html
> + $(top_builddir)/website/virt-ls.1.html \
> + $(top_builddir)/website/virt-tail.1.html
>
> virt-cat.1 $(top_builddir)/website/virt-cat.1.html: stamp-virt-cat.pod
>
> @@ -185,6 +211,17 @@ stamp-virt-ls.pod: virt-ls.pod
> $<
> touch $@
>
> +virt-tail.1 $(top_builddir)/website/virt-tail.1.html: stamp-virt-tail.pod
> +
> +stamp-virt-tail.pod: virt-tail.pod
> + $(PODWRAPPER) \
> + --man virt-tail.1 \
> + --html $(top_builddir)/website/virt-tail.1.html \
> + --license GPLv2+ \
> + --warning safe \
> + $<
> + touch $@
> +
> # Tests.
>
> TESTS_ENVIRONMENT = $(top_builddir)/run --test
> diff --git a/cat/tail.c b/cat/tail.c
> new file mode 100644
> index 0000000..85aa471
> --- /dev/null
> +++ b/cat/tail.c
> @@ -0,0 +1,498 @@
> +/* virt-tail
> + * Copyright (C) 2016 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.
> + */
> +
> +#include <config.h>
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <inttypes.h>
> +#include <unistd.h>
> +#include <getopt.h>
> +#include <signal.h>
> +#include <errno.h>
> +#include <error.h>
> +#include <locale.h>
> +#include <assert.h>
> +#include <libintl.h>
> +#include <sys/types.h>
> +#include <sys/stat.h>
> +
> +#include "getprogname.h"
> +#include "ignore-value.h"
> +
> +#include "guestfs.h"
> +#include "options.h"
> +#include "display-options.h"
> +#include "windows.h"
> +
> +/* Currently open libguestfs handle. */
> +guestfs_h *g;
> +
> +int read_only = 1;
> +int live = 0;
> +int verbose = 0;
> +int keys_from_stdin = 0;
> +int echo_keys = 0;
> +const char *libvirt_uri = NULL;
> +int inspector = 1;
> +
> +static int do_tail (int argc, char *argv[], struct drv *drvs, struct mp *mps);
> +static time_t disk_mtime (struct drv *drvs);
> +static int add_and_mount (struct drv *drvs, struct mp *mps, int *windows_ret);
> +static int reopen_handle (void);
> +
> +static void __attribute__((noreturn))
> +usage (int status)
> +{
> + if (status != EXIT_SUCCESS)
> + fprintf (stderr, _("Try `%s --help' for more information.\n"),
> + getprogname ());
> + else {
> + printf (_("%s: follow (tail) files in a virtual machine\n"
> + "Copyright (C) 2016 Red Hat Inc.\n"
> + "Usage:\n"
> + " %s [--options] -d domname file [file ...]\n"
> + " %s [--options] -a disk.img [-a disk.img ...] file [file ...]\n"
> + "Options:\n"
> + " -a|--add image Add image\n"
> + " -c|--connect uri Specify libvirt URI for -d option\n"
> + " -d|--domain guest Add disks from libvirt guest\n"
> + " --echo-keys Don't turn off echo for passphrases\n"
> + " -f|--follow Ignored for compatibility with tail\n"
> + " --format[=raw|..] Force disk format for -a option\n"
> + " --help Display brief help\n"
> + " --keys-from-stdin Read passphrases from stdin\n"
> + " -m|--mount dev[:mnt[:opts[:fstype]]]\n"
> + " Mount dev on mnt (if omitted, /)\n"
> + " -v|--verbose Verbose messages\n"
> + " -V|--version Display version and exit\n"
> + " -x Trace libguestfs API calls\n"
> + "For more information, see the manpage %s(1).\n"),
> + getprogname (), getprogname (),
> + getprogname (), getprogname ());
> + }
> + exit (status);
> +}
> +
> +int
> +main (int argc, char *argv[])
> +{
> + setlocale (LC_ALL, "");
> + bindtextdomain (PACKAGE, LOCALEBASEDIR);
> + textdomain (PACKAGE);
> +
> + enum { HELP_OPTION = CHAR_MAX + 1 };
> +
> + static const char options[] = "a:c:d:fm:vVx";
> + static const struct option long_options[] = {
> + { "add", 1, 0, 'a' },
> + { "connect", 1, 0, 'c' },
> + { "domain", 1, 0, 'd' },
> + { "echo-keys", 0, 0, 0 },
> + { "follow", 0, 0, 'f' },
> + { "format", 2, 0, 0 },
> + { "help", 0, 0, HELP_OPTION },
> + { "keys-from-stdin", 0, 0, 0 },
> + { "long-options", 0, 0, 0 },
> + { "mount", 1, 0, 'm' },
> + { "short-options", 0, 0, 0 },
> + { "verbose", 0, 0, 'v' },
> + { "version", 0, 0, 'V' },
> + { 0, 0, 0, 0 }
> + };
> + struct drv *drvs = NULL;
> + struct mp *mps = NULL;
> + struct mp *mp;
> + char *p;
> + const char *format = NULL;
> + bool format_consumed = true;
> + int c;
> + int r;
> + int option_index;
> +
> + g = guestfs_create ();
> + if (g == NULL)
> + error (EXIT_FAILURE, errno, "guestfs_create");
> +
> + for (;;) {
> + c = getopt_long (argc, argv, options, long_options, &option_index);
> + if (c == -1) break;
> +
> + switch (c) {
> + case 0: /* options which are long only */
> + if (STREQ (long_options[option_index].name, "long-options"))
> + display_long_options (long_options);
> + else if (STREQ (long_options[option_index].name, "short-options"))
> + display_short_options (options);
> + else if (STREQ (long_options[option_index].name, "keys-from-stdin")) {
> + keys_from_stdin = 1;
> + } else if (STREQ (long_options[option_index].name, "echo-keys")) {
> + echo_keys = 1;
> + } else if (STREQ (long_options[option_index].name, "format")) {
> + OPTION_format;
> + } else
> + error (EXIT_FAILURE, 0,
> + _("unknown long option: %s (%d)"),
> + long_options[option_index].name, option_index);
> + break;
> +
> + case 'a':
> + OPTION_a;
> + break;
> +
> + case 'c':
> + OPTION_c;
> + break;
> +
> + case 'd':
> + OPTION_d;
> + break;
> +
> + case 'f':
> + /* ignored */
> + break;
> +
> + case 'm':
> + OPTION_m;
> + inspector = 0;
> + break;
> +
> + case 'v':
> + OPTION_v;
> + break;
> +
> + case 'V':
> + OPTION_V;
> + break;
> +
> + case 'x':
> + OPTION_x;
> + break;
> +
> + case HELP_OPTION:
> + usage (EXIT_SUCCESS);
> +
> + default:
> + usage (EXIT_FAILURE);
> + }
> + }
> +
> + /* These are really constants, but they have to be variables for the
> + * options parsing code. Assert here that they have known-good
> + * values.
> + */
> + assert (read_only == 1);
> + assert (inspector == 1 || mps != NULL);
> + assert (live == 0);
> +
> + /* User must specify at least one filename on the command line. */
> + if (optind >= argc || argc - optind < 1)
> + usage (EXIT_FAILURE);
I'd add:
fprintf (stderr, _("%s: error: missing filenames on command line.\n"
"Please specify at least one file to follow.\n"),
getprogname ());
so there is a better error message.
> +
> + CHECK_OPTION_format_consumed;
> +
> + /* User must have specified some drives. */
> + if (drvs == NULL) {
> + fprintf (stderr, _("%s: error: you must specify at least one -a or -d option.\n"),
> + getprogname ());
> + usage (EXIT_FAILURE);
> + }
> +
> + r = do_tail (argc - optind, &argv[optind], drvs, mps);
> +
> + free_drives (drvs);
> + free_mps (mps);
> +
> + guestfs_close (g);
> +
> + exit (r == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
> +}
> +
> +struct follow {
> + int64_t mtime; /* For each file, last mtime. */
> + int64_t size; /* For each file, last size. */
> +};
> +
> +static int quit = 0;
I guess this could be better as sig_atomic_t.
> +static void
> +user_cancel (int sig)
> +{
> + quit = 1;
> + ignore_value (guestfs_user_cancel (g));
> +}
> +
> +static int
> +do_tail (int argc, char *argv[], /* list of files in the guest */
> + struct drv *drvs, struct mp *mps)
> +{
> + struct sigaction sa;
> + time_t drvt;
> + int first_iteration = 1;
> + int prev_file_displayed = -1;
> + CLEANUP_FREE struct follow *file = NULL;
> +
> + /* Allocate storage to track each file. */
> + file = calloc (argc, sizeof (struct follow));
> +
> + /* We loop until the user hits ^C. */
> + memset (&sa, 0, sizeof sa);
> + sa.sa_handler = user_cancel;
> + sa.sa_flags = SA_RESTART;
> + sigaction (SIGINT, &sa, NULL);
> + sigaction (SIGQUIT, &sa, NULL);
> +
> + if (guestfs_set_pgroup (g, 1) == -1)
> + exit (EXIT_FAILURE);
> +
> + drvt = disk_mtime (drvs);
> + if (drvt == (time_t)-1)
> + return -1;
> +
> + while (!quit) {
> + time_t t;
> + int i;
> + int windows;
> + int processed;
> +
> + if (add_and_mount (drvs, mps, &windows) == -1)
> + return -1;
> +
> + /* Check files here. */
> + processed = 0;
> + for (i = 0; i < argc; ++i) {
> + CLEANUP_FREE_STATNS struct guestfs_statns *stat = NULL;
> +
> + guestfs_push_error_handler (g, NULL, NULL);
> + stat = guestfs_statns (g, argv[i]);
> + guestfs_pop_error_handler (g);
> + if (stat == NULL) {
> + /* There's an error. Treat ENOENT as if the file was empty size. */
> + if (guestfs_last_errno (g) == ENOENT) {
> + time (&t);
> + file[i].mtime = t;
> + file[i].size = 0;
> + }
> + else {
> + fprintf (stderr, "%s: %s: %s\n",
> + getprogname (), argv[i], guestfs_last_error (g));
> + return -1;
> + }
> + }
> + else {
> + CLEANUP_FREE_STRING_LIST char **lines = NULL;
> + CLEANUP_FREE char *content = NULL;
> +
> + processed++;
> +
> + /* We believe the guest mtime to mean the file changed. This
> + * can include the file changing but the size staying the same,
> + * so be careful.
> + */
> + if (file[i].mtime != stat->st_mtime_sec ||
> + file[i].size != stat->st_size) {
> + /* If we get here, the file changed and we're going to display
> + * something. If there is more than one file, and the file
> + * displayed is different from previously, then display the
> + * filename banner.
> + */
> + if (i != prev_file_displayed)
> + printf ("\n\n--- %s ---\n\n", argv[i]);
> + prev_file_displayed = i;
> +
> + /* If the file grew, display all the new content unless
> + * it's a lot, in which case display the last few lines.
> + * If the file shrank, display the last few lines.
> + * If the file stayed the same size [note that the file
> + * has changed -- see above], redisplay the last few lines.
> + */
> + if (stat->st_size > file[i].size + 10000) { /* grew a lot */
> + goto show_tail;
> + }
> + else if (stat->st_size > file[i].size) { /* grew a bit */
> + int count = stat->st_size - file[i].size;
> + size_t r;
> + guestfs_push_error_handler (g, NULL, NULL);
> + content = guestfs_pread (g, argv[i], count, file[i].size, &r);
> + guestfs_pop_error_handler (g);
> + if (content) {
> + size_t j;
> + for (j = 0; j < r; ++j)
> + putchar (content[j]);
> + }
> + }
> + else if (stat->st_size <= file[i].size) { /* shrank or same size */
> + show_tail:
> + guestfs_push_error_handler (g, NULL, NULL);
> + lines = guestfs_tail (g, argv[i]);
> + guestfs_pop_error_handler (g);
> + if (lines) {
> + size_t j;
> + for (j = 0; lines[j] != NULL; ++j)
> + puts (lines[j]);
> + }
> + }
> +
> + fflush (stdout);
> +
> + file[i].mtime = stat->st_mtime_sec;
> + file[i].size = stat->st_size;
> + }
> + }
> + }
> +
> + /* If no files were found, exit. If this is the first iteration
> + * of the loop, then this is an error, otherwise it's an ordinary
> + * exit when all files get deleted (see man page).
> + */
> + if (processed == 0) {
> + if (first_iteration) {
> + fprintf (stderr,
> + _("%s: error: none of the files were found in the disk image\n"),
> + getprogname ());
> + return -1;
> + }
> + else {
> + printf (_("%s: all files deleted, exiting\n"), getprogname ());
> + return 0;
> + }
> + }
> +
> + /* Do nothing until something happens on the disk image. Even if
> + * the drive changes, always wait min. 30 seconds. For libvirt
> + * (-d) and remote sources we cannot check this so we have to use
> + * a fixed (5 minute) delay instead. Also we recheck every so
> + * often even if nothing seems to have changed. (XXX Can we do
> + * better?)
> + */
> + for (i = 0; i < 10 /* 30 seconds * 10 = 5 mins */; ++i) {
> + time (&t);
> + sleep (30);
> + drvt = disk_mtime (drvs);
> + if (drvt == (time_t)-1)
> + return -1;
> + if (drvt-t < 30) break;
> + }
> +
> + if (reopen_handle () == -1)
> + return -1;
> +
> + first_iteration = 0;
> + }
> +
> + return 0;
> +}
> +
> +/* Add drives, inspect and mount. */
> +static int
> +add_and_mount (struct drv *drvs, struct mp *mps, int *windows_ret)
> +{
The 'windows_ret' out parameter does not seem used outside, so could
be better to drop it for now/
> + int windows = 0;
> + char *root;
> + CLEANUP_FREE_STRING_LIST char **roots = NULL;
> +
> + add_drives (drvs, 'a');
> +
> + if (guestfs_launch (g) == -1)
> + return -1;
> +
> + if (mps != NULL)
> + mount_mps (mps);
> + else
> + inspect_mount ();
> +
> + if (inspector) {
> + /* Get root mountpoint. See: fish/inspect.c:inspect_mount */
> + roots = guestfs_inspect_get_roots (g);
> +
> + assert (roots);
> + assert (roots[0] != NULL);
> + assert (roots[1] == NULL);
> + root = roots[0];
> +
> + /* Windows? Special handling is required. */
> + windows = is_windows (g, root);
> + }
> +
> + *windows_ret = windows;
> +
> + return 0;
> +}
> +
> +/* Return the latest (highest) mtime of any local drive in the list of
> + * drives passed on the command line. If there are no such drives
> + * (eg. the guest is libvirt or remote) then this returns 0. If there
> + * is an error it returns (time_t)-1.
> + */
> +static time_t
> +disk_mtime (struct drv *drvs)
> +{
> + time_t ret;
> +
> + if (drvs == NULL)
> + return 0;
> +
> + ret = disk_mtime (drvs->next);
This may be better written as simple non-recursive loop, to avoid
neverending loop, and make the code slightly more understandable to
follow (IMHO).
> + if (ret == (time_t)-1)
> + return -1;
> +
> + if (drvs->type == drv_a) {
> + struct stat statbuf;
> +
> + if (stat (drvs->a.filename, &statbuf) == -1) {
> + error (0, errno, "stat: %s", drvs->a.filename);
> + return -1;
> + }
> +
> + if (statbuf.st_mtime > ret)
> + ret = statbuf.st_mtime;
> + }
> + /* XXX "look into" libvirt guests for local drives. */
> +
> + return ret;
> +}
> +
> +/* Reopen the handle. Open the new handle first and copy some
> + * settings across. We only need to copy settings which are set
> + * somewhere in the code above, eg by OPTION_v. Settings from
> + * environment variables will be recreated by guestfs_create.
> + *
> + * The global 'g' must never be unset or NULL (visible to code outside
> + * this function).
> + */
> +static int
> +reopen_handle (void)
> +{
> + guestfs_h *g2;
> +
> + g2 = guestfs_create ();
> + if (g2 == NULL) {
> + perror ("guestfs_create");
> + return -1;
> + }
> +
> + guestfs_set_verbose (g2, guestfs_get_verbose (g));
> + guestfs_set_trace (g2, guestfs_get_trace (g));
> + guestfs_set_pgroup (g2, guestfs_get_pgroup (g));
> +
> + guestfs_close (g);
> + g = g2;
> +
> + return 0;
> +}
> diff --git a/cat/test-docs.sh b/cat/test-docs.sh
> index a0ffc61..d8ac358 100755
> --- a/cat/test-docs.sh
> +++ b/cat/test-docs.sh
> @@ -24,3 +24,4 @@ $srcdir/../podcheck.pl virt-filesystems.pod virt-filesystems
> $srcdir/../podcheck.pl virt-log.pod virt-log
> $srcdir/../podcheck.pl virt-ls.pod virt-ls \
> --ignore=--checksums,--extra-stat,--time,--uid
> +$srcdir/../podcheck.pl virt-tail.pod virt-tail
> diff --git a/cat/virt-cat.pod b/cat/virt-cat.pod
> index 87b0e13..a81f4f4 100644
> --- a/cat/virt-cat.pod
> +++ b/cat/virt-cat.pod
> @@ -202,6 +202,8 @@ To list out the log files from guests, see the related tool
> L<virt-log(1)>. It understands binary log formats such as the systemd
> journal.
>
> +To follow (tail) text log files, use L<virt-tail(1)>.
> +
> =head1 WINDOWS PATHS
>
> C<virt-cat> has a limited ability to understand Windows drive letters
> @@ -277,6 +279,7 @@ L<guestfish(1)>,
> L<virt-copy-out(1)>,
> L<virt-edit(1)>,
> L<virt-log(1)>,
> +L<virt-tail(1)>,
> L<virt-tar-out(1)>,
> L<http://libguestfs.org/>.
>
> diff --git a/cat/virt-log.pod b/cat/virt-log.pod
> index a85d0ee..d9a975e 100644
> --- a/cat/virt-log.pod
> +++ b/cat/virt-log.pod
> @@ -17,9 +17,10 @@ This tool understands and displays both plain text log files
> (eg. F</var/log/messages>) and binary formats such as the systemd
> journal.
>
> -To display other types of files, use L<virt-cat(1)>. To copy files
> -out of a virtual machine, use L<virt-copy-out(1)>. To display the
> -contents of the Windows Registry, use L<virt-win-reg(1)>.
> +To display other types of files, use L<virt-cat(1)>. To follow (tail)
> +text log files, use L<virt-tail(1)>. To copy files out of a virtual
> +machine, use L<virt-copy-out(1)>. To display the contents of the
> +Windows Registry, use L<virt-win-reg(1)>.
>
> =head1 EXAMPLES
>
> @@ -138,6 +139,7 @@ L<guestfs(3)>,
> L<guestfish(1)>,
> L<virt-cat(1)>,
> L<virt-copy-out(1)>,
> +L<virt-tail(1)>,
> L<virt-tar-out(1)>,
> L<virt-win-reg(1)>,
> L<http://libguestfs.org/>.
> diff --git a/cat/virt-tail.pod b/cat/virt-tail.pod
> new file mode 100644
> index 0000000..4a53553
> --- /dev/null
> +++ b/cat/virt-tail.pod
> @@ -0,0 +1,253 @@
> +=head1 NAME
> +
> +virt-tail - Follow (tail) files in a virtual machine
> +
> +=head1 SYNOPSIS
> +
> + virt-tail [--options] -d domname file [file ...]
> +
> + virt-tail [--options] -a disk.img [-a disk.img ...] file [file ...]
> +
> +=head1 DESCRIPTION
> +
> +C<virt-tail> is a command line tool to follow (tail) the contents of
> +C<file> where C<file> exists in the named virtual machine (or disk
> +image). It is similar to the ordinary command S<C<tail -f>>.
> +
> +Multiple filenames can be given, in which case each is followed
> +separately. Each filename must be a full path, starting at the root
> +directory (starting with '/').
> +
> +The command keeps running until:
> +
> +=over 4
> +
> +=item *
> +
> +The user presses the ^C or an interrupt signal is received.
> +
> +=item *
> +
> +None of the listed files was found in the guest, or they
> +all get deleted.
> +
> +=item *
> +
> +There is an unrecoverable error.
> +
> +=back
> +
> +=head1 EXAMPLE
> +
> +Follow F</var/log/messages> inside a virtual machine called C<mydomain>:
> +
> + virt-tail -d mydomain /etc/fstab
> +
> +=head1 OPTIONS
> +
> +=over 4
> +
> +=item B<--help>
> +
> +Display brief help.
> +
> +=item B<-a> file
> +
> +=item B<--add> file
> +
> +Add I<file> which should be a disk image from a virtual machine. If
> +the virtual machine has multiple block devices, you must supply all of
> +them with separate I<-a> options.
> +
> +The format of the disk image is auto-detected. To override this and
> +force a particular format use the I<--format=..> option.
> +
> +=item B<-a URI>
> +
> +=item B<--add URI>
> +
> +Add a remote disk. See L<guestfish(1)/ADDING REMOTE STORAGE>.
> +
> +=item B<-c> URI
> +
> +=item B<--connect> URI
> +
> +If using libvirt, connect to the given I<URI>. If omitted, then we
> +connect to the default libvirt hypervisor.
> +
> +If you specify guest block devices directly (I<-a>), then libvirt is
> +not used at all.
> +
> +=item B<-d> guest
> +
> +=item B<--domain> guest
> +
> +Add all the disks from the named libvirt guest. Domain UUIDs can be
> +used instead of names.
> +
> +=item B<--echo-keys>
> +
> +When prompting for keys and passphrases, virt-tail normally turns
> +echoing off so you cannot see what you are typing. If you are not
> +worried about Tempest attacks and there is no one else in the room you
> +can specify this flag to see what you are typing.
> +
> +=item B<-f>
> +
> +=item B<--follow>
> +
> +This option is ignored. virt-tail always behaves like
> +S<L<tail(1)> I<-f>>. You don't need to specify the I<-f> option.
> +
> +=item B<--format=raw|qcow2|..>
> +
> +=item B<--format>
> +
> +The default for the I<-a> option is to auto-detect the format of the
> +disk image. Using this forces the disk format for I<-a> options which
> +follow on the command line. Using I<--format> with no argument
> +switches back to auto-detection for subsequent I<-a> options.
> +
> +For example:
> +
> + virt-tail --format=raw -a disk.img file
> +
> +forces raw format (no auto-detection) for F<disk.img>.
> +
> + virt-tail --format=raw -a disk.img --format -a another.img file
> +
> +forces raw format (no auto-detection) for F<disk.img> and reverts to
> +auto-detection for F<another.img>.
> +
> +If you have untrusted raw-format guest disk images, you should use
> +this option to specify the disk format. This avoids a possible
> +security problem with malicious guests (CVE-2010-3851).
> +
> +=item B<--keys-from-stdin>
> +
> +Read key or passphrase parameters from stdin. The default is
> +to try to read passphrases from the user by opening F</dev/tty>.
> +
> +=item B<-m> dev[:mountpoint[:options[:fstype]]]
> +
> +=item B<--mount> dev[:mountpoint[:options[:fstype]]]
> +
> +Mount the named partition or logical volume on the given mountpoint.
> +
> +If the mountpoint is omitted, it defaults to F</>.
> +
> +Specifying any mountpoint disables the inspection of the guest and
> +the mount of its root and all of its mountpoints, so make sure
> +to mount all the mountpoints needed to work with the filenames
> +given as arguments.
> +
> +If you don't know what filesystems a disk image contains, you can
> +either run guestfish without this option, then list the partitions,
> +filesystems and LVs available (see L</list-partitions>,
> +L</list-filesystems> and L</lvs> commands), or you can use the
> +L<virt-filesystems(1)> program.
> +
> +The third (and rarely used) part of the mount parameter is the list of
> +mount options used to mount the underlying filesystem. If this is not
> +given, then the mount options are either the empty string or C<ro>
> +(the latter if the I<--ro> flag is used). By specifying the mount
> +options, you override this default choice. Probably the only time you
> +would use this is to enable ACLs and/or extended attributes if the
> +filesystem can support them:
> +
> + -m /dev/sda1:/:acl,user_xattr
> +
> +Using this flag is equivalent to using the C<mount-options> command.
> +
> +The fourth part of the parameter is the filesystem driver to use, such
> +as C<ext3> or C<ntfs>. This is rarely needed, but can be useful if
> +multiple drivers are valid for a filesystem (eg: C<ext2> and C<ext3>),
> +or if libguestfs misidentifies a filesystem.
> +
> +=item B<-v>
> +
> +=item B<--verbose>
> +
> +Enable verbose messages for debugging.
> +
> +=item B<-V>
> +
> +=item B<--version>
> +
> +Display version number and exit.
> +
> +=item B<-x>
> +
> +Enable tracing of libguestfs API calls.
> +
> +=back
> +
> +=head1 LOG FILES
> +
> +To list out the log files from guests, see the related tool
> +L<virt-log(1)>. It understands binary log formats such as the systemd
> +journal.
> +
> +=head1 WINDOWS PATHS
> +
> +C<virt-tail> has a limited ability to understand Windows drive letters
> +and paths (eg. F<E:\foo\bar.txt>).
> +
> +If and only if the guest is running Windows then:
> +
> +=over 4
> +
> +=item *
> +
> +Drive letter prefixes like C<C:> are resolved against the
> +Windows Registry to the correct filesystem.
> +
> +=item *
> +
> +Any backslash (C<\>) characters in the path are replaced
> +with forward slashes so that libguestfs can process it.
> +
> +=item *
> +
> +The path is resolved case insensitively to locate the file
> +that should be displayed.
> +
> +=back
> +
> +There are some known shortcomings:
> +
> +=over 4
> +
> +=item *
> +
> +Some NTFS symbolic links may not be followed correctly.
> +
> +=item *
> +
> +NTFS junction points that cross filesystems are not followed.
> +
> +=back
> +
> +=head1 EXIT STATUS
> +
> +This program returns 0 if successful, or non-zero if there was an
> +error.
> +
> +=head1 SEE ALSO
> +
> +L<guestfs(3)>,
> +L<guestfish(1)>,
> +L<virt-copy-out(1)>,
> +L<virt-cat(1)>,
> +L<virt-log(1)>,
> +L<virt-tar-out(1)>,
> +L<tail(1)>,
> +L<http://libguestfs.org/>.
> +
> +=head1 AUTHOR
> +
> +Richard W.M. Jones L<http://people.redhat.com/~rjones/>
> +
> +=head1 COPYRIGHT
> +
> +Copyright (C) 2016 Red Hat Inc.
> diff --git a/docs/guestfs-hacking.pod b/docs/guestfs-hacking.pod
> index 6b7ac1c..46df37f 100644
> --- a/docs/guestfs-hacking.pod
> +++ b/docs/guestfs-hacking.pod
> @@ -73,8 +73,8 @@ L<virt-builder(1)> command and documentation.
>
> =item F<cat>
>
> -The L<virt-cat(1)>, L<virt-filesystems(1)>, L<virt-log(1)>
> -and L<virt-ls(1)> commands and documentation.
> +The L<virt-cat(1)>, L<virt-filesystems(1)>, L<virt-log(1)>,
> +L<virt-ls(1)> and L<virt-tail(1)> commands and documentation.
>
> =item F<contrib>
>
> diff --git a/fish/guestfish.pod b/fish/guestfish.pod
> index b914449..b08f172 100644
> --- a/fish/guestfish.pod
> +++ b/fish/guestfish.pod
> @@ -1623,6 +1623,7 @@ L<virt-rescue(1)>,
> L<virt-resize(1)>,
> L<virt-sparsify(1)>,
> L<virt-sysprep(1)>,
> +L<virt-tail(1)>,
> L<virt-tar(1)>,
> L<virt-tar-in(1)>,
> L<virt-tar-out(1)>,
> diff --git a/src/guestfs.pod b/src/guestfs.pod
> index 864b9db..bdc470b 100644
> --- a/src/guestfs.pod
> +++ b/src/guestfs.pod
> @@ -3519,6 +3519,7 @@ L<virt-rescue(1)>,
> L<virt-resize(1)>,
> L<virt-sparsify(1)>,
> L<virt-sysprep(1)>,
> +L<virt-tail(1)>,
> L<virt-tar(1)>,
> L<virt-tar-in(1)>,
> L<virt-tar-out(1)>,
> diff --git a/tools/virt-win-reg b/tools/virt-win-reg
> index 57188c8..18100e7 100755
> --- a/tools/virt-win-reg
> +++ b/tools/virt-win-reg
> @@ -790,6 +790,7 @@ L<hivexregedit(1)>,
> L<guestfs(3)>,
> L<guestfish(1)>,
> L<virt-cat(1)>,
> +L<virt-tail(1)>,
> L<Sys::Guestfs(3)>,
> L<Win::Hivex(3)>,
> L<Win::Hivex::Regedit(3)>,
> diff --git a/website/index.html.in b/website/index.html.in
> index 05b5112..6d43941 100644
> --- a/website/index.html.in
> +++ b/website/index.html.in
> @@ -101,6 +101,7 @@ on <a href="http://freenode.net/">FreeNode</a>.
> <a href="virt-resize.1.html">virt-resize(1)</a> — resize virtual machines <br/>
> <a href="virt-sparsify.1.html">virt-sparsify(1)</a> — make virtual machines sparse (thin-provisioned) <br/>
> <a href="virt-sysprep.1.html">virt-sysprep(1)</a> — unconfigure a virtual machine before cloning <br/>
> +<a href="virt-tail.1.html">virt-tail(1)</a> — follow log file <br/>
> <a href="virt-tar.1.html">virt-tar(1)</a> — archive and upload files <br/>
> <a href="virt-tar-in.1.html">virt-tar-in(1)</a> — archive and upload files <br/>
> <a href="virt-tar-out.1.html">virt-tar-out(1)</a> — archive and download files <br/>
>
--
Pino Toscano
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: This is a digitally signed message part.
URL: <http://listman.redhat.com/archives/libguestfs/attachments/20161003/0e5b50cc/attachment.sig>
More information about the Libguestfs
mailing list