git: 795153bda6da - stable/14 - timeout(1): Catch all signals and propagate them

From: Konstantin Belousov <kib_at_FreeBSD.org>
Date: Mon, 16 Jun 2025 08:54:07 UTC
The branch stable/14 has been updated by kib:

URL: https://cgit.FreeBSD.org/src/commit/?id=795153bda6dae27ceac4a7298106c00ca706b0ec

commit 795153bda6dae27ceac4a7298106c00ca706b0ec
Author:     Aaron LI <aly@aaronly.me>
AuthorDate: 2025-04-03 01:20:01 +0000
Commit:     Konstantin Belousov <kib@FreeBSD.org>
CommitDate: 2025-06-16 08:51:53 +0000

    timeout(1): Catch all signals and propagate them
    
    (cherry picked from commit 844cef26e810d903e11bf83cc6f6fd2e22a299f1)
---
 bin/timeout/timeout.1 | 22 ++++++++++++
 bin/timeout/timeout.c | 92 +++++++++++++++++++++++++++++++--------------------
 2 files changed, 79 insertions(+), 35 deletions(-)

diff --git a/bin/timeout/timeout.1 b/bin/timeout/timeout.1
index 371a167d19f3..44525daaec59 100644
--- a/bin/timeout/timeout.1
+++ b/bin/timeout/timeout.1
@@ -73,6 +73,28 @@ inherited, except for the signal that will be sent upon timeout,
 which is reset to take the default action and should terminate
 the process.
 .Pp
+If
+.Nm
+receives the
+.Dv SIGALRM
+signal, it will behave as if the time limit has been reached
+and send the specified signal to
+.Ar command .
+For any other signals delivered to
+.Nm ,
+it will propagate them to
+.Ar command ,
+with the exception of
+.Dv SIGKILL
+and
+.Dv SIGSTOP .
+If you want to prevent the
+.Ar command
+from being timed out, send
+.Dv SIGKILL
+to
+.Nm .
+.Pp
 The options are as follows:
 .Bl -tag -width indent
 .It Fl f , Fl -foreground
diff --git a/bin/timeout/timeout.c b/bin/timeout/timeout.c
index 1c4cfa6e017d..6e93e9e2911c 100644
--- a/bin/timeout/timeout.c
+++ b/bin/timeout/timeout.c
@@ -1,6 +1,7 @@
 /*-
  * Copyright (c) 2014 Baptiste Daroussin <bapt@FreeBSD.org>
  * Copyright (c) 2014 Vsevolod Stakhov <vsevolod@FreeBSD.org>
+ * Copyright (c) 2025 Aaron LI <aly@aaronly.me>
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -47,8 +48,10 @@
 #define EXIT_CMD_NOENT	127
 
 static volatile sig_atomic_t sig_chld = 0;
-static volatile sig_atomic_t sig_term = 0;
 static volatile sig_atomic_t sig_alrm = 0;
+static volatile sig_atomic_t sig_term = 0; /* signal to terminate children */
+static volatile sig_atomic_t sig_other = 0; /* signal to propagate */
+static int killsig = SIGTERM; /* signal to kill children */
 static const char *command = NULL;
 static bool verbose = false;
 
@@ -137,19 +140,46 @@ parse_signal(const char *str)
 static void
 sig_handler(int signo)
 {
-	switch (signo) {
-	case SIGINT:
-	case SIGHUP:
-	case SIGQUIT:
-	case SIGTERM:
+	if (signo == killsig) {
 		sig_term = signo;
-		break;
+		return;
+	}
+
+	switch (signo) {
 	case SIGCHLD:
 		sig_chld = 1;
 		break;
 	case SIGALRM:
 		sig_alrm = 1;
 		break;
+	case SIGHUP:
+	case SIGINT:
+	case SIGQUIT:
+	case SIGILL:
+	case SIGTRAP:
+	case SIGABRT:
+	case SIGEMT:
+	case SIGFPE:
+	case SIGBUS:
+	case SIGSEGV:
+	case SIGSYS:
+	case SIGPIPE:
+	case SIGTERM:
+	case SIGXCPU:
+	case SIGXFSZ:
+	case SIGVTALRM:
+	case SIGPROF:
+	case SIGUSR1:
+	case SIGUSR2:
+		/*
+		 * Signals with default action to terminate the process.
+		 * See the sigaction(2) man page.
+		 */
+		sig_term = signo;
+		break;
+	default:
+		sig_other = signo;
+		break;
 	}
 }
 
@@ -214,8 +244,6 @@ main(int argc, char **argv)
 {
 	int ch, status, sig;
 	int pstat = 0;
-	int killsig = SIGTERM;
-	size_t i;
 	pid_t pid, cpid;
 	double first_kill;
 	double second_kill = 0;
@@ -225,17 +253,8 @@ main(int argc, char **argv)
 	bool do_second_kill = false;
 	bool child_done = false;
 	sigset_t zeromask, allmask, oldmask;
-	struct sigaction signals;
+	struct sigaction sa;
 	struct procctl_reaper_status info;
-	int signums[] = {
-		-1,
-		SIGTERM,
-		SIGINT,
-		SIGHUP,
-		SIGCHLD,
-		SIGALRM,
-		SIGQUIT,
-	};
 
 	const char optstr[] = "+fhk:ps:v";
 	const struct option longopts[] = {
@@ -316,22 +335,17 @@ main(int argc, char **argv)
 
 	/* parent continues here */
 
-	memset(&signals, 0, sizeof(signals));
-	sigemptyset(&signals.sa_mask);
-
-	if (killsig != SIGKILL && killsig != SIGSTOP)
-		signums[0] = killsig;
-
-	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++)
-		sigaddset(&signals.sa_mask, signums[i]);
-
-	signals.sa_handler = sig_handler;
-	signals.sa_flags = SA_RESTART;
-
-	for (i = 0; i < sizeof(signums) / sizeof(signums[0]); i++) {
-		if (signums[i] > 0 &&
-		    sigaction(signums[i], &signals, NULL) == -1)
-			err(EXIT_FAILURE, "sigaction()");
+	/* Catch all signals in order to propagate them. */
+	memset(&sa, 0, sizeof(sa));
+	sigfillset(&sa.sa_mask);
+	sa.sa_handler = sig_handler;
+	sa.sa_flags = SA_RESTART;
+	for (sig = 1; sig < sys_nsig; sig++) {
+		if (sig == SIGKILL || sig == SIGSTOP || sig == SIGCONT ||
+		    sig == SIGTTIN || sig == SIGTTOU)
+			continue;
+		if (sigaction(sig, &sa, NULL) == -1)
+			err(EXIT_FAILURE, "sigaction(%d)", sig);
 	}
 
 	/* Don't stop if background child needs TTY */
@@ -399,6 +413,14 @@ main(int argc, char **argv)
 				do_second_kill = false;
 				killsig = SIGKILL;
 			}
+
+		} else if (sig_other) {
+			/* Propagate any other signals. */
+			sig = sig_other;
+			sig_other = 0;
+			logv("received signal %s(%d)", sys_signame[sig], sig);
+
+			send_sig(pid, sig, foreground);
 		}
 	}