From 4e2a13b4b6fe5a741d94bc6063f14896d6f25575 Mon Sep 17 00:00:00 2001 From: aweinstock Date: Mon, 2 Mar 2026 21:12:46 -0500 Subject: [PATCH 1/2] uucore: Support parsing real-time signals. This allows `timeout -s`, `kill -s`, and `env --{block,default,ignore}-signal` to recognize and handle signals of the forms `RTMIN`, `RTMIN+n`, `RTMAX`, and `RTMAX-n`, consistent with the behaviour of GNU coreutils. --- src/uucore/src/lib/features/signals.rs | 47 +++++++++++++++++++++++++- tests/by-util/test_kill.rs | 17 +++++++++- tests/by-util/test_timeout.rs | 21 +++++++++++- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 6b89704fcad..1b18a74cbf1 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -400,7 +400,52 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { } let signal_name = signal_name_upcase.trim_start_matches("SIG"); - ALL_SIGNALS.iter().position(|&s| s == signal_name) + #[cfg(any(target_os = "linux", target_os = "freebsd"))] + return try_parse_sigrt(signal_name_or_value) + .or_else(|| ALL_SIGNALS.iter().position(|&s| s == signal_name)); + #[cfg(not(any(target_os = "linux", target_os = "freebsd")))] + return ALL_SIGNALS.iter().position(|&s| s == signal_name); +} + +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +/// Parses signals of the form RTMIN(+[0-9]\+)?|RTMAX(-[0-9]\+)? +fn try_parse_sigrt(signal_name: &str) -> Option { + if crate::os::is_wsl() { + return None; + } + if let Some(rest) = signal_name.strip_prefix("RTMIN") { + if rest.is_empty() { + Some(libc::SIGRTMIN() as usize) + } else if let Some(rest) = rest.strip_prefix("+") + && let Ok(offset) = rest.parse::() + { + let value = libc::SIGRTMIN() as usize + offset; + if value <= libc::SIGRTMAX() as usize { + Some(value) + } else { + None + } + } else { + None + } + } else if let Some(rest) = signal_name.strip_prefix("RTMAX") { + if rest.is_empty() { + Some(libc::SIGRTMAX() as usize) + } else if let Some(rest) = rest.strip_prefix("-") + && let Ok(offset) = rest.parse::() + { + let value = libc::SIGRTMAX() as usize - offset; + if value >= libc::SIGRTMIN() as usize { + Some(value) + } else { + None + } + } else { + None + } + } else { + None + } } /// Returns true if the given number is a valid signal number. diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index c666db3714d..43cf9909fc1 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -2,7 +2,7 @@ // // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore IAMNOTASIGNAL RTMAX RTMIN SIGRTMAX +// spell-checker:ignore IAMNOTASIGNAL RTMAX RTMIN SIGRTMAX SIGRT SIGRTMIN SIGRTMAX use regex::Regex; use std::os::unix::process::ExitStatusExt; use std::process::{Child, Command}; @@ -493,3 +493,18 @@ fn test_kill_signal_only_no_pid() { .fails() .stderr_contains("no process ID specified"); } + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +fn test_kill_with_signal_sigrt() { + if !uucore::os::is_wsl() { + for signal in &["SIGRTMIN", "SIGRTMIN+1", "SIGRTMAX", "SIGRTMAX-1"] { + let target = Target::new(); + new_ucmd!() + .arg("-s") + .arg(signal) + .arg(format!("{}", target.pid())) + .succeeds(); + } + } +} diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 4bbc532e9cc..559b2f9f23c 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore dont +// spell-checker:ignore dont sigrt sigrtmin sigrtmax use rstest::rstest; use std::time::Duration; @@ -286,3 +286,22 @@ fn test_foreground_signal0_kill_after() { .args(&["--foreground", "-s0", "-k.1", ".1", "sleep", "10"]) .fails_with_code(137); } + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +fn test_timeout_sigrt() { + if !uucore::os::is_wsl() { + new_ucmd!() + .args(&["-s", "SIGRTMIN", "0.1", "sleep", "2"]) + .succeeds(); + new_ucmd!() + .args(&["-s", "SIGRTMIN+1", "0.1", "sleep", "2"]) + .succeeds(); + new_ucmd!() + .args(&["-s", "SIGRTMAX", "0.1", "sleep", "2"]) + .succeeds(); + new_ucmd!() + .args(&["-s", "SIGRTMAX-1", "0.1", "sleep", "2"]) + .succeeds(); + } +} From f8e2db33531a1b43f45312298edb43a0559420b7 Mon Sep 17 00:00:00 2001 From: aweinstock Date: Mon, 16 Mar 2026 19:27:18 -0400 Subject: [PATCH 2/2] uucore: Update tests for realtime signals. --- src/uucore/src/lib/features/signals.rs | 8 +++----- tests/by-util/test_kill.rs | 2 +- tests/by-util/test_timeout.rs | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 1b18a74cbf1..38e7be06037 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -410,9 +410,7 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { #[cfg(any(target_os = "linux", target_os = "freebsd"))] /// Parses signals of the form RTMIN(+[0-9]\+)?|RTMAX(-[0-9]\+)? fn try_parse_sigrt(signal_name: &str) -> Option { - if crate::os::is_wsl() { - return None; - } + realtime_signal_bounds()?; if let Some(rest) = signal_name.strip_prefix("RTMIN") { if rest.is_empty() { Some(libc::SIGRTMIN() as usize) @@ -459,7 +457,7 @@ pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> { } #[cfg(any(target_os = "linux", target_os = "android"))] -fn realtime_signal_bounds() -> Option<(usize, usize)> { +pub fn realtime_signal_bounds() -> Option<(usize, usize)> { let rtmin = libc::SIGRTMIN(); let rtmax = libc::SIGRTMAX(); @@ -467,7 +465,7 @@ fn realtime_signal_bounds() -> Option<(usize, usize)> { } #[cfg(not(any(target_os = "linux", target_os = "android")))] -fn realtime_signal_bounds() -> Option<(usize, usize)> { +pub fn realtime_signal_bounds() -> Option<(usize, usize)> { None } diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index 43cf9909fc1..4aa9f8bcfed 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -497,7 +497,7 @@ fn test_kill_signal_only_no_pid() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd"))] fn test_kill_with_signal_sigrt() { - if !uucore::os::is_wsl() { + if !uucore::os::is_wsl() && uucore::signals::realtime_signal_bounds().is_some() { for signal in &["SIGRTMIN", "SIGRTMIN+1", "SIGRTMAX", "SIGRTMAX-1"] { let target = Target::new(); new_ucmd!() diff --git a/tests/by-util/test_timeout.rs b/tests/by-util/test_timeout.rs index 559b2f9f23c..4668a289feb 100644 --- a/tests/by-util/test_timeout.rs +++ b/tests/by-util/test_timeout.rs @@ -290,7 +290,7 @@ fn test_foreground_signal0_kill_after() { #[test] #[cfg(any(target_os = "linux", target_os = "freebsd"))] fn test_timeout_sigrt() { - if !uucore::os::is_wsl() { + if !uucore::os::is_wsl() && uucore::signals::realtime_signal_bounds().is_some() { new_ucmd!() .args(&["-s", "SIGRTMIN", "0.1", "sleep", "2"]) .succeeds();