diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 16:58:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 16:58:41 +0000 |
commit | e1908ae95dd4c9d19ee4dfabfc8bf8a7f85943fe (patch) | |
tree | f5cc731bedcac0fb7fe14d952e4581e749f8bb87 /tests/tail | |
parent | Initial commit. (diff) | |
download | coreutils-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')
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 |