]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
tail: revert to polling if a followed directory is replaced
authorSebastian Kisela <skisela@redhat.com>
Wed, 5 Apr 2017 07:40:41 +0000 (09:40 +0200)
committerPádraig Brady <P@draigBrady.com>
Thu, 6 Apr 2017 00:45:17 +0000 (17:45 -0700)
* src/tail.c (tail_forever_inotify): Add the IN_DELETE_SELF flag when
creating watch for the parent directory.  After the parent directory
is removed, an event is caught and then we switch from inotify to
polling mode.  Till now, inotify has always frozen because it waited for
an event from a watched dir, which has been already deleted and was not
added again.
* tests/tail-2/inotify-dir-recreate.sh: Add a test case.
* tests/local.mk: Reference the new test.
* NEWS: Mention the bug fix.
Fixes http://bugs.gnu.org/26363
Reported at https://bugzilla.redhat.com/1283760

NEWS
src/tail.c
tests/local.mk
tests/tail-2/inotify-dir-recreate.sh [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index 7f2d895eb8921ec260305e3545d69d98539028c4..07cb2837804bb985cba1444e37902e66e9806045 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -15,6 +15,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   ignoring some non checksum lines.  This also affects sha*sum and b2sum.
   [bug introduced in coreutils-8.14]
 
+  tail -F 'dir/file' now monitored even when 'dir' is replaced.
+  [bug introduced with inotify support added in coreutils-7.5]
+
 ** New features
 
   expand and unexpand now support specifying an offset for tab stops
index d1552d424ff0f005e13f9bc25ea3134af5bda5a4..6328fe0ef8fc41fc05706ad079f84877002de7a0 100644 (file)
@@ -1457,7 +1457,8 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
                   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_MOVED_TO | IN_ATTRIB
+                                                   | IN_DELETE_SELF));
 
               f[i].name[dirlen] = prev;
 
@@ -1628,6 +1629,25 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
       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)
+                {
+                  hash_free (wd_to_name);
+                  error (0, 0,
+                      _("directory containing watched file was removed"));
+                  errno = 0;  /* we've already diagnosed enough errno detail. */
+                  return true;
+                }
+            }
+        }
+
       if (ev->len) /* event on ev->name in watched directory.  */
         {
           size_t j;
index 3fe9ba8476ecc7e818afb83bec0cfaf81f7ac0c5..e890c9afebf8aaba006508bf6186af4574f77b53 100644 (file)
@@ -176,6 +176,7 @@ all_tests =                                 \
   tests/tail-2/descriptor-vs-rename.sh         \
   tests/tail-2/inotify-rotate.sh               \
   tests/tail-2/inotify-rotate-resources.sh     \
+  tests/tail-2/inotify-dir-recreate.sh         \
   tests/chmod/no-x.sh                          \
   tests/chgrp/basic.sh                         \
   tests/rm/dangling-symlink.sh                 \
diff --git a/tests/tail-2/inotify-dir-recreate.sh b/tests/tail-2/inotify-dir-recreate.sh
new file mode 100755 (executable)
index 0000000..eaa8422
--- /dev/null
@@ -0,0 +1,82 @@
+#!/bin/sh
+# Makes sure, inotify will switch to polling mode if directory
+# of the watched file was removed and recreated.
+# (...instead of getting stuck forever)
+
+# Copyright (C) 2006-2017 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 <http://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ tail
+
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+cleanup_fail_ ()
+{
+  warn_ $1
+  cleanup_
+  fail=1
+}
+
+# $check_re - string to be found
+# $check_f - file to be searched
+check_tail_output_ ()
+{
+  local delay="$1"
+  grep $check_re $check_f > /dev/null ||
+    { sleep $delay ; return 1; }
+}
+
+grep_timeout_ ()
+{
+  check_re="$1"
+  check_f="$2"
+  retry_delay_ check_tail_output_ .1 5
+}
+
+# Prepare the file to be watched
+mkdir dir && echo 'inotify' > dir/file || framework_failure_
+
+#tail must print content of the file to stdout, verify
+timeout 60 tail -F dir/file &>out & pid=$!
+grep_timeout_ 'inotify' 'out' ||
+{ cleanup_fail_ 'file to be tailed does not exist'; }
+
+# Remove the directory, should get the message about the deletion
+rm -r dir || framework_failure_
+grep_timeout_ 'polling' 'out' ||
+{ cleanup_fail_ 'tail did not switch to polling mode'; }
+
+# Recreate the dir, must get a message about recreation
+mkdir dir && touch dir/file || framework_failure_
+grep_timeout_ 'appeared' 'out' ||
+{ cleanup_fail_ 'previously removed file did not appear'; }
+
+cleanup_
+
+# Expected result for the whole process
+cat <<\EOF > exp || framework_failure_
+inotify
+tail: 'dir/file' has become inaccessible: No such file or directory
+tail: directory containing watched file was removed
+tail: inotify cannot be used, reverting to polling
+tail: 'dir/file' has appeared;  following new file
+EOF
+
+compare exp out || fail=1
+
+Exit $fail