From fd01fc8075a0df4e9036fdb962b589e60c134e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draig=20Brady?=
Date: Wed, 4 Dec 2024 19:40:55 +0000 Subject: [PATCH] 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 --- NEWS | 4 ++++ src/tail.c | 5 +++-- tests/tail/F-vs-rename.sh | 10 +++++----- tests/tail/follow-name.sh | 13 ++++++++++--- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 5bd9199e4..c1e604ffa 100644 --- a/NEWS +++ b/NEWS @@ -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".] diff --git a/src/tail.c b/src/tail.c index 33acf9ab3..8cefb9c07 100644 --- a/src/tail.c +++ b/src/tail.c @@ -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); diff --git a/tests/tail/F-vs-rename.sh b/tests/tail/F-vs-rename.sh index df2422985..2b66033b8 100755 --- a/tests/tail/F-vs-rename.sh +++ b/tests/tail/F-vs-rename.sh @@ -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 diff --git a/tests/tail/follow-name.sh b/tests/tail/follow-name.sh index 64864edfb..f92839c9e 100755 --- a/tests/tail/follow-name.sh +++ b/tests/tail/follow-name.sh @@ -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 -- 2.47.0