From c46649f534d8338aa0826f58920fa831d61b6608 Mon Sep 17 00:00:00 2001 From: Ondrej Vasik Date: Jan 12 2010 14:05:50 +0000 Subject: Rebase tail.c to coreutils-8.3 version, many tail (especially -F) fixes, too complex to backport easily --- diff --git a/coreutils-7.6-tail-rebase.patch b/coreutils-7.6-tail-rebase.patch new file mode 100644 index 0000000..0570a1f --- /dev/null +++ b/coreutils-7.6-tail-rebase.patch @@ -0,0 +1,670 @@ +diff -urNp coreutils-7.6-orig/src/tail.c coreutils-7.6/src/tail.c +--- coreutils-7.6-orig/src/tail.c 2010-01-12 13:51:04.000000000 +0100 ++++ coreutils-7.6/src/tail.c 2010-01-03 18:06:20.000000000 +0100 +@@ -1,6 +1,5 @@ + /* tail -- output the last part of file(s) +- Copyright (C) 1989, 90, 91, 1995-2006, 2008-2009 Free Software +- Foundation, Inc. ++ Copyright (C) 1989-1991, 1995-2006, 2008-2010 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 +@@ -52,6 +51,12 @@ + # include + /* `select' is used by tail_forever_inotify. */ + # include ++ ++/* inotify needs to know if a file is local. */ ++# include "fs.h" ++# if HAVE_SYS_STATFS_H ++# include ++# endif + #endif + + /* The official name of this program (e.g., no `g' prefix). */ +@@ -105,9 +110,6 @@ struct File_spec + /* The actual file name, or "-" for stdin. */ + char *name; + +- /* File descriptor on which the file is open; -1 if it's not open. */ +- int fd; +- + /* Attributes of the file the last time we checked. */ + off_t size; + struct timespec mtime; +@@ -115,24 +117,27 @@ struct File_spec + ino_t ino; + mode_t mode; + +- /* 1 if O_NONBLOCK is clear, 0 if set, -1 if not known. */ +- int blocking; +- + /* 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 description of DEFAULT_MAX_N_... below. */ +- uintmax_t n_unchanged_stats; ++ /* 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; +@@ -144,6 +149,9 @@ struct File_spec + /* Offset in NAME of the basename part. */ + size_t basename_start; + #endif ++ ++ /* See description of DEFAULT_MAX_N_... below. */ ++ uintmax_t n_unchanged_stats; + }; + + #if HAVE_INOTIFY +@@ -201,8 +209,7 @@ static bool have_read_stdin; + more expensive) code unconditionally. Intended solely for testing. */ + static bool presume_input_pipe; + +-/* If nonzero then don't use inotify even if available. +- Intended solely for testing. */ ++/* 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 +@@ -260,8 +267,8 @@ With no FILE, or when FILE is -, read st + Mandatory arguments to long options are mandatory for short options too.\n\ + "), stdout); + fputs (_("\ +- -c, --bytes=K output the last K bytes; alternatively, use +K to\n\ +- output bytes starting with the Kth of each file\n\ ++ -c, --bytes=K output the last K bytes; alternatively, use -c +K\n\ ++ to output bytes starting with the Kth of each file\n\ + "), stdout); + fputs (_("\ + -f, --follow[={name|descriptor}]\n\ +@@ -272,7 +279,7 @@ Mandatory arguments to long options are + "), stdout); + printf (_("\ + -n, --lines=K output the last K lines, instead of the last %d;\n\ +- or use +K to output lines starting with the Kth\n\ ++ or use -n +K to output lines starting with the Kth\n\ + --max-unchanged-stats=N\n\ + with --follow=name, reopen a FILE which has not\n\ + changed size after N (default %d) iterations\n\ +@@ -308,14 +315,10 @@ GB 1000*1000*1000, G 1024*1024*1024, and + fputs (_("\ + With --follow (-f), tail defaults to following the file descriptor, which\n\ + means that even if a tail'ed file is renamed, tail will continue to track\n\ +-its end. \ +-"), stdout); +- fputs (_("\ +-This default behavior is not desirable when you really want to\n\ ++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 by reopening it periodically to see if it has been removed and\n\ +-recreated by some other program.\n\ ++named file in a way that accommodates renaming, removal and creation.\n\ + "), stdout); + emit_bug_reporting_address (); + } +@@ -865,6 +868,50 @@ start_lines (const char *pretty_filename + } + } + ++#if HAVE_INOTIFY ++/* Without inotify support, always return false. Otherwise, return false ++ when FD is open on a file known to reside 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, const char *name) ++{ ++ bool remote = true; /* be conservative (poll by default). */ ++ ++# if HAVE_FSTATFS && HAVE_STRUCT_STATFS_F_TYPE && defined __linux__ ++ struct statfs buf; ++ int err = fstatfs (fd, &buf); ++ if (err != 0) ++ { ++ error (0, errno, _("cannot determine location of %s. " ++ "reverting to polling"), quote (name)); ++ } ++ else ++ { ++ switch (buf.f_type) ++ { ++ case S_MAGIC_CODA: ++ case S_MAGIC_FUSECTL: ++ case S_MAGIC_LUSTRE: ++ case S_MAGIC_NCP: ++ case S_MAGIC_NFS: ++ case S_MAGIC_NFSD: ++ case S_MAGIC_SMB: ++ break; ++ default: ++ remote = false; ++ } ++ } ++# endif ++ ++ return remote; ++} ++#else ++/* Without inotify support, whether a file is remote is irrelevant. ++ Always return "false" in that case. */ ++# define fremote(fd, name) false ++#endif ++ + /* FIXME: describe */ + + static void +@@ -921,6 +968,15 @@ recheck (struct File_spec *f, bool block + quote (pretty_name (f))); + f->ignore = true; + } ++ else if (!disable_inotify && fremote (fd, pretty_name (f))) ++ { ++ ok = false; ++ f->errnum = -1; ++ error (0, 0, _("%s has been replaced with a remote file. " ++ "giving up on this name"), quote (pretty_name (f))); ++ f->ignore = true; ++ f->remote = true; ++ } + else + { + f->errnum = 0; +@@ -1126,7 +1182,7 @@ tail_forever (struct File_spec *f, size_ + break; + } + +- if ((!any_input | blocking) && fflush (stdout) != 0) ++ if ((!any_input || blocking) && fflush (stdout) != 0) + error (EXIT_FAILURE, errno, _("write error")); + + /* If nothing was read, sleep and/or check for dead writers. */ +@@ -1135,9 +1191,6 @@ tail_forever (struct File_spec *f, size_ + if (writer_is_dead) + break; + +- if (xnanosleep (sleep_interval)) +- error (EXIT_FAILURE, errno, _("cannot read realtime clock")); +- + /* Once the writer is dead, read the files once more to + avoid a race condition. */ + writer_is_dead = (pid != 0 +@@ -1146,12 +1199,44 @@ tail_forever (struct File_spec *f, size_ + signal to the writer, so kill fails and sets + errno to EPERM. */ + && errno != EPERM); ++ ++ if (!writer_is_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) ++{ ++ size_t i; ++ ++ for (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 represents ++ stdin and is tailable. */ ++ ++static bool ++tailable_stdin (const struct File_spec *f, size_t n_files) ++{ ++ size_t i; ++ ++ for (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) + { +@@ -1167,31 +1252,73 @@ wd_comparator (const void *e1, const voi + return spec1->wd == spec2->wd; + } + ++/* Helper function used by `tail_forever_inotify'. */ ++static void ++check_fspec (struct File_spec *fspec, int wd, int *prev_wd) ++{ ++ struct stat stats; ++ char const *name = pretty_name (fspec); ++ ++ if (fstat (fspec->fd, &stats) != 0) ++ { ++ close_fd (fspec->fd, name); ++ fspec->fd = -1; ++ fspec->errnum = errno; ++ return; ++ } ++ ++ 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; ++ } ++ 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 (print_headers) ++ write_header (name); ++ *prev_wd = wd; ++ } ++ ++ uintmax_t bytes_read = dump_remainder (name, fspec->fd, COPY_TO_EOF); ++ fspec->size += bytes_read; ++ ++ if (fflush (stdout) != 0) ++ error (EXIT_FAILURE, errno, _("write error")); ++} ++ + /* Tail N_FILES files forever, or until killed. + Check modifications using the inotify events system. */ +- + static void + tail_forever_inotify (int wd, struct File_spec *f, size_t n_files, + double sleep_interval) + { +- size_t i; + unsigned int max_realloc = 3; +- Hash_table *wd_table; ++ ++ /* Map an inotify watch descriptor to the name of the file it's watching. */ ++ Hash_table *wd_to_name; + + bool found_watchable = false; ++ bool writer_is_dead = false; + int prev_wd; + size_t evlen = 0; + char *evbuf; + size_t evbuf_off = 0; + size_t len = 0; + +- wd_table = hash_initialize (n_files, NULL, wd_hasher, wd_comparator, NULL); +- if (! wd_table) ++ wd_to_name = hash_initialize (n_files, NULL, wd_hasher, wd_comparator, NULL); ++ if (! wd_to_name) + xalloc_die (); + + /* 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) +@@ -1235,7 +1362,7 @@ tail_forever_inotify (int wd, struct Fil + continue; + } + +- if (hash_insert (wd_table, &(f[i])) == NULL) ++ if (hash_insert (wd_to_name, &(f[i])) == NULL) + xalloc_die (); + + found_watchable = true; +@@ -1247,6 +1374,14 @@ tail_forever_inotify (int wd, struct Fil + + prev_wd = f[n_files - 1].wd; + ++ /* Check files again. New 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); ++ } ++ + evlen += sizeof (struct inotify_event) + 1; + evbuf = xmalloc (evlen); + +@@ -1255,41 +1390,37 @@ tail_forever_inotify (int wd, struct Fil + This loop sleeps on the `safe_read' call until a new event is notified. */ + while (1) + { +- char const *name; + struct File_spec *fspec; +- uintmax_t bytes_read; +- struct stat stats; +- + struct inotify_event *ev; + + /* When watching a PID, ensure that a read from WD will not block +- indefinetely. */ ++ indefinitely. */ + if (pid) + { +- fd_set rfd; +- struct timeval select_timeout; +- int n_descriptors; +- +- FD_ZERO (&rfd); +- FD_SET (wd, &rfd); ++ if (writer_is_dead) ++ exit (EXIT_SUCCESS); + +- select_timeout.tv_sec = (time_t) sleep_interval; +- select_timeout.tv_usec = 1000000 * (sleep_interval +- - select_timeout.tv_sec); ++ writer_is_dead = (kill (pid, 0) != 0 && errno != EPERM); + +- n_descriptors = select (wd + 1, &rfd, NULL, NULL, &select_timeout); ++ struct timeval delay; /* how long to wait for file changes. */ ++ if (writer_is_dead) ++ delay.tv_sec = delay.tv_usec = 0; ++ else ++ { ++ delay.tv_sec = (time_t) sleep_interval; ++ delay.tv_usec = 1000000 * (sleep_interval - delay.tv_sec); ++ } + +- if (n_descriptors == -1) +- error (EXIT_FAILURE, errno, _("error monitoring inotify event")); ++ fd_set rfd; ++ FD_ZERO (&rfd); ++ FD_SET (wd, &rfd); + +- if (n_descriptors == 0) +- { +- /* See if the process we are monitoring is still alive. */ +- if (kill (pid, 0) != 0 && errno != EPERM) +- exit (EXIT_SUCCESS); ++ int file_change = select (wd + 1, &rfd, NULL, NULL, &delay); + +- continue; +- } ++ if (file_change == 0) ++ continue; ++ else if (file_change == -1) ++ error (EXIT_FAILURE, errno, _("error monitoring inotify event")); + } + + if (len <= evbuf_off) +@@ -1315,41 +1446,59 @@ tail_forever_inotify (int wd, struct Fil + ev = (struct inotify_event *) (evbuf + evbuf_off); + evbuf_off += sizeof (*ev) + ev->len; + +- if (ev->len) ++ if (ev->len) /* event on ev->name in watched directory */ + { +- for (i = 0; i < n_files; i++) ++ 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[i].parent_wd == ev->wd +- && STREQ (ev->name, f[i].name + f[i].basename_start)) ++ 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 (i == n_files) ++ if (j == n_files) + continue; + +- f[i].wd = inotify_add_watch (wd, f[i].name, inotify_wd_mask); +- +- if (f[i].wd < 0) ++ /* It's fine to add the same file more than once. */ ++ int new_wd = inotify_add_watch (wd, f[j].name, inotify_wd_mask); ++ if (new_wd < 0) + { +- error (0, errno, _("cannot watch %s"), quote (f[i].name)); ++ error (0, errno, _("cannot watch %s"), quote (f[j].name)); + continue; + } + +- fspec = &(f[i]); +- if (hash_insert (wd_table, fspec) == NULL) ++ 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; ++ ++ /* 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 (&(f[i]), false); ++ recheck (fspec, false); + } + else + { + struct File_spec key; + key.wd = ev->wd; +- fspec = hash_lookup (wd_table, &key); ++ fspec = hash_lookup (wd_to_name, &key); + } + + if (! fspec) +@@ -1357,53 +1506,23 @@ tail_forever_inotify (int wd, struct Fil + + 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)) ++ /* 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)) + { +- inotify_rm_watch (wd, f[i].wd); +- hash_delete (wd_table, &(f[i])); ++ inotify_rm_watch (wd, fspec->wd); ++ hash_delete (wd_to_name, fspec); + } + if (follow_mode == Follow_name) + recheck (fspec, false); + + continue; + } +- +- name = pretty_name (fspec); +- +- if (fstat (fspec->fd, &stats) != 0) +- { +- close_fd (fspec->fd, name); +- fspec->fd = -1; +- fspec->errnum = errno; +- continue; +- } +- +- if (S_ISREG (fspec->mode) && stats.st_size < fspec->size) +- { +- error (0, 0, _("%s: file truncated"), name); +- prev_wd = ev->wd; +- xlseek (fspec->fd, stats.st_size, SEEK_SET, name); +- fspec->size = stats.st_size; +- } +- +- if (ev->wd != prev_wd) +- { +- if (print_headers) +- write_header (name); +- prev_wd = ev->wd; +- } +- +- bytes_read = dump_remainder (name, fspec->fd, COPY_TO_EOF); +- fspec->size += bytes_read; +- +- if (fflush (stdout) != 0) +- error (EXIT_FAILURE, errno, _("write error")); ++ check_fspec (fspec, ev->wd, &prev_wd); + } + } + #endif +@@ -1602,7 +1721,7 @@ tail_file (struct File_spec *f, uintmax_ + /* 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. */ +- sleep (1); ++ xnanosleep (1); + #endif + f->errnum = ok - 1; + if (fstat (fd, &stats) < 0) +@@ -1632,6 +1751,7 @@ tail_file (struct File_spec *f, uintmax_ + to avoid a race condition described by Ken Raeburn: + http://mail.gnu.org/archive/html/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 +@@ -1872,6 +1992,35 @@ parse_options (int argc, char **argv, + } + } + ++/* 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; ++ ++ size_t i; ++ for (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].ignore = true; ++ else ++ ++n_viable; ++ } ++ ++ return n_viable; ++} ++ + int + main (int argc, char **argv) + { +@@ -1963,41 +2112,31 @@ main (int argc, char **argv) + for (i = 0; i < n_files; i++) + ok &= tail_file (&F[i], n_units); + +- /* 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: +- ignore any "-" operand that corresponds to a pipe or FIFO. */ +- size_t n_viable = 0; +- for (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].ignore = true; +- else +- ++n_viable; +- } +- +- if (forever && n_viable) ++ if (forever && ignore_fifo_and_pipe (F, n_files)) + { + #if HAVE_INOTIFY +- /* If the user specifies stdin via a command line argument of "-", +- or implicitly by providing no arguments, we won't use 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. */ +- bool stdin_cmdline_arg = false; ++ non-inotify-based tail_forever is easy and portable. + +- for (i = 0; i < n_files; i++) +- if (!F[i].ignore && STREQ (F[i].name, "-")) +- stdin_cmdline_arg = true; ++ 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. ++ ++ 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. */ ++ if (tailable_stdin (F, n_files) || any_remote_file (F, n_files)) ++ disable_inotify = true; + +- if (!disable_inotify && !stdin_cmdline_arg) ++ if (!disable_inotify) + { + int wd = inotify_init (); + if (wd < 0) diff --git a/coreutils.spec b/coreutils.spec index 2adca18..dbfd25d 100644 --- a/coreutils.spec +++ b/coreutils.spec @@ -26,6 +26,7 @@ Patch5: coreutils-7.6-trueexecve.patch Patch6: coreutils-7.6-ls-infloop.patch Patch7: coreutils-7.6-ls-derefdanglinginode.patch Patch8: coreutils-CVE-2009-4135.patch +Patch9: coreutils-7.6-tail-rebase.patch # Our patches Patch100: coreutils-6.10-configuration.patch @@ -124,6 +125,7 @@ Libraries for coreutils package. %patch5 -p1 -b .execve %patch6 -p1 -b .infloop %patch8 -p1 -b .unsafetmp +%patch9 -p1 -b .rebase # Our patches %patch100 -p1 -b .configure