From d0a51c614def6cf4f39fafe8ca7dcef925c4a503 Mon Sep 17 00:00:00 2001 From: =?utf8?q?P=C3=A1draig=20Brady?= Date: Thu, 20 Nov 2025 16:02:43 +0000 Subject: [PATCH] timeout: handle all terminating signals * src/timeout.c (term_sig): A new global list of all signals whose default disposition is to terminate the process. (install_cleanup): Iterate over the TERM_SIG list, rather than installing the handler for a specific subset. (block_cleanup_and_chld): Likewise. * tests/timeout/timeout.sh: Add a test case for SIGPIPE. * NEWS: Mention the bug fix. --- NEWS | 4 +++ src/timeout.c | 66 ++++++++++++++++++++++++++++++++++------ tests/timeout/timeout.sh | 10 +++++- 3 files changed, 69 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index b021ed40a5..834c490d15 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,10 @@ GNU coreutils NEWS -*- outline -*- This also applies to the sha*sum and b2sum utilities. [This bug was present in "the beginning".] + 'timeout' will now propagate all terminating signals to the monitored command. + Previously 'timeout' could have exited and left the monitored command running. + [bug introduced with timeout in coreutils-7.0] + wc now documents its --debug option, currently used to indicate the line count acceleration being used. [bug introduced in coreutils-9.0] diff --git a/src/timeout.c b/src/timeout.c index 3443cab4b9..541017cd7e 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -72,6 +72,14 @@ # define SA_RESTART 0 #endif +#ifndef SIGRTMIN +# define SIGRTMIN 0 +# undef SIGRTMAX +#endif +#ifndef SIGRTMAX +# define SIGRTMAX (SIGRTMIN - 1) +#endif + #define PROGRAM_NAME "timeout" #define AUTHORS proper_name_lite ("Padraig Brady", "P\303\241draig Brady") @@ -191,6 +199,41 @@ chld (MAYBE_UNUSED int sig) { } +static int const term_sig[] = + { + SIGALRM, /* our timeout. */ + SIGINT, /* Ctrl-C at terminal for example. */ + SIGQUIT, /* Ctrl-\ at terminal for example. */ + SIGHUP, /* terminal closed for example. */ + SIGTERM, /* if terminated, stop monitored proc. */ + + SIGPIPE, SIGUSR1, SIGUSR2, + SIGILL, SIGTRAP, SIGABRT, SIGBUS, SIGFPE, SIGSEGV, +#ifdef SIGXCPU + SIGXCPU, +#endif +#ifdef SIGXFSZ + SIGXFSZ, +#endif +#ifdef SIGSYS + SIGSYS, +#endif +#ifdef SIGVTALRM + SIGVTALRM, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPWR + SIGPWR, +#endif +#ifdef SIGSTKFLT + SIGSTKFLT, +#endif + }; static void cleanup (int sig) @@ -410,11 +453,13 @@ install_cleanup (int sigterm) sa.sa_flags = SA_RESTART; /* Restart syscalls if possible, as that's more likely to work cleanly. */ - sigaction (SIGALRM, &sa, nullptr); /* our timeout. */ - sigaction (SIGINT, &sa, nullptr); /* Ctrl-C at terminal for example. */ - sigaction (SIGQUIT, &sa, nullptr); /* Ctrl-\ at terminal for example. */ - sigaction (SIGHUP, &sa, nullptr); /* terminal closed for example. */ - sigaction (SIGTERM, &sa, nullptr); /* if killed, stop monitored proc. */ + for (int i = 0; i < countof (term_sig); i++) + sigaction (term_sig[i], &sa, nullptr); + + /* Real Time signals also terminate by default. */ + for (int s = SIGRTMIN; s <= SIGRTMAX; s++) + sigaction (s, &sa, nullptr); + sigaction (sigterm, &sa, nullptr); /* user specified termination signal. */ } @@ -429,11 +474,12 @@ block_cleanup_and_chld (int sigterm, sigset_t *old_set) sigset_t block_set; sigemptyset (&block_set); - sigaddset (&block_set, SIGALRM); - sigaddset (&block_set, SIGINT); - sigaddset (&block_set, SIGQUIT); - sigaddset (&block_set, SIGHUP); - sigaddset (&block_set, SIGTERM); + for (int i = 0; i < countof (term_sig); i++) + sigaddset (&block_set, term_sig[i]); + + for (int s = SIGRTMIN; s <= SIGRTMAX; s++) + sigaddset (&block_set, s); + sigaddset (&block_set, sigterm); sigaddset (&block_set, SIGCHLD); diff --git a/tests/timeout/timeout.sh b/tests/timeout/timeout.sh index e3defdd4e2..d799cbfd96 100755 --- a/tests/timeout/timeout.sh +++ b/tests/timeout/timeout.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 kill require_trap_signame_ # no timeout @@ -71,4 +71,12 @@ for opt in -v --verbose; do compare exp err || fail=1 done +# Ensure we propagate all terminating signals. +# Specifically here we're testing that SIGPIPE is handled. +# I.e., that we're not killed by the SIGPIPE (and leave the sleep running). +# timeout would exit with 141 usually if SIGPIPE wasn't being handled. +echo 125 > timeout.exp || framework_failure_ +{ timeout -v .1 sleep 10 2>&1; echo $? >timeout.status; } | : +compare timeout.exp timeout.status || fail=1 + Exit $fail -- 2.47.3