tail: ensure --follow=name unfollows renamed files

Require --retry to continue to track files upon rename.
We already unfollowed a file if it was renamed
to another file system (unlinked), so this makes the behavior
consistent if renaming to a file in the same file system.
I.e. --follow=name without --retry, means unfollow if the
name is unlinked or moved, so this change ensures that
behavior for all rename cases.
Related commits: v8.0-121-g3b997a9bc, v8.23-161-gd313a0b24

* src/tail.c (tail_forever_notify): Remove watch for a renamed file
if --retry is not specified.
* tests/tail/F-vs-rename.sh: Related test cleanup.
* tests/tail/follow-name.sh: Add a test case.
* NEWS: Mention the bug fix.
Fixes https://bugs.gnu.org/74653
This commit is contained in:
Pádraig Brady
2024-12-04 19:40:55 +00:00
parent df71ac8343
commit fd01fc8075
4 changed files with 22 additions and 10 deletions

4
NEWS
View File

@@ -28,6 +28,10 @@ GNU coreutils NEWS -*- outline -*-
'shuf' generates more-random output when the output is small.
[bug introduced in coreutils-8.6]
`tail --follow=name` no longer waits indefinitely for watched
file names that are moved elsewhere within the same file system.
[bug introduced in coreutils-8.24]
'tail -c 4096 /dev/zero' no longer loops forever.
[This bug was present in "the beginning".]

View File

@@ -1812,10 +1812,11 @@ tail_forever_inotify (int wd, struct File_spec *f, size_t n_files,
if (ev->mask & (IN_ATTRIB | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF))
{
/* Note for IN_MOVE_SELF (the file we're watching has
been clobbered via a rename) we leave the watch
been clobbered via a rename) without --retry we leave the watch
in place since it may still be part of the set
of watched names. */
if (ev->mask & IN_DELETE_SELF)
if (ev->mask & IN_DELETE_SELF
|| (!reopen_inaccessible_files && (ev->mask & IN_MOVE_SELF)))
{
inotify_rm_watch (wd, fspec->wd);
hash_remove (wd_to_name, fspec);

View File

@@ -63,26 +63,26 @@ for mode in '' '---disable-inotify'; do
{ cat out; fail=1; }
# Wait up to 12.7s for "x" to be displayed:
file='b' data='x' retry_delay_ check_tail_output .1 7 ||
{ echo "$0: b: unexpected delay?"; cat out; fail=1; }
{ echo "$0: b: unexpected delay 1?"; cat out; fail=1; }
echo x2 > a
# Wait up to 12.7s for this to appear in the output:
# "tail: '...' has appeared; following new file"
tail_re='has appeared' retry_delay_ check_tail_output .1 7 ||
{ echo "$0: a: unexpected delay?"; cat out; fail=1; }
{ echo "$0: a: unexpected delay 2?"; cat out; fail=1; }
# Wait up to 12.7s for "x2" to be displayed:
file='a' data='x2' retry_delay_ check_tail_output .1 7 ||
{ echo "$0: a: unexpected delay 2?"; cat out; fail=1; }
{ echo "$0: a: unexpected delay 3?"; cat out; fail=1; }
echo y >> b
# Wait up to 12.7s for "y" to appear in the output:
file='b' data='y' retry_delay_ check_tail_output .1 7 ||
{ echo "$0: b: unexpected delay 2?"; cat out; fail=1; }
{ echo "$0: b: unexpected delay 4?"; cat out; fail=1; }
echo z >> a
# Wait up to 12.7s for "z" to appear in the output:
file='a' data='z' retry_delay_ check_tail_output .1 7 ||
{ echo "$0: a: unexpected delay 3?"; cat out; fail=1; }
{ echo "$0: a: unexpected delay 5?"; cat out; fail=1; }
cleanup_
done

View File

@@ -23,13 +23,20 @@ cat <<\EOF > exp || framework_failure_
tail: cannot open 'no-such' for reading: No such file or directory
tail: no files remaining
EOF
returns_ 1 timeout 10 tail --follow=name no-such > out 2> err || fail=1
# Remove an inconsequential inotify warning so
# we can compare against the above error
sed '/inotify cannot be used/d' err > k && mv k err
compare exp err || fail=1
# Between coreutils 8.34 and 9.5 inclusive, tail would have
# waited indefinitely when a file was moved to the same file system
cleanup_() { kill $pid 2>/dev/null && wait $pid; }
touch file || framework_failure_
timeout 10 tail --follow=name file & pid=$!
sleep .1 # Usually in inotify loop here
mv file file.unfollow || framework_failure_
wait $pid
test $? = 1 || fail=1
Exit $fail