mirror of
https://github.com/uutils/coreutils.git
synced 2026-05-06 07:26:38 -04:00
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:
Vendored
+2
-2
@@ -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
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user