Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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;
}
Expand Down
63 changes: 39 additions & 24 deletions src/uu/kill/src/kill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -159,43 +162,55 @@ 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 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);
}

signal_value
.checked_sub(OFFSET)
.filter(|value| *value <= signal_number_upper_bound())
}

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();

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}");
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()),
))
}

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}");
}
}
}

Expand Down
112 changes: 112 additions & 0 deletions src/uucore/src/lib/features/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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> {
Expand Down Expand Up @@ -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));
}
76 changes: 75 additions & 1 deletion tests/by-util/test_kill.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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();
Expand All @@ -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!()
Expand Down Expand Up @@ -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!()
Expand Down Expand Up @@ -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();
Expand Down
Loading