1#!/bin/sh 2# Exercise tail's behavior regarding missing files with/without --retry. 3 4# Copyright (C) 2013-2023 Free Software Foundation, Inc. 5 6# This program is free software: you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation, either version 3 of the License, or 9# (at your option) any later version. 10 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15 16# You should have received a copy of the GNU General Public License 17# along with this program. If not, see <https://www.gnu.org/licenses/>. 18 19. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src 20print_ver_ tail 21 22# Function to count number of lines from tail 23# while ignoring transient errors due to resource limits 24countlines_ () 25{ 26 grep -Ev 'inotify (resources exhausted|cannot be used)' out | wc -l 27} 28 29# Function to check the expected line count in 'out'. 30# Called via retry_delay_(). Sleep some time - see retry_delay_() - if the 31# line count is still smaller than expected. 32wait4lines_ () 33{ 34 local delay=$1 35 local elc=$2 # Expected line count. 36 [ "$(countlines_)" -ge "$elc" ] || { sleep $delay; return 1; } 37} 38 39# Terminate any background tail process 40cleanup_() { kill $pid 2>/dev/null && wait $pid; } 41 42# Speedup the non inotify case 43fastpoll='-s.1 --max-unchanged-stats=1' 44 45# === Test: 46# Retry without --follow results in a warning. 47touch file 48tail --retry file > out 2>&1 || fail=1 49[ "$(countlines_)" = 1 ] || { cat out; fail=1; } 50grep -F 'tail: warning: --retry ignored' out || { cat out; fail=1; } 51 52# === Test: 53# The same with a missing file: expect error message and exit 1. 54returns_ 1 tail --retry missing > out 2>&1 || fail=1 55[ "$(countlines_)" = 2 ] || { cat out; fail=1; } 56grep -F 'tail: warning: --retry ignored' out || { cat out; fail=1; } 57 58for mode in '' '---disable-inotify'; do 59 60# === Test: 61# Ensure that "tail --retry --follow=name" waits for the file to appear. 62# Clear 'out' so that we can check its contents without races 63>out || framework_failure_ 64timeout 10 \ 65 tail $mode $fastpoll --follow=name --retry missing >out 2>&1 & pid=$! 66# Wait for "cannot open" error. 67retry_delay_ wait4lines_ .1 6 1 || { cat out; fail=1; } 68echo "X" > missing || framework_failure_ 69# Wait for the expected output. 70retry_delay_ wait4lines_ .1 6 3 || { cat out; fail=1; } 71cleanup_ 72# Expect 3 lines in the output file. 73[ "$(countlines_)" = 3 ] || { fail=1; cat out; } 74grep -F 'cannot open' out || { fail=1; cat out; } 75grep -F 'has appeared' out || { fail=1; cat out; } 76grep '^X$' out || { fail=1; cat out; } 77rm -f missing out || framework_failure_ 78 79# === Test: 80# Ensure that "tail --retry --follow=descriptor" waits for the file to appear. 81# tail-8.21 failed at this (since the implementation of the inotify support). 82timeout 10 \ 83 tail $mode $fastpoll --follow=descriptor --retry missing >out 2>&1 & pid=$! 84# Wait for "cannot open" error. 85retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; } 86echo "X1" > missing || framework_failure_ 87# Wait for the expected output. 88retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; } 89# Ensure truncation is detected 90# tail-8.25 failed at this (as assumed non file and went into blocking mode) 91echo "X" > missing || framework_failure_ 92retry_delay_ wait4lines_ .1 6 6 || { cat out; fail=1; } 93cleanup_ 94[ "$(countlines_)" = 6 ] || { fail=1; cat out; } 95grep -F 'retry only effective for the initial open' out \ 96 || { fail=1; cat out; } 97grep -F 'cannot open' out || { fail=1; cat out; } 98grep -F 'has appeared' out || { fail=1; cat out; } 99grep '^X1$' out || { fail=1; cat out; } 100grep -F 'file truncated' out || { fail=1; cat out; } 101grep '^X$' out || { fail=1; cat out; } 102rm -f missing out || framework_failure_ 103 104# === Test: 105# Ensure that tail --follow=descriptor --retry exits when the file appears 106# untailable. Expect exit status 1. 107timeout 10 \ 108 tail $mode $fastpoll --follow=descriptor --retry missing >out 2>&1 & pid=$! 109# Wait for "cannot open" error. 110retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; } 111mkdir missing || framework_failure_ # Create untailable 112# Wait for the expected output. 113retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; } 114wait $pid 115rc=$? 116[ "$(countlines_)" = 4 ] || { fail=1; cat out; } 117grep -F 'retry only effective for the initial open' out \ 118 || { fail=1; cat out; } 119grep -F 'cannot open' out || { fail=1; cat out; } 120grep -F 'replaced with an untailable file' out || { fail=1; cat out; } 121grep -F 'no files remaining' out || { fail=1; cat out; } 122[ $rc = 1 ] || { fail=1; cat out; } 123rm -fd missing out || framework_failure_ 124 125# === Test: 126# Ensure that --follow=descriptor (without --retry) does *not* try 127# to open a file after an initial fail, even when there are other 128# tailable files. This was an issue in <= 8.25. 129touch existing || framework_failure_ 130tail $mode $fastpoll --follow=descriptor missing existing >out 2>&1 & pid=$! 131retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; } 132[ "$(countlines_)" = 2 ] || { fail=1; cat out; } 133grep -F 'cannot open' out || { fail=1; cat out; } 134echo "Y" > missing || framework_failure_ 135echo "X" > existing || framework_failure_ 136retry_delay_ wait4lines_ .1 6 3 || { cat out; fail=1; } 137[ "$(countlines_)" = 3 ] || { fail=1; cat out; } 138grep '^X$' out || { fail=1; cat out; } 139grep '^Y$' out && { fail=1; cat out; } 140cleanup_ 141rm -f missing out existing || framework_failure_ 142 143# === Test: 144# Ensure that --follow=descriptor (without --retry) does *not wait* for the 145# file to appear. Expect 2 lines in the output file ("cannot open" + 146# "no files remaining") and exit status 1. 147returns_ 1 tail $mode --follow=descriptor missing >out 2>&1 || fail=1 148[ "$(countlines_)" = 2 ] || { fail=1; cat out; } 149grep -F 'cannot open' out || { fail=1; cat out; } 150grep -F 'no files remaining' out || { fail=1; cat out; } 151rm -f out || framework_failure_ 152 153# === Test: 154# Likewise for --follow=name (without --retry). 155returns_ 1 tail $mode --follow=name missing >out 2>&1 || fail=1 156[ "$(countlines_)" = 2 ] || { fail=1; cat out; } 157grep -F 'cannot open' out || { fail=1; cat out; } 158grep -F 'no files remaining' out || { fail=1; cat out; } 159rm -f out || framework_failure_ 160 161# === Test: 162# Ensure that tail -F retries when the file is initially untailable. 163if ! cat . >/dev/null; then 164mkdir untailable || framework_failure_ 165timeout 10 \ 166 tail $mode $fastpoll -F untailable >out 2>&1 & pid=$! 167# Wait for "cannot follow" error. 168retry_delay_ wait4lines_ .1 6 2 || { cat out; fail=1; } 169{ rmdir untailable; echo foo > untailable; } || framework_failure_ 170# Wait for the expected output. 171retry_delay_ wait4lines_ .1 6 4 || { cat out; fail=1; } 172cleanup_ 173[ "$(countlines_)" = 4 ] || { fail=1; cat out; } 174grep -F 'cannot follow' out || { fail=1; cat out; } 175# The first is the common case, "has appeared" arises with slow rmdir. 176grep -E 'become accessible|has appeared' out || { fail=1; cat out; } 177grep -F 'giving up' out && { fail=1; cat out; } 178grep -F 'foo' out || { fail=1; cat out; } 179rm -fd untailable out || framework_failure_ 180fi 181 182done 183 184Exit $fail 185