summaryrefslogtreecommitdiff
path: root/src/tail.c
diff options
context:
space:
mode:
authorIgor Pashev <pashev.igor@gmail.com>2015-07-04 17:13:50 +0300
committerIgor Pashev <pashev.igor@gmail.com>2015-07-04 17:13:50 +0300
commit71cd8e3a743046573744123777061b64881bf372 (patch)
tree82522befe647f4fff186a5630cad0cad33f8ef53 /src/tail.c
parentc18578632fd3c9e513e613a86ba2b7c4ebee6c45 (diff)
downloadcoreutils-upstream.tar.gz
Imported Upstream version 8.24upstream/8.24upstream
Diffstat (limited to 'src/tail.c')
-rw-r--r--src/tail.c377
1 files changed, 218 insertions, 159 deletions
diff --git a/src/tail.c b/src/tail.c
index 5ff738df..c062d403 100644
--- a/src/tail.c
+++ b/src/tail.c
@@ -1,5 +1,5 @@
/* tail -- output the last part of file(s)
- Copyright (C) 1989-2014 Free Software Foundation, Inc.
+ Copyright (C) 1989-2015 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
@@ -40,9 +40,11 @@
#include "posixver.h"
#include "quote.h"
#include "safe-read.h"
+#include "stat-size.h"
#include "stat-time.h"
#include "xfreopen.h"
#include "xnanosleep.h"
+#include "xdectoint.h"
#include "xstrtol.h"
#include "xstrtod.h"
@@ -157,13 +159,6 @@ struct File_spec
uintmax_t n_unchanged_stats;
};
-#if HAVE_INOTIFY
-/* The events mask used with inotify on files. This mask is not used on
- directories. */
-static const uint32_t inotify_wd_mask = (IN_MODIFY | IN_ATTRIB
- | IN_DELETE_SELF | IN_MOVE_SELF);
-#endif
-
/* 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;
@@ -262,9 +257,9 @@ Usage: %s [OPTION]... [FILE]...\n\
printf (_("\
Print the last %d lines of each FILE to standard output.\n\
With more than one FILE, precede each with a header giving the file name.\n\
-With no FILE, or when FILE is -, read standard input.\n\
"), DEFAULT_N_LINES);
+ emit_stdin_note ();
emit_mandatory_arg_note ();
fputs (_("\
@@ -321,7 +316,7 @@ 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 ();
+ emit_ancillary_info (PROGRAM_NAME);
}
exit (status);
}
@@ -930,12 +925,10 @@ fremote (int fd, const char *name)
# define fremote(fd, name) false
#endif
-/* FIXME: describe */
-
+/* open/fstat F->name and handle changes. */
static void
recheck (struct File_spec *f, bool blocking)
{
- /* open/fstat the file and announce if dev/ino have changed */
struct stat new_stats;
bool ok = true;
bool is_stdin = (STREQ (f->name, "-"));
@@ -1026,42 +1019,45 @@ recheck (struct File_spec *f, bool blocking)
assert (f->fd == -1);
error (0, 0, _("%s has become accessible"), quote (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"),
+ quote (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;
- if (f->fd == -1)
- {
- error (0, 0,
- _("%s has appeared; following end of new file"),
- quote (pretty_name (f)));
- }
- else
- {
- /* Close the old one. */
- close_fd (f->fd, pretty_name (f));
-
- /* File has been replaced (e.g., via log rotation) --
- tail the new one. */
- error (0, 0,
- _("%s has been replaced; following end of new file"),
- quote (pretty_name (f)));
- }
+
+ error (0, 0,
+ _("%s has been replaced; following new file"),
+ quote (pretty_name (f)));
+
+ /* Close the old one. */
+ close_fd (f->fd, pretty_name (f));
+
}
else
{
- if (f->fd == -1)
- {
- /* This happens when one iteration finds the file missing,
- then the preceding <dev,inode> pair is reused as the
- file is recreated. */
- new_file = true;
- }
- else
- {
- close_fd (fd, pretty_name (f));
- }
+ /* 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. */
@@ -1200,13 +1196,16 @@ tail_forever (struct File_spec *f, size_t n_files, double sleep_interval)
/* 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"), name);
- last = i;
- xlseek (fd, stats.st_size, SEEK_SET, name);
- f[i].size = stats.st_size;
- continue;
+ /* 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)
@@ -1317,9 +1316,9 @@ wd_comparator (const void *e1, const void *e2)
return spec1->wd == spec2->wd;
}
-/* Helper function used by 'tail_forever_inotify'. */
+/* Output (new) data for FSPEC->fd. */
static void
-check_fspec (struct File_spec *fspec, int wd, int *prev_wd)
+check_fspec (struct File_spec *fspec, struct File_spec **prev_fspec)
{
struct stat stats;
char const *name;
@@ -1337,22 +1336,26 @@ check_fspec (struct File_spec *fspec, int wd, int *prev_wd)
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"), name);
- *prev_wd = wd;
- xlseek (fspec->fd, stats.st_size, SEEK_SET, name);
- fspec->size = stats.st_size;
+ 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;
- if (wd != *prev_wd)
+ if (fspec != *prev_fspec)
{
if (print_headers)
write_header (name);
- *prev_wd = wd;
+ *prev_fspec = fspec;
}
uintmax_t bytes_read = dump_remainder (name, fspec->fd, COPY_TO_EOF);
@@ -1369,16 +1372,22 @@ static bool
tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
double sleep_interval)
{
+# 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;
- int prev_wd;
+ struct File_spec *prev_fspec;
size_t evlen = 0;
char *evbuf;
size_t evbuf_off = 0;
@@ -1388,6 +1397,13 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
if (! wd_to_name)
xalloc_die ();
+ /* 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. */
@@ -1436,10 +1452,13 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
if (f[i].wd < 0)
{
- if (errno == ENOSPC)
+ 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"), quote (f[i].name));
@@ -1456,24 +1475,53 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
/* 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. */
- if (no_inotify_resources || found_unwatchable_dir)
+ 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))
{
- /* FIXME: release hash and inotify resources allocated above. */
+ hash_free (wd_to_name);
+
errno = 0;
return true;
}
if (follow_mode == Follow_descriptor && !found_watchable_file)
return false;
- prev_wd = f[n_files - 1].wd;
+ prev_fspec = &(f[n_files - 1]);
- /* Check files again. New data can be available since last time we checked
- and before they are watched by inotify. */
+ /* 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_fspec (&f[i], f[i].wd, &prev_wd);
+ 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"),
+ quote (pretty_name (&(f[i]))));
+ hash_free (wd_to_name);
+
+ errno = 0;
+ return true;
+ }
+ }
+
+ /* check for new data. */
+ check_fspec (&f[i], &prev_fspec);
+ }
}
evlen += sizeof (struct inotify_event) + 1;
@@ -1553,7 +1601,7 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
ev = void_ev;
evbuf_off += sizeof (*ev) + ev->len;
- if (ev->len) /* event on ev->name in watched directory */
+ if (ev->len) /* event on ev->name in watched directory. */
{
size_t j;
for (j = 0; j < n_files; j++)
@@ -1569,35 +1617,58 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
if (j == n_files)
continue;
- /* It's fine to add the same file more than once. */
+ fspec = &(f[j]);
+
+ /* Adding the same inode again will look up any existing wd. */
int new_wd = inotify_add_watch (wd, f[j].name, inotify_wd_mask);
if (new_wd < 0)
{
- /* Can get ENOENT for a dangling symlink for example. */
- error (0, errno, _("cannot watch %s"), quote (f[j].name));
- continue;
+ if (errno == ENOSPC || errno == ENOMEM)
+ {
+ error (0, 0, _("inotify resources exhausted"));
+ hash_free (wd_to_name);
+ errno = 0;
+ return true; /* revert to polling. */
+ }
+ else
+ {
+ /* Can get ENOENT for a dangling symlink for example. */
+ error (0, errno, _("cannot watch %s"), quote (f[j].name));
+ }
+ /* We'll continue below after removing the existing watch. */
}
- fspec = &(f[j]);
-
- /* Remove 'fspec' and re-add it using 'new_fd' as its key. */
- hash_delete (wd_to_name, fspec);
- fspec->wd = new_wd;
+ /* This will be false if only attributes of file change. */
+ bool new_watch = fspec->wd < 0 || new_wd != fspec->wd;
- /* 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_delete (wd_to_name, fspec);
- if (prev && prev != fspec)
+ if (new_watch)
{
- if (follow_mode == Follow_name)
- recheck (prev, false);
- prev->wd = -1;
- close_fd (prev->fd, pretty_name (prev));
- }
+ if (0 <= fspec->wd)
+ {
+ inotify_rm_watch (wd, fspec->wd);
+ hash_delete (wd_to_name, fspec);
+ }
- if (hash_insert (wd_to_name, fspec) == NULL)
- xalloc_die ();
+ 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_delete (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);
@@ -1614,24 +1685,21 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
if (ev->mask & (IN_ATTRIB | IN_DELETE_SELF | IN_MOVE_SELF))
{
- /* For IN_DELETE_SELF, we always want to remove the watch.
- However, for IN_MOVE_SELF (the file we're watching has
- been clobbered via a rename), when tailing by NAME, we
- must continue to watch the file. It's only when following
- by file descriptor that we must remove the watch. */
- if ((ev->mask & IN_DELETE_SELF)
- || ((ev->mask & IN_MOVE_SELF)
- && follow_mode == Follow_descriptor))
+ /* 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_delete (wd_to_name, fspec);
}
- if (follow_mode == Follow_name)
- recheck (fspec, false);
+
+ recheck (fspec, false);
continue;
}
- check_fspec (fspec, ev->wd, &prev_wd);
+ check_fspec (fspec, &prev_fspec);
}
}
#endif
@@ -1665,40 +1733,30 @@ tail_bytes (const char *pretty_filename, int fd, uintmax_t n_bytes,
if (t)
return t < 0;
}
- *read_pos += dump_remainder (pretty_filename, fd, COPY_TO_EOF);
+ n_bytes = COPY_TO_EOF;
}
else
{
- if ( ! presume_input_pipe
- && S_ISREG (stats.st_mode) && n_bytes <= OFF_T_MAX)
+ off_t end_pos = ((! presume_input_pipe && usable_st_size (&stats)
+ && n_bytes <= OFF_T_MAX)
+ ? stats.st_size : -1);
+ if (end_pos <= ST_BLKSIZE (stats))
+ return pipe_bytes (pretty_filename, fd, n_bytes, read_pos);
+ off_t current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename);
+ if (current_pos < end_pos)
{
- off_t current_pos = xlseek (fd, 0, SEEK_CUR, pretty_filename);
- off_t end_pos = xlseek (fd, 0, SEEK_END, pretty_filename);
- off_t diff = end_pos - current_pos;
- /* Be careful here. The current position may actually be
- beyond the end of the file. */
- off_t bytes_remaining = diff < 0 ? 0 : diff;
- off_t nb = n_bytes;
-
- if (bytes_remaining <= nb)
- {
- /* From the current position to end of file, there are no
- more bytes than have been requested. So reposition the
- file pointer to the incoming current position and print
- everything after that. */
- *read_pos = xlseek (fd, current_pos, SEEK_SET, pretty_filename);
- }
- else
+ off_t bytes_remaining = end_pos - current_pos;
+
+ if (n_bytes < bytes_remaining)
{
- /* There are more bytes remaining than were requested.
- Back up. */
- *read_pos = xlseek (fd, -nb, SEEK_END, pretty_filename);
+ current_pos = end_pos - n_bytes;
+ xlseek (fd, current_pos, SEEK_SET, pretty_filename);
}
- *read_pos += dump_remainder (pretty_filename, fd, n_bytes);
}
- else
- return pipe_bytes (pretty_filename, fd, n_bytes, read_pos);
+ *read_pos = current_pos;
}
+
+ *read_pos += dump_remainder (pretty_filename, fd, n_bytes);
return true;
}
@@ -1826,7 +1884,7 @@ tail_file (struct File_spec *f, uintmax_t n_units)
{
struct stat stats;
-#if TEST_RACE_BETWEEN_FINAL_READ_AND_INITIAL_FSTAT
+#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. */
@@ -1958,7 +2016,10 @@ parse_obsolete_option (int argc, char * const *argv, uintmax_t *n_units)
else if ((xstrtoumax (n_string, NULL, 10, n_units, "b")
& ~LONGINT_INVALID_SUFFIX_CHAR)
!= LONGINT_OK)
- error (EXIT_FAILURE, 0, _("number in %s is too large"), quote (argv[1]));
+ {
+ error (EXIT_FAILURE, errno, "%s: %s", _("invalid number"),
+ quote (argv[1]));
+ }
/* Set globals. */
from_start = t_from_start;
@@ -1995,17 +2056,10 @@ parse_options (int argc, char **argv,
else if (*optarg == '-')
++optarg;
- {
- strtol_error s_err;
- s_err = xstrtoumax (optarg, NULL, 10, n_units, "bkKmMGTPEZY0");
- if (s_err != LONGINT_OK)
- {
- error (EXIT_FAILURE, 0, "%s: %s", optarg,
- (c == 'n'
- ? _("invalid number of lines")
- : _("invalid number of bytes")));
- }
- }
+ *n_units = xdectoumax (optarg, 0, UINTMAX_MAX, "bkKmMGTPEZY0",
+ count_lines
+ ? _("invalid number of lines")
+ : _("invalid number of bytes"), 0);
break;
case 'f':
@@ -2024,15 +2078,9 @@ parse_options (int argc, char **argv,
case MAX_UNCHANGED_STATS_OPTION:
/* --max-unchanged-stats=N */
- if (xstrtoumax (optarg, NULL, 10,
- &max_n_unchanged_stats_between_opens,
- "")
- != LONGINT_OK)
- {
- error (EXIT_FAILURE, 0,
- _("%s: invalid maximum number of unchanged stats between opens"),
- optarg);
- }
+ 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:
@@ -2040,16 +2088,7 @@ parse_options (int argc, char **argv,
break;
case PID_OPTION:
- {
- strtol_error s_err;
- unsigned long int tmp_ulong;
- s_err = xstrtoul (optarg, NULL, 10, &tmp_ulong, "");
- if (s_err != LONGINT_OK || tmp_ulong > PID_T_MAX)
- {
- error (EXIT_FAILURE, 0, _("%s: invalid PID"), optarg);
- }
- pid = tmp_ulong;
- }
+ pid = xdectoumax (optarg, 0, PID_T_MAX, "", _("invalid PID"), 0);
break;
case PRESUME_INPUT_PIPE_OPTION:
@@ -2065,7 +2104,7 @@ parse_options (int argc, char **argv,
double s;
if (! (xstrtod (optarg, NULL, &s, c_strtod) && 0 <= s))
error (EXIT_FAILURE, 0,
- _("%s: invalid number of seconds"), optarg);
+ _("invalid number of seconds: %s"), quote (optarg));
*sleep_interval = s;
}
break;
@@ -2185,6 +2224,8 @@ main (int argc, char **argv)
--n_units;
}
+ IF_LINT (assert (0 <= argc));
+
if (optind < argc)
{
n_files = argc - optind;
@@ -2218,7 +2259,7 @@ main (int argc, char **argv)
/* Don't read anything if we'll never output anything. */
if (! n_units && ! forever && ! from_start)
- exit (EXIT_SUCCESS);
+ return EXIT_SUCCESS;
F = xnmalloc (n_files, sizeof *F);
for (i = 0; i < n_files; i++)
@@ -2267,7 +2308,12 @@ main (int argc, char **argv)
FIXME: when using inotify, and a directory for a watched file
is recreated, then we don't recheck any new file when
- follow_mode == Follow_name */
+ follow_mode == Follow_name.
+
+ 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_symlinks (F, n_files)
@@ -2285,10 +2331,23 @@ main (int argc, char **argv)
if (fflush (stdout) != 0)
error (EXIT_FAILURE, errno, _("write error"));
- if (!tail_forever_inotify (wd, F, n_files, sleep_interval))
- exit (EXIT_FAILURE);
+ if (! tail_forever_inotify (wd, F, n_files, sleep_interval))
+ return EXIT_FAILURE;
}
error (0, errno, _("inotify cannot be used, reverting to polling"));
+
+ /* Free resources as this process can be long lived,
+ and we may have exhausted system resources above. */
+
+ for (i = 0; i < n_files; i++)
+ {
+ /* It's OK to remove the same watch multiple times,
+ ignoring the EINVAL from redundant calls. */
+ if (F[i].wd != -1)
+ inotify_rm_watch (wd, F[i].wd);
+ if (F[i].parent_wd != -1)
+ inotify_rm_watch (wd, F[i].parent_wd);
+ }
}
#endif
disable_inotify = true;
@@ -2297,5 +2356,5 @@ main (int argc, char **argv)
if (have_read_stdin && close (STDIN_FILENO) < 0)
error (EXIT_FAILURE, errno, "-");
- exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
+ return ok ? EXIT_SUCCESS : EXIT_FAILURE;
}