mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -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 $@
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
502
cat/tail.c
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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/>.
|
||||
|
||||
|
||||
@@ -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
253
cat/virt-tail.pod
Normal 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.
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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)>,
|
||||
|
||||
@@ -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)>,
|
||||
|
||||
@@ -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)>,
|
||||
|
||||
@@ -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/>
|
||||
|
||||
Reference in New Issue
Block a user