New tool: virt-tail.

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
This commit is contained in:
Richard W.M. Jones
2016-10-01 13:32:22 +01:00
parent 19332919a2
commit 8b076d87a1
14 changed files with 824 additions and 11 deletions

3
.gitignore vendored
View File

@@ -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

View File

@@ -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 $@

View File

@@ -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

View File

@@ -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

502
cat/tail.c Normal file
View File

@@ -0,0 +1,502 @@
/* 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 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) {
fprintf (stderr, _("%s: error: missing filenames on command line.\n"
"Please specify at least one file to follow.\n"),
getprogname ());
usage (EXIT_FAILURE);
}
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 sig_atomic_t quit = 0;
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 = 0;
char *root;
CLEANUP_FREE_STRING_LIST char **roots = NULL;
int processed;
/* Add drives, inspect and mount. */
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);
}
/* Check files here. */
processed = 0;
for (i = 0; i < argc; ++i) {
CLEANUP_FREE char *filename = NULL;
CLEANUP_FREE_STATNS struct guestfs_statns *stat = NULL;
if (windows) {
filename = windows_path (g, root, filename, 1 /* readonly */);
if (filename == NULL)
return -1; /* windows_path printed an error */
}
else {
filename = strdup (argv[i]);
if (filename == NULL) {
perror ("malloc");
return -1;
}
}
guestfs_push_error_handler (g, NULL, NULL);
stat = guestfs_statns (g, filename);
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 (), filename, 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", filename);
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, filename, 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, filename);
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;
}
/* 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);
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;
}

View File

@@ -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

View File

@@ -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/>.

View File

@@ -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/>.

253
cat/virt-tail.pod Normal file
View File

@@ -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.

View File

@@ -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>

View File

@@ -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)>,

View File

@@ -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)>,

View File

@@ -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)>,

View File

@@ -101,6 +101,7 @@ on <a href="http://freenode.net/">FreeNode</a>.
<a href="virt-resize.1.html">virt-resize(1)</a> &mdash; resize virtual machines <br/>
<a href="virt-sparsify.1.html">virt-sparsify(1)</a> &mdash; make virtual machines sparse (thin-provisioned) <br/>
<a href="virt-sysprep.1.html">virt-sysprep(1)</a> &mdash; unconfigure a virtual machine before cloning <br/>
<a href="virt-tail.1.html">virt-tail(1)</a> &mdash; follow log file <br/>
<a href="virt-tar.1.html">virt-tar(1)</a> &mdash; archive and upload files <br/>
<a href="virt-tar-in.1.html">virt-tar-in(1)</a> &mdash; archive and upload files <br/>
<a href="virt-tar-out.1.html">virt-tar-out(1)</a> &mdash; archive and download files <br/>