From ba6582e95ce2a041423e1ff34c93abe7b4702332 Mon Sep 17 00:00:00 2001 From: =?utf8?q?P=C3=A1draig=20Brady?=
Date: Fri, 13 Sep 2013 17:31:24 +0200
Subject: [PATCH] tail: improve inotify handling of symlinks
Previous behavior failed to read contents of a (re)appearing file,
when symlinked by tail's watched file. Also we now diagnose other
edge cases when running in inotify mode, where an initially
missing or regular file changes to a symlink.
* src/tail.c (main): If any arg is a symlink, use polling mode.
(recheck): Diagnose the edge case where a symlink appears during
inotify processing.
* tests/tail-2/symlink.sh: Test the fix. Mention the edge cases.
* tests/local.mk: Reference the new test.
* NEWS: Mention the fix.
Reported by: Ondrej Oprala
---
NEWS | 7 ++--
src/tail.c | 39 ++++++++++++++++++++-
tests/local.mk | 3 +-
tests/tail-2/symlink.sh | 78 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 123 insertions(+), 4 deletions(-)
create mode 100755 tests/tail-2/symlink.sh
diff --git a/NEWS b/NEWS
index 5ea592c550..5791a27013 100644
--- a/NEWS
+++ b/NEWS
@@ -43,8 +43,11 @@ GNU coreutils NEWS -*- outline -*-
by the alignment bug introduced in coreutils-6.0]
tail --retry -f now waits for the files specified to appear. Before, tail
- would immediately exit when such a file is inaccessible during the initial
- open.
+ would immediately exit when such a file is initially inaccessible.
+ [This bug was introduced when inotify support was added in coreutils-7.5]
+
+ tail -F has improved handling of symlinks. Previously tail didn't respond
+ to the symlink target (re)appearing after being (re)created.
[This bug was introduced when inotify support was added in coreutils-7.5]
** New features
diff --git a/src/tail.c b/src/tail.c
index e7fefda71d..c781dc2fac 100644
--- a/src/tail.c
+++ b/src/tail.c
@@ -945,7 +945,20 @@ recheck (struct File_spec *f, bool blocking)
then mark the file as not tailable. */
f->tailable = !(reopen_inaccessible_files && fd == -1);
- if (fd == -1 || fstat (fd, &new_stats) < 0)
+ if (! disable_inotify && ! lstat (f->name, &new_stats)
+ && S_ISLNK (new_stats.st_mode))
+ {
+ /* Diagnose the edge case where a regular file is changed
+ to a symlink. We avoid inotify with symlinks since
+ it's awkward to match between symlink name and target. */
+ ok = false;
+ f->errnum = -1;
+ f->ignore = true;
+
+ error (0, 0, _("%s has been replaced with a symbolic link. "
+ "giving up on this name"), quote (pretty_name (f)));
+ }
+ else if (fd == -1 || fstat (fd, &new_stats) < 0)
{
ok = false;
f->errnum = errno;
@@ -1251,6 +1264,23 @@ any_remote_file (const struct File_spec *f, size_t n_files)
return false;
}
+/* Return true if any of the N_FILES files in F is a symlink.
+ Note we don't worry about the edge case where "-" exists,
+ since that will have the same consequences for inotify,
+ which is the only context this function is currently used. */
+
+static bool
+any_symlinks (const struct File_spec *f, size_t n_files)
+{
+ size_t i;
+
+ struct stat st;
+ for (i = 0; i < n_files; i++)
+ if (lstat (f[i].name, &st) == 0 && S_ISLNK (st.st_mode))
+ return true;
+ return false;
+}
+
/* Return true if any of the N_FILES files in F represents
stdin and is tailable. */
@@ -1531,6 +1561,7 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
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;
}
@@ -2206,6 +2237,11 @@ main (int argc, char **argv)
in this case because it would miss any updates to the file
that were not initiated from the local system.
+ any_symlinks() checks if the user has specified any symbolic links.
+ inotify is not used in this case because it returns updated _targets_
+ which would not match the specified names. If we tried to always
+ use the target names, then we would miss changes to the symlink itself.
+
ok is false when one of the files specified could not be opened for
reading. In this case and when following by descriptor,
tail_forever_inotify() cannot be used (in its current implementation).
@@ -2222,6 +2258,7 @@ main (int argc, char **argv)
follow_mode == Follow_name */
if (!disable_inotify && (tailable_stdin (F, n_files)
|| any_remote_file (F, n_files)
+ || any_symlinks (F, n_files)
|| (!ok && follow_mode == Follow_descriptor)))
disable_inotify = true;
diff --git a/tests/local.mk b/tests/local.mk
index 59bf07f01b..2dd7e2063a 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -388,7 +388,8 @@ all_tests = \
tests/misc/uniq-perf.sh \
tests/misc/xattr.sh \
tests/tail-2/wait.sh \
- tests/tail-2/retry.sh \
+ tests/tail-2/retry.sh \
+ tests/tail-2/symlink.sh \
tests/chmod/c-option.sh \
tests/chmod/equal-x.sh \
tests/chmod/equals.sh \
diff --git a/tests/tail-2/symlink.sh b/tests/tail-2/symlink.sh
new file mode 100755
index 0000000000..bca0797fa2
--- /dev/null
+++ b/tests/tail-2/symlink.sh
@@ -0,0 +1,78 @@
+#!/bin/sh
+# Ensure tail tracks symlinks properly.
+
+# Copyright (C) 2013 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