mirror of
https://git.savannah.gnu.org/git/coreutils.git
synced 2025-09-10 07:59:52 +02:00
This better matches the treatment of POSIX form, e.g., ‘tail +Nc’ is now like ‘tail -c +N’ even when N is large. * src/tail.c: Don’t include xstrtol.h. (parse_obsolete_option): Treat numbers greater than UINTMAX_MAX as if they are UINTMAX_MAX. Parse the number by hand with saturating arithmetic; nowadays that’s simpler than using xstrtoumax. There is no need for a diagnostic now, as the error cannot happen any more. * tests/tail/tail.pl (obs-plus-c3): New test.
2540 lines
78 KiB
C
2540 lines
78 KiB
C
/* tail -- output the last part of file(s)
|
|
Copyright (C) 1989-2025 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 3 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, see <https://www.gnu.org/licenses/>. */
|
|
|
|
/* 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>.
|
|
inotify back-end by Giuseppe Scrivano <gscrivano@gnu.org>. */
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
|
|
#include "system.h"
|
|
#include "argmatch.h"
|
|
#include "assure.h"
|
|
#include "c-ctype.h"
|
|
#include "cl-strtod.h"
|
|
#include "dtimespec-bound.h"
|
|
#include "fcntl--.h"
|
|
#include "iopoll.h"
|
|
#include "isapipe.h"
|
|
#include "posixver.h"
|
|
#include "quote.h"
|
|
#include "safe-read.h"
|
|
#include "stat-size.h"
|
|
#include "stat-time.h"
|
|
#include "xbinary-io.h"
|
|
#include "xdectoint.h"
|
|
#include "xnanosleep.h"
|
|
|
|
#if HAVE_INOTIFY
|
|
# include "hash.h"
|
|
# include <poll.h>
|
|
# include <sys/inotify.h>
|
|
#endif
|
|
|
|
/* Linux can optimize the handling of local files. */
|
|
#if defined __linux__ || defined __ANDROID__
|
|
# include "fs.h"
|
|
# include "fs-is-local.h"
|
|
# if HAVE_SYS_STATFS_H
|
|
# include <sys/statfs.h>
|
|
# elif HAVE_SYS_VFS_H
|
|
# include <sys/vfs.h>
|
|
# endif
|
|
#endif
|
|
|
|
/* The official name of this program (e.g., no 'g' prefix). */
|
|
#define PROGRAM_NAME "tail"
|
|
|
|
#define AUTHORS \
|
|
proper_name ("Paul Rubin"), \
|
|
proper_name ("David MacKenzie"), \
|
|
proper_name ("Ian Lance Taylor"), \
|
|
proper_name ("Jim Meyering")
|
|
|
|
/* Number of items to tail. */
|
|
#define DEFAULT_N_LINES 10
|
|
|
|
/* Special values for dump_remainder's N_BYTES parameter. */
|
|
#define COPY_TO_EOF UINTMAX_MAX
|
|
#define COPY_A_BUFFER (UINTMAX_MAX - 1)
|
|
static_assert (OFF_T_MAX < COPY_A_BUFFER);
|
|
|
|
/* 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_ISSOCK (Mode) || S_ISCHR (Mode))
|
|
|
|
static char const *const follow_mode_string[] =
|
|
{
|
|
"descriptor", "name", nullptr
|
|
};
|
|
|
|
static enum Follow_mode const follow_mode_map[] =
|
|
{
|
|
Follow_descriptor, Follow_name,
|
|
};
|
|
|
|
struct File_spec
|
|
{
|
|
/* The actual file name, or "-" for stdin. */
|
|
char *name;
|
|
|
|
/* Attributes of the file the last time we checked. */
|
|
off_t size;
|
|
struct timespec mtime;
|
|
dev_t dev;
|
|
ino_t ino;
|
|
mode_t mode;
|
|
|
|
/* 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. */
|
|
bool ignore;
|
|
|
|
/* See the description of fremote. */
|
|
bool remote;
|
|
|
|
/* A file is tailable if it exists, is readable, and is of type
|
|
IS_TAILABLE_FILE_TYPE. */
|
|
bool tailable;
|
|
|
|
/* File descriptor on which the file is open; -1 if it's not open. */
|
|
int fd;
|
|
|
|
/* The value of errno seen last time we checked this file. */
|
|
int errnum;
|
|
|
|
/* 1 if O_NONBLOCK is clear, 0 if set, -1 if not known. */
|
|
int blocking;
|
|
|
|
#if HAVE_INOTIFY
|
|
/* The watch descriptor used by inotify. */
|
|
int wd;
|
|
|
|
/* The parent directory watch descriptor. It is used only
|
|
* when Follow_name is used. */
|
|
int parent_wd;
|
|
|
|
/* Offset in NAME of the basename part. */
|
|
size_t basename_start;
|
|
#endif
|
|
|
|
/* See description of DEFAULT_MAX_N_... below. */
|
|
uintmax_t n_unchanged_stats;
|
|
};
|
|
|
|
/* 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 bool reopen_inaccessible_files;
|
|
|
|
/* If true, interpret the numeric argument as the number of lines.
|
|
Otherwise, interpret it as the number of bytes. */
|
|
static bool 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 true, read from the ends of all specified files until killed. */
|
|
static bool forever;
|
|
|
|
/* If true, monitor output so we exit if pipe reader terminates. */
|
|
static bool monitor_output;
|
|
|
|
/* If true, count from start of file instead of end. */
|
|
static bool from_start;
|
|
|
|
/* If true, print filename headers. */
|
|
static bool print_headers;
|
|
|
|
/* Character to split lines by. */
|
|
static char line_end;
|
|
|
|
/* 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 file has not changed, 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 uintmax_t max_n_unchanged_stats_between_opens =
|
|
DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS;
|
|
|
|
/* The process IDs of the processes to watch (those writing the followed
|
|
files, or perhaps other processes the user cares about). */
|
|
static int nbpids = 0;
|
|
static pid_t * pids = nullptr;
|
|
static idx_t pids_alloc;
|
|
|
|
/* Used to determine the buffer size when scanning backwards in a file. */
|
|
static idx_t page_size;
|
|
|
|
/* True if we have ever read standard input. */
|
|
static bool have_read_stdin;
|
|
|
|
/* If nonzero, skip the is-regular-file test used to determine whether
|
|
to use the lseek optimization. Instead, use the more general (and
|
|
more expensive) code unconditionally. Intended solely for testing. */
|
|
static bool presume_input_pipe;
|
|
|
|
/* If nonzero then don't use inotify even if available. */
|
|
static bool disable_inotify;
|
|
|
|
/* 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,
|
|
PID_OPTION,
|
|
PRESUME_INPUT_PIPE_OPTION,
|
|
LONG_FOLLOW_OPTION,
|
|
DISABLE_INOTIFY_OPTION
|
|
};
|
|
|
|
static struct option const long_options[] =
|
|
{
|
|
{"bytes", required_argument, nullptr, 'c'},
|
|
{"follow", optional_argument, nullptr, LONG_FOLLOW_OPTION},
|
|
{"lines", required_argument, nullptr, 'n'},
|
|
{"max-unchanged-stats", required_argument, nullptr,
|
|
MAX_UNCHANGED_STATS_OPTION},
|
|
{"-disable-inotify", no_argument, nullptr,
|
|
DISABLE_INOTIFY_OPTION}, /* do not document */
|
|
{"pid", required_argument, nullptr, PID_OPTION},
|
|
{"-presume-input-pipe", no_argument, nullptr,
|
|
PRESUME_INPUT_PIPE_OPTION}, /* do not document */
|
|
{"quiet", no_argument, nullptr, 'q'},
|
|
{"retry", no_argument, nullptr, RETRY_OPTION},
|
|
{"silent", no_argument, nullptr, 'q'},
|
|
{"sleep-interval", required_argument, nullptr, 's'},
|
|
{"verbose", no_argument, nullptr, 'v'},
|
|
{"zero-terminated", no_argument, nullptr, 'z'},
|
|
{GETOPT_HELP_OPTION_DECL},
|
|
{GETOPT_VERSION_OPTION_DECL},
|
|
{nullptr, 0, nullptr, 0}
|
|
};
|
|
|
|
void
|
|
usage (int status)
|
|
{
|
|
if (status != EXIT_SUCCESS)
|
|
emit_try_help ();
|
|
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\
|
|
"), DEFAULT_N_LINES);
|
|
|
|
emit_stdin_note ();
|
|
emit_mandatory_arg_note ();
|
|
|
|
fputs (_("\
|
|
-c, --bytes=[+]NUM output the last NUM bytes; or use -c +NUM to\n\
|
|
output starting with byte NUM of each file\n\
|
|
"), stdout);
|
|
fputs (_("\
|
|
-f, --follow[={name|descriptor}]\n\
|
|
output appended data as the file grows;\n\
|
|
an absent option argument means 'descriptor'\n\
|
|
-F same as --follow=name --retry\n\
|
|
"), stdout);
|
|
printf (_("\
|
|
-n, --lines=[+]NUM output the last NUM lines, instead of the last %d;\n\
|
|
or use -n +NUM to skip NUM-1 lines at the start\n\
|
|
"),
|
|
DEFAULT_N_LINES
|
|
);
|
|
printf (_("\
|
|
--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\
|
|
with inotify, this option is rarely useful\n\
|
|
"),
|
|
DEFAULT_MAX_N_UNCHANGED_STATS_BETWEEN_OPENS
|
|
);
|
|
fputs (_("\
|
|
--pid=PID with -f, terminate after process ID, PID dies;\n\
|
|
can be repeated to watch multiple processes\n\
|
|
-q, --quiet, --silent never output headers giving file names\n\
|
|
--retry keep trying to open a file if it is inaccessible\n\
|
|
"), stdout);
|
|
fputs (_("\
|
|
-s, --sleep-interval=N with -f, sleep for approximately N seconds\n\
|
|
(default 1.0) between iterations;\n\
|
|
with inotify and --pid=P, check process P at\n\
|
|
least once every N seconds\n\
|
|
-v, --verbose always output headers giving file names\n\
|
|
"), stdout);
|
|
fputs (_("\
|
|
-z, --zero-terminated line delimiter is NUL, not newline\n\
|
|
"), stdout);
|
|
fputs (HELP_OPTION_DESCRIPTION, stdout);
|
|
fputs (VERSION_OPTION_DESCRIPTION, stdout);
|
|
fputs (_("\
|
|
\n\
|
|
NUM may have a multiplier suffix:\n\
|
|
b 512, kB 1000, K 1024, MB 1000*1000, M 1024*1024,\n\
|
|
GB 1000*1000*1000, G 1024*1024*1024, and so on for T, P, E, Z, Y, R, Q.\n\
|
|
Binary prefixes can be used, too: KiB=K, MiB=M, and so on.\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. 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 in a way that accommodates renaming, removal and creation.\n\
|
|
"), stdout);
|
|
emit_ancillary_info (PROGRAM_NAME);
|
|
}
|
|
exit (status);
|
|
}
|
|
|
|
/* Ensure exit, either with SIGPIPE or EXIT_FAILURE status. */
|
|
static void
|
|
die_pipe (void)
|
|
{
|
|
raise (SIGPIPE);
|
|
exit (EXIT_FAILURE);
|
|
}
|
|
|
|
/* If the output has gone away, then terminate
|
|
as we would if we had written to this output. */
|
|
static void
|
|
check_output_alive (void)
|
|
{
|
|
if (! monitor_output)
|
|
return;
|
|
|
|
if (iopoll (-1, STDOUT_FILENO, false) == IOPOLL_BROKEN_OUTPUT)
|
|
die_pipe ();
|
|
}
|
|
|
|
MAYBE_UNUSED static bool
|
|
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 const *
|
|
pretty_name (struct File_spec const *f)
|
|
{
|
|
return (STREQ (f->name, "-") ? _("standard input") : f->name);
|
|
}
|
|
|
|
/* Record a file F with descriptor FD, size SIZE, status ST, and
|
|
blocking status BLOCKING. */
|
|
|
|
static void
|
|
record_open_fd (struct File_spec *f, int fd,
|
|
off_t size, struct stat const *st,
|
|
int blocking)
|
|
{
|
|
f->fd = fd;
|
|
f->size = size;
|
|
f->mtime = get_stat_mtime (st);
|
|
f->dev = st->st_dev;
|
|
f->ino = st->st_ino;
|
|
f->mode = st->st_mode;
|
|
f->blocking = blocking;
|
|
f->n_unchanged_stats = 0;
|
|
f->ignore = false;
|
|
}
|
|
|
|
/* Close the file with descriptor FD and name FILENAME. */
|
|
|
|
static void
|
|
close_fd (int fd, char const *filename)
|
|
{
|
|
if (fd != -1 && fd != STDIN_FILENO && close (fd))
|
|
{
|
|
error (0, errno, _("closing %s (fd=%d)"), quoteaf (filename), fd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
write_header (char const *pretty_filename)
|
|
{
|
|
static bool first_file = true;
|
|
|
|
printf ("%s==> %s <==\n", (first_file ? "" : "\n"), pretty_filename);
|
|
first_file = false;
|
|
}
|
|
|
|
/* Write N_BYTES from BUFFER to stdout.
|
|
Exit immediately on error with a single diagnostic. */
|
|
|
|
static void
|
|
xwrite_stdout (char const *buffer, size_t n_bytes)
|
|
{
|
|
if (n_bytes > 0 && fwrite (buffer, 1, n_bytes, stdout) < n_bytes)
|
|
{
|
|
clearerr (stdout); /* To avoid redundant close_stdout diagnostic. */
|
|
error (EXIT_FAILURE, errno, _("error writing %s"),
|
|
quoteaf ("standard output"));
|
|
}
|
|
}
|
|
|
|
/* 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.
|
|
If N_BYTES is COPY_A_BUFFER, then copy at most one buffer's worth.
|
|
Return the number of bytes read from the file. */
|
|
|
|
static uintmax_t
|
|
dump_remainder (bool want_header, char const *pretty_filename, int fd,
|
|
uintmax_t n_bytes)
|
|
{
|
|
uintmax_t n_written;
|
|
uintmax_t n_remaining = n_bytes;
|
|
|
|
n_written = 0;
|
|
while (true)
|
|
{
|
|
char buffer[BUFSIZ];
|
|
idx_t n = MIN (n_remaining, BUFSIZ);
|
|
ptrdiff_t bytes_read = safe_read (fd, buffer, n);
|
|
if (bytes_read < 0)
|
|
{
|
|
if (errno != EAGAIN)
|
|
error (EXIT_FAILURE, errno, _("error reading %s"),
|
|
quoteaf (pretty_filename));
|
|
break;
|
|
}
|
|
if (bytes_read == 0)
|
|
break;
|
|
if (want_header)
|
|
{
|
|
write_header (pretty_filename);
|
|
want_header = false;
|
|
}
|
|
xwrite_stdout (buffer, bytes_read);
|
|
n_written += bytes_read;
|
|
if (n_bytes != COPY_TO_EOF)
|
|
{
|
|
n_remaining -= bytes_read;
|
|
if (n_remaining == 0 || n_bytes == COPY_A_BUFFER)
|
|
break;
|
|
}
|
|
}
|
|
|
|
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.
|
|
Otherwise, return the resulting offset. */
|
|
|
|
static off_t
|
|
xlseek (int fd, off_t offset, int whence, char const *filename)
|
|
{
|
|
off_t new_offset = lseek (fd, offset, whence);
|
|
|
|
if (0 <= new_offset)
|
|
return new_offset;
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
error (EXIT_FAILURE, errno, _("%s: cannot seek to offset %jd"),
|
|
quotef (filename), (intmax_t) offset);
|
|
break;
|
|
case SEEK_CUR:
|
|
error (EXIT_FAILURE, errno, _("%s: cannot seek to relative offset %jd"),
|
|
quotef (filename), (intmax_t) offset);
|
|
break;
|
|
case SEEK_END:
|
|
error (EXIT_FAILURE, errno,
|
|
_("%s: cannot seek to end-relative offset %jd"),
|
|
quotef (filename), (intmax_t) offset);
|
|
break;
|
|
default:
|
|
unreachable ();
|
|
}
|
|
}
|
|
|
|
/* 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).
|
|
END_POS is the file offset of EOF (one larger than offset of last byte).
|
|
Return true if successful. */
|
|
|
|
static bool
|
|
file_lines (char const *pretty_filename, int fd, struct stat const *sb,
|
|
uintmax_t n_lines, off_t start_pos, off_t end_pos,
|
|
uintmax_t *read_pos)
|
|
{
|
|
char *buffer;
|
|
blksize_t bufsize = BUFSIZ;
|
|
off_t pos = end_pos;
|
|
bool ok = true;
|
|
|
|
if (n_lines == 0)
|
|
return true;
|
|
|
|
/* Be careful with files with sizes that are a multiple of the page size,
|
|
as on /proc or /sys file systems these files accept seeking to within
|
|
the file, but then return no data when read. So use a buffer that's
|
|
at least PAGE_SIZE to avoid seeking within such files.
|
|
|
|
We could also indirectly use a large enough buffer through io_blksize()
|
|
however this would be less efficient in the common case, as it would
|
|
generally pick a larger buffer size, resulting in reading more data
|
|
from the end of the file. */
|
|
affirm (S_ISREG (sb->st_mode));
|
|
if (sb->st_size % page_size == 0)
|
|
bufsize = MAX (BUFSIZ, page_size);
|
|
|
|
buffer = xmalloc (bufsize);
|
|
|
|
/* Set 'bytes_read' to the size of the last, probably partial, buffer;
|
|
0 < 'bytes_read' <= 'bufsize'. */
|
|
ptrdiff_t bytes_read = (pos - start_pos) % bufsize;
|
|
if (bytes_read == 0)
|
|
bytes_read = bufsize;
|
|
/* Make 'pos' a multiple of 'bufsize' (0 if the file is short), so that all
|
|
reads will be on block boundaries, which might increase efficiency. */
|
|
pos -= bytes_read;
|
|
xlseek (fd, pos, SEEK_SET, pretty_filename);
|
|
bytes_read = safe_read (fd, buffer, bytes_read);
|
|
if (bytes_read < 0)
|
|
{
|
|
error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
|
|
ok = false;
|
|
goto free_buffer;
|
|
}
|
|
*read_pos = pos + bytes_read;
|
|
|
|
/* Count the incomplete line on files that don't end with a newline. */
|
|
if (bytes_read && buffer[bytes_read - 1] != line_end)
|
|
--n_lines;
|
|
|
|
do
|
|
{
|
|
/* Scan backward, counting the newlines in this bufferfull. */
|
|
|
|
idx_t n = bytes_read;
|
|
while (n)
|
|
{
|
|
char const *nl;
|
|
nl = memrchr (buffer, line_end, n);
|
|
if (nl == nullptr)
|
|
break;
|
|
n = nl - buffer;
|
|
if (n_lines-- == 0)
|
|
{
|
|
/* If this newline isn't the last character in the buffer,
|
|
output the part that is after it. */
|
|
xwrite_stdout (nl + 1, bytes_read - (n + 1));
|
|
*read_pos += dump_remainder (false, pretty_filename, fd,
|
|
end_pos - (pos + bytes_read));
|
|
goto free_buffer;
|
|
}
|
|
}
|
|
|
|
/* Not enough newlines in that bufferfull. */
|
|
if (pos == start_pos)
|
|
{
|
|
/* Not enough lines in the file; print everything from
|
|
start_pos to the end. */
|
|
xlseek (fd, start_pos, SEEK_SET, pretty_filename);
|
|
*read_pos = start_pos + dump_remainder (false, pretty_filename, fd,
|
|
end_pos);
|
|
goto free_buffer;
|
|
}
|
|
pos -= bufsize;
|
|
xlseek (fd, pos, SEEK_SET, pretty_filename);
|
|
|
|
bytes_read = safe_read (fd, buffer, bufsize);
|
|
if (bytes_read < 0)
|
|
{
|
|
error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
|
|
ok = false;
|
|
goto free_buffer;
|
|
}
|
|
|
|
*read_pos = pos + bytes_read;
|
|
}
|
|
while (bytes_read > 0);
|
|
|
|
free_buffer:
|
|
free (buffer);
|
|
return ok;
|
|
}
|
|
|
|
/* Print the last N_LINES lines from the end of the standard input,
|
|
open for reading as pipe FD.
|
|
Buffer the text as a linked list of LBUFFERs, adding them as needed.
|
|
Return true if successful. */
|
|
|
|
static bool
|
|
pipe_lines (char const *pretty_filename, int fd, uintmax_t n_lines,
|
|
uintmax_t *read_pos)
|
|
{
|
|
struct linebuffer
|
|
{
|
|
char buffer[BUFSIZ];
|
|
size_t nbytes;
|
|
size_t nlines;
|
|
struct linebuffer *next;
|
|
};
|
|
typedef struct linebuffer LBUFFER;
|
|
LBUFFER *first, *last, *tmp;
|
|
size_t total_lines = 0; /* Total number of newlines in all buffers. */
|
|
bool ok = true;
|
|
ptrdiff_t n_read; /* Size in bytes of most recent read */
|
|
|
|
first = last = xmalloc (sizeof (LBUFFER));
|
|
first->nbytes = first->nlines = 0;
|
|
first->next = nullptr;
|
|
tmp = xmalloc (sizeof (LBUFFER));
|
|
|
|
/* Input is always read into a fresh buffer. */
|
|
while (true)
|
|
{
|
|
n_read = safe_read (fd, tmp->buffer, BUFSIZ);
|
|
if (n_read <= 0)
|
|
break;
|
|
tmp->nbytes = n_read;
|
|
*read_pos += n_read;
|
|
tmp->nlines = 0;
|
|
tmp->next = nullptr;
|
|
|
|
/* Count the number of newlines just read. */
|
|
{
|
|
char const *buffer_end = tmp->buffer + n_read;
|
|
char const *p = tmp->buffer;
|
|
while ((p = memchr (p, line_end, buffer_end - p)))
|
|
{
|
|
++p;
|
|
++tmp->nlines;
|
|
}
|
|
}
|
|
total_lines += tmp->nlines;
|
|
|
|
/* If there is enough room in the last buffer read, just append the new
|
|
one to it. This is because when reading from a pipe, 'n_read' can
|
|
often be very small. */
|
|
if (tmp->nbytes + last->nbytes < BUFSIZ)
|
|
{
|
|
memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes);
|
|
last->nbytes += tmp->nbytes;
|
|
last->nlines += tmp->nlines;
|
|
}
|
|
else
|
|
{
|
|
/* If there's not enough room, link the new buffer onto the end of
|
|
the list, then either free up the oldest buffer for the next
|
|
read if that would leave enough lines, or else malloc a new one.
|
|
Some compaction mechanism is possible but probably not
|
|
worthwhile. */
|
|
last = last->next = tmp;
|
|
if (total_lines - first->nlines > n_lines)
|
|
{
|
|
tmp = first;
|
|
total_lines -= first->nlines;
|
|
first = first->next;
|
|
}
|
|
else
|
|
tmp = xmalloc (sizeof (LBUFFER));
|
|
}
|
|
}
|
|
|
|
free (tmp);
|
|
|
|
if (n_read < 0 && errno != EAGAIN)
|
|
{
|
|
error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
|
|
ok = false;
|
|
goto free_lbuffers;
|
|
}
|
|
|
|
/* If the file is empty, then bail out. */
|
|
if (last->nbytes == 0)
|
|
goto free_lbuffers;
|
|
|
|
/* This prevents a core dump when the pipe contains no newlines. */
|
|
if (n_lines == 0)
|
|
goto free_lbuffers;
|
|
|
|
/* Count the incomplete line on files that don't end with a newline. */
|
|
if (last->buffer[last->nbytes - 1] != line_end)
|
|
{
|
|
++last->nlines;
|
|
++total_lines;
|
|
}
|
|
|
|
/* Run through the list, printing lines. First, skip over unneeded
|
|
buffers. */
|
|
for (tmp = first; total_lines - tmp->nlines > n_lines; tmp = tmp->next)
|
|
total_lines -= tmp->nlines;
|
|
|
|
/* Find the correct beginning, then print the rest of the file. */
|
|
{
|
|
char const *beg = tmp->buffer;
|
|
char const *buffer_end = tmp->buffer + tmp->nbytes;
|
|
if (total_lines > n_lines)
|
|
{
|
|
/* Skip 'total_lines' - 'n_lines' newlines. We made sure that
|
|
'total_lines' - 'n_lines' <= 'tmp->nlines'. */
|
|
size_t j;
|
|
for (j = total_lines - n_lines; j; --j)
|
|
{
|
|
beg = rawmemchr (beg, line_end);
|
|
++beg;
|
|
}
|
|
}
|
|
|
|
xwrite_stdout (beg, buffer_end - beg);
|
|
}
|
|
|
|
for (tmp = tmp->next; tmp; tmp = tmp->next)
|
|
xwrite_stdout (tmp->buffer, tmp->nbytes);
|
|
|
|
free_lbuffers:
|
|
while (first)
|
|
{
|
|
tmp = first->next;
|
|
free (first);
|
|
first = tmp;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/* Print the last N_BYTES characters from the end of FD.
|
|
Work even if the input is a pipe.
|
|
This is a stripped down version of pipe_lines.
|
|
Return true if successful. */
|
|
|
|
static bool
|
|
pipe_bytes (char const *pretty_filename, int fd, uintmax_t n_bytes,
|
|
uintmax_t *read_pos)
|
|
{
|
|
struct charbuffer
|
|
{
|
|
char buffer[BUFSIZ];
|
|
size_t nbytes;
|
|
struct charbuffer *next;
|
|
};
|
|
typedef struct charbuffer CBUFFER;
|
|
CBUFFER *first, *last, *tmp;
|
|
size_t i; /* Index into buffers. */
|
|
size_t total_bytes = 0; /* Total characters in all buffers. */
|
|
bool ok = true;
|
|
ptrdiff_t n_read;
|
|
|
|
first = last = xmalloc (sizeof (CBUFFER));
|
|
first->nbytes = 0;
|
|
first->next = nullptr;
|
|
tmp = xmalloc (sizeof (CBUFFER));
|
|
|
|
/* Input is always read into a fresh buffer. */
|
|
while (true)
|
|
{
|
|
n_read = safe_read (fd, tmp->buffer, BUFSIZ);
|
|
if (n_read <= 0)
|
|
break;
|
|
*read_pos += n_read;
|
|
tmp->nbytes = n_read;
|
|
tmp->next = nullptr;
|
|
|
|
total_bytes += tmp->nbytes;
|
|
/* If there is enough room in the last buffer read, just append the new
|
|
one to it. This is because when reading from a pipe, 'nbytes' can
|
|
often be very small. */
|
|
if (tmp->nbytes + last->nbytes < BUFSIZ)
|
|
{
|
|
memcpy (&last->buffer[last->nbytes], tmp->buffer, tmp->nbytes);
|
|
last->nbytes += tmp->nbytes;
|
|
}
|
|
else
|
|
{
|
|
/* If there's not enough room, link the new buffer onto the end of
|
|
the list, then either free up the oldest buffer for the next
|
|
read if that would leave enough characters, or else malloc a new
|
|
one. Some compaction mechanism is possible but probably not
|
|
worthwhile. */
|
|
last = last->next = tmp;
|
|
if (total_bytes - first->nbytes > n_bytes)
|
|
{
|
|
tmp = first;
|
|
total_bytes -= first->nbytes;
|
|
first = first->next;
|
|
}
|
|
else
|
|
{
|
|
tmp = xmalloc (sizeof (CBUFFER));
|
|
}
|
|
}
|
|
}
|
|
|
|
free (tmp);
|
|
|
|
if (n_read < 0 && errno != EAGAIN)
|
|
{
|
|
error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
|
|
ok = false;
|
|
goto free_cbuffers;
|
|
}
|
|
|
|
/* Run through the list, printing characters. First, skip over unneeded
|
|
buffers. */
|
|
for (tmp = first; total_bytes - tmp->nbytes > n_bytes; tmp = tmp->next)
|
|
total_bytes -= tmp->nbytes;
|
|
|
|
/* Find the correct beginning, then print the rest of the file.
|
|
We made sure that 'total_bytes' - 'n_bytes' <= 'tmp->nbytes'. */
|
|
if (total_bytes > n_bytes)
|
|
i = total_bytes - n_bytes;
|
|
else
|
|
i = 0;
|
|
xwrite_stdout (&tmp->buffer[i], tmp->nbytes - i);
|
|
|
|
for (tmp = tmp->next; tmp; tmp = tmp->next)
|
|
xwrite_stdout (tmp->buffer, tmp->nbytes);
|
|
|
|
free_cbuffers:
|
|
while (first)
|
|
{
|
|
tmp = first->next;
|
|
free (first);
|
|
first = tmp;
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
/* Skip N_BYTES characters from the start of pipe FD, and print
|
|
any extra characters that were read beyond that.
|
|
Return 1 on error, 0 if ok, -1 if EOF. */
|
|
|
|
static int
|
|
start_bytes (char const *pretty_filename, int fd, uintmax_t n_bytes,
|
|
uintmax_t *read_pos)
|
|
{
|
|
char buffer[BUFSIZ];
|
|
|
|
while (0 < n_bytes)
|
|
{
|
|
ptrdiff_t bytes_read = safe_read (fd, buffer, BUFSIZ);
|
|
if (bytes_read == 0)
|
|
return -1;
|
|
if (bytes_read < 0)
|
|
{
|
|
error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
|
|
return 1;
|
|
}
|
|
*read_pos += bytes_read;
|
|
if (bytes_read <= n_bytes)
|
|
n_bytes -= bytes_read;
|
|
else
|
|
{
|
|
size_t n_remaining = bytes_read - n_bytes;
|
|
/* Print extra characters if there are any. */
|
|
xwrite_stdout (&buffer[n_bytes], n_remaining);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Skip N_LINES lines at the start of file or pipe FD, and print
|
|
any extra characters that were read beyond that.
|
|
Return 1 on error, 0 if ok, -1 if EOF. */
|
|
|
|
static int
|
|
start_lines (char const *pretty_filename, int fd, uintmax_t n_lines,
|
|
uintmax_t *read_pos)
|
|
{
|
|
if (n_lines == 0)
|
|
return 0;
|
|
|
|
while (true)
|
|
{
|
|
char buffer[BUFSIZ];
|
|
ptrdiff_t bytes_read = safe_read (fd, buffer, BUFSIZ);
|
|
if (bytes_read == 0) /* EOF */
|
|
return -1;
|
|
if (bytes_read < 0) /* error */
|
|
{
|
|
error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
|
|
return 1;
|
|
}
|
|
|
|
char *buffer_end = buffer + bytes_read;
|
|
|
|
*read_pos += bytes_read;
|
|
|
|
char *p = buffer;
|
|
while ((p = memchr (p, line_end, buffer_end - p)))
|
|
{
|
|
++p;
|
|
if (--n_lines == 0)
|
|
{
|
|
if (p < buffer_end)
|
|
xwrite_stdout (p, buffer_end - p);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return false when FD is open on a file residing on a local file system.
|
|
If fstatfs fails, give a diagnostic and return true.
|
|
If fstatfs cannot be called, return true. */
|
|
static bool
|
|
fremote (int fd, char const *name)
|
|
{
|
|
bool remote = true; /* be conservative (poll by default). */
|
|
|
|
#if HAVE_FSTATFS && HAVE_STRUCT_STATFS_F_TYPE \
|
|
&& (defined __linux__ || defined __ANDROID__)
|
|
struct statfs buf;
|
|
int err = fstatfs (fd, &buf);
|
|
if (err != 0)
|
|
{
|
|
/* On at least linux-2.6.38, fstatfs fails with ENOSYS when FD
|
|
is open on a pipe. Treat that like a remote file. */
|
|
if (errno != ENOSYS)
|
|
error (0, errno, _("cannot determine location of %s. "
|
|
"reverting to polling"), quoteaf (name));
|
|
}
|
|
else
|
|
{
|
|
/* Treat unrecognized file systems as "remote", so caller polls.
|
|
Note README-release has instructions for syncing the internal
|
|
list with the latest Linux kernel file system constants. */
|
|
remote = is_local_fs_type (buf.f_type) <= 0;
|
|
}
|
|
#endif
|
|
|
|
return remote;
|
|
}
|
|
|
|
/* open/fstat F->name and handle changes. */
|
|
static void
|
|
recheck (struct File_spec *f, bool blocking)
|
|
{
|
|
struct stat new_stats;
|
|
bool ok = true;
|
|
bool is_stdin = (STREQ (f->name, "-"));
|
|
bool was_tailable = f->tailable;
|
|
int prev_errnum = f->errnum;
|
|
bool new_file;
|
|
int fd = (is_stdin
|
|
? STDIN_FILENO
|
|
: open (f->name, O_RDONLY | (blocking ? 0 : O_NONBLOCK)));
|
|
|
|
affirm (valid_file_spec (f));
|
|
|
|
/* If the open fails because the file doesn't exist,
|
|
then mark the file as not tailable. */
|
|
f->tailable = !(reopen_inaccessible_files && fd == -1);
|
|
|
|
if (! disable_inotify && ! lstat (f->name, &new_stats)
|
|
&& S_ISLNK (new_stats.st_mode))
|
|
{
|
|
/* Diagnose the edge case where a regular file is changed
|
|
to a symlink. We avoid inotify with symlinks since
|
|
it's awkward to match between symlink name and target. */
|
|
ok = false;
|
|
f->errnum = -1;
|
|
f->ignore = true;
|
|
|
|
error (0, 0, _("%s has been replaced with an untailable symbolic link"),
|
|
quoteaf (pretty_name (f)));
|
|
}
|
|
else if (fd == -1 || fstat (fd, &new_stats) < 0)
|
|
{
|
|
ok = false;
|
|
f->errnum = errno;
|
|
if (!f->tailable)
|
|
{
|
|
if (was_tailable)
|
|
{
|
|
/* FIXME-maybe: detect the case in which the file first becomes
|
|
unreadable (perms), and later becomes readable again and can
|
|
be seen to be the same file (dev/ino). Otherwise, tail prints
|
|
the entire contents of the file when it becomes readable. */
|
|
error (0, f->errnum, _("%s has become inaccessible"),
|
|
quoteaf (pretty_name (f)));
|
|
}
|
|
else
|
|
{
|
|
/* say nothing... it's still not tailable */
|
|
}
|
|
}
|
|
else if (prev_errnum != errno)
|
|
error (0, errno, "%s", quotef (pretty_name (f)));
|
|
}
|
|
else if (!IS_TAILABLE_FILE_TYPE (new_stats.st_mode))
|
|
{
|
|
ok = false;
|
|
f->errnum = -1;
|
|
f->tailable = false;
|
|
f->ignore = ! (reopen_inaccessible_files && follow_mode == Follow_name);
|
|
if (was_tailable || prev_errnum != f->errnum)
|
|
error (0, 0, _("%s has been replaced with an untailable file%s"),
|
|
quoteaf (pretty_name (f)),
|
|
f->ignore ? _("; giving up on this name") : "");
|
|
}
|
|
else if ((f->remote = fremote (fd, pretty_name (f))) && ! disable_inotify)
|
|
{
|
|
ok = false;
|
|
f->errnum = -1;
|
|
error (0, 0, _("%s has been replaced with an untailable remote file"),
|
|
quoteaf (pretty_name (f)));
|
|
f->ignore = true;
|
|
f->remote = true;
|
|
}
|
|
else
|
|
{
|
|
f->errnum = 0;
|
|
}
|
|
|
|
new_file = false;
|
|
if (!ok)
|
|
{
|
|
close_fd (fd, pretty_name (f));
|
|
close_fd (f->fd, pretty_name (f));
|
|
f->fd = -1;
|
|
}
|
|
else if (prev_errnum && prev_errnum != ENOENT)
|
|
{
|
|
new_file = true;
|
|
affirm (f->fd == -1);
|
|
error (0, 0, _("%s has become accessible"), quoteaf (pretty_name (f)));
|
|
}
|
|
else if (f->fd == -1)
|
|
{
|
|
/* A new file even when inodes haven't changed as <dev,inode>
|
|
pairs can be reused, and we know the file was missing
|
|
on the previous iteration. Note this also means the file
|
|
is redisplayed in --follow=name mode if renamed away from
|
|
and back to a monitored name. */
|
|
new_file = true;
|
|
|
|
error (0, 0,
|
|
_("%s has appeared; following new file"),
|
|
quoteaf (pretty_name (f)));
|
|
}
|
|
else if (f->ino != new_stats.st_ino || f->dev != new_stats.st_dev)
|
|
{
|
|
/* File has been replaced (e.g., via log rotation) --
|
|
tail the new one. */
|
|
new_file = true;
|
|
|
|
error (0, 0,
|
|
_("%s has been replaced; following new file"),
|
|
quoteaf (pretty_name (f)));
|
|
|
|
/* Close the old one. */
|
|
close_fd (f->fd, pretty_name (f));
|
|
|
|
}
|
|
else
|
|
{
|
|
/* No changes detected, so close new fd. */
|
|
close_fd (fd, pretty_name (f));
|
|
}
|
|
|
|
/* FIXME: When a log is rotated, daemons tend to log to the
|
|
old file descriptor until the new file is present and
|
|
the daemon is sent a signal. Therefore tail may miss entries
|
|
being written to the old file. Perhaps we should keep
|
|
the older file open and continue to monitor it until
|
|
data is written to a new file. */
|
|
if (new_file)
|
|
{
|
|
/* Start at the beginning of the file. */
|
|
record_open_fd (f, fd, 0, &new_stats, (is_stdin ? -1 : blocking));
|
|
if (S_ISREG (new_stats.st_mode))
|
|
xlseek (fd, 0, SEEK_SET, pretty_name (f));
|
|
}
|
|
}
|
|
|
|
/* Return true if any of the N_FILES files in F are live, i.e., have
|
|
open file descriptors, or should be checked again (see --retry).
|
|
When following descriptors, checking should only continue when any
|
|
of the files is not yet ignored. */
|
|
|
|
static bool
|
|
any_live_files (const struct File_spec *f, size_t n_files)
|
|
{
|
|
/* In inotify mode, ignore may be set for files
|
|
which may later be replaced with new files.
|
|
So always consider files live in -F mode. */
|
|
if (reopen_inaccessible_files && follow_mode == Follow_name)
|
|
return true;
|
|
|
|
for (size_t i = 0; i < n_files; i++)
|
|
{
|
|
if (0 <= f[i].fd)
|
|
return true;
|
|
else
|
|
{
|
|
if (! f[i].ignore && reopen_inaccessible_files)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Determine whether all watched writers are dead.
|
|
Returns true only if all processes' states can be determined,
|
|
and all processes no longer exist. */
|
|
|
|
static bool
|
|
writers_are_dead (void)
|
|
{
|
|
if (!nbpids)
|
|
return false;
|
|
|
|
for (int i = 0; i < nbpids; i++)
|
|
{
|
|
if (kill (pids[i], 0) == 0 || errno == EPERM)
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Tail N_FILES files forever, or until killed.
|
|
The pertinent information for each file is stored in an entry of F.
|
|
Loop over each of them, doing an fstat to see if they have changed size,
|
|
and an occasional open/fstat to see if any dev/ino pair has changed.
|
|
If none of them have changed size in one iteration, sleep for a
|
|
while and try again. Continue until the user interrupts us. */
|
|
|
|
static void
|
|
tail_forever (struct File_spec *f, size_t n_files, double sleep_interval)
|
|
{
|
|
/* Use blocking I/O as an optimization, when it's easy. */
|
|
bool blocking = (!nbpids && follow_mode == Follow_descriptor
|
|
&& n_files == 1 && f[0].fd != -1 && ! S_ISREG (f[0].mode));
|
|
size_t last;
|
|
bool writers_dead = false;
|
|
|
|
last = n_files - 1;
|
|
|
|
while (true)
|
|
{
|
|
size_t i;
|
|
bool any_input = false;
|
|
|
|
for (i = 0; i < n_files; i++)
|
|
{
|
|
int fd;
|
|
char const *name;
|
|
mode_t mode;
|
|
struct stat stats;
|
|
uintmax_t bytes_read;
|
|
|
|
if (f[i].ignore)
|
|
continue;
|
|
|
|
if (f[i].fd < 0)
|
|
{
|
|
recheck (&f[i], blocking);
|
|
continue;
|
|
}
|
|
|
|
fd = f[i].fd;
|
|
name = pretty_name (&f[i]);
|
|
mode = f[i].mode;
|
|
|
|
if (f[i].blocking != blocking)
|
|
{
|
|
int old_flags = fcntl (fd, F_GETFL);
|
|
int new_flags = old_flags | (blocking ? 0 : O_NONBLOCK);
|
|
if (old_flags < 0
|
|
|| (new_flags != old_flags
|
|
&& fcntl (fd, F_SETFL, new_flags) == -1))
|
|
{
|
|
/* Don't update f[i].blocking if fcntl fails. */
|
|
if (S_ISREG (f[i].mode) && errno == EPERM)
|
|
{
|
|
/* This happens when using tail -f on a file with
|
|
the append-only attribute. */
|
|
}
|
|
else
|
|
error (EXIT_FAILURE, errno,
|
|
_("%s: cannot change nonblocking mode"),
|
|
quotef (name));
|
|
}
|
|
else
|
|
f[i].blocking = blocking;
|
|
}
|
|
|
|
bool read_unchanged = false;
|
|
if (!f[i].blocking)
|
|
{
|
|
if (fstat (fd, &stats) != 0)
|
|
{
|
|
f[i].fd = -1;
|
|
f[i].errnum = errno;
|
|
error (0, errno, "%s", quotef (name));
|
|
close (fd); /* ignore failure */
|
|
continue;
|
|
}
|
|
|
|
if (f[i].mode == stats.st_mode
|
|
&& (! S_ISREG (stats.st_mode) || f[i].size == stats.st_size)
|
|
&& timespec_cmp (f[i].mtime, get_stat_mtime (&stats)) == 0)
|
|
{
|
|
if ((max_n_unchanged_stats_between_opens
|
|
<= f[i].n_unchanged_stats++)
|
|
&& follow_mode == Follow_name)
|
|
{
|
|
recheck (&f[i], f[i].blocking);
|
|
f[i].n_unchanged_stats = 0;
|
|
}
|
|
if (fd != f[i].fd || S_ISREG (stats.st_mode) || 1 < n_files)
|
|
continue;
|
|
else
|
|
read_unchanged = true;
|
|
}
|
|
|
|
affirm (fd == f[i].fd);
|
|
|
|
/* This file has changed. Print out what we can, and
|
|
then keep looping. */
|
|
|
|
f[i].mtime = get_stat_mtime (&stats);
|
|
f[i].mode = stats.st_mode;
|
|
|
|
/* reset counter */
|
|
if (! read_unchanged)
|
|
f[i].n_unchanged_stats = 0;
|
|
|
|
/* XXX: This is only a heuristic, as the file may have also
|
|
been truncated and written to if st_size >= size
|
|
(in which case we ignore new data <= size). */
|
|
if (S_ISREG (mode) && stats.st_size < f[i].size)
|
|
{
|
|
error (0, 0, _("%s: file truncated"), quotef (name));
|
|
/* Assume the file was truncated to 0,
|
|
and therefore output all "new" data. */
|
|
xlseek (fd, 0, SEEK_SET, name);
|
|
f[i].size = 0;
|
|
}
|
|
|
|
if (i != last)
|
|
{
|
|
if (print_headers)
|
|
write_header (name);
|
|
last = i;
|
|
}
|
|
}
|
|
|
|
/* Don't read more than st_size on networked file systems
|
|
because it was seen on glusterfs at least, that st_size
|
|
may be smaller than the data read on a _subsequent_ stat call. */
|
|
uintmax_t bytes_to_read;
|
|
if (f[i].blocking)
|
|
bytes_to_read = COPY_A_BUFFER;
|
|
else if (S_ISREG (mode) && f[i].remote)
|
|
bytes_to_read = stats.st_size - f[i].size;
|
|
else
|
|
bytes_to_read = COPY_TO_EOF;
|
|
|
|
bytes_read = dump_remainder (false, name, fd, bytes_to_read);
|
|
|
|
if (read_unchanged && bytes_read)
|
|
f[i].n_unchanged_stats = 0;
|
|
|
|
any_input |= (bytes_read != 0);
|
|
f[i].size += bytes_read;
|
|
}
|
|
|
|
if (! any_live_files (f, n_files))
|
|
{
|
|
error (EXIT_FAILURE, 0, _("no files remaining"));
|
|
break;
|
|
}
|
|
|
|
if ((!any_input || blocking) && fflush (stdout) != 0)
|
|
write_error ();
|
|
|
|
check_output_alive ();
|
|
|
|
/* If nothing was read, sleep and/or check for dead writers. */
|
|
if (!any_input)
|
|
{
|
|
if (writers_dead)
|
|
break;
|
|
|
|
/* Once the writer is dead, read the files once more to
|
|
avoid a race condition. */
|
|
writers_dead = writers_are_dead ();
|
|
|
|
if (!writers_dead && xnanosleep (sleep_interval))
|
|
error (EXIT_FAILURE, errno, _("cannot read realtime clock"));
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
#if HAVE_INOTIFY
|
|
|
|
/* Return true if any of the N_FILES files in F is remote, i.e., has
|
|
an open file descriptor and is on a network file system. */
|
|
|
|
static bool
|
|
any_remote_file (const struct File_spec *f, size_t n_files)
|
|
{
|
|
for (size_t i = 0; i < n_files; i++)
|
|
if (0 <= f[i].fd && f[i].remote)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if any of the N_FILES files in F is non remote, i.e., has
|
|
an open file descriptor and is not on a network file system. */
|
|
|
|
static bool
|
|
any_non_remote_file (const struct File_spec *f, size_t n_files)
|
|
{
|
|
for (size_t i = 0; i < n_files; i++)
|
|
if (0 <= f[i].fd && ! f[i].remote)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if any of the N_FILES files in F is a symlink.
|
|
Note we don't worry about the edge case where "-" exists,
|
|
since that will have the same consequences for inotify,
|
|
which is the only context this function is currently used. */
|
|
|
|
static bool
|
|
any_symlinks (const struct File_spec *f, size_t n_files)
|
|
{
|
|
struct stat st;
|
|
for (size_t i = 0; i < n_files; i++)
|
|
if (lstat (f[i].name, &st) == 0 && S_ISLNK (st.st_mode))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if any of the N_FILES files in F is not
|
|
a regular file or fifo. This is used to avoid adding inotify
|
|
watches on a device file for example, which inotify
|
|
will accept, but not give any events for. */
|
|
|
|
static bool
|
|
any_non_regular_fifo (const struct File_spec *f, size_t n_files)
|
|
{
|
|
for (size_t i = 0; i < n_files; i++)
|
|
if (0 <= f[i].fd && ! S_ISREG (f[i].mode) && ! S_ISFIFO (f[i].mode))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/* Return true if any of the N_FILES files in F represents
|
|
stdin and is tailable. */
|
|
|
|
static bool
|
|
tailable_stdin (const struct File_spec *f, size_t n_files)
|
|
{
|
|
for (size_t i = 0; i < n_files; i++)
|
|
if (!f[i].ignore && STREQ (f[i].name, "-"))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
static size_t
|
|
wd_hasher (const void *entry, size_t tabsize)
|
|
{
|
|
const struct File_spec *spec = entry;
|
|
return spec->wd % tabsize;
|
|
}
|
|
|
|
static bool
|
|
wd_comparator (const void *e1, const void *e2)
|
|
{
|
|
const struct File_spec *spec1 = e1;
|
|
const struct File_spec *spec2 = e2;
|
|
return spec1->wd == spec2->wd;
|
|
}
|
|
|
|
/* Output (new) data for FSPEC->fd.
|
|
PREV_FSPEC records the last File_spec for which we output. */
|
|
static void
|
|
check_fspec (struct File_spec *fspec, struct File_spec **prev_fspec)
|
|
{
|
|
struct stat stats;
|
|
char const *name;
|
|
|
|
if (fspec->fd == -1)
|
|
return;
|
|
|
|
name = pretty_name (fspec);
|
|
|
|
if (fstat (fspec->fd, &stats) != 0)
|
|
{
|
|
fspec->errnum = errno;
|
|
close_fd (fspec->fd, name);
|
|
fspec->fd = -1;
|
|
return;
|
|
}
|
|
|
|
/* XXX: This is only a heuristic, as the file may have also
|
|
been truncated and written to if st_size >= size
|
|
(in which case we ignore new data <= size).
|
|
Though in the inotify case it's more likely we'll get
|
|
separate events for truncate() and write(). */
|
|
if (S_ISREG (fspec->mode) && stats.st_size < fspec->size)
|
|
{
|
|
error (0, 0, _("%s: file truncated"), quotef (name));
|
|
xlseek (fspec->fd, 0, SEEK_SET, name);
|
|
fspec->size = 0;
|
|
}
|
|
else if (S_ISREG (fspec->mode) && stats.st_size == fspec->size
|
|
&& timespec_cmp (fspec->mtime, get_stat_mtime (&stats)) == 0)
|
|
return;
|
|
|
|
bool want_header = print_headers && (fspec != *prev_fspec);
|
|
|
|
uintmax_t bytes_read = dump_remainder (want_header, name, fspec->fd,
|
|
COPY_TO_EOF);
|
|
fspec->size += bytes_read;
|
|
|
|
if (bytes_read)
|
|
{
|
|
*prev_fspec = fspec;
|
|
if (fflush (stdout) != 0)
|
|
write_error ();
|
|
}
|
|
}
|
|
|
|
/* Attempt to tail N_FILES files forever, or until killed.
|
|
Check modifications using the inotify events system.
|
|
Exit if finished or on fatal error; return to revert to polling. */
|
|
static void
|
|
tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
|
|
double sleep_interval, Hash_table **wd_to_namep)
|
|
{
|
|
# if TAIL_TEST_SLEEP
|
|
/* Delay between open() and inotify_add_watch()
|
|
to help trigger different cases. */
|
|
xnanosleep (1000000);
|
|
# endif
|
|
unsigned int max_realloc = 3;
|
|
|
|
/* Map an inotify watch descriptor to the name of the file it's watching. */
|
|
Hash_table *wd_to_name;
|
|
|
|
bool found_watchable_file = false;
|
|
bool tailed_but_unwatchable = false;
|
|
bool found_unwatchable_dir = false;
|
|
bool no_inotify_resources = false;
|
|
bool writers_dead = false;
|
|
struct File_spec *prev_fspec;
|
|
size_t evlen = 0;
|
|
char *evbuf;
|
|
size_t evbuf_off = 0;
|
|
|
|
wd_to_name = hash_initialize (n_files, nullptr, wd_hasher, wd_comparator,
|
|
nullptr);
|
|
if (! wd_to_name)
|
|
xalloc_die ();
|
|
*wd_to_namep = wd_to_name;
|
|
|
|
/* The events mask used with inotify on files (not directories). */
|
|
uint32_t inotify_wd_mask = IN_MODIFY;
|
|
/* TODO: Perhaps monitor these events in Follow_descriptor mode also,
|
|
to tag reported file names with "deleted", "moved" etc. */
|
|
if (follow_mode == Follow_name)
|
|
inotify_wd_mask |= (IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF);
|
|
|
|
/* Add an inotify watch for each watched file. If -F is specified then watch
|
|
its parent directory too, in this way when they re-appear we can add them
|
|
again to the watch list. */
|
|
size_t i;
|
|
for (i = 0; i < n_files; i++)
|
|
{
|
|
if (!f[i].ignore)
|
|
{
|
|
size_t fnlen = strlen (f[i].name);
|
|
if (evlen < fnlen)
|
|
evlen = fnlen;
|
|
|
|
f[i].wd = -1;
|
|
|
|
if (follow_mode == Follow_name)
|
|
{
|
|
size_t dirlen = dir_len (f[i].name);
|
|
char prev = f[i].name[dirlen];
|
|
f[i].basename_start = last_component (f[i].name) - f[i].name;
|
|
|
|
f[i].name[dirlen] = '\0';
|
|
|
|
/* It's fine to add the same directory more than once.
|
|
In that case the same watch descriptor is returned. */
|
|
f[i].parent_wd = inotify_add_watch (wd, dirlen ? f[i].name : ".",
|
|
(IN_CREATE | IN_DELETE
|
|
| IN_MOVED_TO | IN_ATTRIB
|
|
| IN_DELETE_SELF));
|
|
|
|
f[i].name[dirlen] = prev;
|
|
|
|
if (f[i].parent_wd < 0)
|
|
{
|
|
if (errno != ENOSPC) /* suppress confusing error. */
|
|
error (0, errno, _("cannot watch parent directory of %s"),
|
|
quoteaf (f[i].name));
|
|
else
|
|
error (0, 0, _("inotify resources exhausted"));
|
|
found_unwatchable_dir = true;
|
|
/* We revert to polling below. Note invalid uses
|
|
of the inotify API will still be diagnosed. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
f[i].wd = inotify_add_watch (wd, f[i].name, inotify_wd_mask);
|
|
|
|
if (f[i].wd < 0)
|
|
{
|
|
if (f[i].fd != -1) /* already tailed. */
|
|
tailed_but_unwatchable = true;
|
|
if (errno == ENOSPC || errno == ENOMEM)
|
|
{
|
|
no_inotify_resources = true;
|
|
error (0, 0, _("inotify resources exhausted"));
|
|
break;
|
|
}
|
|
else if (errno != f[i].errnum)
|
|
error (0, errno, _("cannot watch %s"), quoteaf (f[i].name));
|
|
continue;
|
|
}
|
|
|
|
if (hash_insert (wd_to_name, &(f[i])) == nullptr)
|
|
xalloc_die ();
|
|
|
|
found_watchable_file = true;
|
|
}
|
|
}
|
|
|
|
/* Linux kernel 2.6.24 at least has a bug where eventually, ENOSPC is always
|
|
returned by inotify_add_watch. In any case we should revert to polling
|
|
when there are no inotify resources. Also a specified directory may not
|
|
be currently present or accessible, so revert to polling. Also an already
|
|
tailed but unwatchable due rename/unlink race, should also revert. */
|
|
if (no_inotify_resources || found_unwatchable_dir
|
|
|| (follow_mode == Follow_descriptor && tailed_but_unwatchable))
|
|
return;
|
|
if (follow_mode == Follow_descriptor && !found_watchable_file)
|
|
exit (EXIT_FAILURE);
|
|
|
|
prev_fspec = &(f[n_files - 1]);
|
|
|
|
/* Check files again. New files or data can be available since last time we
|
|
checked and before they are watched by inotify. */
|
|
for (i = 0; i < n_files; i++)
|
|
{
|
|
if (! f[i].ignore)
|
|
{
|
|
/* check for new files. */
|
|
if (follow_mode == Follow_name)
|
|
recheck (&(f[i]), false);
|
|
else if (f[i].fd != -1)
|
|
{
|
|
/* If the file was replaced in the small window since we tailed,
|
|
then assume the watch is on the wrong item (different to
|
|
that we've already produced output for), and so revert to
|
|
polling the original descriptor. */
|
|
struct stat stats;
|
|
|
|
if (stat (f[i].name, &stats) == 0
|
|
&& (f[i].dev != stats.st_dev || f[i].ino != stats.st_ino))
|
|
{
|
|
error (0, errno, _("%s was replaced"),
|
|
quoteaf (pretty_name (&(f[i]))));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* check for new data. */
|
|
check_fspec (&f[i], &prev_fspec);
|
|
}
|
|
}
|
|
|
|
evlen += sizeof (struct inotify_event) + 1;
|
|
evbuf = xmalloc (evlen);
|
|
|
|
/* Wait for inotify events and handle them. Events on directories
|
|
ensure that watched files can be re-added when following by name.
|
|
This loop blocks on the 'safe_read' call until a new event is notified.
|
|
But when --pid=P is specified, tail usually waits via poll. */
|
|
ptrdiff_t len = 0;
|
|
while (true)
|
|
{
|
|
struct File_spec *fspec;
|
|
struct inotify_event *ev;
|
|
void *void_ev;
|
|
|
|
/* When following by name without --retry, and the last file has
|
|
been unlinked or renamed-away, diagnose it and return. */
|
|
if (follow_mode == Follow_name
|
|
&& ! reopen_inaccessible_files
|
|
&& hash_get_n_entries (wd_to_name) == 0)
|
|
error (EXIT_FAILURE, 0, _("no files remaining"));
|
|
|
|
if (len <= evbuf_off)
|
|
{
|
|
/* Poll for inotify events. When watching a PID, ensure
|
|
that a read from WD will not block indefinitely.
|
|
If MONITOR_OUTPUT, also poll for a broken output pipe. */
|
|
|
|
int file_change;
|
|
struct pollfd pfd[2];
|
|
do
|
|
{
|
|
/* How many ms to wait for changes. -1 means wait forever. */
|
|
int delay = -1;
|
|
|
|
if (nbpids)
|
|
{
|
|
if (writers_dead)
|
|
exit (EXIT_SUCCESS);
|
|
|
|
writers_dead = writers_are_dead ();
|
|
|
|
if (writers_dead || sleep_interval <= 0)
|
|
delay = 0;
|
|
else if (sleep_interval < INT_MAX / 1000 - 1)
|
|
{
|
|
/* delay = ceil (sleep_interval * 1000), sans libm. */
|
|
double ddelay = sleep_interval * 1000;
|
|
delay = ddelay;
|
|
delay += delay < ddelay;
|
|
}
|
|
}
|
|
|
|
pfd[0].fd = wd;
|
|
pfd[0].events = POLLIN;
|
|
pfd[1].fd = STDOUT_FILENO;
|
|
pfd[1].events = pfd[1].revents = 0;
|
|
file_change = poll (pfd, monitor_output + 1, delay);
|
|
}
|
|
while (file_change == 0);
|
|
|
|
if (file_change < 0)
|
|
error (EXIT_FAILURE, errno,
|
|
_("error waiting for inotify and output events"));
|
|
if (pfd[1].revents)
|
|
die_pipe ();
|
|
|
|
len = safe_read (wd, evbuf, evlen);
|
|
evbuf_off = 0;
|
|
|
|
/* For kernels prior to 2.6.21, read returns 0 when the buffer
|
|
is too small. */
|
|
if ((len == 0 || (len < 0 && errno == EINVAL))
|
|
&& max_realloc--)
|
|
{
|
|
len = 0;
|
|
evlen *= 2;
|
|
evbuf = xrealloc (evbuf, evlen);
|
|
continue;
|
|
}
|
|
|
|
if (len <= 0)
|
|
error (EXIT_FAILURE, errno, _("error reading inotify event"));
|
|
}
|
|
|
|
void_ev = evbuf + evbuf_off;
|
|
ev = void_ev;
|
|
evbuf_off += sizeof (*ev) + ev->len;
|
|
|
|
/* If a directory is deleted, IN_DELETE_SELF is emitted
|
|
with ev->name of length 0.
|
|
We need to catch it, otherwise it would wait forever,
|
|
as wd for directory becomes inactive. Revert to polling now. */
|
|
if ((ev->mask & IN_DELETE_SELF) && ! ev->len)
|
|
{
|
|
for (i = 0; i < n_files; i++)
|
|
{
|
|
if (ev->wd == f[i].parent_wd)
|
|
{
|
|
error (0, 0,
|
|
_("directory containing watched file was removed"));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ev->len) /* event on ev->name in watched directory. */
|
|
{
|
|
size_t j;
|
|
for (j = 0; j < n_files; j++)
|
|
{
|
|
/* With N=hundreds of frequently-changing files, this O(N^2)
|
|
process might be a problem. FIXME: use a hash table? */
|
|
if (f[j].parent_wd == ev->wd
|
|
&& STREQ (ev->name, f[j].name + f[j].basename_start))
|
|
break;
|
|
}
|
|
|
|
/* It is not a watched file. */
|
|
if (j == n_files)
|
|
continue;
|
|
|
|
fspec = &(f[j]);
|
|
|
|
int new_wd = -1;
|
|
bool deleting = !! (ev->mask & IN_DELETE);
|
|
|
|
if (! deleting)
|
|
{
|
|
/* Adding the same inode again will look up any existing wd. */
|
|
new_wd = inotify_add_watch (wd, f[j].name, inotify_wd_mask);
|
|
}
|
|
|
|
if (! deleting && new_wd < 0)
|
|
{
|
|
if (errno == ENOSPC || errno == ENOMEM)
|
|
{
|
|
error (0, 0, _("inotify resources exhausted"));
|
|
return; /* revert to polling. */
|
|
}
|
|
else
|
|
{
|
|
/* Can get ENOENT for a dangling symlink for example. */
|
|
error (0, errno, _("cannot watch %s"), quoteaf (f[j].name));
|
|
}
|
|
/* We'll continue below after removing the existing watch. */
|
|
}
|
|
|
|
/* This will be false if only attributes of file change. */
|
|
bool new_watch;
|
|
new_watch = (! deleting) && (fspec->wd < 0 || new_wd != fspec->wd);
|
|
|
|
if (new_watch)
|
|
{
|
|
if (0 <= fspec->wd)
|
|
{
|
|
inotify_rm_watch (wd, fspec->wd);
|
|
hash_remove (wd_to_name, fspec);
|
|
}
|
|
|
|
fspec->wd = new_wd;
|
|
|
|
if (new_wd == -1)
|
|
continue;
|
|
|
|
/* If the file was moved then inotify will use the source file wd
|
|
for the destination file. Make sure the key is not present in
|
|
the table. */
|
|
struct File_spec *prev = hash_remove (wd_to_name, fspec);
|
|
if (prev && prev != fspec)
|
|
{
|
|
if (follow_mode == Follow_name)
|
|
recheck (prev, false);
|
|
prev->wd = -1;
|
|
close_fd (prev->fd, pretty_name (prev));
|
|
}
|
|
|
|
if (hash_insert (wd_to_name, fspec) == nullptr)
|
|
xalloc_die ();
|
|
}
|
|
|
|
if (follow_mode == Follow_name)
|
|
recheck (fspec, false);
|
|
}
|
|
else
|
|
{
|
|
struct File_spec key;
|
|
key.wd = ev->wd;
|
|
fspec = hash_lookup (wd_to_name, &key);
|
|
}
|
|
|
|
if (! fspec)
|
|
continue;
|
|
|
|
if (ev->mask & (IN_ATTRIB | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF))
|
|
{
|
|
/* Note for IN_MOVE_SELF (the file we're watching has
|
|
been clobbered via a rename) without --retry we leave the watch
|
|
in place since it may still be part of the set
|
|
of watched names. */
|
|
if (ev->mask & IN_DELETE_SELF
|
|
|| (!reopen_inaccessible_files && (ev->mask & IN_MOVE_SELF)))
|
|
{
|
|
inotify_rm_watch (wd, fspec->wd);
|
|
hash_remove (wd_to_name, fspec);
|
|
}
|
|
|
|
/* Note we get IN_ATTRIB for unlink() as st_nlink decrements.
|
|
The usual path is a close() done in recheck() triggers
|
|
an IN_DELETE_SELF event as the inode is removed.
|
|
However sometimes open() will succeed as even though
|
|
st_nlink is decremented, the dentry (cache) is not updated.
|
|
Thus we depend on the IN_DELETE event on the directory
|
|
to trigger processing for the removed file. */
|
|
|
|
recheck (fspec, false);
|
|
|
|
continue;
|
|
}
|
|
check_fspec (fspec, &prev_fspec);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* Output the last N_BYTES bytes of file FILENAME open for reading in FD.
|
|
Return true if successful. */
|
|
|
|
static bool
|
|
tail_bytes (char const *pretty_filename, int fd, uintmax_t n_bytes,
|
|
uintmax_t *read_pos)
|
|
{
|
|
struct stat stats;
|
|
|
|
if (fstat (fd, &stats))
|
|
{
|
|
error (0, errno, _("cannot fstat %s"), quoteaf (pretty_filename));
|
|
return false;
|
|
}
|
|
|
|
if (from_start)
|
|
{
|
|
if (! presume_input_pipe && n_bytes <= OFF_T_MAX
|
|
&& 0 <= lseek (fd, n_bytes, SEEK_CUR))
|
|
*read_pos += n_bytes;
|
|
else
|
|
{
|
|
int t = start_bytes (pretty_filename, fd, n_bytes, read_pos);
|
|
if (t)
|
|
return t < 0;
|
|
}
|
|
n_bytes = COPY_TO_EOF;
|
|
}
|
|
else
|
|
{
|
|
off_t end_pos = -1;
|
|
off_t current_pos = -1;
|
|
bool copy_from_current_pos = false;
|
|
|
|
if (! presume_input_pipe && n_bytes <= OFF_T_MAX)
|
|
{
|
|
if (usable_st_size (&stats))
|
|
{
|
|
/* Use st_size only if it's so large that this is
|
|
probably not a /proc or similar file, where st_size
|
|
is notional. */
|
|
end_pos = stats.st_size;
|
|
off_t smallish_size = STP_BLKSIZE (&stats);
|
|
copy_from_current_pos = smallish_size < end_pos;
|
|
}
|
|
else
|
|
{
|
|
current_pos = lseek (fd, -n_bytes, SEEK_END);
|
|
copy_from_current_pos = current_pos != -1;
|
|
if (copy_from_current_pos)
|
|
end_pos = current_pos + n_bytes;
|
|
}
|
|
}
|
|
if (! copy_from_current_pos)
|
|
return pipe_bytes (pretty_filename, fd, n_bytes, read_pos);
|
|
if (current_pos == -1)
|
|
current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename);
|
|
if (current_pos < end_pos)
|
|
{
|
|
off_t bytes_remaining = end_pos - current_pos;
|
|
|
|
if (n_bytes < bytes_remaining)
|
|
{
|
|
current_pos = end_pos - n_bytes;
|
|
xlseek (fd, current_pos, SEEK_SET, pretty_filename);
|
|
}
|
|
}
|
|
*read_pos = current_pos;
|
|
}
|
|
|
|
*read_pos += dump_remainder (false, pretty_filename, fd, n_bytes);
|
|
return true;
|
|
}
|
|
|
|
/* Output the last N_LINES lines of file FILENAME open for reading in FD.
|
|
Return true if successful. */
|
|
|
|
static bool
|
|
tail_lines (char const *pretty_filename, int fd, uintmax_t n_lines,
|
|
uintmax_t *read_pos)
|
|
{
|
|
struct stat stats;
|
|
|
|
if (fstat (fd, &stats))
|
|
{
|
|
error (0, errno, _("cannot fstat %s"), quoteaf (pretty_filename));
|
|
return false;
|
|
}
|
|
|
|
if (from_start)
|
|
{
|
|
/* If skipping all input use lseek if possible, for speed. */
|
|
off_t pos;
|
|
if (n_lines == UINTMAX_MAX && 0 <= (pos = lseek (fd, 0, SEEK_END)))
|
|
*read_pos = pos;
|
|
else
|
|
{
|
|
int t = start_lines (pretty_filename, fd, n_lines, read_pos);
|
|
if (t)
|
|
return t < 0;
|
|
*read_pos += dump_remainder (false, pretty_filename, fd, COPY_TO_EOF);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
off_t start_pos = -1;
|
|
off_t end_pos;
|
|
|
|
/* Use file_lines only if FD refers to a regular file for
|
|
which lseek (... SEEK_END) works. */
|
|
if ( ! presume_input_pipe
|
|
&& S_ISREG (stats.st_mode)
|
|
&& (start_pos = lseek (fd, 0, SEEK_CUR)) != -1
|
|
&& start_pos < (end_pos = lseek (fd, 0, SEEK_END)))
|
|
{
|
|
*read_pos = end_pos;
|
|
if (end_pos != 0
|
|
&& ! file_lines (pretty_filename, fd, &stats, n_lines,
|
|
start_pos, end_pos, read_pos))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
/* Under very unlikely circumstances, it is possible to reach
|
|
this point after positioning the file pointer to end of file
|
|
via the 'lseek (...SEEK_END)' above. In that case, reposition
|
|
the file pointer back to start_pos before calling pipe_lines. */
|
|
if (start_pos != -1)
|
|
xlseek (fd, start_pos, SEEK_SET, pretty_filename);
|
|
|
|
return pipe_lines (pretty_filename, fd, n_lines, read_pos);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Display the last N_UNITS units of file FILENAME, open for reading
|
|
via FD. Set *READ_POS to the position of the input stream pointer.
|
|
*READ_POS is usually the number of bytes read and corresponds to an
|
|
offset from the beginning of a file. However, it may be larger than
|
|
OFF_T_MAX (as for an input pipe), and may also be larger than the
|
|
number of bytes read (when an input pointer is initially not at
|
|
beginning of file), and may be far greater than the number of bytes
|
|
actually read for an input file that is seekable.
|
|
Return true if successful. */
|
|
|
|
static bool
|
|
tail (char const *filename, int fd, uintmax_t n_units,
|
|
uintmax_t *read_pos)
|
|
{
|
|
*read_pos = 0;
|
|
if (count_lines)
|
|
return tail_lines (filename, fd, n_units, read_pos);
|
|
else
|
|
return tail_bytes (filename, fd, n_units, read_pos);
|
|
}
|
|
|
|
/* Display the last N_UNITS units of the file described by F.
|
|
Return true if successful. */
|
|
|
|
static bool
|
|
tail_file (struct File_spec *f, uintmax_t n_files, uintmax_t n_units)
|
|
{
|
|
int fd;
|
|
bool ok;
|
|
|
|
/* Avoid blocking if we may need to process asynchronously. */
|
|
bool nonblocking = forever && (nbpids || n_files > 1);
|
|
|
|
bool is_stdin = (STREQ (f->name, "-"));
|
|
|
|
if (is_stdin)
|
|
{
|
|
have_read_stdin = true;
|
|
fd = STDIN_FILENO;
|
|
xset_binary_mode (STDIN_FILENO, O_BINARY);
|
|
}
|
|
else
|
|
fd = open (f->name, O_RDONLY | O_BINARY | (nonblocking ? O_NONBLOCK : 0));
|
|
|
|
f->tailable = !(reopen_inaccessible_files && fd == -1);
|
|
|
|
if (fd == -1)
|
|
{
|
|
if (forever)
|
|
{
|
|
f->fd = -1;
|
|
f->errnum = errno;
|
|
f->ignore = ! reopen_inaccessible_files;
|
|
f->ino = 0;
|
|
f->dev = 0;
|
|
}
|
|
error (0, errno, _("cannot open %s for reading"),
|
|
quoteaf (pretty_name (f)));
|
|
ok = false;
|
|
}
|
|
else
|
|
{
|
|
uintmax_t read_pos;
|
|
|
|
if (print_headers)
|
|
write_header (pretty_name (f));
|
|
ok = tail (pretty_name (f), fd, n_units, &read_pos);
|
|
if (forever)
|
|
{
|
|
struct stat stats;
|
|
|
|
#if TAIL_TEST_SLEEP
|
|
/* Before the tail function provided 'read_pos', there was
|
|
a race condition described in the URL below. This sleep
|
|
call made the window big enough to exercise the problem. */
|
|
xnanosleep (1);
|
|
#endif
|
|
f->errnum = ok - 1;
|
|
if (fstat (fd, &stats) < 0)
|
|
{
|
|
ok = false;
|
|
f->errnum = errno;
|
|
error (0, errno, _("error reading %s"),
|
|
quoteaf (pretty_name (f)));
|
|
}
|
|
else if (!IS_TAILABLE_FILE_TYPE (stats.st_mode))
|
|
{
|
|
ok = false;
|
|
f->errnum = -1;
|
|
f->tailable = false;
|
|
f->ignore = ! reopen_inaccessible_files;
|
|
error (0, 0, _("%s: cannot follow end of this type of file%s"),
|
|
quotef (pretty_name (f)),
|
|
f->ignore ? _("; giving up on this name") : "");
|
|
}
|
|
|
|
if (!ok)
|
|
{
|
|
f->ignore = ! reopen_inaccessible_files;
|
|
close_fd (fd, pretty_name (f));
|
|
f->fd = -1;
|
|
}
|
|
else
|
|
{
|
|
/* Note: we must use read_pos here, not stats.st_size,
|
|
to avoid a race condition described by Ken Raeburn:
|
|
https://lists.gnu.org/r/bug-textutils/2003-05/msg00007.html */
|
|
record_open_fd (f, fd, read_pos, &stats, (is_stdin ? -1 : 1));
|
|
f->remote = fremote (fd, pretty_name (f));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!is_stdin && close (fd))
|
|
{
|
|
error (0, errno, _("error reading %s"),
|
|
quoteaf (pretty_name (f)));
|
|
ok = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/* If obsolete usage is allowed, and the command line arguments are of
|
|
the obsolete form and the option string is well-formed, set
|
|
*N_UNITS, the globals COUNT_LINES, FOREVER, and FROM_START, and
|
|
return true. If the command line arguments are obviously incorrect
|
|
(e.g., because obsolete usage is not allowed and the arguments are
|
|
incorrect for non-obsolete usage), report an error and exit.
|
|
Otherwise, return false and don't modify any parameter or global
|
|
variable. */
|
|
|
|
static bool
|
|
parse_obsolete_option (int argc, char * const *argv, uintmax_t *n_units)
|
|
{
|
|
bool t_from_start;
|
|
bool t_count_lines = true;
|
|
bool t_forever = false;
|
|
|
|
/* With the obsolete form, there is one option string and at most
|
|
one file argument. Watch out for "-" and "--", though. */
|
|
if (! (argc == 2
|
|
|| (argc == 3 && ! (argv[2][0] == '-' && argv[2][1]))
|
|
|| (3 <= argc && argc <= 4 && STREQ (argv[2], "--"))))
|
|
return false;
|
|
|
|
int posix_ver = posix2_version ();
|
|
bool obsolete_usage = posix_ver < 200112;
|
|
bool traditional_usage = obsolete_usage || 200809 <= posix_ver;
|
|
char const *p = argv[1];
|
|
|
|
switch (*p++)
|
|
{
|
|
default:
|
|
return false;
|
|
|
|
case '+':
|
|
/* Leading "+" is a file name in the standard form. */
|
|
if (!traditional_usage)
|
|
return false;
|
|
|
|
t_from_start = true;
|
|
break;
|
|
|
|
case '-':
|
|
/* In the non-obsolete form, "-" is standard input and "-c"
|
|
requires an option-argument. The obsolete multidigit options
|
|
are supported as a GNU extension even when conforming to
|
|
POSIX 1003.1-2001 or later, so don't complain about them. */
|
|
if (!obsolete_usage && !p[p[0] == 'c'])
|
|
return false;
|
|
|
|
t_from_start = false;
|
|
break;
|
|
}
|
|
|
|
uintmax_t n;
|
|
if (!c_isdigit (*p))
|
|
n = DEFAULT_N_LINES;
|
|
else
|
|
for (n = 0; c_isdigit (*p); p++)
|
|
n = ckd_mul (&n, n, 10) || ckd_add (&n, n, *p - '0') ? UINTMAX_MAX : n;
|
|
|
|
switch (*p)
|
|
{
|
|
case 'b': n = ckd_mul (&n, n, 512) ? UINTMAX_MAX : n; FALLTHROUGH;
|
|
case 'c': t_count_lines = false; FALLTHROUGH;
|
|
case 'l': p++; break;
|
|
}
|
|
|
|
if (*p == 'f')
|
|
{
|
|
t_forever = true;
|
|
++p;
|
|
}
|
|
|
|
if (*p)
|
|
return false;
|
|
|
|
*n_units = n;
|
|
|
|
/* Set globals. */
|
|
from_start = t_from_start;
|
|
count_lines = t_count_lines;
|
|
forever = t_forever;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
parse_options (int argc, char **argv,
|
|
uintmax_t *n_units, enum header_mode *header_mode,
|
|
double *sleep_interval)
|
|
{
|
|
int c;
|
|
|
|
while ((c = getopt_long (argc, argv, "c:n:fFqs:vz0123456789",
|
|
long_options, nullptr))
|
|
!= -1)
|
|
{
|
|
switch (c)
|
|
{
|
|
case 'F':
|
|
forever = true;
|
|
follow_mode = Follow_name;
|
|
reopen_inaccessible_files = true;
|
|
break;
|
|
|
|
case 'c':
|
|
case 'n':
|
|
count_lines = (c == 'n');
|
|
if (*optarg == '+')
|
|
from_start = true;
|
|
else if (*optarg == '-')
|
|
++optarg;
|
|
|
|
*n_units = xnumtoumax (optarg, 10, 0, UINTMAX_MAX, "bkKmMGTPEZYRQ0",
|
|
(count_lines
|
|
? _("invalid number of lines")
|
|
: _("invalid number of bytes"))
|
|
, 0, XTOINT_MAX_QUIET);
|
|
break;
|
|
|
|
case 'f':
|
|
case LONG_FOLLOW_OPTION:
|
|
forever = true;
|
|
if (optarg == nullptr)
|
|
follow_mode = DEFAULT_FOLLOW_MODE;
|
|
else
|
|
follow_mode = XARGMATCH ("--follow", optarg,
|
|
follow_mode_string, follow_mode_map);
|
|
break;
|
|
|
|
case RETRY_OPTION:
|
|
reopen_inaccessible_files = true;
|
|
break;
|
|
|
|
case MAX_UNCHANGED_STATS_OPTION:
|
|
/* --max-unchanged-stats=N */
|
|
max_n_unchanged_stats_between_opens =
|
|
xnumtoumax (optarg, 10, 0, UINTMAX_MAX, "",
|
|
_("invalid maximum number of unchanged stats"
|
|
" between opens"),
|
|
0, XTOINT_MAX_QUIET);
|
|
break;
|
|
|
|
case DISABLE_INOTIFY_OPTION:
|
|
disable_inotify = true;
|
|
break;
|
|
|
|
case PID_OPTION:
|
|
if (nbpids == pids_alloc)
|
|
pids = xpalloc (pids, &pids_alloc, 1,
|
|
MIN (INT_MAX, PTRDIFF_MAX), sizeof *pids);
|
|
pids[nbpids++] = xdectoumax (optarg, 0, PID_T_MAX, "",
|
|
_("invalid PID"), 0);
|
|
break;
|
|
|
|
case PRESUME_INPUT_PIPE_OPTION:
|
|
presume_input_pipe = true;
|
|
break;
|
|
|
|
case 'q':
|
|
*header_mode = never;
|
|
break;
|
|
|
|
case 's':
|
|
{
|
|
char *ep;
|
|
errno = 0;
|
|
double s = cl_strtod (optarg, &ep);
|
|
if (optarg == ep || *ep || ! (0 <= s))
|
|
error (EXIT_FAILURE, 0,
|
|
_("invalid number of seconds: %s"), quote (optarg));
|
|
*sleep_interval = dtimespec_bound (s, errno);
|
|
}
|
|
break;
|
|
|
|
case 'v':
|
|
*header_mode = always;
|
|
break;
|
|
|
|
case 'z':
|
|
line_end = '\0';
|
|
break;
|
|
|
|
case_GETOPT_HELP_CHAR;
|
|
|
|
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
|
|
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
error (EXIT_FAILURE, 0, _("option used in invalid context -- %c"), c);
|
|
|
|
default:
|
|
usage (EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (reopen_inaccessible_files)
|
|
{
|
|
if (!forever)
|
|
{
|
|
reopen_inaccessible_files = false;
|
|
error (0, 0, _("warning: --retry ignored; --retry is useful"
|
|
" only when following"));
|
|
}
|
|
else if (follow_mode == Follow_descriptor)
|
|
error (0, 0, _("warning: --retry only effective for the initial open"));
|
|
}
|
|
|
|
if (nbpids && !forever)
|
|
error (0, 0,
|
|
_("warning: PID ignored; --pid=PID is useful only when following"));
|
|
else if (nbpids && kill (pids[0], 0) != 0 && errno == ENOSYS)
|
|
{
|
|
error (0, 0, _("warning: --pid=PID is not supported on this system"));
|
|
nbpids = 0;
|
|
free (pids);
|
|
}
|
|
}
|
|
|
|
/* Mark as '.ignore'd each member of F that corresponds to a
|
|
pipe or fifo, and return the number of non-ignored members. */
|
|
static size_t
|
|
ignore_fifo_and_pipe (struct File_spec *f, size_t n_files)
|
|
{
|
|
/* When there is no FILE operand and stdin is a pipe or FIFO
|
|
POSIX requires that tail ignore the -f option.
|
|
Since we allow multiple FILE operands, we extend that to say: with -f,
|
|
ignore any "-" operand that corresponds to a pipe or FIFO. */
|
|
size_t n_viable = 0;
|
|
|
|
for (size_t i = 0; i < n_files; i++)
|
|
{
|
|
bool is_a_fifo_or_pipe =
|
|
(STREQ (f[i].name, "-")
|
|
&& !f[i].ignore
|
|
&& 0 <= f[i].fd
|
|
&& (S_ISFIFO (f[i].mode)
|
|
|| (HAVE_FIFO_PIPES != 1 && isapipe (f[i].fd))));
|
|
if (is_a_fifo_or_pipe)
|
|
{
|
|
f[i].fd = -1;
|
|
f[i].ignore = true;
|
|
}
|
|
else
|
|
++n_viable;
|
|
}
|
|
|
|
return n_viable;
|
|
}
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
enum header_mode header_mode = multiple_files;
|
|
bool ok = true;
|
|
/* If from_start, the number of items to skip before printing; otherwise,
|
|
the number of items at the end of the file to print. */
|
|
uintmax_t n_units = DEFAULT_N_LINES;
|
|
size_t n_files;
|
|
char **file;
|
|
struct File_spec *F;
|
|
size_t i;
|
|
bool obsolete_option;
|
|
|
|
/* The number of seconds to sleep between iterations.
|
|
During one iteration, every file name or descriptor is checked to
|
|
see if it has changed. */
|
|
double sleep_interval = 1.0;
|
|
|
|
initialize_main (&argc, &argv);
|
|
set_program_name (argv[0]);
|
|
setlocale (LC_ALL, "");
|
|
bindtextdomain (PACKAGE, LOCALEDIR);
|
|
textdomain (PACKAGE);
|
|
|
|
atexit (close_stdout);
|
|
|
|
page_size = getpagesize ();
|
|
|
|
have_read_stdin = false;
|
|
|
|
count_lines = true;
|
|
forever = from_start = print_headers = false;
|
|
line_end = '\n';
|
|
obsolete_option = parse_obsolete_option (argc, argv, &n_units);
|
|
argc -= obsolete_option;
|
|
argv += obsolete_option;
|
|
parse_options (argc, argv, &n_units, &header_mode, &sleep_interval);
|
|
|
|
/* To start printing with item N_UNITS from the start of the file, skip
|
|
N_UNITS - 1 items. 'tail -n +0' is actually meaningless, but for Unix
|
|
compatibility it's treated the same as 'tail -n +1'. */
|
|
n_units -= from_start && 0 < n_units && n_units < UINTMAX_MAX;
|
|
|
|
if (optind < argc)
|
|
{
|
|
n_files = argc - optind;
|
|
file = argv + optind;
|
|
}
|
|
else
|
|
{
|
|
static char *dummy_stdin = (char *) "-";
|
|
n_files = 1;
|
|
file = &dummy_stdin;
|
|
}
|
|
|
|
{
|
|
bool found_hyphen = false;
|
|
|
|
for (i = 0; i < n_files; i++)
|
|
if (STREQ (file[i], "-"))
|
|
found_hyphen = true;
|
|
|
|
/* When following by name, there must be a name. */
|
|
if (found_hyphen && follow_mode == Follow_name)
|
|
error (EXIT_FAILURE, 0, _("cannot follow %s by name"), quoteaf ("-"));
|
|
|
|
/* When following forever, and not using simple blocking, warn if
|
|
any file is '-' as the stats() used to check for input are ineffective.
|
|
This is only a warning, since tail's output (before a failing seek,
|
|
and that from any non-stdin files) might still be useful. */
|
|
if (forever && found_hyphen)
|
|
{
|
|
struct stat in_stat;
|
|
bool blocking_stdin;
|
|
blocking_stdin = (!nbpids && follow_mode == Follow_descriptor
|
|
&& n_files == 1 && ! fstat (STDIN_FILENO, &in_stat)
|
|
&& ! S_ISREG (in_stat.st_mode));
|
|
|
|
if (! blocking_stdin && isatty (STDIN_FILENO))
|
|
error (0, 0, _("warning: following standard input"
|
|
" indefinitely is ineffective"));
|
|
}
|
|
}
|
|
|
|
/* Don't read anything if we'll never output anything. */
|
|
if (! forever && n_units == (from_start ? UINTMAX_MAX : 0))
|
|
return EXIT_SUCCESS;
|
|
|
|
F = xnmalloc (n_files, sizeof *F);
|
|
for (i = 0; i < n_files; i++)
|
|
F[i].name = file[i];
|
|
|
|
if (header_mode == always
|
|
|| (header_mode == multiple_files && n_files > 1))
|
|
print_headers = true;
|
|
|
|
xset_binary_mode (STDOUT_FILENO, O_BINARY);
|
|
|
|
for (i = 0; i < n_files; i++)
|
|
ok &= tail_file (&F[i], n_files, n_units);
|
|
|
|
if (forever && ignore_fifo_and_pipe (F, n_files))
|
|
{
|
|
/* If stdout is a fifo or pipe, then monitor it
|
|
so that we exit if the reader goes away. */
|
|
struct stat out_stat;
|
|
if (fstat (STDOUT_FILENO, &out_stat) < 0)
|
|
error (EXIT_FAILURE, errno, _("standard output"));
|
|
monitor_output = (S_ISFIFO (out_stat.st_mode)
|
|
|| (HAVE_FIFO_PIPES != 1 && isapipe (STDOUT_FILENO)));
|
|
|
|
#if HAVE_INOTIFY
|
|
/* tailable_stdin() checks if the user specifies stdin via "-",
|
|
or implicitly by providing no arguments. If so, we won't use inotify.
|
|
Technically, on systems with a working /dev/stdin, we *could*,
|
|
but would it be worth it? Verifying that it's a real device
|
|
and hooked up to stdin is not trivial, while reverting to
|
|
non-inotify-based tail_forever is easy and portable.
|
|
|
|
any_remote_file() checks if the user has specified any
|
|
files that reside on remote file systems. inotify is not used
|
|
in this case because it would miss any updates to the file
|
|
that were not initiated from the local system.
|
|
|
|
any_non_remote_file() checks if the user has specified any
|
|
files that don't reside on remote file systems. inotify is not used
|
|
if there are no open files, as we can't determine if those file
|
|
will be on a remote file system.
|
|
|
|
any_symlinks() checks if the user has specified any symbolic links.
|
|
inotify is not used in this case because it returns updated _targets_
|
|
which would not match the specified names. If we tried to always
|
|
use the target names, then we would miss changes to the symlink itself.
|
|
|
|
ok is false when one of the files specified could not be opened for
|
|
reading. In this case and when following by descriptor,
|
|
tail_forever_inotify() cannot be used (in its current implementation).
|
|
|
|
FIXME: inotify doesn't give any notification when a new
|
|
(remote) file or directory is mounted on top a watched file.
|
|
When follow_mode == Follow_name we would ideally like to detect that.
|
|
Note if there is a change to the original file then we'll
|
|
recheck it and follow the new file, or ignore it if the
|
|
file has changed to being remote.
|
|
|
|
FIXME-maybe: inotify has a watch descriptor per inode, and hence with
|
|
our current hash implementation will only --follow data for one
|
|
of the names when multiple hardlinked files are specified, or
|
|
for one name when a name is specified multiple times. */
|
|
if (!disable_inotify && (tailable_stdin (F, n_files)
|
|
|| any_remote_file (F, n_files)
|
|
|| ! any_non_remote_file (F, n_files)
|
|
|| any_symlinks (F, n_files)
|
|
|| any_non_regular_fifo (F, n_files)
|
|
|| (!ok && follow_mode == Follow_descriptor)))
|
|
disable_inotify = true;
|
|
|
|
if (!disable_inotify)
|
|
{
|
|
int wd = inotify_init ();
|
|
if (0 <= wd)
|
|
{
|
|
/* Flush any output from tail_file, now, since
|
|
tail_forever_inotify flushes only after writing,
|
|
not before reading. */
|
|
if (fflush (stdout) != 0)
|
|
write_error ();
|
|
|
|
Hash_table *ht;
|
|
tail_forever_inotify (wd, F, n_files, sleep_interval, &ht);
|
|
hash_free (ht);
|
|
close (wd);
|
|
errno = 0;
|
|
}
|
|
error (0, errno, _("inotify cannot be used, reverting to polling"));
|
|
}
|
|
#endif
|
|
disable_inotify = true;
|
|
tail_forever (F, n_files, sleep_interval);
|
|
}
|
|
|
|
if (have_read_stdin && close (STDIN_FILENO) < 0)
|
|
error (EXIT_FAILURE, errno, "-");
|
|
main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
|
|
}
|