'tail' now accepts the --debug option, which is currently used to
detail the --follow implementation being used.
+** Changes in behavior
+
+ 'timeout' now honors ignored signals and will not propagate them. E.g.,
+ timeout(1) in a shell backgrounded job, will not terminate upon receiving
+ SIGINT or SIGQUIT, as these are ignored by default in shell background jobs.
+
* Noteworthy changes in release 9.9 (2025-11-10) [stable]
unblock_signal (SIGCHLD);
}
+/* Filter out signals that were ignored. */
+
+static bool
+sig_needs_handling (int sig, int sigterm)
+{
+ if (sig == SIGALRM || sig == sigterm)
+ return true; /* We can't ignore these. */
+
+ /* Note background jobs in shells have SIGINT and SIGQUIT
+ set to SIG_IGN by default. I.e., those signals will
+ not be propagated through background timeout jobs. */
+ struct sigaction old_sa;
+ sigaction (sig, nullptr, &old_sa);
+ bool ret = old_sa.sa_handler != SIG_IGN;
+ return ret;
+}
+
static void
install_cleanup (int sigterm)
{
more likely to work cleanly. */
for (int i = 0; i < countof (term_sig); i++)
- sigaction (term_sig[i], &sa, nullptr);
+ if (sig_needs_handling (term_sig[i], sigterm))
+ sigaction (term_sig[i], &sa, nullptr);
/* Real Time signals also terminate by default. */
for (int s = SIGRTMIN; s <= SIGRTMAX; s++)
- sigaction (s, &sa, nullptr);
+ if (sig_needs_handling (s, sigterm))
+ sigaction (s, &sa, nullptr);
sigaction (sigterm, &sa, nullptr); /* user specified termination signal. */
}
sigemptyset (&block_set);
for (int i = 0; i < countof (term_sig); i++)
- sigaddset (&block_set, term_sig[i]);
+ if (sig_needs_handling (term_sig[i], sigterm))
+ sigaddset (&block_set, term_sig[i]);
for (int s = SIGRTMIN; s <= SIGRTMAX; s++)
- sigaddset (&block_set, s);
+ if (sig_needs_handling (s, sigterm))
+ sigaddset (&block_set, s);
sigaddset (&block_set, sigterm);
# along with this program. If not, see <https://www.gnu.org/licenses/>.
. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
-print_ver_ timeout
+print_ver_ timeout env
require_trap_signame_
require_kill_group_
# group.sh - separate group
# timeout.cmd - same group as group.sh
#
-# We then send a SIGINT to the "separate group"
-# to simulate what happens when a Ctrl-C
+# We then send a SIGUSR1 to the "separate group"
+# to simulate what happens when a terminating signal
# is sent to the foreground group.
setsid true || skip_ "setsid required to control groups"
printf '%s\n' '#!'"$SHELL" > timeout.cmd || framework_failure_
cat >> timeout.cmd <<\EOF
-trap 'touch int.received; exit' INT
+trap 'touch sig.received; exit' USR1
+trap
touch timeout.running
count=$1
-until test -e int.received || test $count = 0; do
+until test -e sig.received || test $count = 0; do
sleep 1
count=$(expr $count - 1)
done
cat > group.sh <<EOF
#!$SHELL
-trap '' INT
-timeout --foreground 25 ./timeout.cmd 20&
+
+# trap '' ensures this script ignores the signal,
+# so that the 'wait' below is not interrupted.
+# Note this then requires env --default... to reset
+# the signal disposition so that 'timeout' handles it.
+# Alternatively one could use trap ':' USR1
+# and then handle the retry in wait like:
+# while wait; test \$? -gt 128; do :; done
+# Note also INT and QUIT signals are special for backgrounded
+# processes like this in shell as they're auto ignored
+# and can't be reset with trap to any other disposition.
+# Therefore we use the ignored signal method so any
+# termination signal can be used.
+trap '' USR1
+
+env --default-signal=USR1 \
+timeout -v --foreground 25 ./timeout.cmd 20&
wait
+echo group.sh wait returned \$ret
EOF
chmod a+x group.sh
# Wait 6.3s for timeout.cmd to start
retry_delay_ check_timeout_cmd_running .1 6 || fail=1
# Simulate a Ctrl-C to the group to test timely exit
-kill -INT -- -$pid
+kill -USR1 -- -$pid
wait
-test -e int.received || fail=1
+test -e sig.received || fail=1
-rm -f int.received timeout.running
+rm -f sig.received timeout.running
# Ensure cascaded timeouts work
# Note the first timeout must send a signal that
# the second is handling for it to be propagated to the command.
-# SIGINT, SIGTERM, SIGALRM etc. are implicit.
-timeout -sALRM 30 timeout -sINT 25 ./timeout.cmd 20 & pid=$!
+# termination signals are implicitly handled unless ignored.
+timeout -sALRM 30 timeout -sUSR1 25 ./timeout.cmd 20 & pid=$!
# Wait 6.3s for timeout.cmd to start
retry_delay_ check_timeout_cmd_running .1 6 || fail=1
kill -ALRM $pid # trigger the alarm of the first timeout command
ret=$?
test $ret -eq 124 ||
skip_ "timeout returned $ret. SIGALRM not handled?"
-test -e int.received || fail=1
+test -e sig.received || fail=1
end=$(date +%s)
echo 125 > timeout.exp || framework_failure_
{ timeout -v .1 sleep 10 2>&1; echo $? >timeout.status; } | :
compare timeout.exp timeout.status || fail=1
+# Ensure we don't catch/propagate ignored signals
+(trap '' PIPE && timeout 10 yes |:) 2>&1 |
+ grep 'Broken pipe' >/dev/null || fail=1
Exit $fail