Skip to content

Commit 005cf50

Browse files
authored
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
1 parent f00a050 commit 005cf50

4 files changed

Lines changed: 228 additions & 27 deletions

File tree

src/uu/env/src/env.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ use uucore::display::{Quotable, print_all_env_vars};
4343
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
4444
use uucore::line_ending::LineEnding;
4545
#[cfg(unix)]
46-
use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value};
46+
use uucore::signals::{signal_by_name_or_value, signal_name_by_value, signal_number_upper_bound};
4747
use uucore::translate;
4848
use uucore::{format_usage, show_warning};
4949

@@ -216,7 +216,7 @@ impl SignalRequest {
216216
f(sig, true)?;
217217
}
218218
if self.apply_all {
219-
for sig_value in 1..ALL_SIGNALS.len() {
219+
for sig_value in 1..=signal_number_upper_bound() {
220220
if self.signals.contains(&sig_value) {
221221
continue;
222222
}

src/uu/kill/src/kill.rs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ use uucore::display::Quotable;
1313
use uucore::error::{FromIo, UResult, USimpleError};
1414
use uucore::translate;
1515

16-
use uucore::signals::{ALL_SIGNALS, signal_by_name_or_value, signal_name_by_value};
16+
use uucore::signals::{
17+
signal_by_name_or_value, signal_list_name_by_value, signal_list_value_by_name_or_number,
18+
signal_name_by_value, signal_number_upper_bound,
19+
};
1720
use uucore::{format_usage, show};
1821

1922
// When the -l option is selected, the program displays the type of signal related to a certain
@@ -159,43 +162,55 @@ fn handle_obsolete(args: &mut Vec<String>) -> Option<usize> {
159162
}
160163

161164
fn table() {
162-
for (idx, signal) in ALL_SIGNALS.iter().enumerate() {
163-
println!("{idx: >#2} {signal}");
165+
for signal_value in 0..=signal_number_upper_bound() {
166+
if let Some(signal_name) = signal_list_name_by_value(signal_value) {
167+
println!("{signal_value: >#2} {signal_name}");
168+
}
169+
}
170+
}
171+
172+
fn normalize_list_signal_value(signal_value: usize) -> Option<usize> {
173+
// `kill -l` also accepts wait-status-like values and decodes the signal
174+
// number from the low 8 bits.
175+
let lower_8_bits = signal_value & 0xff;
176+
if lower_8_bits <= signal_number_upper_bound() {
177+
return Some(lower_8_bits);
164178
}
179+
180+
signal_value
181+
.checked_sub(OFFSET)
182+
.filter(|value| *value <= signal_number_upper_bound())
165183
}
166184

167185
fn print_signal(signal_name_or_value: &str) -> UResult<()> {
168-
// Closure used to track the last 8 bits of the signal value
169-
// when the -l option is passed only the lower 8 bits are important
170-
// or the value is in range [128, 159]
171-
// Example: kill -l 143 => TERM because 143 = 15 + 128
172-
// Example: kill -l 2304 => EXIT
173-
let lower_8_bits = |x: usize| x & 0xff;
174-
let option_num_parse = signal_name_or_value.parse::<usize>().ok();
175-
176-
for (value, &signal) in ALL_SIGNALS.iter().enumerate() {
177-
if signal.eq_ignore_ascii_case(signal_name_or_value)
178-
|| format!("SIG{signal}").eq_ignore_ascii_case(signal_name_or_value)
179-
{
180-
println!("{value}");
181-
return Ok(());
182-
} else if signal_name_or_value == value.to_string()
183-
|| option_num_parse.is_some_and(|signal_value| lower_8_bits(signal_value) == value)
184-
|| option_num_parse.is_some_and(|signal_value| signal_value == value + OFFSET)
185-
{
186-
println!("{signal}");
186+
if let Ok(signal_value) = signal_name_or_value.parse::<usize>() {
187+
// GNU kill accepts plain signal numbers, values masked to the low 8 bits,
188+
// and exit statuses that encode `128 + signal`.
189+
if let Some(signal_value) = normalize_list_signal_value(signal_value) {
190+
println!(
191+
"{}",
192+
signal_list_name_by_value(signal_value).unwrap_or_else(|| signal_value.to_string())
193+
);
187194
return Ok(());
188195
}
189196
}
197+
198+
if let Some(signal_value) = signal_list_value_by_name_or_number(signal_name_or_value) {
199+
println!("{signal_value}");
200+
return Ok(());
201+
}
202+
190203
Err(USimpleError::new(
191204
1,
192205
translate!("kill-error-invalid-signal", "signal" => signal_name_or_value.quote()),
193206
))
194207
}
195208

196209
fn print_signals() {
197-
for signal in ALL_SIGNALS {
198-
println!("{signal}");
210+
for signal_value in 0..=signal_number_upper_bound() {
211+
if let Some(signal_name) = signal_list_name_by_value(signal_value) {
212+
println!("{signal_name}");
213+
}
199214
}
200215
}
201216

src/uucore/src/lib/features/signals.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
1313
#[cfg(unix)]
1414
use nix::errno::Errno;
15+
#[cfg(any(target_os = "linux", target_os = "android"))]
16+
use nix::libc;
1517
#[cfg(unix)]
1618
use nix::sys::signal::{
1719
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> {
411413
ALL_SIGNALS.get(signal_value).copied()
412414
}
413415

416+
#[cfg(any(target_os = "linux", target_os = "android"))]
417+
fn realtime_signal_bounds() -> Option<(usize, usize)> {
418+
let rtmin = libc::SIGRTMIN();
419+
let rtmax = libc::SIGRTMAX();
420+
421+
(0 < rtmin && rtmin <= rtmax).then_some((rtmin as usize, rtmax as usize))
422+
}
423+
424+
#[cfg(not(any(target_os = "linux", target_os = "android")))]
425+
fn realtime_signal_bounds() -> Option<(usize, usize)> {
426+
None
427+
}
428+
429+
/// Returns the largest signal number that list-style interfaces should accept.
430+
pub fn signal_number_upper_bound() -> usize {
431+
let base = ALL_SIGNALS.len() - 1;
432+
433+
realtime_signal_bounds().map_or(base, |(_, rtmax)| rtmax.max(base))
434+
}
435+
436+
/// Returns the signal name for list-style interfaces.
437+
pub fn signal_list_name_by_value(signal_value: usize) -> Option<String> {
438+
if let Some(signal_name) = signal_name_by_value(signal_value) {
439+
return Some(signal_name.to_string());
440+
}
441+
442+
realtime_signal_bounds().and_then(|(rtmin, rtmax)| {
443+
if signal_value == rtmin {
444+
Some("RTMIN".to_string())
445+
} else if signal_value == rtmax {
446+
Some("RTMAX".to_string())
447+
} else {
448+
None
449+
}
450+
})
451+
}
452+
453+
/// Returns the signal value for list-style interfaces.
454+
pub fn signal_list_value_by_name_or_number(spec: &str) -> Option<usize> {
455+
let spec_upcase = spec.to_uppercase();
456+
457+
if let Ok(value) = spec_upcase.parse::<usize>() {
458+
return (value <= signal_number_upper_bound()).then_some(value);
459+
}
460+
461+
if let Some(value) = signal_by_name_or_value(&spec_upcase) {
462+
return Some(value);
463+
}
464+
465+
let signal_name = spec_upcase.trim_start_matches("SIG");
466+
realtime_signal_bounds().and_then(|(rtmin, rtmax)| match signal_name {
467+
"RTMIN" => Some(rtmin),
468+
"RTMAX" => Some(rtmax),
469+
_ => None,
470+
})
471+
}
472+
414473
/// Restores SIGPIPE to default behavior (process terminates on broken pipe).
415474
#[cfg(unix)]
416475
pub fn enable_pipe_errors() -> Result<(), Errno> {
@@ -642,3 +701,56 @@ fn name() {
642701
assert_eq!(signal_name_by_value(value), Some(*signal));
643702
}
644703
}
704+
705+
#[test]
706+
fn list_signal_names_match_static_signal_names() {
707+
for (value, signal) in ALL_SIGNALS.iter().enumerate() {
708+
assert_eq!(signal_list_name_by_value(value), Some(signal.to_string()));
709+
}
710+
}
711+
712+
#[test]
713+
fn list_signal_numbers_follow_upper_bound() {
714+
assert_eq!(
715+
signal_list_value_by_name_or_number(&signal_number_upper_bound().to_string()),
716+
Some(signal_number_upper_bound())
717+
);
718+
assert_eq!(
719+
signal_list_value_by_name_or_number(&(signal_number_upper_bound() + 1).to_string()),
720+
None
721+
);
722+
}
723+
724+
#[cfg(any(target_os = "linux", target_os = "android"))]
725+
#[test]
726+
fn linux_realtime_signal_upper_bound_includes_rtmax() {
727+
let (_, rtmax) = realtime_signal_bounds().unwrap();
728+
assert!(signal_number_upper_bound() >= rtmax);
729+
}
730+
731+
#[cfg(any(target_os = "linux", target_os = "android"))]
732+
#[test]
733+
fn linux_realtime_signal_names_are_listed() {
734+
let (rtmin, rtmax) = realtime_signal_bounds().unwrap();
735+
736+
assert_eq!(signal_list_name_by_value(rtmin), Some("RTMIN".to_string()));
737+
assert_eq!(signal_list_name_by_value(rtmax), Some("RTMAX".to_string()));
738+
}
739+
740+
#[cfg(any(target_os = "linux", target_os = "android"))]
741+
#[test]
742+
fn linux_realtime_signal_names_resolve_to_runtime_values() {
743+
let (rtmin, rtmax) = realtime_signal_bounds().unwrap();
744+
745+
assert_eq!(signal_list_value_by_name_or_number("RTMIN"), Some(rtmin));
746+
assert_eq!(signal_list_value_by_name_or_number("RTMAX"), Some(rtmax));
747+
assert_eq!(signal_list_value_by_name_or_number("SIGRTMIN"), Some(rtmin));
748+
assert_eq!(signal_list_value_by_name_or_number("SIGRTMAX"), Some(rtmax));
749+
}
750+
751+
#[cfg(any(target_os = "linux", target_os = "android"))]
752+
#[test]
753+
fn linux_unnamed_signal_numbers_are_valid_for_lists() {
754+
assert_eq!(signal_list_value_by_name_or_number("32"), Some(32));
755+
assert_eq!(signal_list_value_by_name_or_number("33"), Some(33));
756+
}

tests/by-util/test_kill.rs

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
5-
// spell-checker:ignore IAMNOTASIGNAL
5+
// spell-checker:ignore IAMNOTASIGNAL RTMAX RTMIN SIGRTMAX
66
use regex::Regex;
77
use std::os::unix::process::ExitStatusExt;
88
use std::process::{Child, Command};
@@ -67,6 +67,16 @@ fn test_kill_list_all_signals() {
6767
.stdout_contains("EXIT");
6868
}
6969

70+
#[cfg(any(target_os = "linux", target_os = "android"))]
71+
#[test]
72+
fn test_kill_list_contains_realtime_signals() {
73+
new_ucmd!()
74+
.arg("-l")
75+
.succeeds()
76+
.stdout_contains("RTMIN")
77+
.stdout_contains("RTMAX");
78+
}
79+
7080
#[test]
7181
fn test_kill_list_final_new_line() {
7282
let re = Regex::new("\\n$").unwrap();
@@ -85,6 +95,16 @@ fn test_kill_list_all_signals_as_table() {
8595
.stdout_contains("EXIT");
8696
}
8797

98+
#[cfg(any(target_os = "linux", target_os = "android"))]
99+
#[test]
100+
fn test_kill_table_contains_realtime_signals() {
101+
new_ucmd!()
102+
.arg("-t")
103+
.succeeds()
104+
.stdout_contains("RTMIN")
105+
.stdout_contains("RTMAX");
106+
}
107+
88108
#[test]
89109
fn test_kill_table_starts_at_0() {
90110
new_ucmd!()
@@ -118,6 +138,16 @@ fn test_kill_list_one_signal_from_number() {
118138
.stdout_only("KILL\n");
119139
}
120140

141+
#[cfg(any(target_os = "linux", target_os = "android"))]
142+
#[test]
143+
fn test_kill_list_rtmax_from_name() {
144+
new_ucmd!()
145+
.arg("-l")
146+
.arg("RTMAX")
147+
.succeeds()
148+
.stdout_only(format!("{}\n", libc::SIGRTMAX()));
149+
}
150+
121151
#[test]
122152
fn test_kill_list_one_signal_from_invalid_number() {
123153
new_ucmd!()
@@ -385,6 +415,50 @@ fn test_kill_with_list_lower_bits_unrecognized() {
385415
new_ucmd!().arg("-l").arg("384").fails();
386416
}
387417

418+
#[cfg(any(target_os = "linux", target_os = "android"))]
419+
#[test]
420+
fn test_kill_with_list_unnamed_signal_numbers() {
421+
new_ucmd!()
422+
.arg("-l")
423+
.arg("32")
424+
.succeeds()
425+
.stdout_only("32\n");
426+
new_ucmd!()
427+
.arg("-l")
428+
.arg("33")
429+
.succeeds()
430+
.stdout_only("33\n");
431+
}
432+
433+
#[cfg(any(target_os = "linux", target_os = "android"))]
434+
#[test]
435+
fn test_kill_with_list_all_signal_numbers_up_to_last_named_signal() {
436+
let last_signal_name = new_ucmd!()
437+
.arg("-l")
438+
.succeeds()
439+
.stdout_str()
440+
.lines()
441+
.last()
442+
.unwrap()
443+
.to_string();
444+
445+
let last_signal_number: usize = new_ucmd!()
446+
.arg("-l")
447+
.arg("--")
448+
.arg(&last_signal_name)
449+
.succeeds()
450+
.stdout_str()
451+
.trim()
452+
.parse()
453+
.unwrap();
454+
455+
let args = std::iter::once(String::from("--"))
456+
.chain((0..=last_signal_number).map(|signal| signal.to_string()))
457+
.collect::<Vec<_>>();
458+
459+
new_ucmd!().arg("-l").args(&args).succeeds();
460+
}
461+
388462
#[test]
389463
fn test_kill_with_signal_and_table() {
390464
let target = Target::new();

0 commit comments

Comments
 (0)