kill: fix GNU compatibility tests for RTMIN and RTMAX (#11224)

* kill: list Linux realtime signals correctly

Add list-specific signal helpers for RTMIN/RTMAX and unnamed
signal numbers, and use them in kill and env range iteration.
Keep send-oriented signal parsing unchanged for signal delivery.

* document low-byte signal decoding in list mode
This commit is contained in:
karanabe
2026-03-08 17:25:53 +09:00
committed by GitHub
parent f00a050106
commit 005cf50a1a
4 changed files with 228 additions and 27 deletions
+2 -2
View File
@@ -43,7 +43,7 @@ use uucore::display::{Quotable, print_all_env_vars};
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
use uucore::line_ending::LineEnding;
#[cfg(unix)]
use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value};
use uucore::signals::{signal_by_name_or_value, signal_name_by_value, signal_number_upper_bound};
use uucore::translate;
use uucore::{format_usage, show_warning};
@@ -216,7 +216,7 @@ impl SignalRequest {
f(sig, true)?;
}
if self.apply_all {
for sig_value in 1..ALL_SIGNALS.len() {
for sig_value in 1..=signal_number_upper_bound() {
if self.signals.contains(&sig_value) {
continue;
}
+39 -24
View File
@@ -13,7 +13,10 @@ use uucore::display::Quotable;
use uucore::error::{FromIo, UResult, USimpleError};
use uucore::translate;
use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value};
use uucore::signals::{
signal_by_name_or_value, signal_list_name_by_value, signal_list_value_by_name_or_number,
signal_name_by_value, signal_number_upper_bound,
};
use uucore::{format_usage, show};
// When the -l option is selected, the program displays the type of signal related to a certain
@@ -159,34 +162,44 @@ fn handle_obsolete(args: &mut Vec<String>) -> Option<usize> {
}
fn table() {
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
println!("{idx: >#2} {signal}");
for signal_value in 0..=signal_number_upper_bound() {
if let Some(signal_name) = signal_list_name_by_value(signal_value) {
println!("{signal_value: >#2} {signal_name}");
}
}
}
fn print_signal(signal_name_or_value: &str) -> UResult<()> {
// Closure used to track the last 8 bits of the signal value
// when the -l option is passed only the lower 8 bits are important
// or the value is in range [128, 159]
// Example: kill -l 143 => TERM because 143 = 15 + 128
// Example: kill -l 2304 => EXIT
let lower_8_bits = |x: usize| x & 0xff;
let option_num_parse = signal_name_or_value.parse::<usize>().ok();
fn normalize_list_signal_value(signal_value: usize) -> Option<usize> {
// `kill -l` also accepts wait-status-like values and decodes the signal
// number from the low 8 bits.
let lower_8_bits = signal_value & 0xff;
if lower_8_bits <= signal_number_upper_bound() {
return Some(lower_8_bits);
}
for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
if signal.eq_ignore_ascii_case(signal_name_or_value)
|| format!("SIG{signal}").eq_ignore_ascii_case(signal_name_or_value)
{
println!("{value}");
return Ok(());
} else if signal_name_or_value == value.to_string()
|| option_num_parse.is_some_and(|signal_value| lower_8_bits(signal_value) == value)
|| option_num_parse.is_some_and(|signal_value| signal_value == value + OFFSET)
{
println!("{signal}");
signal_value
.checked_sub(OFFSET)
.filter(|value| *value <= signal_number_upper_bound())
}
fn print_signal(signal_name_or_value: &str) -> UResult<()> {
if let Ok(signal_value) = signal_name_or_value.parse::<usize>() {
// GNU kill accepts plain signal numbers, values masked to the low 8 bits,
// and exit statuses that encode `128 + signal`.
if let Some(signal_value) = normalize_list_signal_value(signal_value) {
println!(
"{}",
signal_list_name_by_value(signal_value).unwrap_or_else(|| signal_value.to_string())
);
return Ok(());
}
}
if let Some(signal_value) = signal_list_value_by_name_or_number(signal_name_or_value) {
println!("{signal_value}");
return Ok(());
}
Err(USimpleError::new(
1,
translate!("kill-error-invalid-signal", "signal" => signal_name_or_value.quote()),
@@ -194,8 +207,10 @@ fn print_signal(signal_name_or_value: &str) -> UResult<()> {
}
fn print_signals() {
for signal in ALL_SIGNALS {
println!("{signal}");
for signal_value in 0..=signal_number_upper_bound() {
if let Some(signal_name) = signal_list_name_by_value(signal_value) {
println!("{signal_name}");
}
}
}
+112
View File
@@ -12,6 +12,8 @@
#[cfg(unix)]
use nix::errno::Errno;
#[cfg(any(target_os = "linux", target_os = "android"))]
use nix::libc;
#[cfg(unix)]
use nix::sys::signal::{
SaFlags, SigAction, SigHandler, SigHandler::SigDfl, SigHandler::SigIgn, SigSet, Signal,
@@ -411,6 +413,63 @@ pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> {
ALL_SIGNALS.get(signal_value).copied()
}
#[cfg(any(target_os = "linux", target_os = "android"))]
fn realtime_signal_bounds() -> Option<(usize, usize)> {
let rtmin = libc::SIGRTMIN();
let rtmax = libc::SIGRTMAX();
(0 < rtmin && rtmin <= rtmax).then_some((rtmin as usize, rtmax as usize))
}
#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn realtime_signal_bounds() -> Option<(usize, usize)> {
None
}
/// Returns the largest signal number that list-style interfaces should accept.
pub fn signal_number_upper_bound() -> usize {
let base = ALL_SIGNALS.len() - 1;
realtime_signal_bounds().map_or(base, |(_, rtmax)| rtmax.max(base))
}
/// Returns the signal name for list-style interfaces.
pub fn signal_list_name_by_value(signal_value: usize) -> Option<String> {
if let Some(signal_name) = signal_name_by_value(signal_value) {
return Some(signal_name.to_string());
}
realtime_signal_bounds().and_then(|(rtmin, rtmax)| {
if signal_value == rtmin {
Some("RTMIN".to_string())
} else if signal_value == rtmax {
Some("RTMAX".to_string())
} else {
None
}
})
}
/// Returns the signal value for list-style interfaces.
pub fn signal_list_value_by_name_or_number(spec: &str) -> Option<usize> {
let spec_upcase = spec.to_uppercase();
if let Ok(value) = spec_upcase.parse::<usize>() {
return (value <= signal_number_upper_bound()).then_some(value);
}
if let Some(value) = signal_by_name_or_value(&spec_upcase) {
return Some(value);
}
let signal_name = spec_upcase.trim_start_matches("SIG");
realtime_signal_bounds().and_then(|(rtmin, rtmax)| match signal_name {
"RTMIN" => Some(rtmin),
"RTMAX" => Some(rtmax),
_ => None,
})
}
/// Restores SIGPIPE to default behavior (process terminates on broken pipe).
#[cfg(unix)]
pub fn enable_pipe_errors() -> Result<(), Errno> {
@@ -642,3 +701,56 @@ fn name() {
assert_eq!(signal_name_by_value(value), Some(*signal));
}
}
#[test]
fn list_signal_names_match_static_signal_names() {
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
assert_eq!(signal_list_name_by_value(value), Some(signal.to_string()));
}
}
#[test]
fn list_signal_numbers_follow_upper_bound() {
assert_eq!(
signal_list_value_by_name_or_number(&signal_number_upper_bound().to_string()),
Some(signal_number_upper_bound())
);
assert_eq!(
signal_list_value_by_name_or_number(&(signal_number_upper_bound() + 1).to_string()),
None
);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn linux_realtime_signal_upper_bound_includes_rtmax() {
let (_, rtmax) = realtime_signal_bounds().unwrap();
assert!(signal_number_upper_bound() >= rtmax);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn linux_realtime_signal_names_are_listed() {
let (rtmin, rtmax) = realtime_signal_bounds().unwrap();
assert_eq!(signal_list_name_by_value(rtmin), Some("RTMIN".to_string()));
assert_eq!(signal_list_name_by_value(rtmax), Some("RTMAX".to_string()));
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn linux_realtime_signal_names_resolve_to_runtime_values() {
let (rtmin, rtmax) = realtime_signal_bounds().unwrap();
assert_eq!(signal_list_value_by_name_or_number("RTMIN"), Some(rtmin));
assert_eq!(signal_list_value_by_name_or_number("RTMAX"), Some(rtmax));
assert_eq!(signal_list_value_by_name_or_number("SIGRTMIN"), Some(rtmin));
assert_eq!(signal_list_value_by_name_or_number("SIGRTMAX"), Some(rtmax));
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn linux_unnamed_signal_numbers_are_valid_for_lists() {
assert_eq!(signal_list_value_by_name_or_number("32"), Some(32));
assert_eq!(signal_list_value_by_name_or_number("33"), Some(33));
}
+75 -1
View File
@@ -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
// spell-checker:ignore IAMNOTASIGNAL RTMAX RTMIN SIGRTMAX
use regex::Regex;
use std::os::unix::process::ExitStatusExt;
use std::process::{Child, Command};
@@ -67,6 +67,16 @@ fn test_kill_list_all_signals() {
.stdout_contains("EXIT");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_list_contains_realtime_signals() {
new_ucmd!()
.arg("-l")
.succeeds()
.stdout_contains("RTMIN")
.stdout_contains("RTMAX");
}
#[test]
fn test_kill_list_final_new_line() {
let re = Regex::new("\\n$").unwrap();
@@ -85,6 +95,16 @@ fn test_kill_list_all_signals_as_table() {
.stdout_contains("EXIT");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_table_contains_realtime_signals() {
new_ucmd!()
.arg("-t")
.succeeds()
.stdout_contains("RTMIN")
.stdout_contains("RTMAX");
}
#[test]
fn test_kill_table_starts_at_0() {
new_ucmd!()
@@ -118,6 +138,16 @@ fn test_kill_list_one_signal_from_number() {
.stdout_only("KILL\n");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_list_rtmax_from_name() {
new_ucmd!()
.arg("-l")
.arg("RTMAX")
.succeeds()
.stdout_only(format!("{}\n", libc::SIGRTMAX()));
}
#[test]
fn test_kill_list_one_signal_from_invalid_number() {
new_ucmd!()
@@ -385,6 +415,50 @@ fn test_kill_with_list_lower_bits_unrecognized() {
new_ucmd!().arg("-l").arg("384").fails();
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_with_list_unnamed_signal_numbers() {
new_ucmd!()
.arg("-l")
.arg("32")
.succeeds()
.stdout_only("32\n");
new_ucmd!()
.arg("-l")
.arg("33")
.succeeds()
.stdout_only("33\n");
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_kill_with_list_all_signal_numbers_up_to_last_named_signal() {
let last_signal_name = new_ucmd!()
.arg("-l")
.succeeds()
.stdout_str()
.lines()
.last()
.unwrap()
.to_string();
let last_signal_number: usize = new_ucmd!()
.arg("-l")
.arg("--")
.arg(&last_signal_name)
.succeeds()
.stdout_str()
.trim()
.parse()
.unwrap();
let args = std::iter::once(String::from("--"))
.chain((0..=last_signal_number).map(|signal| signal.to_string()))
.collect::<Vec<_>>();
new_ucmd!().arg("-l").args(&args).succeeds();
}
#[test]
fn test_kill_with_signal_and_table() {
let target = Target::new();