* 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