* src/timeout.c (main): Save the process ID before creating a child
process. Check if the result of getppid is different than the saved
process ID instead of checking if it is 1.
* tests/timeout/init-parent.sh: New file.
* tests/local.mk (all_tests): Add the new test.
* NEWS: Mention the bug fix. Also mention that this change allows
'timeout' to work when reparented by a subreaper process instead of
init.
---
 NEWS                         |  7 +++++++
 src/timeout.c                | 12 ++++++++++--
 tests/local.mk               |  1 +
 tests/timeout/init-parent.sh | 32 ++++++++++++++++++++++++++++++++
 4 files changed, 50 insertions(+), 2 deletions(-)
 create mode 100755 tests/timeout/init-parent.sh

diff --git a/NEWS b/NEWS
index bbd2eeba0..42d1f2e2e 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,10 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   environment variable.
   [bug introduced in coreutils-8.26]
 
+  'timeout' no longer exits abruptly when its parent is the init process, e.g.,
+  when started by the entrypoint of a container.
+  [bug introduced in coreutils-9.10]
+
 ** New Features
 
   'date --date' now parses dot delimited dd.mm.yy format common in Europe.
@@ -55,6 +59,9 @@ GNU coreutils NEWS                                    -*- 
outline -*-
   'shuf -i' now operates up to two times faster on systems with unlocked stdio
   functions.
 
+  'timeout' now properly detects when it is reparented by a subreaper process 
on
+  Linux instead of init, e.g., the 'systemd --user' process.
+
   'wc -l' now operates up to four and a half times faster on hosts that support
   Neon instructions.
 
diff --git a/src/timeout.c b/src/timeout.c
index 65dedb7fe..94d2c41cb 100644
--- a/src/timeout.c
+++ b/src/timeout.c
@@ -586,6 +586,12 @@ main (int argc, char **argv)
   sigset_t orig_set;
   block_cleanup_and_chld (term_signal, &orig_set);
 
+  /* Get the parent process ID so we can check if we are reparented later.
+     Traditionally processes would be reparented to init.  On Linux a process
+     can be come a subreaper using PR_SET_CHILD_SUBREAPER to fulfill the role
+     of init for child processes, as is done by systemd(1).  */
+  pid_t timeout_pid = getpid ();
+
   /* We cannot use posix_spawn here since the child will have an exit status of
      127 for any failure.  If implemented through fork and exec, posix_spawn
      will return successfully and 'timeout' will have no way to determine if it
@@ -603,8 +609,10 @@ main (int argc, char **argv)
       /* Add protection if the parent dies without signaling child.  */
       prctl (PR_SET_PDEATHSIG, term_signal);
 #endif
-      /* If we're already reparented to init, don't proceed.  */
-      if (getppid () == 1)
+      /* If we're already reparented to init, don't proceed.  Be aware that
+         'timeout' may actually be started by the init process.  E.g., when
+         the shell is the entrypoint to a container.  */
+      if (getppid () != timeout_pid)
         return EXIT_CANCELED;
 
       /* Restore signal mask for child.  */
diff --git a/tests/local.mk b/tests/local.mk
index f9cbf9a5d..b459ccc75 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -484,6 +484,7 @@ all_tests =                                 \
   tests/test/test-diag.pl                      \
   tests/test/test-file.sh                      \
   tests/misc/time-style.sh                     \
+  tests/timeout/init-parent.sh                 \
   tests/timeout/timeout.sh                     \
   tests/timeout/timeout-blocked.pl             \
   tests/timeout/timeout-group.sh               \
diff --git a/tests/timeout/init-parent.sh b/tests/timeout/init-parent.sh
new file mode 100755
index 000000000..765927aab
--- /dev/null
+++ b/tests/timeout/init-parent.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+# Test the behavior of 'timeout' with the init process as its parent.
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+. "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
+print_ver_ timeout
+
+newpids () { unshare --pid --fork --map-root-user "$@"; }
+
+newpids true || skip_ 'unshare --pid --fork --map-root-user is unavailable'
+
+# In coreutils-9.10 'timeout' would exit immediately if its parent was the init
+# process.  This was discovered because Docker entrypoints have a process ID
+# of 1.
+newpids timeout .1 sleep 10
+test $? = 124 || fail=1
+
+Exit $fail
-- 
2.53.0


Reply via email to