]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
timeout: ensure we terminate command upon abnormal exit
authorPádraig Brady <P@draigBrady.com>
Fri, 28 Nov 2025 17:34:23 +0000 (17:34 +0000)
committerPádraig Brady <P@draigBrady.com>
Sat, 29 Nov 2025 12:17:27 +0000 (12:17 +0000)
* 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.

NEWS
src/timeout.c
tests/timeout/timeout-group.sh

diff --git a/NEWS b/NEWS
index 2319e45175ea0f86df1ce79163ca5a0f9c7739ae..62916cb5534e93a6947342a368cb665eb2847e9a 100644 (file)
--- 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
index 68ddfd5d603ee33107a7a7bb0970c306798bbcdb..7634323d4b131c54add4526a21be480e4d0b5ae7 100644 (file)
@@ -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)
         {
index 81dadcf9d5baffce5ef7a51b87cbe417a984dfd0..ebe299f3039e54ca65415aed6fff33904ffe4802 100755 (executable)
@@ -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