1#!/bin/sh
2# ensure that tail -F doesn't leak inotify resources
3
4# Copyright (C) 2015-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# Inotify not used on remote file systems
23require_local_dir_
24
25grep '^#define HAVE_INOTIFY 1' "$CONFIG_HEADER" >/dev/null \
26  || skip_ 'inotify required'
27
28require_strace_ 'inotify_add_watch,inotify_rm_watch'
29
30check_tail_output()
31{
32  local delay="$1"
33  grep "$tail_re" out > /dev/null ||
34    { sleep $delay; return 1; }
35}
36
37# Wait up to 25.5 seconds for grep REGEXP 'out' to succeed.
38grep_timeout() { tail_re="$1" retry_delay_ check_tail_output .1 8; }
39
40check_strace()
41{
42  local delay="$1"
43  grep "$strace_re" strace.out > /dev/null ||
44    { sleep $delay; return 1; }
45}
46
47cleanup_fail()
48{
49  cat out
50  warn_ $1
51  fail=1
52}
53
54# Terminate any background tail process
55cleanup_() { kill $pid 2>/dev/null && wait $pid; }
56
57fastpoll='-s.1 --max-unchanged-stats=1'
58
59touch k || framework_failure_
60
61# Note the timeout guard isn't strictly necessary here,
62# however without it strace will ignore SIGTERM.
63# strace does always honor SIGTERM with the -I2 option,
64# though that's not available on RHEL6 for example.
65timeout 180 strace -e inotify_add_watch,inotify_rm_watch -o strace.out \
66  tail -F $fastpoll k >> out 2>&1 & pid=$!
67
68reverted_to_polling_=0
69for i in $(seq 2); do
70    echo $i
71
72    echo 'tailed' > k;
73
74    # Wait for watch on (new) file
75    strace_re='inotify_add_watch.*MODIFY' retry_delay_ check_strace .1 8 ||
76      no_watch_=1
77
78    # Assume this is not because we're leaking
79    # (resources may already be depleted)
80    # The explicit check for inotify_rm_watch should confirm that.
81    grep -F 'reverting to polling' out >/dev/null && skip_ 'inotify unused'
82
83    # Otherwise failure is unknown
84    test "$no_watch_" && { cat out; framework_failure_ 'no inotify_add_watch'; }
85
86    mv k k.tmp
87    # wait for tail to detect the rename
88    grep_timeout 'inaccessible' ||
89      { cleanup_fail 'failed to detect rename'; break; }
90
91    # Note we strace here rather than consuming all available watches
92    # to be more efficient, but more importantly avoid depleting resources.
93    # Note also available resources can currently be tuned with:
94    #  sudo sysctl -w fs.inotify.max_user_watches=$smallish_number
95    # However that impacts all processes for the current user, and also
96    # may not be supported in future, instead being auto scaled to RAM
97    # like the Linux epoll resources were.
98    if test "$i" -gt 1; then
99      strace_re='inotify_rm_watch' retry_delay_ check_strace .1 8 ||
100        { cleanup_fail 'failed to find inotify_rm_watch syscall'; break; }
101    fi
102
103    >out && >strace.out || framework_failure_ 'failed to reset output files'
104done
105
106cleanup_
107
108Exit $fail
109