summaryrefslogtreecommitdiffstats
path: root/src/tail.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/tail.c')
-rw-r--r--src/tail.c2504
1 files changed, 2504 insertions, 0 deletions
diff --git a/src/tail.c b/src/tail.c
new file mode 100644
index 0000000..a4a590a
--- /dev/null
+++ b/src/tail.c
@@ -0,0 +1,2504 @@
+/* tail -- output the last part of file(s)
+ Copyright (C) 1989-2022 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 <assert.h>
+#include <getopt.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#include "system.h"
+#include "argmatch.h"
+#include "cl-strtod.h"
+#include "die.h"
+#include "error.h"
+#include "fcntl--.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"
+#include "xstrtol.h"
+#include "xstrtod.h"
+
+#if HAVE_INOTIFY
+# include "hash.h"
+# include <sys/inotify.h>
+#endif
+
+#if defined _AIX || defined __sun || HAVE_INOTIFY
+# include <poll.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)
+
+/* 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", NULL
+};
+
+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 ID of the process (presumably on the current host)
+ that is writing to all followed files. */
+static pid_t pid;
+
+/* 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, NULL, 'c'},
+ {"follow", optional_argument, NULL, LONG_FOLLOW_OPTION},
+ {"lines", required_argument, NULL, 'n'},
+ {"max-unchanged-stats", required_argument, NULL, MAX_UNCHANGED_STATS_OPTION},
+ {"-disable-inotify", no_argument, NULL,
+ DISABLE_INOTIFY_OPTION}, /* do not document */
+ {"pid", required_argument, NULL, PID_OPTION},
+ {"-presume-input-pipe", no_argument, NULL,
+ PRESUME_INPUT_PIPE_OPTION}, /* do not document */
+ {"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'},
+ {"zero-terminated", no_argument, NULL, 'z'},
+ {GETOPT_HELP_OPTION_DECL},
+ {GETOPT_VERSION_OPTION_DECL},
+ {NULL, 0, NULL, 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 output starting with line NUM\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\
+ with inotify, this option is rarely useful\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\
+ --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.\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;
+
+ /* Use 'poll' on AIX (where 'select' was seen to give a readable
+ event immediately) or if using inotify (which relies on 'poll'
+ anyway). Otherwise, use 'select' as it's more portable;
+ 'poll' doesn't work for this application on macOS. */
+#if defined _AIX || defined __sun || HAVE_INOTIFY
+ struct pollfd pfd;
+ pfd.fd = STDOUT_FILENO;
+ pfd.events = pfd.revents = 0;
+
+ if (poll (&pfd, 1, 0) >= 0 && (pfd.revents & (POLLERR | POLLHUP)))
+ die_pipe ();
+#else
+ struct timeval delay;
+ delay.tv_sec = delay.tv_usec = 0;
+
+ fd_set rfd;
+ FD_ZERO (&rfd);
+ FD_SET (STDOUT_FILENO, &rfd);
+
+ /* readable event on STDOUT is equivalent to POLLERR,
+ and implies an error condition on output like broken pipe. */
+ if (select (STDOUT_FILENO + 1, &rfd, NULL, NULL, &delay) == 1)
+ die_pipe ();
+#endif
+
+}
+
+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. */
+ die (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];
+ size_t n = MIN (n_remaining, BUFSIZ);
+ size_t bytes_read = safe_read (fd, buffer, n);
+ if (bytes_read == SAFE_READ_ERROR)
+ {
+ if (errno != EAGAIN)
+ die (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);
+ char buf[INT_BUFSIZE_BOUND (offset)];
+ char *s;
+
+ if (0 <= new_offset)
+ return new_offset;
+
+ s = offtostr (offset, buf);
+ switch (whence)
+ {
+ case SEEK_SET:
+ error (0, errno, _("%s: cannot seek to offset %s"),
+ quotef (filename), s);
+ break;
+ case SEEK_CUR:
+ error (0, errno, _("%s: cannot seek to relative offset %s"),
+ quotef (filename), s);
+ break;
+ case SEEK_END:
+ error (0, errno, _("%s: cannot seek to end-relative offset %s"),
+ quotef (filename), s);
+ break;
+ default:
+ abort ();
+ }
+
+ exit (EXIT_FAILURE);
+}
+
+/* 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, uintmax_t n_lines,
+ off_t start_pos, off_t end_pos, uintmax_t *read_pos)
+{
+ char buffer[BUFSIZ];
+ size_t bytes_read;
+ off_t pos = end_pos;
+
+ if (n_lines == 0)
+ return true;
+
+ /* Set 'bytes_read' to the size of the last, probably partial, buffer;
+ 0 < 'bytes_read' <= 'BUFSIZ'. */
+ bytes_read = (pos - start_pos) % BUFSIZ;
+ if (bytes_read == 0)
+ bytes_read = BUFSIZ;
+ /* Make 'pos' a multiple of 'BUFSIZ' (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 == SAFE_READ_ERROR)
+ {
+ error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
+ return false;
+ }
+ *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. */
+
+ size_t n = bytes_read;
+ while (n)
+ {
+ char const *nl;
+ nl = memrchr (buffer, line_end, n);
+ if (nl == NULL)
+ 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));
+ return true;
+ }
+ }
+
+ /* 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);
+ return true;
+ }
+ pos -= BUFSIZ;
+ xlseek (fd, pos, SEEK_SET, pretty_filename);
+
+ bytes_read = safe_read (fd, buffer, BUFSIZ);
+ if (bytes_read == SAFE_READ_ERROR)
+ {
+ error (0, errno, _("error reading %s"), quoteaf (pretty_filename));
+ return false;
+ }
+
+ *read_pos = pos + bytes_read;
+ }
+ while (bytes_read > 0);
+
+ return true;
+}
+
+/* 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;
+ size_t n_read; /* Size in bytes of most recent read */
+
+ first = last = xmalloc (sizeof (LBUFFER));
+ first->nbytes = first->nlines = 0;
+ first->next = NULL;
+ 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 || n_read == SAFE_READ_ERROR)
+ break;
+ tmp->nbytes = n_read;
+ *read_pos += n_read;
+ tmp->nlines = 0;
+ tmp->next = NULL;
+
+ /* 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 == SAFE_READ_ERROR)
+ {
+ 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 pipe FD.
+ 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;
+ size_t n_read;
+
+ first = last = xmalloc (sizeof (CBUFFER));
+ first->nbytes = 0;
+ first->next = NULL;
+ 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 || n_read == SAFE_READ_ERROR)
+ break;
+ *read_pos += n_read;
+ tmp->nbytes = n_read;
+ tmp->next = NULL;
+
+ 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 == SAFE_READ_ERROR)
+ {
+ 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)
+ {
+ size_t bytes_read = safe_read (fd, buffer, BUFSIZ);
+ if (bytes_read == 0)
+ return -1;
+ if (bytes_read == SAFE_READ_ERROR)
+ {
+ 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];
+ size_t bytes_read = safe_read (fd, buffer, BUFSIZ);
+ if (bytes_read == 0) /* EOF */
+ return -1;
+ if (bytes_read == SAFE_READ_ERROR) /* 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
+ {
+ switch (is_local_fs_type (buf.f_type))
+ {
+ case 0:
+ break;
+ case -1:
+ /* 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. */
+ break;
+ case 1:
+ remote = false;
+ break;
+ default:
+ assert (!"unexpected return value from is_local_fs_type");
+ }
+ }
+#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)));
+
+ assert (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;
+ assert (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));
+ 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;
+}
+
+/* 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 = (pid == 0 && follow_mode == Follow_descriptor
+ && n_files == 1 && f[0].fd != -1 && ! S_ISREG (f[0].mode));
+ size_t last;
+ bool writer_is_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
+ die (EXIT_FAILURE, errno,
+ _("%s: cannot change nonblocking mode"),
+ quotef (name));
+ }
+ else
+ f[i].blocking = blocking;
+ }
+
+ 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;
+ }
+ continue;
+ }
+
+ /* 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 */
+ 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);
+
+ any_input |= (bytes_read != 0);
+ f[i].size += bytes_read;
+ }
+
+ if (! any_live_files (f, n_files))
+ {
+ error (0, 0, _("no files remaining"));
+ break;
+ }
+
+ if ((!any_input || blocking) && fflush (stdout) != 0)
+ die (EXIT_FAILURE, errno, _("write error"));
+
+ check_output_alive ();
+
+ /* If nothing was read, sleep and/or check for dead writers. */
+ if (!any_input)
+ {
+ if (writer_is_dead)
+ break;
+
+ /* Once the writer is dead, read the files once more to
+ avoid a race condition. */
+ writer_is_dead = (pid != 0
+ && kill (pid, 0) != 0
+ /* Handle the case in which you cannot send a
+ signal to the writer, so kill fails and sets
+ errno to EPERM. */
+ && errno != EPERM);
+
+ if (!writer_is_dead && xnanosleep (sleep_interval))
+ die (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)
+ die (EXIT_FAILURE, errno, _("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 writer_is_dead = false;
+ struct File_spec *prev_fspec;
+ size_t evlen = 0;
+ char *evbuf;
+ size_t evbuf_off = 0;
+ size_t len = 0;
+
+ wd_to_name = hash_initialize (n_files, NULL, wd_hasher, wd_comparator, NULL);
+ 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])) == NULL)
+ 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. */
+ 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)
+ die (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 (pid)
+ {
+ if (writer_is_dead)
+ exit (EXIT_SUCCESS);
+
+ writer_is_dead = (kill (pid, 0) != 0 && errno != EPERM);
+
+ if (writer_is_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)
+ die (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 == SAFE_READ_ERROR && errno == EINVAL))
+ && max_realloc--)
+ {
+ len = 0;
+ evlen *= 2;
+ evbuf = xrealloc (evbuf, evlen);
+ continue;
+ }
+
+ if (len == 0 || len == SAFE_READ_ERROR)
+ die (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) == NULL)
+ 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) we leave the watch
+ in place since it may still be part of the set
+ of watched names. */
+ if (ev->mask & IN_DELETE_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
+ && ((S_ISREG (stats.st_mode)
+ && xlseek (fd, n_bytes, SEEK_CUR, pretty_filename) >= 0)
+ || lseek (fd, n_bytes, SEEK_CUR) != -1))
+ *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;
+
+ if (! presume_input_pipe && n_bytes <= OFF_T_MAX)
+ {
+ if (usable_st_size (&stats))
+ end_pos = stats.st_size;
+ else if ((current_pos = lseek (fd, -n_bytes, SEEK_END)) != -1)
+ end_pos = current_pos + n_bytes;
+ }
+ if (end_pos <= (off_t) ST_BLKSIZE (stats))
+ 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)
+ {
+ 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, 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_units)
+{
+ int fd;
+ bool ok;
+
+ 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);
+
+ 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)
+{
+ char const *p;
+ char const *n_string;
+ char const *n_string_end;
+ int default_count = DEFAULT_N_LINES;
+ 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;
+ 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;
+ }
+
+ n_string = p;
+ while (ISDIGIT (*p))
+ p++;
+ n_string_end = p;
+
+ switch (*p)
+ {
+ case 'b': default_count *= 512; FALLTHROUGH;
+ case 'c': t_count_lines = false; FALLTHROUGH;
+ case 'l': p++; break;
+ }
+
+ if (*p == 'f')
+ {
+ t_forever = true;
+ ++p;
+ }
+
+ if (*p)
+ return false;
+
+ if (n_string == n_string_end)
+ *n_units = default_count;
+ else if ((xstrtoumax (n_string, NULL, 10, n_units, "b")
+ & ~LONGINT_INVALID_SUFFIX_CHAR)
+ != LONGINT_OK)
+ {
+ die (EXIT_FAILURE, errno, "%s: %s", _("invalid number"),
+ quote (argv[1]));
+ }
+
+ /* 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, NULL))
+ != -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 = xdectoumax (optarg, 0, UINTMAX_MAX, "bkKmMGTPEZY0",
+ count_lines
+ ? _("invalid number of lines")
+ : _("invalid number of bytes"), 0);
+ break;
+
+ case 'f':
+ case LONG_FOLLOW_OPTION:
+ forever = true;
+ if (optarg == NULL)
+ 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 =
+ xdectoumax (optarg, 0, UINTMAX_MAX, "",
+ _("invalid maximum number of unchanged stats between opens"), 0);
+ break;
+
+ case DISABLE_INOTIFY_OPTION:
+ disable_inotify = true;
+ break;
+
+ case PID_OPTION:
+ pid = 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':
+ {
+ double s;
+ if (! (xstrtod (optarg, NULL, &s, cl_strtod) && 0 <= s))
+ die (EXIT_FAILURE, 0,
+ _("invalid number of seconds: %s"), quote (optarg));
+ *sleep_interval = s;
+ }
+ 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':
+ die (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 (pid && !forever)
+ error (0, 0,
+ _("warning: PID ignored; --pid=PID is useful only when following"));
+ else if (pid && kill (pid, 0) != 0 && errno == ENOSYS)
+ {
+ error (0, 0, _("warning: --pid=PID is not supported on this system"));
+ pid = 0;
+ }
+}
+
+/* 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. Although the type
+ is signed, the value is never negative. */
+ 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);
+
+ 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'. */
+ if (from_start)
+ {
+ if (n_units)
+ --n_units;
+ }
+
+ 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)
+ die (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 = (pid == 0 && 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 (! n_units && ! forever && ! from_start)
+ 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_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)
+ die (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)
+ die (EXIT_FAILURE, errno, _("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)
+ die (EXIT_FAILURE, errno, "-");
+ main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+}