Blob Blame History Raw
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 15:26:31.000000000 +0100
+++ coreutils-7.6/src/tail.c	2010-01-12 15:27:18.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 <sys/inotify.h>
 /* `select' is used by tail_forever_inotify.  */
 # include <sys/select.h>
+
+/* inotify needs to know if a file is local.  */
+# include "fs.h"
+# if HAVE_SYS_STATFS_H
+#  include <sys/statfs.h>
+# 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)