From: Pádraig Brady Date: Fri, 21 Nov 2025 11:43:35 +0000 (+0000) Subject: timeout: honor ignored signal dispositions X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8c24619334114a2524c8f87dd831fa0912560555;p=thirdparty%2Fcoreutils.git timeout: honor ignored signal dispositions This behavior was depended on in our trap_sigpipe_or_skip_ helper, and now that we're handling all terminating signals, we should consistently honor their ignored signal dispositions. * NEWS: Mention the change in behavior, especially in regard to shell background jobs. * src/timeout.c (sig_needs_handling): A new helper that return TRUE, for --signal, SIG_ALRM, or non ignored signals. (cleanup_install): Filter handled signals with the helper. (block_cleanup_and_chld): Likewise. * tests/timeout/timeout-group.sh: Adjust to use the now required `env --default-signal=...` wrapper to reset (auto) ignored signals. Also change the termination signal from SIGINT to SIGUSR1 to generalize the test signals not specially handled by the shell, and newly handled by timeout(1). * tests/timeout/timeout.sh: Add a test case for SIGPIPE to ensure the ignored signal disposition is honored. --- diff --git a/NEWS b/NEWS index 834c490d15..86dd62c5dd 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,12 @@ GNU coreutils NEWS -*- outline -*- '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] diff --git a/src/timeout.c b/src/timeout.c index 541017cd7e..2e770e9ff5 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -444,6 +444,23 @@ install_sigchld (void) 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) { @@ -454,11 +471,13 @@ 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. */ } @@ -475,10 +494,12 @@ block_cleanup_and_chld (int sigterm, sigset_t *old_set) 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); diff --git a/tests/timeout/timeout-group.sh b/tests/timeout/timeout-group.sh index f73e6fb7c1..81dadcf9d5 100755 --- a/tests/timeout/timeout-group.sh +++ b/tests/timeout/timeout-group.sh @@ -17,7 +17,7 @@ # along with this program. If not, see . . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src -print_ver_ timeout +print_ver_ timeout env require_trap_signame_ require_kill_group_ @@ -26,18 +26,19 @@ 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 @@ -46,9 +47,25 @@ chmod a+x timeout.cmd cat > group.sh < 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