tail -f: avoid a race condition
authorGiuseppe Scrivano <gscrivano@gnu.org>
Mon, 12 Oct 2009 20:16:03 +0000 (22:16 +0200)
committerJim Meyering <meyering@redhat.com>
Thu, 22 Oct 2009 07:11:24 +0000 (09:11 +0200)
* NEWS (Bug fixes): Mention it.
* src/tail.c (check_fspec): New function.
(tail_forever_inotify): Ensure there is no new data before entering the
inotify events wait loop.

NEWS
gnulib
src/tail.c

diff --git a/NEWS b/NEWS
index 29bedac..7057d87 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -8,15 +8,22 @@ GNU coreutils NEWS                                    -*- outline -*-
   Even then, chcon may still be useful.
   [bug introduced in coreutils-8.0]
 
-  stat -f recognizes more file system types: afs, cifs, anon-inode FS,
-  btrfs, cgroupfs, cramfs-wend, debugfs, futexfs, hfs, inotifyfs, minux3,
-  nilfs, securityfs, selinux, xenfs
-
   md5sum now prints checksums atomically so that concurrent
   processes will not intersperse their output.
   This also affected sum, sha1sum, sha224sum, sha384sum and sha512sum.
   [the bug dates back to the initial implementation]
 
+  stat -f recognizes more file system types: afs, cifs, anon-inode FS,
+  btrfs, cgroupfs, cramfs-wend, debugfs, futexfs, hfs, inotifyfs, minux3,
+  nilfs, securityfs, selinux, xenfs
+
+  tail -f (inotify-enabled) now avoids a race condition.
+  Before, any data appended in the tiny interval between the initial
+  read-to-EOF and the inotify watch initialization would be ignored
+  initially (until more data was appended), or forever, if the file
+  were first renamed or unlinked or never modified.
+  [The race was introduced in coreutils-7.5]
+
 ** New features
 
   md5sum --check now also accepts openssl-style checksums.
diff --git a/gnulib b/gnulib
index 447ce74..f7f4cd9 160000 (submodule)
--- a/gnulib
+++ b/gnulib
@@ -1 +1 @@
-Subproject commit 447ce7436647899cfe03ee154bb2b2789d3a8eac
+Subproject commit f7f4cd99ff9ce8efee94aafdd5980cb4bb1d1395
index 97aa8d4..09afeec 100644 (file)
@@ -1168,6 +1168,47 @@ wd_comparator (const void *e1, const void *e2)
   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.  */
 
@@ -1249,6 +1290,14 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
 
   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);
 
@@ -1257,11 +1306,7 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
      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
@@ -1369,37 +1414,7 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
 
           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