From: Pádraig Brady
Date: Fri, 28 Nov 2025 17:34:23 +0000 (+0000) Subject: timeout: ensure we terminate command upon abnormal exit X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=fc5c7b2e47fbe55034f4c8aa2f67caabcc5933dd;p=thirdparty%2Fcoreutils.git timeout: ensure we terminate command upon abnormal exit * src/timeout.c (main): Use PR_SET_PDEATHSIG to ensure the child is terminated even if the parent terminates abnormally. * tests/timeout/timeout-group.sh: Add a case to ensure sending SIGKILL results in the termination of the monitored command. * NEWS: Mention the improvement. --- diff --git a/NEWS b/NEWS index 2319e45175..62916cb553 100644 --- a/NEWS +++ b/NEWS @@ -46,6 +46,9 @@ GNU coreutils NEWS -*- outline -*- csplit, ls, and sort, now handle a more complete set of terminating signals. + 'timeout' on Linux will always terminate the child in the case where the + timeout process itself dies, like when it receives a KILL signal for example. + ** Build-related 'kill' and 'uptime' are no longer built by default. These programs can be diff --git a/src/timeout.c b/src/timeout.c index 68ddfd5d60..7634323d4b 100644 --- a/src/timeout.c +++ b/src/timeout.c @@ -592,6 +592,14 @@ main (int argc, char **argv) } else if (monitored_pid == 0) /* child */ { +#if HAVE_PRCTL + /* Add protection if the parent dies without signalling child. */ + prctl (PR_SET_PDEATHSIG, term_signal); +#endif + /* If we're already reparented to init, don't proceed. */ + if (getppid () == 1) + return EXIT_CANCELED; + /* Restore signal mask for child. */ if (sigprocmask (SIG_SETMASK, &orig_set, nullptr) != 0) { diff --git a/tests/timeout/timeout-group.sh b/tests/timeout/timeout-group.sh index 81dadcf9d5..ebe299f303 100755 --- a/tests/timeout/timeout-group.sh +++ b/tests/timeout/timeout-group.sh @@ -76,6 +76,13 @@ check_timeout_cmd_running() { sleep $delay; return 1; } } +check_timeout_cmd_exiting() +{ + local delay="$1" + test -e sig.received || + { sleep $delay; return 1; } +} + # Terminate any background processes cleanup_() { kill $pid 2>/dev/null && wait $pid; } @@ -88,9 +95,20 @@ retry_delay_ check_timeout_cmd_running .1 6 || fail=1 kill -USR1 -- -$pid wait test -e sig.received || fail=1 - rm -f sig.received timeout.running +# On Linux ensure we kill the monitored command +# even if we're terminated abnormally (e.g., get SIGKILL). +if grep '^#define HAVE_PRCTL 1' "$CONFIG_HEADER" >/dev/null; then + 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 -KILL -- $pid + wait + # Wait 6.3s for timeout.cmd to exit + retry_delay_ check_timeout_cmd_exiting .1 6 || fail=1 + rm -f sig.received timeout.running +fi # Ensure cascaded timeouts work # or more generally, ensure we timeout