summaryrefslogtreecommitdiffstats
path: root/tests/tail
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 16:58:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 16:58:41 +0000
commite1908ae95dd4c9d19ee4dfabfc8bf8a7f85943fe (patch)
treef5cc731bedcac0fb7fe14d952e4581e749f8bb87 /tests/tail
parentInitial commit. (diff)
downloadcoreutils-upstream.tar.xz
coreutils-upstream.zip
Adding upstream version 9.4.upstream/9.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tests/tail')
-rwxr-xr-xtests/tail/F-headers.sh59
-rwxr-xr-xtests/tail/F-vs-missing.sh59
-rwxr-xr-xtests/tail/F-vs-rename.sh90
-rwxr-xr-xtests/tail/append-only.sh46
-rwxr-xr-xtests/tail/assert-2.sh57
-rwxr-xr-xtests/tail/assert.sh68
-rwxr-xr-xtests/tail/big-4gb.sh50
-rwxr-xr-xtests/tail/descriptor-vs-rename.sh56
-rwxr-xr-xtests/tail/end-of-device.sh48
-rwxr-xr-xtests/tail/flush-initial.sh44
-rwxr-xr-xtests/tail/follow-name.sh35
-rwxr-xr-xtests/tail/follow-stdin.sh85
-rwxr-xr-xtests/tail/inotify-dir-recreate.sh88
-rwxr-xr-xtests/tail/inotify-hash-abuse.sh71
-rwxr-xr-xtests/tail/inotify-hash-abuse2.sh46
-rwxr-xr-xtests/tail/inotify-only-regular.sh32
-rwxr-xr-xtests/tail/inotify-race.sh101
-rwxr-xr-xtests/tail/inotify-race2.sh106
-rwxr-xr-xtests/tail/inotify-rotate-resources.sh108
-rwxr-xr-xtests/tail/inotify-rotate.sh77
-rwxr-xr-xtests/tail/overlay-headers.sh81
-rwxr-xr-xtests/tail/pid.sh49
-rwxr-xr-xtests/tail/pipe-f.sh67
-rwxr-xr-xtests/tail/pipe-f2.sh47
-rwxr-xr-xtests/tail/proc-ksyms.sh28
-rwxr-xr-xtests/tail/retry.sh184
-rwxr-xr-xtests/tail/start-middle.sh33
-rwxr-xr-xtests/tail/symlink.sh94
-rwxr-xr-xtests/tail/tail-c.sh38
-rwxr-xr-xtests/tail/tail-n0f.sh62
-rwxr-xr-xtests/tail/tail.pl148
-rwxr-xr-xtests/tail/truncate.sh56
-rwxr-xr-xtests/tail/wait.sh90
33 files changed, 2303 insertions, 0 deletions
diff --git a/tests/tail/F-headers.sh b/tests/tail/F-headers.sh
new file mode 100755
index 0000000..ef4b9bc
--- /dev/null
+++ b/tests/tail/F-headers.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+# Ensure tail -F distinguishes output with the correct headers
+# Between coreutils 7.5 and 8.23 inclusive, 'tail -F ...' would
+# not output headers for or created/renamed files in certain cases.
+
+# Copyright (C) 2015-2023 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
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ rm -f a b out
+
+ tail $mode -F $fastpoll a b > out 2>&1 & pid=$!
+
+ # Wait up to 12.7s for tail to start.
+ tail_re="cannot open 'b'" retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+
+ echo x > a
+ # Wait up to 12.7s for a's header to appear in the output:
+ tail_re='==> a <==' retry_delay_ check_tail_output .1 7 ||
+ { echo "$0: a: unexpected delay?"; cat out; fail=1; }
+
+ echo y > b
+ # Wait up to 12.7s for b's header to appear in the output:
+ tail_re='==> b <==' retry_delay_ check_tail_output .1 7 ||
+ { echo "$0: b: unexpected delay?"; cat out; fail=1; }
+
+ cleanup_
+done
+
+Exit $fail
diff --git a/tests/tail/F-vs-missing.sh b/tests/tail/F-vs-missing.sh
new file mode 100755
index 0000000..da4d4f7
--- /dev/null
+++ b/tests/tail/F-vs-missing.sh
@@ -0,0 +1,59 @@
+#!/bin/sh
+# demonstrate that tail -F works for currently missing dirs
+# Before coreutils-8.6, tail -F missing/file would not
+# notice any subsequent availability of the missing/file.
+
+# Copyright (C) 2010-2023 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
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out > /dev/null ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ rm -rf out missing
+
+ tail $mode -F $fastpoll missing/file > out 2>&1 & pid=$!
+
+ # Wait up to 12.7s for tail to start with diagnostic:
+ # tail: cannot open 'missing/file' for reading: No such file or directory
+ tail_re='cannot open' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+
+ mkdir missing || framework_failure_
+ (cd missing && echo x > file) || framework_failure_
+
+ # 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: file: unexpected delay?"; cat out; fail=1; }
+
+ cleanup_
+done
+
+
+Exit $fail
diff --git a/tests/tail/F-vs-rename.sh b/tests/tail/F-vs-rename.sh
new file mode 100755
index 0000000..1048054
--- /dev/null
+++ b/tests/tail/F-vs-rename.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+# Demonstrate that tail -F works when renaming the tailed files.
+# Between coreutils 7.5 and 8.2 inclusive, 'tail -F a b' would
+# stop tracking additions to b after 'mv a b'.
+
+# Copyright (C) 2009-2023 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
+
+check_tail_output() {
+ local delay="$1"
+ if [ "$tail_re" ]; then
+ grep "$tail_re" err ||
+ { sleep $delay; return 1; }
+ else
+ local tail_re="==> $file <==@$data@"
+ tr '\n' @ < out | grep "$tail_re" > /dev/null ||
+ { sleep $delay; return 1; }
+ fi
+ unset tail_re
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ rm -f a b out err
+ touch a b || framework_failure_
+
+ tail $mode -F $fastpoll a b >out 2>err & pid=$!
+
+ # Wait up to 12.7s for tail to start.
+ echo x > a
+ file=a data=x retry_delay_ check_tail_output .1 7 || { cat out; fail=1; }
+
+ mv a b || framework_failure_
+
+ # Wait 12.7s for this diagnostic:
+ # tail: 'a' has become inaccessible: No such file or directory
+ tail_re='inaccessible' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+
+ # Wait 12.7s for this diagnostic:
+ # tail: 'b' has been replaced; following new file
+ tail_re='replaced' retry_delay_ check_tail_output .1 7 ||
+ { 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 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; }
+ # 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 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 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; }
+
+ cleanup_
+done
+
+Exit $fail
diff --git a/tests/tail/append-only.sh b/tests/tail/append-only.sh
new file mode 100755
index 0000000..9ce855b
--- /dev/null
+++ b/tests/tail/append-only.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# Ensure that tail -f works on an append-only file
+# Requires root access to do chattr +a, as well as an ext[23] or xfs file system
+
+# Copyright (C) 2006-2023 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_root_
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+chattr_a_works=1
+touch f
+chattr +a f 2>/dev/null || chattr_a_works=0
+( echo x > f ) 2>/dev/null && chattr_a_works=0
+echo x >> f || chattr_a_works=0
+
+if test $chattr_a_works = 0; then
+ skip_ "chattr +a doesn't work on this file system"
+fi
+
+
+for mode in '' '---disable-inotify'; do
+ sleep 1 & pid=$!
+ tail --pid=$pid -f $mode f || fail=1
+ cleanup_
+done
+
+chattr -a f 2>/dev/null
+
+Exit $fail
diff --git a/tests/tail/assert-2.sh b/tests/tail/assert-2.sh
new file mode 100755
index 0000000..4592c8c
--- /dev/null
+++ b/tests/tail/assert-2.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+# This variant of 'assert' would get a Uninit Mem Read reliably in 2.0.9.
+# Due to a race condition in the test, the 'assert' script would get
+# the UMR on Solaris only some of the time, and not at all on Linux/GNU.
+
+# Copyright (C) 2000-2023 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
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ rm -f a foo out
+ touch a || framework_failure_
+
+ tail $mode $fastpoll -F a foo > out 2>&1 & pid=$!
+
+ # Wait up to 12.7s for tail to start.
+ echo x > a || framework_failure_
+ tail_re='^x$' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; break; }
+
+ # Wait up to 12.7s for tail to notice new foo file
+ ok='ok ok ok'
+ echo "$ok" > foo || framework_failure_
+ tail_re="^$ok$" retry_delay_ check_tail_output .1 7 ||
+ { echo "$0: foo: unexpected delay?"; cat out; fail=1; break; }
+
+ cleanup_
+done
+
+Exit $fail
diff --git a/tests/tail/assert.sh b/tests/tail/assert.sh
new file mode 100755
index 0000000..dda9904
--- /dev/null
+++ b/tests/tail/assert.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+# Test for assertion failure in "test".
+
+# Copyright (C) 1999-2023 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/>.
+
+
+# This test fails with tail from textutils-2.0.
+# It would get something like this:
+# tail: tail.c:718: recheck: Assertion 'valid_file_spec (f)' failed.
+# Aborted
+# due to a race condition in which a dev/inode pair is reused.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ tail
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ rm -f a foo out
+ touch a foo || framework_failure_
+
+ tail $mode --follow=name $fastpoll a foo > out 2>&1 & pid=$!
+
+ # Wait up to 12.7s for tail to start.
+ echo x > a || framework_failure_
+ tail_re='^x$' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; break; }
+
+ # Wait 12.7s for this diagnostic:
+ # tail: foo: No such file or directory
+ rm foo || framework_failure_
+ tail_re='No such file' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; break; }
+
+ # Wait up to 12.7s for tail to notice new foo file
+ ok='ok ok ok'
+ echo "$ok" > foo || framework_failure_
+ tail_re="^$ok$" retry_delay_ check_tail_output .1 7 ||
+ { echo "$0: foo: unexpected delay?"; cat out; fail=1; break; }
+
+ cleanup_
+done
+
+Exit $fail
diff --git a/tests/tail/big-4gb.sh b/tests/tail/big-4gb.sh
new file mode 100755
index 0000000..2e8379b
--- /dev/null
+++ b/tests/tail/big-4gb.sh
@@ -0,0 +1,50 @@
+#!/bin/sh
+# Demonstrate a bug in 'tail -cN' when operating on files of size 4G and larger
+# Fixed in coreutils-4.5.2.
+
+# Copyright (C) 2002-2023 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
+expensive_
+
+# Create a file of size exactly 4GB (2^32) with 8 bytes
+# at the beginning and another set of 8 bytes at the end.
+# The rest will be NUL bytes. On most modern systems, the following
+# creates a file that takes up only a few KB. Here, du -sh says 16K.
+echo abcdefgh | tr -d '\n' > big || framework_failure_
+echo 87654321 | tr -d '\n' > tmp || framework_failure_
+# Seek 4GB - 8
+dd bs=1 seek=4294967288 if=tmp of=big 2> err || dd_failed=1
+if test "$dd_failed" = 1; then
+ cat err 1>&2
+ skip_ \
+'cannot create a file large enough for this test,
+possibly because this system does not support large files;
+Consider rerunning this test on a different file system.'
+fi
+
+
+tail -c1 big > out || fail=1
+# Append a newline.
+echo >> out
+cat <<\EOF > exp
+1
+EOF
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/tail/descriptor-vs-rename.sh b/tests/tail/descriptor-vs-rename.sh
new file mode 100755
index 0000000..f99a4af
--- /dev/null
+++ b/tests/tail/descriptor-vs-rename.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+# Demonstrate that tail -f works when renaming the tailed files.
+# Between coreutils 7.5 and 8.23 inclusive, 'tail -f a' would
+# stop tracking additions to b after 'mv a b'.
+
+# Copyright (C) 2015-2023 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
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ rm -f a out
+ touch a || framework_failure_
+
+ tail $mode $fastpoll -f a > out 2>&1 & pid=$!
+
+ # Wait up to 12.7s for tail to start.
+ echo x > a
+ tail_re='^x$' retry_delay_ check_tail_output .1 7 || { cat out; fail=1; }
+
+ mv a b || framework_failure_
+
+ echo y >> b
+ # Wait up to 12.7s for "y" to appear in the output:
+ tail_re='^y$' retry_delay_ check_tail_output .1 7 || { cat out; fail=1; }
+
+ cleanup_
+done
+
+Exit $fail
diff --git a/tests/tail/end-of-device.sh b/tests/tail/end-of-device.sh
new file mode 100755
index 0000000..8c0da1d
--- /dev/null
+++ b/tests/tail/end-of-device.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+# Ensure that tail seeks to the end of a device
+
+# Copyright (C) 2017-2023 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
+
+# need write access to local device
+# (even though we don't actually write anything)
+require_root_
+require_local_dir_
+
+get_device_size() {
+ BLOCKDEV=blockdev
+ $BLOCKDEV -V >/dev/null 2>&1 || BLOCKDEV=/sbin/blockdev
+ $BLOCKDEV --getsize64 "$1"
+}
+
+# Get path to device the current dir is on.
+# Note df can only get fs size, not device size.
+device=$(df --output=source . | tail -n1) || framework_failure_
+
+dev_size=$(get_device_size "$device") ||
+ skip_ "failed to determine size of $device"
+
+tail_offset=$(expr $dev_size - 1023) ||
+ skip_ "failed to determine tail offset"
+
+timeout 10 tail -c 1024 "$device" > end1 || fail=1
+timeout 10 tail -c +"$tail_offset" "$device" > end2 || fail=1
+test $(wc -c < end1) = 1024 || fail=1
+cmp end1 end2 || fail=1
+
+Exit $fail
diff --git a/tests/tail/flush-initial.sh b/tests/tail/flush-initial.sh
new file mode 100755
index 0000000..00ca535
--- /dev/null
+++ b/tests/tail/flush-initial.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+# inotify-based tail -f didn't flush its initial output before blocking
+
+# Copyright (C) 2009-2023 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
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+echo line > in || framework_failure_
+# Output should be buffered since we're writing to file
+# so we're depending on the flush to write out
+tail $fastpoll -f in > out & pid=$!
+
+# Wait for 3.1s for the file to be flushed.
+tail_flush()
+{
+ local delay="$1"
+ sleep $delay
+ test -s out
+}
+retry_delay_ tail_flush .1 5 || fail=1
+
+cleanup_
+
+Exit $fail
diff --git a/tests/tail/follow-name.sh b/tests/tail/follow-name.sh
new file mode 100755
index 0000000..a414b69
--- /dev/null
+++ b/tests/tail/follow-name.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+# ensure that --follow=name does not imply --retry
+
+# Copyright (C) 2011-2023 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
+
+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
+
+Exit $fail
diff --git a/tests/tail/follow-stdin.sh b/tests/tail/follow-stdin.sh
new file mode 100755
index 0000000..0c415d3
--- /dev/null
+++ b/tests/tail/follow-stdin.sh
@@ -0,0 +1,85 @@
+#!/bin/sh
+# Test tail -f -
+
+# Copyright (C) 2009-2023 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
+
+#################
+# tail -f - would fail with the initial inotify implementation
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+echo line > in || framework_failure_
+echo line > exp || framework_failure_
+
+for mode in '' '---disable-inotify'; do
+ > out || framework_failure_
+
+ tail $mode -f $fastpoll < in > out 2> err & pid=$!
+
+ # Wait up to 12.7s for output to appear:
+ tail_re='line' retry_delay_ check_tail_output .1 7 ||
+ { echo "$0: a: unexpected delay?"; cat out; fail=1; }
+
+ # Ensure there was no error output.
+ compare /dev/null err || fail=1
+
+ cleanup_
+done
+
+
+#################
+# Before coreutils-8.26 this would induce an UMR under UBSAN
+returns_ 1 timeout 10 tail -f - <&- 2>errt || fail=1
+cat <<\EOF >exp || framework_failure_
+tail: cannot fstat 'standard input'
+tail: error reading 'standard input'
+tail: no files remaining
+tail: -
+EOF
+sed 's/\(tail:.*\):.*/\1/' errt > err || framework_failure_
+compare exp err || fail=1
+
+
+#################
+# Before coreutils-8.28 this would erroneously issue a warning
+if tty -s </dev/tty && test -t 0; then
+ for input in '' '-' '/dev/tty'; do
+ returns_ 124 timeout 0.1 tail -f $input 2>err || fail=1
+ compare /dev/null err || fail=1
+ done
+
+ tail -f - /dev/null </dev/tty 2> out & pid=$!
+ # Wait up to 12.7s for output to appear:
+ tail_re='ineffective' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+ cleanup_
+fi
+
+Exit $fail
diff --git a/tests/tail/inotify-dir-recreate.sh b/tests/tail/inotify-dir-recreate.sh
new file mode 100755
index 0000000..3d2ce99
--- /dev/null
+++ b/tests/tail/inotify-dir-recreate.sh
@@ -0,0 +1,88 @@
+#!/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) 2017-2023 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
+
+grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null && is_local_dir_ . \
+ || skip_ 'inotify is not supported'
+
+# 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 --pid=$$ -F dir/file >out 2>&1 & pid=$!
+grep_timeout_ 'inotify' 'out' ||
+{ cleanup_fail_ 'file to be tailed does not exist'; }
+
+inotify_failed_re='inotify (resources exhausted|cannot be used)'
+grep -E "$inotify_failed_re" out &&
+ skip_ "inotify can't be used"
+
+# 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
diff --git a/tests/tail/inotify-hash-abuse.sh b/tests/tail/inotify-hash-abuse.sh
new file mode 100755
index 0000000..26af820
--- /dev/null
+++ b/tests/tail/inotify-hash-abuse.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+# Exercise an abort-inducing flaw in inotify-enabled tail -F.
+
+# Copyright (C) 2009-2023 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
+
+# 9 is a magic number, related to internal details of tail.c and hash.c
+n=9
+seq $n | xargs touch || framework_failure_
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out > /dev/null ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ rm -f out
+
+ tail $mode $fastpoll -qF $(seq $n) > out 2>&1 & pid=$!
+
+ # Wait up to 12.7s for tail to start
+ echo x > $n
+ tail_re='^x$' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+
+ mv 1 f || framework_failure_
+
+ # Wait 12.7s for this diagnostic:
+ # tail: '1' has become inaccessible: No such file or directory
+ tail_re='inaccessible' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+
+ # Trigger the bug. Before the fix, this would provoke the abort.
+ echo a > 1 || framework_failure_
+
+ # Wait up to 6.3s for the "tail: '1' has appeared; ..." message
+ # (or for the buggy tail to die)
+ tail_re='has appeared' retry_delay_ check_tail_output .1 6 ||
+ { cat out; fail=1; }
+
+ # Double check that tail hasn't aborted
+ kill -0 $pid || fail=1
+
+ cleanup_
+done
+
+
+Exit $fail
diff --git a/tests/tail/inotify-hash-abuse2.sh b/tests/tail/inotify-hash-abuse2.sh
new file mode 100755
index 0000000..ba97d3b
--- /dev/null
+++ b/tests/tail/inotify-hash-abuse2.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+# Exercise an abort-inducing flaw in inotify-enabled tail -F.
+# Like inotify-hash-abuse, but without a hard-coded "9".
+
+# Copyright (C) 2009-2023 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
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ touch f || framework_failure_
+
+ tail $mode $fastpoll -F f & pid=$!
+
+ for i in $(seq 200); do
+ kill -0 $pid || break;
+ touch g
+ mv g f
+ done
+
+ # Ensure tail hasn't aborted
+ kill -0 $pid || fail=1
+
+ cleanup_
+done
+
+Exit $fail
diff --git a/tests/tail/inotify-only-regular.sh b/tests/tail/inotify-only-regular.sh
new file mode 100755
index 0000000..2ecf66b
--- /dev/null
+++ b/tests/tail/inotify-only-regular.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# ensure that tail -f only uses inotify for regular files or fifos
+
+# Copyright (C) 2017-2023 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
+
+grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null \
+ || skip_ 'inotify support required'
+
+require_strace_ 'inotify_add_watch'
+
+returns_ 124 timeout .1 strace -e inotify_add_watch -o strace.out \
+ tail -f /dev/null || fail=1
+
+grep 'inotify' strace.out && fail=1
+
+Exit $fail
diff --git a/tests/tail/inotify-race.sh b/tests/tail/inotify-race.sh
new file mode 100755
index 0000000..63f9065
--- /dev/null
+++ b/tests/tail/inotify-race.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+# Ensure that tail does not ignore data that is appended to a tailed-forever
+# file between tail's initial read-to-EOF, and when the inotify watches
+# are established in tail_forever_inotify. That data could be ignored
+# indefinitely if no *other* data is appended, but it would be printed as
+# soon as any additional appended data is detected.
+
+# Copyright (C) 2009-2023 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 sleep
+
+grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null && is_local_dir_ . \
+ || skip_ 'inotify is not supported'
+
+# Terminate any background gdb/tail process
+cleanup_() {
+ kill $pid 2>/dev/null && wait $pid
+ kill $sleep 2>/dev/null && wait $sleep
+}
+
+touch file || framework_failure_
+touch tail.out || framework_failure_
+
+( timeout 10s gdb --version ) > gdb.out 2>&1
+case $(cat gdb.out) in
+ *'GNU gdb'*) ;;
+ *) skip_ "can't run gdb";;
+esac
+
+# Break on a line rather than a symbol, to cater for inline functions
+break_src="$abs_top_srcdir/src/tail.c"
+break_line=$(grep -n ^tail_forever_inotify "$break_src") || framework_failure_
+break_line=$(echo "$break_line" | cut -d: -f1) || framework_failure_
+
+
+# Note we get tail to monitor a background sleep process
+# rather than using timeout(1), as timeout sends SIGCONT
+# signals to its monitored process, and gdb (7.9 at least)
+# has _intermittent_ issues with this.
+# Sending SIGCONT resulted in either delayed child termination,
+# or no child termination resulting in a hung test.
+# See https://sourceware.org/bugzilla/show_bug.cgi?id=18364
+
+env sleep 10 & sleep=$!
+
+# See if gdb works and
+# tail_forever_inotify is compiled and run
+gdb -nx --batch-silent \
+ --eval-command="break $break_line" \
+ --eval-command="run --pid=$sleep -f file" \
+ --eval-command='quit' \
+ tail < /dev/null > gdb.out 2>&1
+
+kill $sleep || skip_ 'breakpoint not hit'
+wait $sleep
+
+# FIXME: The above is seen to _intermittently_ fail with:
+# warning: .dynamic section for "/lib/libc.so.6" is not at the expected address
+# warning: difference appears to be caused by prelink, adjusting expectations
+compare /dev/null gdb.out || skip_ "can't set breakpoints in tail"
+
+env sleep 10 & sleep=$!
+
+# Run "tail -f file", stopping to append a line just before
+# inotify initialization, and then continue. Before the fix,
+# that just-appended line would never be output.
+gdb -nx --batch-silent \
+ --eval-command="break $break_line" \
+ --eval-command="run --pid=$sleep -f file >> tail.out" \
+ --eval-command='shell echo never-seen-with-tail-7.5 >> file' \
+ --eval-command='continue' \
+ --eval-command='quit' \
+ tail < /dev/null > /dev/null 2>&1 & pid=$!
+
+tail --pid=$pid -f tail.out | (read REPLY; kill $pid)
+
+# gdb has a bug in Debian's gdb-6.8-3 at least that causes it to not
+# cleanup and exit correctly when it receives a SIGTERM, but
+# killing sleep, should cause the tail process and thus gdb to exit.
+kill $sleep
+wait $sleep
+
+wait $pid
+
+compare /dev/null tail.out && fail=1
+
+Exit $fail
diff --git a/tests/tail/inotify-race2.sh b/tests/tail/inotify-race2.sh
new file mode 100755
index 0000000..19219b7
--- /dev/null
+++ b/tests/tail/inotify-race2.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+# Ensure that tail does not ignore a tailed-forever file that has been
+# replaced between tail's initial read-to-EOF, and when the inotify watches
+# are established in tail_forever_inotify. That new file would be ignored
+# indefinitely.
+
+# Copyright (C) 2015-2023 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 sleep
+
+grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null && is_local_dir_ . \
+ || skip_ 'inotify is not supported'
+
+# Terminate any background gdb/tail process
+cleanup_() {
+ kill $pid 2>/dev/null && wait $pid
+ kill $sleep 2>/dev/null && wait $sleep
+}
+
+touch file || framework_failure_
+touch tail.out || framework_failure_
+
+( timeout 10s gdb --version ) > gdb.out 2>&1
+case $(cat gdb.out) in
+ *'GNU gdb'*) ;;
+ *) skip_ "can't run gdb";;
+esac
+
+# Break on a line rather than a symbol, to cater for inline functions
+break_src="$abs_top_srcdir/src/tail.c"
+break_line=$(grep -n ^tail_forever_inotify "$break_src") || framework_failure_
+break_line=$(echo "$break_line" | cut -d: -f1) || framework_failure_
+
+
+# Note we get tail to monitor a background sleep process
+# rather than using timeout(1), as timeout sends SIGCONT
+# signals to its monitored process, and gdb (7.9 at least)
+# has _intermittent_ issues with this.
+# Sending SIGCONT resulted in either delayed child termination,
+# or no child termination resulting in a hung test.
+# See https://sourceware.org/bugzilla/show_bug.cgi?id=18364
+
+env sleep 10 & sleep=$!
+
+# See if gdb works and
+# tail_forever_inotify is compiled and run
+gdb -nx --batch-silent \
+ --eval-command="break $break_line" \
+ --eval-command="run --pid=$sleep -f file" \
+ --eval-command='quit' \
+ tail < /dev/null > gdb.out 2>&1
+
+kill $sleep || skip_ 'breakpoint not hit'
+wait $sleep
+
+# FIXME: The above is seen to _intermittently_ fail with:
+# warning: .dynamic section for "/lib/libc.so.6" is not at the expected address
+# warning: difference appears to be caused by prelink, adjusting expectations
+compare /dev/null gdb.out || skip_ "can't set breakpoints in tail"
+
+env sleep 10 & sleep=$!
+
+echo never-seen-with-tail-8.23 > file.new || framework_failure_
+
+# Run "tail -F file", stopping to replace with a new file before
+# inotify initialization, and then continue. Before the fix,
+# changes to the new file would effectively be ignored.
+gdb -nx --batch-silent \
+ --eval-command="break $break_line" \
+ --eval-command="run --pid=$sleep -F file 2>tail.err >>tail.out" \
+ --eval-command='shell mv file.new file' \
+ --eval-command='continue' \
+ --eval-command='quit' \
+ tail < /dev/null > /dev/null 2>&1 & pid=$!
+
+# Note even updating the watched 'file' wouldn't have output
+# anything between coreutils 7.5 and 8.23 inclusive as
+# The old file descriptor (still held open by tail) was being fstat().
+
+tail --pid=$pid -f tail.out | (read REPLY; kill $pid)
+
+# gdb has a bug in Debian's gdb-6.8-3 at least that causes it to not
+# cleanup and exit correctly when it receives a SIGTERM, but
+# killing sleep, should cause the tail process and thus gdb to exit.
+kill $sleep
+wait $sleep
+
+wait $pid
+
+compare /dev/null tail.out && { cat tail.err; fail=1; }
+
+Exit $fail
diff --git a/tests/tail/inotify-rotate-resources.sh b/tests/tail/inotify-rotate-resources.sh
new file mode 100755
index 0000000..543529e
--- /dev/null
+++ b/tests/tail/inotify-rotate-resources.sh
@@ -0,0 +1,108 @@
+#!/bin/sh
+# ensure that tail -F doesn't leak inotify resources
+
+# Copyright (C) 2015-2023 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
+
+# Inotify not used on remote file systems
+require_local_dir_
+
+grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null \
+ || skip_ 'inotify required'
+
+require_strace_ 'inotify_add_watch,inotify_rm_watch'
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out > /dev/null ||
+ { sleep $delay; return 1; }
+}
+
+# Wait up to 25.5 seconds for grep REGEXP 'out' to succeed.
+grep_timeout() { tail_re="$1" retry_delay_ check_tail_output .1 8; }
+
+check_strace()
+{
+ local delay="$1"
+ grep "$strace_re" strace.out > /dev/null ||
+ { sleep $delay; return 1; }
+}
+
+cleanup_fail()
+{
+ cat out
+ warn_ $1
+ fail=1
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+touch k || framework_failure_
+
+# Note the timeout guard isn't strictly necessary here,
+# however without it strace will ignore SIGTERM.
+# strace does always honor SIGTERM with the -I2 option,
+# though that's not available on RHEL6 for example.
+timeout 180 strace -e inotify_add_watch,inotify_rm_watch -o strace.out \
+ tail -F $fastpoll k >> out 2>&1 & pid=$!
+
+reverted_to_polling_=0
+for i in $(seq 2); do
+ echo $i
+
+ echo 'tailed' > k;
+
+ # Wait for watch on (new) file
+ strace_re='inotify_add_watch.*MODIFY' retry_delay_ check_strace .1 8 ||
+ no_watch_=1
+
+ # Assume this is not because we're leaking
+ # (resources may already be depleted)
+ # The explicit check for inotify_rm_watch should confirm that.
+ grep -F 'reverting to polling' out >/dev/null && skip_ 'inotify unused'
+
+ # Otherwise failure is unknown
+ test "$no_watch_" && { cat out; framework_failure_ 'no inotify_add_watch'; }
+
+ mv k k.tmp
+ # wait for tail to detect the rename
+ grep_timeout 'inaccessible' ||
+ { cleanup_fail 'failed to detect rename'; break; }
+
+ # Note we strace here rather than consuming all available watches
+ # to be more efficient, but more importantly avoid depleting resources.
+ # Note also available resources can currently be tuned with:
+ # sudo sysctl -w fs.inotify.max_user_watches=$smallish_number
+ # However that impacts all processes for the current user, and also
+ # may not be supported in future, instead being auto scaled to RAM
+ # like the Linux epoll resources were.
+ if test "$i" -gt 1; then
+ strace_re='inotify_rm_watch' retry_delay_ check_strace .1 8 ||
+ { cleanup_fail 'failed to find inotify_rm_watch syscall'; break; }
+ fi
+
+ >out && >strace.out || framework_failure_ 'failed to reset output files'
+done
+
+cleanup_
+
+Exit $fail
diff --git a/tests/tail/inotify-rotate.sh b/tests/tail/inotify-rotate.sh
new file mode 100755
index 0000000..55338b5
--- /dev/null
+++ b/tests/tail/inotify-rotate.sh
@@ -0,0 +1,77 @@
+#!/bin/sh
+# ensure that tail -F handles rotation
+
+# Copyright (C) 2009-2023 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
+
+grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null \
+ || expensive_
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out > /dev/null ||
+ { sleep $delay; return 1; }
+}
+
+# Wait up to 25.5 seconds for grep REGEXP 'out' to succeed.
+grep_timeout() { tail_re="$1" retry_delay_ check_tail_output .1 8; }
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+cleanup_fail()
+{
+ cat out
+ warn_ $1
+ cleanup_
+ fail=1
+}
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+# Perform at least this many iterations, because on multi-core systems
+# the offending sequence of events can be surprisingly uncommon.
+# See: https://lists.gnu.org/r/bug-coreutils/2009-11/msg00213.html
+for i in $(seq 50); do
+ echo $i
+ rm -f k x out
+
+ # Normally less than a second is required here, but with heavy load
+ # and a lot of file system activity, even 20 seconds is insufficient, which
+ # leads to this timeout killing tail before the "ok" is written below.
+ >k && >x || framework_failure_ failed to initialize files
+ timeout 60 tail $fastpoll -F k > out 2>&1 & pid=$!
+
+ echo 'tailed' > k;
+ # wait for 'tailed' to appear in out
+ grep_timeout 'tailed' || { cleanup_fail 'failed to find "tailed"'; break; }
+
+ mv x k
+ # wait for tail to detect the rename
+ grep_timeout 'tail:' || { cleanup_fail 'failed to detect rename'; break; }
+
+ echo ok >> k
+ # wait for "ok" to appear in 'out'
+ grep_timeout 'ok' || { cleanup_fail 'failed to detect echoed ok'; break; }
+
+ cleanup_
+done
+
+Exit $fail
diff --git a/tests/tail/overlay-headers.sh b/tests/tail/overlay-headers.sh
new file mode 100755
index 0000000..59f6e1e
--- /dev/null
+++ b/tests/tail/overlay-headers.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+# inotify-based tail would output redundant headers for
+# overlapping inotify events while it was suspended
+
+# Copyright (C) 2017-2023 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 sleep
+
+# Function to count number of lines from tail
+# while ignoring transient errors due to resource limits
+countlines_ ()
+{
+ grep -Ev 'inotify (resources exhausted|cannot be used)' out | wc -l
+}
+
+# Function to check the expected line count in 'out'.
+# Called via retry_delay_(). Sleep some time - see retry_delay_() - if the
+# line count is still smaller than expected.
+wait4lines_ ()
+{
+ local delay=$1
+ local elc=$2 # Expected line count.
+ [ "$(countlines_)" -ge "$elc" ] || { sleep $delay; return 1; }
+}
+
+# Speedup the non inotify case
+fastpoll='---dis -s.1 --max-unchanged-stats=1'
+
+# Terminate any background tail process
+cleanup_() {
+ kill $pid 2>/dev/null && wait $pid;
+ kill $sleep 2>/dev/null && wait $sleep
+}
+
+echo start > file1 || framework_failure_
+echo start > file2 || framework_failure_
+
+# Use this as a way to gracefully terminate tail
+env sleep 20 & sleep=$!
+
+tail $fastpoll --pid=$sleep -f file1 file2 > out & pid=$!
+
+kill -0 $pid || fail=1
+
+# Wait for 5 initial lines
+retry_delay_ wait4lines_ .1 6 5 || fail=1
+
+# Suspend tail so single read() caters for multiple inotify events
+kill -STOP $pid || fail=1
+
+# Interleave writes to files to generate overlapping inotify events
+echo line >> file1 || framework_failure_
+echo line >> file2 || framework_failure_
+echo line >> file1 || framework_failure_
+echo line >> file2 || framework_failure_
+
+# Resume tail processing
+kill -CONT $pid || fail=1
+
+# Wait for 8 more lines
+retry_delay_ wait4lines_ .1 6 13 || fail=1
+
+kill $sleep && wait || framework_failure_
+
+test "$(countlines_)" = 13 || fail=1
+
+Exit $fail
diff --git a/tests/tail/pid.sh b/tests/tail/pid.sh
new file mode 100755
index 0000000..f931b2c
--- /dev/null
+++ b/tests/tail/pid.sh
@@ -0,0 +1,49 @@
+#!/bin/sh
+# Test the --pid option of tail.
+
+# Copyright (C) 2003-2023 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
+getlimits_
+
+touch empty here || framework_failure_
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+for mode in '' '---disable-inotify'; do
+ # Use tail itself to create a background process to monitor,
+ # which will auto exit when "here" is removed.
+ tail -f $mode here & pid=$!
+
+ # Ensure that tail --pid=PID does not exit when PID is alive.
+ returns_ 124 timeout 1 tail -f -s.1 --pid=$pid $mode here || fail=1
+
+ cleanup_
+
+ # Ensure that tail --pid=PID exits with success status when PID is dead.
+ # Use an unlikely-to-be-live PID
+ timeout 10 tail -f -s.1 --pid=$PID_T_MAX $mode empty
+ ret=$?
+ test $ret = 124 && skip_ "pid $PID_T_MAX present or tail too slow"
+ test $ret = 0 || fail=1
+
+ # Ensure tail doesn't wait for data when PID is dead
+ returns_ 124 timeout 10 tail -f -s10 --pid=$PID_T_MAX $mode empty && fail=1
+done
+
+Exit $fail
diff --git a/tests/tail/pipe-f.sh b/tests/tail/pipe-f.sh
new file mode 100755
index 0000000..46bcbbe
--- /dev/null
+++ b/tests/tail/pipe-f.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+# ensure that tail -f doesn't hang in various cases
+
+# Copyright (C) 2009-2023 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 test head
+trap_sigpipe_or_skip_
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+# Ensure :|tail -f doesn't hang, per POSIX
+echo oo > exp || framework_failure_
+echo foo | timeout 10 tail -f $mode $fastpoll -c3 > out || fail=1
+compare exp out || fail=1
+cat <<\EOF > exp || framework_failure_
+==> standard input <==
+ar
+EOF
+echo bar | returns_ 1 \
+ timeout 10 tail -f $mode $fastpoll -c3 - missing > out || fail=1
+compare exp out || fail=1
+
+# This would wait indefinitely before v8.28 due to no EPIPE being
+# generated due to no data written after the first small amount.
+# Also check tail exits if SIGPIPE is being ignored.
+# Note 'trap - SIGPIPE' is ineffective if the initiating shell
+# has ignored SIGPIPE, but that's not the normal case.
+case $host_triplet in
+ *aix*) echo 'avoiding due to no way to detect closed outputs on AIX' ;;
+ *)
+for disposition in '' '-'; do
+ (trap "$disposition" PIPE;
+ returns_ 124 timeout 10 \
+ tail -n2 -f $mode $fastpoll out && touch timed_out) |
+ head -n2 > out2
+ test -e timed_out && fail=1
+ compare exp out2 || fail=1
+ rm -f timed_out
+done ;;
+esac
+
+# This would wait indefinitely before v8.28 (until first write)
+# test -w /dev/stdout is used to check that >&- is effective
+# which was seen not to be the case on NetBSD 7.1 / x86_64:
+if env test -w /dev/stdout >/dev/null &&
+ env test ! -w /dev/stdout >&-; then
+ (returns_ 1 timeout 10 tail -f $mode $fastpoll /dev/null >&-) || fail=1
+fi
+done
+
+Exit $fail
diff --git a/tests/tail/pipe-f2.sh b/tests/tail/pipe-f2.sh
new file mode 100755
index 0000000..edbe3ff
--- /dev/null
+++ b/tests/tail/pipe-f2.sh
@@ -0,0 +1,47 @@
+#!/bin/sh
+# Ensure that "tail -f fifo" tails indefinitely.
+
+# Copyright (C) 2009-2023 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
+
+mkfifo_or_skip_ fifo
+
+echo 1 > fifo &
+echo 1 > exp || framework_failure_
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+timeout 10 tail $fastpoll -f fifo > out & pid=$!
+
+check_tail_output() { sleep $1; test -s out; }
+
+# Wait 12.7s for tail to write something.
+retry_delay_ check_tail_output .1 7 || fail=1
+
+compare exp out || fail=1
+
+# Ensure tail is still running
+kill -0 $pid || fail=1
+
+cleanup_
+
+Exit $fail
diff --git a/tests/tail/proc-ksyms.sh b/tests/tail/proc-ksyms.sh
new file mode 100755
index 0000000..5f9b239
--- /dev/null
+++ b/tests/tail/proc-ksyms.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+# Prior to textutils-2.0.17, 'tail /proc/ksyms' would segfault on Linux.
+
+# Copyright (C) 2001-2023 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
+
+
+ksyms=/proc/ksyms
+if test -r $ksyms; then
+ tail $ksyms > /dev/null || fail=1
+fi
+
+Exit $fail
diff --git a/tests/tail/retry.sh b/tests/tail/retry.sh
new file mode 100755
index 0000000..cb75e05
--- /dev/null
+++ b/tests/tail/retry.sh
@@ -0,0 +1,184 @@
+#!/bin/sh
+# Exercise tail's behavior regarding missing files with/without --retry.
+
+# Copyright (C) 2013-2023 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
+
+# Function to count number of lines from tail
+# while ignoring transient errors due to resource limits
+countlines_ ()
+{
+ grep -Ev 'inotify (resources exhausted|cannot be used)' out | wc -l
+}
+
+# Function to check the expected line count in 'out'.
+# Called via retry_delay_(). Sleep some time - see retry_delay_() - if the
+# line count is still smaller than expected.
+wait4lines_ ()
+{
+ local delay=$1
+ local elc=$2 # Expected line count.
+ [ "$(countlines_)" -ge "$elc" ] || { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+# === Test:
+# Retry without --follow results in a warning.
+touch file
+tail --retry file > out 2>&1 || fail=1
+[ "$(countlines_)" = 1 ] || { cat out; fail=1; }
+grep -F 'tail: warning: --retry ignored' out || { cat out; fail=1; }
+
+# === Test:
+# The same with a missing file: expect error message and exit 1.
+returns_ 1 tail --retry missing > out 2>&1 || fail=1
+[ "$(countlines_)" = 2 ] || { cat out; fail=1; }
+grep -F 'tail: warning: --retry ignored' out || { cat out; fail=1; }
+
+for mode in '' '---disable-inotify'; do
+
+# === Test:
+# Ensure that "tail --retry --follow=name" waits for the file to appear.
+# Clear 'out' so that we can check its contents without races
+>out || framework_failure_
+timeout 10 \
+ tail $mode $fastpoll --follow=name --retry missing >out 2>&1 & pid=$!
+# Wait for "cannot open" error.
+retry_delay_ wait4lines_ .1 6 1 || { cat out; fail=1; }
+echo "X" > missing || framework_failure_
+# Wait for the expected output.
+retry_delay_ wait4lines_ .1 6 3 || { cat out; fail=1; }
+cleanup_
+# Expect 3 lines in the output file.
+[ "$(countlines_)" = 3 ] || { fail=1; cat out; }
+grep -F 'cannot open' out || { fail=1; cat out; }
+grep -F 'has appeared' out || { fail=1; cat out; }
+grep '^X$' out || { fail=1; cat out; }
+rm -f missing out || framework_failure_
+
+# === Test:
+# Ensure that "tail --retry --follow=descriptor" waits for the file to appear.
+# tail-8.21 failed at this (since the implementation of the inotify support).
+timeout 10 \
+ tail $mode $fastpoll --follow=descriptor --retry missing >out 2>&1 & pid=$!
+# Wait for "cannot open" error.
+retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
+echo "X1" > missing || framework_failure_
+# Wait for the expected output.
+retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; }
+# Ensure truncation is detected
+# tail-8.25 failed at this (as assumed non file and went into blocking mode)
+echo "X" > missing || framework_failure_
+retry_delay_ wait4lines_ .1 6 6 || { cat out; fail=1; }
+cleanup_
+[ "$(countlines_)" = 6 ] || { fail=1; cat out; }
+grep -F 'retry only effective for the initial open' out \
+ || { fail=1; cat out; }
+grep -F 'cannot open' out || { fail=1; cat out; }
+grep -F 'has appeared' out || { fail=1; cat out; }
+grep '^X1$' out || { fail=1; cat out; }
+grep -F 'file truncated' out || { fail=1; cat out; }
+grep '^X$' out || { fail=1; cat out; }
+rm -f missing out || framework_failure_
+
+# === Test:
+# Ensure that tail --follow=descriptor --retry exits when the file appears
+# untailable. Expect exit status 1.
+timeout 10 \
+ tail $mode $fastpoll --follow=descriptor --retry missing >out 2>&1 & pid=$!
+# Wait for "cannot open" error.
+retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
+mkdir missing || framework_failure_ # Create untailable
+# Wait for the expected output.
+retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; }
+wait $pid
+rc=$?
+[ "$(countlines_)" = 4 ] || { fail=1; cat out; }
+grep -F 'retry only effective for the initial open' out \
+ || { fail=1; cat out; }
+grep -F 'cannot open' out || { fail=1; cat out; }
+grep -F 'replaced with an untailable file' out || { fail=1; cat out; }
+grep -F 'no files remaining' out || { fail=1; cat out; }
+[ $rc = 1 ] || { fail=1; cat out; }
+rm -fd missing out || framework_failure_
+
+# === Test:
+# Ensure that --follow=descriptor (without --retry) does *not* try
+# to open a file after an initial fail, even when there are other
+# tailable files. This was an issue in <= 8.25.
+touch existing || framework_failure_
+tail $mode $fastpoll --follow=descriptor missing existing >out 2>&1 & pid=$!
+retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
+[ "$(countlines_)" = 2 ] || { fail=1; cat out; }
+grep -F 'cannot open' out || { fail=1; cat out; }
+echo "Y" > missing || framework_failure_
+echo "X" > existing || framework_failure_
+retry_delay_ wait4lines_ .1 6 3 || { cat out; fail=1; }
+[ "$(countlines_)" = 3 ] || { fail=1; cat out; }
+grep '^X$' out || { fail=1; cat out; }
+grep '^Y$' out && { fail=1; cat out; }
+cleanup_
+rm -f missing out existing || framework_failure_
+
+# === Test:
+# Ensure that --follow=descriptor (without --retry) does *not wait* for the
+# file to appear. Expect 2 lines in the output file ("cannot open" +
+# "no files remaining") and exit status 1.
+returns_ 1 tail $mode --follow=descriptor missing >out 2>&1 || fail=1
+[ "$(countlines_)" = 2 ] || { fail=1; cat out; }
+grep -F 'cannot open' out || { fail=1; cat out; }
+grep -F 'no files remaining' out || { fail=1; cat out; }
+rm -f out || framework_failure_
+
+# === Test:
+# Likewise for --follow=name (without --retry).
+returns_ 1 tail $mode --follow=name missing >out 2>&1 || fail=1
+[ "$(countlines_)" = 2 ] || { fail=1; cat out; }
+grep -F 'cannot open' out || { fail=1; cat out; }
+grep -F 'no files remaining' out || { fail=1; cat out; }
+rm -f out || framework_failure_
+
+# === Test:
+# Ensure that tail -F retries when the file is initially untailable.
+if ! cat . >/dev/null; then
+mkdir untailable || framework_failure_
+timeout 10 \
+ tail $mode $fastpoll -F untailable >out 2>&1 & pid=$!
+# Wait for "cannot follow" error.
+retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
+{ rmdir untailable; echo foo > untailable; } || framework_failure_
+# Wait for the expected output.
+retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; }
+cleanup_
+[ "$(countlines_)" = 4 ] || { fail=1; cat out; }
+grep -F 'cannot follow' out || { fail=1; cat out; }
+# The first is the common case, "has appeared" arises with slow rmdir.
+grep -E 'become accessible|has appeared' out || { fail=1; cat out; }
+grep -F 'giving up' out && { fail=1; cat out; }
+grep -F 'foo' out || { fail=1; cat out; }
+rm -fd untailable out || framework_failure_
+fi
+
+done
+
+Exit $fail
diff --git a/tests/tail/start-middle.sh b/tests/tail/start-middle.sh
new file mode 100755
index 0000000..a64bb2d
--- /dev/null
+++ b/tests/tail/start-middle.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+# Verify that tail works even when it's reading from a file
+# that is not at its beginning. Based on a report from John Roll.
+
+# Copyright (C) 2001-2023 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
+
+(echo 1; echo 2) > k || framework_failure_
+
+
+sh -c 'read x; tail' < k > out || fail=1
+cat <<EOF > exp
+2
+EOF
+
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/tail/symlink.sh b/tests/tail/symlink.sh
new file mode 100755
index 0000000..97184d4
--- /dev/null
+++ b/tests/tail/symlink.sh
@@ -0,0 +1,94 @@
+#!/bin/sh
+# Ensure tail tracks symlinks properly.
+
+# Copyright (C) 2013-2023 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
+
+# Function to count number of lines from tail
+# while ignoring transient errors due to resource limits
+countlines_ ()
+{
+ grep -Ev 'inotify (resources exhausted|cannot be used)' out | wc -l
+}
+
+# Function to check the expected line count in 'out'.
+# Called via retry_delay_(). Sleep some time - see retry_delay_() - if the
+# line count is still smaller than expected.
+wait4lines_ ()
+{
+ local delay=$1
+ local elc=$2 # Expected line count.
+ [ "$(countlines_)" -ge "$elc" ] || { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# speedup non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+# Ensure changing targets of cli specified symlinks are handled.
+# Prior to v8.22, inotify would fail to recognize changes in the targets.
+# Clear 'out' so that we can check its contents without races.
+>out || framework_failure_
+ln -nsf target symlink || framework_failure_
+timeout 10 tail $fastpoll -F symlink >out 2>&1 & pid=$!
+# Wait for "cannot open..."
+retry_delay_ wait4lines_ .1 6 1 || { cat out; fail=1; }
+echo "X" > target || framework_failure_
+# Wait for the expected output.
+retry_delay_ wait4lines_ .1 6 3 || { cat out; fail=1; }
+cleanup_
+# Expect 3 lines in the output file.
+[ "$(countlines_)" = 3 ] || { fail=1; cat out; }
+grep -F 'cannot open' out || { fail=1; cat out; }
+grep -F 'has appeared' out || { fail=1; cat out; }
+grep '^X$' out || { fail=1; cat out; }
+rm -f target out || framework_failure_
+
+# Ensure we correctly handle the source symlink itself changing.
+# I.e., that we don't operate solely on the targets.
+# Clear 'out' so that we can check its contents without races.
+>out || framework_failure_
+echo "X1" > target1 || framework_failure_
+ln -nsf target1 symlink || framework_failure_
+timeout 10 tail $fastpoll -F symlink >out 2>&1 & pid=$!
+# Wait for the expected output.
+retry_delay_ wait4lines_ .1 6 1 || { cat out; fail=1; }
+ln -nsf target2 symlink || framework_failure_
+# Wait for "become inaccess..."
+retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; }
+echo "X2" > target2 || framework_failure_
+# Wait for the expected output.
+retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; }
+cleanup_
+# Expect 4 lines in the output file.
+[ "$(countlines_)" = 4 ] || { fail=1; cat out; }
+grep -F 'become inacce' out || { fail=1; cat out; }
+grep -F 'has appeared' out || { fail=1; cat out; }
+grep '^X1$' out || { fail=1; cat out; }
+grep '^X2$' out || { fail=1; cat out; }
+rm -f target1 target2 out || framework_failure_
+
+# Note other symlink edge cases are currently just diagnosed
+# rather than being handled. I.e., if you specify a missing item,
+# or existing file that later change to a symlink, if inotify
+# is in use, you'll get a diagnostic saying that link will
+# no longer be tailed.
+
+Exit $fail
diff --git a/tests/tail/tail-c.sh b/tests/tail/tail-c.sh
new file mode 100755
index 0000000..cd92909
--- /dev/null
+++ b/tests/tail/tail-c.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# exercise tail -c
+
+# Copyright 2014-2023 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
+
+# Make sure it works on funny files in /proc and /sys.
+for file in /proc/version /sys/kernel/profiling; do
+ if test -r $file; then
+ cp -f $file copy &&
+ tail -c -1 copy > exp || framework_failure_
+
+ tail -c -1 $file > out || fail=1
+ compare exp out || fail=1
+ fi
+done
+
+# Make sure it works for pipes
+printf '123456' | tail -c3 > out || fail=1
+printf '456' > exp || framework_failure_
+compare exp out || fail=1
+
+Exit $fail
diff --git a/tests/tail/tail-n0f.sh b/tests/tail/tail-n0f.sh
new file mode 100755
index 0000000..a3ce5f8
--- /dev/null
+++ b/tests/tail/tail-n0f.sh
@@ -0,0 +1,62 @@
+#!/bin/sh
+# Make sure that 'tail -n0 -f' and 'tail -c0 -f' sleep
+# rather than doing what amounted to a busy-wait.
+
+# Copyright (C) 2003-2023 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/>.
+
+# This bug was fixed for 5.0.91
+# It skips the test if your system lacks a /proc/$pid/status
+# file, or if its contents don't look right.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ tail
+
+require_proc_pid_status_
+
+touch empty || framework_failure_
+echo anything > nonempty || framework_failure_
+
+# First verify that -[nc]0 without -f, exit without reading
+touch unreadable || framework_failure_
+chmod 0 unreadable || framework_failure_
+tail -c0 unreadable || fail=1
+tail -n0 unreadable || fail=1
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+for mode in '' '---disable-inotify'; do
+ for file in empty nonempty; do
+ for c_or_n in c n; do
+ tail --sleep=4 -${c_or_n} 0 -f $mode $file & pid=$!
+ tail_sleeping()
+ {
+ local delay="$1"; sleep $delay
+ state=$(get_process_status_ $pid)
+ case $state in
+ S*) ;;
+ *) return 1;;
+ esac
+ }
+ # Wait up to 1.5s for tail to sleep
+ retry_delay_ tail_sleeping .1 4 ||
+ { echo $0: process in unexpected state: $state >&2; fail=1; }
+ cleanup_
+ done
+ done
+done
+
+Exit $fail
diff --git a/tests/tail/tail.pl b/tests/tail/tail.pl
new file mode 100755
index 0000000..1ff1a90
--- /dev/null
+++ b/tests/tail/tail.pl
@@ -0,0 +1,148 @@
+#!/usr/bin/perl
+# Test tail.
+
+# Copyright (C) 2008-2023 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/>.
+
+use strict;
+
+my $prog = 'tail';
+my $normalize_strerror = "s/': .*/'/";
+
+# Turn off localization of executable's output.
+@ENV{qw(LANGUAGE LANG LC_ALL)} = ('C') x 3;
+
+my @tv = (
+# test name, options, input, expected output, expected return code
+#
+['obs-plus-c1', '+2c', 'abcd', 'bcd', 0],
+['obs-plus-c2', '+8c', 'abcd', '', 0],
+['obs-c3', '-1c', 'abcd', 'd', 0],
+['obs-c4', '-9c', 'abcd', 'abcd', 0],
+['obs-c5', '-12c', 'x' . ('y' x 12) . 'z', ('y' x 11) . 'z', 0],
+
+['obs-l1', '-1l', 'x', 'x', 0],
+['obs-l2', '-1l', "x\ny\n", "y\n", 0],
+['obs-l3', '-1l', "x\ny", "y", 0],
+['obs-plus-l4', '+1l', "x\ny\n", "x\ny\n", 0],
+['obs-plus-l5', '+2l', "x\ny\n", "y\n", 0],
+
+# Same as -l tests, but without the 'l'.
+['obs-1', '-1', 'x', 'x', 0],
+['obs-2', '-1', "x\ny\n", "y\n", 0],
+['obs-3', '-1', "x\ny", "y", 0],
+['obs-plus-4', '+1', "x\ny\n", "x\ny\n", 0],
+['obs-plus-5', '+2', "x\ny\n", "y\n", 0],
+
+# This is equivalent to +10c
+['obs-plus-x1', '+c', 'x' . ('y' x 10) . 'z', 'yyz', 0],
+# This is equivalent to +10l
+['obs-plus-x2', '+l', "x\n" . ("y\n" x 10) . 'z', "y\ny\nz", 0],
+# With no number, this is like -10l
+['obs-l', '-l', "x\n" . ("y\n" x 10) . 'z', ("y\n" x 9) . 'z', 0],
+
+['obs-b', '-b', "x\n" x (512 * 10 / 2 + 1), "x\n" x (512 * 10 / 2), 0],
+
+['err-1', '+cl', '', '', 1,
+ "$prog: cannot open '+cl' for reading: No such file or directory\n"],
+
+['err-2', '-cl', '', '', 1,
+ "$prog: invalid number of bytes: 'l'\n", $normalize_strerror],
+
+['err-3', '+2cz', '', '', 1,
+ "$prog: cannot open '+2cz' for reading: No such file or directory\n"],
+
+# This should get 'tail: invalid option -- 2'
+['err-4', '-2cX', '', '', 1,
+ "$prog: option used in invalid context -- 2\n"],
+
+# Since the number is larger than 2^64, this should provoke
+# the diagnostic: 'tail: 99999999999999999999: invalid number of bytes'
+# on all systems... probably, for now, maybe.
+['err-5', '-c99999999999999999999', '', '', 1,
+ "$prog: invalid number of bytes: '99999999999999999999'\n",
+ $normalize_strerror],
+['err-6', '-c --', '', '', 1,
+ "$prog: invalid number of bytes: '-'\n", $normalize_strerror],
+
+# Same as -n 10
+['minus-1', '-', '', '', 0],
+['minus-2', '-', "x\n" . ("y\n" x 10) . 'z', ("y\n" x 9) . 'z', 0],
+
+['c-2', '-c 2', "abcd\n", "d\n", 0],
+['c-2-minus', '-c 2 --', "abcd\n", "d\n", 0],
+['c2', '-c2', "abcd\n", "d\n", 0],
+['c2-minus', '-c2 --', "abcd\n", "d\n", 0],
+
+['n-1', '-n 10', "x\n" . ("y\n" x 10) . 'z', ("y\n" x 9) . 'z', 0],
+['n-2', '-n -10', "x\n" . ("y\n" x 10) . 'z', ("y\n" x 9) . 'z', 0],
+['n-3', '-n +10', "x\n" . ("y\n" x 10) . 'z', "y\ny\nz", 0],
+
+# Accept +0 as synonym for +1.
+['n-4', '-n +0', "y\n" x 5, "y\n" x 5, 0],
+['n-4a', '-n +1', "y\n" x 5, "y\n" x 5, 0],
+
+# Note that -0 is *not* a synonym for -1.
+['n-5', '-n -0', "y\n" x 5, '', 0],
+['n-5a', '-n -1', "y\n" x 5, "y\n", 0],
+['n-5b', '-n 0', "y\n" x 5, '', 0],
+
+# With textutils-1.22, this failed.
+['f-pipe-1', '-f -n 1', "a\nb\n", "b\n", 0],
+
+# --zero-terminated
+['zero-1', '-z -n 1', "x\0y", "y", 0],
+['zero-2', '-z -n 2', "x\0y", "x\0y", 0],
+);
+
+my @Tests;
+
+foreach my $t (@tv)
+ {
+ my ($test_name, $flags, $in, $exp, $ret, $err_msg, $err_sub) = @$t;
+ my $e = [$test_name, $flags, {IN=>$in}, {OUT=>$exp}];
+ $ret
+ and push @$e, {EXIT=>$ret}, {ERR=>$err_msg}, {ERR_SUBST=>$err_sub};
+
+ $test_name =~ /^minus-/
+ and push @$e, {ENV=>'_POSIX2_VERSION=199209'};
+
+ $test_name =~ /^(err-6|c-2)$/
+ and push @$e, {ENV=>'_POSIX2_VERSION=200112'};
+
+ $test_name =~ /^obs-plus-/
+ and push @$e, {ENV=>'_POSIX2_VERSION=200809'};
+
+ $test_name =~ /^f-pipe-/
+ and push @$e, {ENV=>'POSIXLY_CORRECT=1'};
+
+ push @Tests, $e;
+ }
+
+@Tests = triple_test \@Tests;
+
+# If you run the minus* tests with a FILE arg they'd hang.
+# If you run the err-1 or err-3 tests with a FILE, they'd misinterpret
+# the arg unless we are using the obsolete form.
+@Tests = grep { $_->[0] !~ /^(minus|err-[13])/ || $_->[0] =~ /\.[rp]$/ } @Tests;
+
+# Using redirection or a file would make this hang.
+@Tests = grep { $_->[0] !~ /^f-/ || $_->[0] =~ /\.p$/ } @Tests;
+
+my $save_temps = $ENV{DEBUG};
+my $verbose = $ENV{VERBOSE};
+
+my $fail = run_tests ($prog, $prog, \@Tests, $save_temps, $verbose);
+exit $fail;
diff --git a/tests/tail/truncate.sh b/tests/tail/truncate.sh
new file mode 100755
index 0000000..7c9fbfc
--- /dev/null
+++ b/tests/tail/truncate.sh
@@ -0,0 +1,56 @@
+#!/bin/sh
+# Ensure all logs are output upon file truncation
+
+# Copyright (C) 2015-2023 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
+
+check_tail_output()
+{
+ local delay="$1"
+ grep "$tail_re" out > /dev/null ||
+ { sleep $delay; return 1; }
+}
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# Speedup the non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for follow in '-f' '-F'; do
+ for mode in '' '---disable-inotify'; do
+ rm -f out
+ seq 10 > f || framework_failure_
+
+ tail $follow $mode $fastpoll f > out 2>&1 & pid=$!
+
+ # Wait up to 12.7s for tail to start
+ tail_re='^10$' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+
+ seq 11 15 > f || framework_failure_
+
+ # Wait up to 12.7s for new data
+ tail_re='^15$' retry_delay_ check_tail_output .1 7 ||
+ { cat out; fail=1; }
+
+ cleanup_
+ done
+done
+
+Exit $fail
diff --git a/tests/tail/wait.sh b/tests/tail/wait.sh
new file mode 100755
index 0000000..12b906b
--- /dev/null
+++ b/tests/tail/wait.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+# Make sure that 'tail -f' returns immediately if a file doesn't exist
+# while 'tail -F' waits for it to appear.
+
+# Copyright (C) 2003-2023 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
+
+grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null \
+ && HAVE_INOTIFY=1
+
+inotify_failed_re='inotify (resources exhausted|cannot be used)'
+
+touch here || framework_failure_
+{ touch unreadable && chmod a-r unreadable; } || framework_failure_
+
+# Terminate any background tail process
+cleanup_() { kill $pid 2>/dev/null && wait $pid; }
+
+# speedup non inotify case
+fastpoll='-s.1 --max-unchanged-stats=1'
+
+for mode in '' '---disable-inotify'; do
+ returns_ 124 timeout 10 tail $fastpoll -f $mode not_here && fail=1
+
+ if test ! -r unreadable; then # can't test this when root
+ returns_ 124 timeout 10 tail $fastpoll -f $mode unreadable && fail=1
+ fi
+
+ returns_ 124 timeout .1 tail $fastpoll -f $mode here 2>tail.err || fail=1
+
+ # 'tail -F' must wait in any case.
+
+ returns_ 124 timeout .1 tail $fastpoll -F $mode here 2>>tail.err || fail=1
+
+ if test ! -r unreadable; then # can't test this when root
+ returns_ 124 timeout .1 tail $fastpoll -F $mode unreadable || fail=1
+ fi
+
+ returns_ 124 timeout .1 tail $fastpoll -F $mode not_here || fail=1
+
+ grep -Ev "$inotify_failed_re" tail.err > x
+ mv x tail.err
+ compare /dev/null tail.err || fail=1
+ >tail.err
+done
+
+if test "$HAVE_INOTIFY" && test -z "$mode" && is_local_dir_ .; then
+ # Ensure -F never follows a descriptor after rename
+ # either with tiny or significant delays between operations
+ tail_F()
+ {
+ local delay="$1"
+
+ > k && > tail.out && > tail.err || framework_failure_
+ tail $fastpoll -F $mode k >tail.out 2>tail.err & pid=$!
+ sleep $delay
+ mv k l
+ sleep $delay
+ touch k
+ mv k l
+ sleep $delay
+ echo NO >> l
+ sleep $delay
+ cleanup_
+ rm -f k l
+
+ test -s tail.out \
+ && ! grep -E "$inotify_failed_re" tail.err >/dev/null
+ }
+
+ retry_delay_ tail_F 0 1 && { cat tail.out; fail=1; }
+ retry_delay_ tail_F .2 1 && { cat tail.out; fail=1; }
+fi
+
+Exit $fail