tail: fix handling of inotify event queue overflow

* src/tail.c (tail_forever_inotify): Fallback to polling upon overlow.
* tests/tail/inotify-overflow.sh: Add a new test.
* tests/local.mk: Reference the new test.
* NEWS: Mention the bug fix.
Discovered by Anthropic's OSS Scanner, as bug ANT-2026-UUNT73BK.
This commit is contained in:
Pádraig Brady committed 2026-10-23 17:02:33 +01:00
1 parent 38aaf5e611
commit 12244b4f2b
4 files changed
+91

No files matched your search

+6
View File
@@ -52,6 +52,12 @@ GNU coreutils NEWS -*- outline -*-
it would have failed if the control flag encoding was different.
[This bug was present in "the beginning".]
'tail --follow' no longer ignores an overflow of the inotify event queue,
and now reverts to polling in that case. Previously tail could fail
to notice new data, or that a file followed by name was replaced,
if events arrived faster than tail could process them.
[bug introduced in coreutils-7.5]
'unexpand' no longer overflows a heap buffer on non-GLIBC platforms,
given whitespace characters whose display width is negative. E.g.,
this edge case can occur with \u0085 (next line) characters on musl.
+9
View File
@@ -1766,6 +1766,15 @@ tail_forever_inotify (int wd, struct File_spec *f, int n_files,
ev = void_ev;
evbuf_off += sizeof (*ev) + ev->len;
/* If the kernel discarded events for any of the watched files
or directories, so we can no longer depend on our view of them.
Revert to polling now. */
if (ev->mask & IN_Q_OVERFLOW)
{
error (0, 0, _("inotify event queue overflowed"));
return;
}
/* 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,
+1
View File
@@ -207,6 +207,7 @@ all_tests = \
tests/tail/inotify-rotate-resources.sh \
tests/tail/inotify-dir-recreate.sh \
tests/tail/inotify-only-regular.sh \
tests/tail/inotify-overflow.sh \
tests/tail/quote-headers.sh \
tests/chmod/no-x.sh \
tests/chgrp/basic.sh \
+75
View File
@@ -0,0 +1,75 @@
#!/bin/sh
# Ensure tail -F copes with the kernel discarding inotify events
# Copyright (C) 2026 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 <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
print_ver_ tail
require_inotify_supported_
max_events=$(cat /proc/sys/fs/inotify/max_queued_events) &&
test "$max_events" -le 100000 ||
skip_ 'inotify queue limit unknown or too large'
# Terminate any background tail process
cleanup_()
{
kill -CONT "$pid" 2>/dev/null
kill "$pid" 2>/dev/null
wait "$pid" 2>/dev/null
pid=
}
# $check_re - string to be found
check_tail_output_ ()
{
local delay="$1"
grep "$check_re" out > /dev/null ||
{ sleep $delay ; return 1; }
}
grep_timeout_ ()
{
check_re="$1"
retry_delay_ check_tail_output_ .1 7
}
# Speed up the fallback-to-polling phase.
fastpoll='-s.1 --max-unchanged-stats=1'
mkdir dir && echo x > dir/file || framework_failure_
tail --debug --pid=$$ -F $fastpoll dir/file >out 2>&1 & pid=$!
grep_timeout_ 'using notification mode' ||
{ cat out; skip_ "inotify can't be used"; }
# Fill the event queue while tail is not reading it,
# so that the events for the following rotation are discarded.
kill -STOP $pid || framework_failure_
(cd dir && seq $max_events | xargs touch) || framework_failure_
mv dir/file dir/file.1 &&
echo y > dir/file &&
kill -CONT $pid || framework_failure_
# From coreutils-7.5 to 9.12 inclusive, the new file never noticed.
grep_timeout_ '^y$' || { cat out; fail=1; }
grep 'inotify event queue overflowed' out || {
cat out
test "$fail" = 1 || framework_failure_
}
Exit $fail