diff --git a/src/uu/env/src/env.rs b/src/uu/env/src/env.rs index 3343544253e..a97a1653432 100644 --- a/src/uu/env/src/env.rs +++ b/src/uu/env/src/env.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 (ToDO) chdir progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction Sigmask sigprocmask elidable +// spell-checker:ignore (ToDO) chdir progname subcommand subcommands unsets setenv putenv spawnp SIGSEGV SIGBUS sigaction Sigmask sigprocmask elidable sigset sigaddset sigemptyset pub mod native_int_str; pub mod split_iterator; @@ -20,10 +20,7 @@ use native_int_str::{ #[cfg(unix)] use nix::libc; #[cfg(unix)] -use nix::sys::signal::{ - SigHandler::{SigDfl, SigIgn}, - SigSet, SigmaskHow, Signal, signal, sigprocmask, -}; +use nix::sys::signal::{SigSet, SigmaskHow, Signal, sigprocmask}; #[cfg(unix)] use nix::unistd::execvp; use std::borrow::Cow; @@ -37,13 +34,18 @@ use std::io; use std::io::Write as _; use std::io::stderr; #[cfg(unix)] +use std::mem::zeroed; +#[cfg(unix)] use std::os::unix::ffi::OsStrExt; 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::{signal_by_name_or_value, signal_name_by_value, signal_number_upper_bound}; +use uucore::signals::{ + realtime_signal_bounds, signal_by_name_or_value, signal_name_by_value, + signal_number_upper_bound, +}; use uucore::translate; use uucore::{format_usage, show_warning}; @@ -294,19 +296,6 @@ fn build_signal_request( Ok(request) } -#[cfg(unix)] -fn signal_from_value(sig_value: usize) -> UResult { - Signal::try_from(sig_value as i32).map_err(|_| { - USimpleError::new( - 125, - translate!( - "env-error-invalid-signal", - "signal" => sig_value.to_string().quote() - ), - ) - }) -} - fn load_config_file(opts: &mut Options) -> UResult<()> { // NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files // ... * but support for actual INI files, although working, is not intended, nor claimed @@ -1064,6 +1053,19 @@ fn apply_specified_env_vars(opts: &Options<'_>) { } } +#[cfg(unix)] +fn signal_is_valid(sig: usize) -> bool { + if Signal::try_from(sig as i32).is_err() { + // nix::sys::signal does not know about real-time signals, so check that + // ourselves. + if let Some((rtmin, rtmax)) = realtime_signal_bounds() { + return sig >= rtmin && sig <= rtmax; + } + } + + true +} + #[cfg(unix)] fn apply_signal_action( request: &SignalRequest, @@ -1072,15 +1074,16 @@ fn apply_signal_action( signal_fn: F, ) -> UResult<()> where - F: Fn(Signal) -> UResult<()>, + F: Fn(usize) -> UResult<()>, { request.for_each_signal(|sig_value, explicit| { // On some platforms ALL_SIGNALS may contain values that are not valid in libc. // Skip those invalid ones and continue (GNU env also ignores undefined signals). - let Ok(sig) = signal_from_value(sig_value) else { + if !signal_is_valid(sig_value) { return Ok(()); - }; - signal_fn(sig)?; + } + + signal_fn(sig_value)?; log.record(sig_value, action_kind, explicit); // Set environment variable to communicate to Rust child processes @@ -1096,9 +1099,14 @@ where } #[cfg(unix)] -fn ignore_signal(sig: Signal) -> UResult<()> { +fn ignore_signal(sig: usize) -> UResult<()> { // SAFETY: This is safe because we write the handler for each signal only once, and therefore "the current handler is the default", as the documentation requires it. - let result = unsafe { signal(sig, SigIgn) }; + // nix::sys::signal::Signal does not cover real-time signals, so we need to call + // libc::signal directly. + let result = unsafe { + let res = libc::signal(sig as libc::c_int, libc::SIG_IGN); + nix::errno::Errno::result(res) + }; if let Err(err) = result { return Err(USimpleError::new( 125, @@ -1109,8 +1117,13 @@ fn ignore_signal(sig: Signal) -> UResult<()> { } #[cfg(unix)] -fn reset_signal(sig: Signal) -> UResult<()> { - let result = unsafe { signal(sig, SigDfl) }; +fn reset_signal(sig: usize) -> UResult<()> { + // nix::sys::signal::Signal does not cover real-time signals, so we need to call + // libc::signal directly. + let result = unsafe { + let res = libc::signal(sig as libc::c_int, libc::SIG_DFL); + nix::errno::Errno::result(res) + }; if let Err(err) = result { return Err(USimpleError::new( 125, @@ -1121,9 +1134,37 @@ fn reset_signal(sig: Signal) -> UResult<()> { } #[cfg(unix)] -fn block_signal(sig: Signal) -> UResult<()> { - let mut set = SigSet::empty(); - set.add(sig); +fn block_signal(sig: usize) -> UResult<()> { + // nix::sys::signal::Signal does not cover real time signals, so we need to build + // sigset_t manually using libc. + let set = unsafe { + let mut sigset: libc::sigset_t = zeroed(); + + if let Err(err) = nix::errno::Errno::result(libc::sigemptyset(&raw mut sigset)) { + return Err(USimpleError::new( + 125, + translate!( + "env-error-failed-set-signal-action", + "signal" => (sig as i32), + "error" => err.desc() + ), + )); + } + if let Err(err) = + nix::errno::Errno::result(libc::sigaddset(&raw mut sigset, sig as libc::c_int)) + { + return Err(USimpleError::new( + 125, + translate!( + "env-error-failed-set-signal-action", + "signal" => (sig as i32), + "error" => err.desc() + ), + )); + } + SigSet::from_sigset_t_unchecked(sigset) + }; + if let Err(err) = sigprocmask(SigmaskHow::SIG_BLOCK, Some(&set), None) { return Err(USimpleError::new( 125, @@ -1148,7 +1189,7 @@ fn list_signal_handling(log: &SignalActionLog) { SignalActionKind::Ignore => "IGNORE", SignalActionKind::Block => "BLOCK", }; - let signal_name = signal_name_by_value(sig_value).unwrap_or("?"); + let signal_name = signal_name_by_value(sig_value).unwrap_or("?".to_string()); eprintln!("{signal_name:<10} ({}): {action}", sig_value as i32); } } diff --git a/src/uu/kill/src/kill.rs b/src/uu/kill/src/kill.rs index e92a47da3b4..6fff054c2fc 100644 --- a/src/uu/kill/src/kill.rs +++ b/src/uu/kill/src/kill.rs @@ -6,8 +6,8 @@ // spell-checker:ignore (ToDO) signalname pids killpg use clap::{Arg, ArgAction, Command}; -use nix::sys::signal::{self, Signal}; -use nix::unistd::Pid; +use nix::errno::Errno; +use nix::libc; use std::io::Error; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, USimpleError}; @@ -15,7 +15,7 @@ use uucore::translate; 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, + signal_number_upper_bound, }; use uucore::{format_usage, show}; @@ -68,18 +68,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { 15_usize //SIGTERM }; - let sig_name = signal_name_by_value(sig); - // Signal does not support converting from EXIT - // Instead, nix::signal::kill expects Option::None to properly handle EXIT - let sig: Option = if sig_name.is_some_and(|name| name == "EXIT") { - None - } else { - let sig = (sig as i32) - .try_into() - .map_err(|e| Error::from_raw_os_error(e as i32))?; - Some(sig) - }; - let pids = parse_pids(&pids_or_signals)?; if pids.is_empty() { Err(USimpleError::new(1, translate!("kill-error-no-process-id"))) @@ -250,9 +238,14 @@ fn parse_pids(pids: &[String]) -> UResult> { .collect() } -fn kill(sig: Option, pids: &[i32]) { +fn kill(sig: usize, pids: &[i32]) { for &pid in pids { - if let Err(e) = signal::kill(Pid::from_raw(pid), sig) { + let result = unsafe { + let res = libc::kill(pid, sig as libc::c_int); + Errno::result(res) + }; + + if let Err(e) = result { show!( Error::from_raw_os_error(e as i32) .map_err_context(|| { translate!("kill-error-sending-signal", "pid" => pid) }) diff --git a/src/uucore/src/lib/features/signals.rs b/src/uucore/src/lib/features/signals.rs index 275f7acd266..c75554857b0 100644 --- a/src/uucore/src/lib/features/signals.rs +++ b/src/uucore/src/lib/features/signals.rs @@ -399,9 +399,6 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { if is_signal(value) { return Some(value); } - return realtime_signal_bounds() - .filter(|&(rtmin, rtmax)| value >= rtmin && value <= rtmax) - .map(|_| value); } let signal_name = signal_name_upcase.trim_start_matches("SIG"); @@ -409,33 +406,74 @@ pub fn signal_by_name_or_value(signal_name_or_value: &str) -> Option { return Some(pos); } - realtime_signal_bounds().and_then(|(rtmin, rtmax)| match signal_name { - "RTMIN" => Some(rtmin), - "RTMAX" => Some(rtmax), - _ => None, + realtime_signal_bounds().and_then(|(rtmin, rtmax)| { + if signal_name.starts_with("RTMIN+") { + if let Ok(n) = signal_name.trim_start_matches("RTMIN+").parse::() { + let value = rtmin + n; + return (value >= rtmin && value <= rtmax).then_some(value); + } + } + + if signal_name.starts_with("RTMAX-") { + if let Ok(n) = signal_name.trim_start_matches("RTMAX-").parse::() { + let value = rtmax - n; + return (value >= rtmin && value <= rtmax).then_some(value); + } + } + + match signal_name { + "RTMIN" => Some(rtmin), + "RTMAX" => Some(rtmax), + _ => None, + } }) } /// Returns true if the given number is a valid signal number. pub fn is_signal(num: usize) -> bool { - num < ALL_SIGNALS.len() + if num < ALL_SIGNALS.len() { + return true; + } + + if let Some((rtmin, rtmax)) = realtime_signal_bounds() { + return num >= rtmin && num <= rtmax; + } + + false } /// Returns the signal name for a given signal value. -pub fn signal_name_by_value(signal_value: usize) -> Option<&'static str> { - ALL_SIGNALS.get(signal_value).copied() +pub fn signal_name_by_value(signal_value: usize) -> Option { + if let Some(name) = ALL_SIGNALS.get(signal_value).copied() { + return Some(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 if signal_value > rtmin && signal_value < rtmax { + let n = signal_value - rtmin; + Some(format!("RTMIN+{n}")) + } else { + None + } + }) } +/// Returns the values of SIGRTMIN and SIGRTMAX if defined on this platform. #[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(); (0 < rtmin && rtmin <= rtmax).then_some((rtmin as usize, rtmax as usize)) } +/// Returns the values of SIGRTMIN and SIGRTMAX if defined on this platform. #[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 } @@ -449,7 +487,7 @@ pub fn signal_number_upper_bound() -> usize { /// Returns the signal name for list-style interfaces. pub fn signal_list_name_by_value(signal_value: usize) -> Option { if let Some(signal_name) = signal_name_by_value(signal_value) { - return Some(signal_name.to_string()); + return Some(signal_name); } realtime_signal_bounds().and_then(|(rtmin, rtmax)| { @@ -719,7 +757,7 @@ fn signal_by_long_name() { #[test] fn name() { for (value, signal) in ALL_SIGNALS.iter().enumerate() { - assert_eq!(signal_name_by_value(value), Some(*signal)); + assert_eq!(signal_name_by_value(value), Some(signal.to_string())); } } @@ -783,11 +821,35 @@ fn linux_realtime_signals_resolve_by_name_or_value() { // By name assert_eq!(signal_by_name_or_value("RTMIN"), Some(rtmin)); + for i in 1..rtmax - rtmin - 1 { + assert_eq!( + signal_by_name_or_value(&format!("RTMIN+{i}")), + Some(rtmin + i) + ); + assert_eq!( + signal_by_name_or_value(&format!("RTMAX-{i}")), + Some(rtmax - i) + ); + } assert_eq!(signal_by_name_or_value("RTMAX"), Some(rtmax)); assert_eq!(signal_by_name_or_value("SIGRTMIN"), Some(rtmin)); + for i in 1..rtmax - rtmin - 1 { + assert_eq!( + signal_by_name_or_value(&format!("SIGRTMIN+{i}")), + Some(rtmin + i) + ); + assert_eq!( + signal_by_name_or_value(&format!("SIGRTMAX-{i}")), + Some(rtmax - i) + ); + } assert_eq!(signal_by_name_or_value("SIGRTMAX"), Some(rtmax)); // By numeric value assert_eq!(signal_by_name_or_value(&rtmin.to_string()), Some(rtmin)); + for i in 1..rtmax - rtmin - 1 { + let value = rtmin + i; + assert_eq!(signal_by_name_or_value(&value.to_string()), Some(value)); + } assert_eq!(signal_by_name_or_value(&rtmax.to_string()), Some(rtmax)); } diff --git a/tests/by-util/test_env.rs b/tests/by-util/test_env.rs index 1a1d504a13a..eafc89753de 100644 --- a/tests/by-util/test_env.rs +++ b/tests/by-util/test_env.rs @@ -945,6 +945,21 @@ fn test_env_block_signal_flag() { .no_stderr(); } +#[test] +#[cfg(any(target_os = "linux", target_os = "android"))] +fn test_env_block_realtime_signal() { + new_ucmd!() + .env("PATH", PATH) + .args(&["--block-signal=SIGRTMIN+7", "true"]) + .succeeds() + .no_stderr(); + new_ucmd!() + .env("PATH", PATH) + .args(&["--block-signal=SIGRTMAX-7", "true"]) + .succeeds() + .no_stderr(); +} + #[test] #[cfg(unix)] fn test_env_list_signal_handling_reports_ignore() { diff --git a/tests/by-util/test_kill.rs b/tests/by-util/test_kill.rs index c666db3714d..a81bfdaa054 100644 --- a/tests/by-util/test_kill.rs +++ b/tests/by-util/test_kill.rs @@ -6,6 +6,8 @@ use regex::Regex; use std::os::unix::process::ExitStatusExt; use std::process::{Child, Command}; +#[cfg(unix)] +use uucore::signals::realtime_signal_bounds; use uutests::new_ucmd; // A child process the tests will try to kill. @@ -493,3 +495,33 @@ fn test_kill_signal_only_no_pid() { .fails() .stderr_contains("no process ID specified"); } + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_kill_with_rtmin_offset() { + let (rtmin, _) = realtime_signal_bounds().unwrap(); + let sig: i32 = (rtmin as i32) + 7; + + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("SIGRTMIN+7") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(sig)); +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_kill_with_rtmax_offset() { + let (_, rtmax) = realtime_signal_bounds().unwrap(); + let sig: i32 = (rtmax as i32) - 7; + + let mut target = Target::new(); + new_ucmd!() + .arg("-s") + .arg("SIGRTMAX-7") + .arg(format!("{}", target.pid())) + .succeeds(); + assert_eq!(target.wait_for_signal(), Some(sig)); +}