📄 tall.c
字号:
/* tail -- output the last part of file(s)
Copyright (C) 1989, 90, 91, 1995-2002 Free Software Foundation, 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* Can display any amount of data, unlike the Unix version, which uses
a fixed size buffer and therefore can only deliver a limited number
of lines.
Original version by Paul Rubin <phr@ocf.berkeley.edu>.
Extensions by David MacKenzie <djm@gnu.ai.mit.edu>.
tail -f for multiple files by Ian Lance Taylor <ian@airs.com>. */
#include <config.h>
#include <stdio.h>
#include <assert.h>
#include <getopt.h>
#include <sys/types.h>
#include <signal.h>
#include "system.h"
#include "closeout.h"
#include "argmatch.h"
#include "error.h"
#include "human.h"
#include "posixver.h"
#include "safe-read.h"
#include "xnanosleep.h"
#include "xstrtol.h"
#include "xstrtod.h"
/* The official name of this program (e.g., no `g' prefix). */
#define PROGRAM_NAME "tail"
#define AUTHORS \
N_ ("Paul Rubin, David MacKenzie, Ian Lance Taylor, and Jim Meyering")
#ifndef ENOSYS
/* Some systems don't have ENOSYS -- this should be a big enough
value that no valid errno value will match it. */
# define ENOSYS 99999
#endif
/* Number of items to tail. */
#define DEFAULT_N_LINES 10
/* Size of atomic reads. */
#ifndef BUFSIZ
# define BUFSIZ (512 * 8)
#endif
/* A special value for dump_remainder's N_BYTES parameter. */
#define COPY_TO_EOF OFF_T_MAX
/* FIXME: make Follow_name the default? */
#define DEFAULT_FOLLOW_MODE Follow_descriptor
enum Follow_mode
{
/* Follow the name of each file: if the file is renamed, try to reopen
that name and track the end of the new file if/when it's recreated.
This is useful for tracking logs that are occasionally rotated. */
Follow_name = 1,
/* Follow each descriptor obtained upon opening a file.
That means we'll continue to follow the end of a file even after
it has been renamed or unlinked. */
Follow_descriptor = 2
};
/* The types of files for which tail works. */
#define IS_TAILABLE_FILE_TYPE(Mode) \
(S_ISREG (Mode) || S_ISFIFO (Mode) || S_ISCHR (Mode))
static char const *const follow_mode_string[] =
{
"descriptor", "name", 0
};
static enum Follow_mode const follow_mode_map[] =
{
Follow_descriptor, Follow_name,
};
struct File_spec
{
/* The actual file name, or "-" for stdin. */
char *name;
/* File descriptor on which the file is open; -1 if it's not open. */
int fd;
/* The size of the file the last time we checked. */
off_t size;
/* The device and inode of the file the last time we checked. */
dev_t dev;
ino_t ino;
/* The specified name initially referred to a directory or some other
type for which tail isn't meaningful. Unlike for a permission problem
(tailable, below) once this is set, the name is not checked ever again. */
int ignore;
/* See description of DEFAULT_MAX_N_... below. */
unsigned int n_unchanged_stats;
/* See description of DEFAULT_MAX_N_... below. */
unsigned int n_consecutive_size_changes;
/* A file is tailable if it exists, is readable, and is of type
IS_TAILABLE_FILE_TYPE. */
int tailable;
/* The value of errno seen last time we checked this file. */
int errnum;
};
/* Keep trying to open a file even if it is inaccessible when tail starts
or if it becomes inaccessible later -- useful only with -f. */
static int reopen_inaccessible_files;
/* If nonzero, interpret the numeric argument as the number of lines.
Otherwise, interpret it as the number of bytes. */
static int count_lines;
/* Whether we follow the name of each file or the file descriptor
that is initially associated with each name. */
static enum Follow_mode follow_mode = Follow_descriptor;
/* If nonzero, read from the ends of all specified files until killed. */
static int forever;
/* If nonzero, count from start of file instead of end. */
static int from_start;
/* If nonzero, print filename headers. */
static int print_headers;
/* When to print the filename banners. */
enum header_mode
{
multiple_files, always, never
};
/* When tailing a file by name, if there have been this many consecutive
iterations for which the size has remained the same, then open/fstat
the file to determine if that file name is still associated with the
same device/inode-number pair as before. This option is meaningful only
when following by name. --max-unchanged-stats=N */
#define DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS 5
static unsigned long max_n_unchanged_stats_between_opens =
DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS;
/* This variable is used to ensure that a file that is unlinked or moved
aside, yet always growing will be recognized as having been renamed.
After detecting this many consecutive size changes for a file, open/fstat
the file to determine if that file name is still associated with the
same device/inode-number pair as before. This option is meaningful only
when following by name. --max-consecutive-size-changes=N */
#define DEFAULT_MAX_N_CONSECUTIVE_SIZE_CHANGES 200
static unsigned long max_n_consecutive_size_changes_between_opens =
DEFAULT_MAX_N_CONSECUTIVE_SIZE_CHANGES;
/* The name this program was run with. */
char *program_name;
/* The process ID of the process (presumably on the current host)
that is writing to all followed files. */
static pid_t pid;
/* Nonzero if we have ever read standard input. */
static int have_read_stdin;
/* For long options that have no equivalent short option, use a
non-character as a pseudo short option, starting with CHAR_MAX + 1. */
enum
{
RETRY_OPTION = CHAR_MAX + 1,
MAX_UNCHANGED_STATS_OPTION,
/* FIXME: remove this in 2001, unless someone can show a good
reason to keep it. */
MAX_CONSECUTIVE_SIZE_CHANGES_OPTION,
PID_OPTION,
LONG_FOLLOW_OPTION
};
static struct option const long_options[] =
{
/* --allow-missing is deprecated; use --retry instead
FIXME: remove it some day */
{"allow-missing", no_argument, NULL, RETRY_OPTION},
{"bytes", required_argument, NULL, 'c'},
{"follow", optional_argument, NULL, LONG_FOLLOW_OPTION},
{"lines", required_argument, NULL, 'n'},
{"max-unchanged-stats", required_argument, NULL, MAX_UNCHANGED_STATS_OPTION},
{"max-consecutive-size-changes", required_argument, NULL,
MAX_CONSECUTIVE_SIZE_CHANGES_OPTION},
{"pid", required_argument, NULL, PID_OPTION},
{"quiet", no_argument, NULL, 'q'},
{"retry", no_argument, NULL, RETRY_OPTION},
{"silent", no_argument, NULL, 'q'},
{"sleep-interval", required_argument, NULL, 's'},
{"verbose", no_argument, NULL, 'v'},
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
{NULL, 0, NULL, 0}
};
void
usage (int status)
{
if (status != 0)
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
else
{
printf (_("\
Usage: %s [OPTION]... [FILE]...\n\
"),
program_name);
printf (_("\
Print the last %d lines of each FILE to standard output.\n\
With more than one FILE, precede each with a header giving the file name.\n\
With no FILE, or when FILE is -, read standard input.\n\
\n\
"), DEFAULT_N_LINES);
fputs (_("\
Mandatory arguments to long options are mandatory for short options too.\n\
"), stdout);
fputs (_("\
--retry keep trying to open a file even if it is\n\
inaccessible when tail starts or if it becomes\n\
inaccessible later -- useful only with -f\n\
-c, --bytes=N output the last N bytes\n\
"), stdout);
fputs (_("\
-f, --follow[={name|descriptor}]\n\
output appended data as the file grows;\n\
-f, --follow, and --follow=descriptor are\n\
equivalent\n\
-F same as --follow=name --retry\n\
"), stdout);
printf (_("\
-n, --lines=N output the last N lines, instead of the last %d\n\
--max-unchanged-stats=N\n\
with --follow=name, reopen a FILE which has not\n\
changed size after N (default %d) iterations\n\
to see if it has been unlinked or renamed\n\
(this is the usual case of rotated log files)\n\
"),
DEFAULT_N_LINES,
DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS
);
fputs (_("\
--pid=PID with -f, terminate after process ID, PID dies\n\
-q, --quiet, --silent never output headers giving file names\n\
-s, --sleep-interval=S with -f, sleep for approximately S seconds\n\
(default 1.0) between iterations.\n\
-v, --verbose always output headers giving file names\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
fputs (_("\
\n\
If the first character of N (the number of bytes or lines) is a `+',\n\
print beginning with the Nth item from the start of each file, otherwise,\n\
print the last N items in the file. N may have a multiplier suffix:\n\
b for 512, k for 1024, m for 1048576 (1 Meg).\n\
\n\
"), stdout);
fputs (_("\
With --follow (-f), tail defaults to following the file descriptor, which\n\
means that even if a tail'ed file is renamed, tail will continue to track\n\
its end. \
"), stdout);
fputs (_("\
This default behavior is not desirable when you really want to\n\
track the actual name of the file, not the file descriptor (e.g., log\n\
rotation). Use --follow=name in that case. That causes tail to track the\n\
named file by reopening it periodically to see if it has been removed and\n\
recreated by some other program.\n\
"), stdout);
printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
}
exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
}
static int
valid_file_spec (struct File_spec const *f)
{
/* Exactly one of the following subexpressions must be true. */
return ((f->fd == -1) ^ (f->errnum == 0));
}
static char *
pretty_name (struct File_spec const *f)
{
return (STREQ (f->name, "-") ? "standard input" : f->name);
}
static void
xwrite (int fd, char const *buffer, size_t n_bytes)
{
assert (fd == STDOUT_FILENO);
if (n_bytes > 0 && fwrite (buffer, 1, n_bytes, stdout) == 0)
error (EXIT_FAILURE, errno, _("write error"));
}
static void
close_fd (int fd, const char *filename)
{
if (fd != -1 && fd != STDIN_FILENO && close (fd))
{
error (0, errno, _("closing %s (fd=%d)"), filename, fd);
}
}
static void
write_header (const char *pretty_filename)
{
static int first_file = 1;
printf ("%s==> %s <==\n", (first_file ? "" : "\n"), pretty_filename);
first_file = 0;
}
/* Read and output N_BYTES of file PRETTY_FILENAME starting at the current
position in FD. If N_BYTES is COPY_TO_EOF, then copy until end of file.
Return the number of bytes read from the file. */
static long
dump_remainder (const char *pretty_filename, int fd, off_t n_bytes)
{
char buffer[BUFSIZ];
size_t bytes_read;
long n_written;
off_t n_remaining = n_bytes;
n_written = 0;
while (1)
{
long n = MIN (n_remaining, (off_t) BUFSIZ);
bytes_read = safe_read (fd, buffer, n);
if (bytes_read == SAFE_READ_ERROR)
error (EXIT_FAILURE, errno, "%s", pretty_filename);
if (bytes_read == 0)
break;
xwrite (STDOUT_FILENO, buffer, bytes_read);
n_remaining -= bytes_read;
n_written += bytes_read;
}
return n_written;
}
/* Call lseek with the specified arguments, where file descriptor FD
corresponds to the file, FILENAME.
Give a diagnostic and exit nonzero if lseek fails. */
static void
xlseek (int fd, off_t offset, int whence, char const *filename)
{
off_t new_offset = lseek (fd, offset, whence);
char buf[LONGEST_HUMAN_READABLE + 1];
char *s;
char const *sign;
if (0 <= new_offset)
return;
sign = offset < 0 ? "-" : "";
if (offset < 0)
offset = -offset;
s = human_readable ((uintmax_t) offset, buf, 1, 1);
switch (whence)
{
case SEEK_SET:
error (EXIT_FAILURE, errno, _("%s: cannot seek to offset %s%s"),
filename, sign, s);
break;
case SEEK_CUR:
error (EXIT_FAILURE, errno, _("%s: cannot seek to relative offset %s%s"),
filename, sign, s);
break;
case SEEK_END:
error (EXIT_FAILURE, errno,
_("%s: cannot seek to end-relative offset %s%s"),
filename, sign, s);
break;
default:
abort ();
}
}
/* Print the last N_LINES lines from the end of file FD.
Go backward through the file, reading `BUFSIZ' bytes at a time (except
probably the first), until we hit the start of the file or have
read NUMBER newlines.
START_POS is the starting position of the read pointer for the file
associated with FD (may be nonzero).
FILE_LENGTH is the length of the file (one more than the offset of the
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -