Skip to content
Closed
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
3 changes: 3 additions & 0 deletions src/uu/env/locales/en-US.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ env-help-debug = print verbose information for each processing step
env-help-split-string = process and split S into separate arguments; used to pass multiple arguments on shebang lines
env-help-argv0 = Override the zeroth argument passed to the command being executed. Without this option a default value of `command` is used.
env-help-ignore-signal = set handling of SIG signal(s) to do nothing
env-help-default-signal = set handling of SIG signal(s) to the default
env-help-block-signal = block delivery of SIG signal(s) to COMMAND
env-help-list-signal-handling = list non default signal handling to stderr

# Error messages
env-error-missing-closing-quote = no terminating quote in -S string at position { $position } for quote '{ $quote }'
Expand Down
179 changes: 152 additions & 27 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ use ini::Ini;
use native_int_str::{
Convert, NCvt, NativeIntStr, NativeIntString, NativeStr, from_native_int_representation_owned,
};
#[cfg(unix)]
use nix::libc;
#[cfg(unix)]
use nix::sys::signal::{SigHandler::SigIgn, Signal, signal};
use std::borrow::Cow;
use std::env;
use std::ffi::{OsStr, OsString};
Expand All @@ -29,12 +25,15 @@ use std::io::{self, Write};
use std::os::unix::ffi::OsStrExt;
#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(unix)]
use uucore::signals::{
SIGKILL, SIGSTOP, SignalStatus, block_signals, get_signal_status, ignore_signal, reset_signal,
signal_by_name_or_value, signal_name_by_value,
};

use uucore::display::Quotable;
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
use uucore::line_ending::LineEnding;
#[cfg(unix)]
use uucore::signals::signal_by_name_or_value;
use uucore::translate;
use uucore::{format_usage, show_warning};

Expand Down Expand Up @@ -84,6 +83,9 @@ mod options {
pub const SPLIT_STRING: &str = "split-string";
pub const ARGV0: &str = "argv0";
pub const IGNORE_SIGNAL: &str = "ignore-signal";
pub const DEFAULT_SIGNAL: &str = "default-signal";
pub const BLOCK_SIGNAL: &str = "block-signal";
pub const LIST_SIGNAL_HANDLING: &str = "list-signal-handling";
}

struct Options<'a> {
Expand All @@ -97,6 +99,12 @@ struct Options<'a> {
argv0: Option<&'a OsStr>,
#[cfg(unix)]
ignore_signal: Vec<usize>,
#[cfg(unix)]
default_signal: Vec<usize>,
#[cfg(unix)]
block_signal: Vec<usize>,
#[cfg(unix)]
list_signal_handling: bool,
}

/// print `name=value` env pairs on screen
Expand Down Expand Up @@ -155,11 +163,11 @@ fn parse_signal_value(signal_name: &str) -> UResult<usize> {
}

#[cfg(unix)]
fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
fn parse_signal_opt(signal_vec: &mut Vec<usize>, opt: &OsStr) -> UResult<()> {
if opt.is_empty() {
return Ok(());
}
let signals: Vec<&'a OsStr> = opt
let signals: Vec<&OsStr> = opt
.as_bytes()
.split(|&b| b == b',')
.map(OsStr::from_bytes)
Expand All @@ -179,14 +187,35 @@ fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
));
};
let sig_val = parse_signal_value(sig_str)?;
if !opts.ignore_signal.contains(&sig_val) {
opts.ignore_signal.push(sig_val);
if !signal_vec.contains(&sig_val) {
signal_vec.push(sig_val);
}
}

Ok(())
}

/// Parse signal option that can be empty (meaning all signals)
#[cfg(unix)]
fn parse_signal_opt_or_all(signal_vec: &mut Vec<usize>, opt: &OsStr) -> UResult<()> {
if opt.is_empty() {
// Empty means all signals - add all valid signal numbers (1-31 typically)
// Skip SIGKILL and SIGSTOP which cannot be caught or ignored
let sigkill = SIGKILL as usize;
let sigstop = SIGSTOP as usize;
for sig_val in 1..32 {
if sig_val == sigkill || sig_val == sigstop {
continue; // SIGKILL and SIGSTOP cannot be modified
}
if !signal_vec.contains(&sig_val) {
signal_vec.push(sig_val);
}
}
return Ok(());
}
parse_signal_opt(signal_vec, opt)
}

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
Expand Down Expand Up @@ -307,9 +336,40 @@ pub fn uu_app() -> Command {
.long(options::IGNORE_SIGNAL)
.value_name("SIG")
.action(ArgAction::Append)
.num_args(0..=1)
.default_missing_value("")
.require_equals(true)
.value_parser(ValueParser::os_string())
.help(translate!("env-help-ignore-signal")),
)
.arg(
Arg::new(options::DEFAULT_SIGNAL)
.long(options::DEFAULT_SIGNAL)
.value_name("SIG")
.action(ArgAction::Append)
.num_args(0..=1)
.default_missing_value("")
.require_equals(true)
.value_parser(ValueParser::os_string())
.help(translate!("env-help-default-signal")),
)
.arg(
Arg::new(options::BLOCK_SIGNAL)
.long(options::BLOCK_SIGNAL)
.value_name("SIG")
.action(ArgAction::Append)
.num_args(0..=1)
.default_missing_value("")
.require_equals(true)
.value_parser(ValueParser::os_string())
.help(translate!("env-help-block-signal")),
)
.arg(
Arg::new(options::LIST_SIGNAL_HANDLING)
.long(options::LIST_SIGNAL_HANDLING)
.action(ArgAction::SetTrue)
.help(translate!("env-help-list-signal-handling")),
)
}

pub fn parse_args_from_str(text: &NativeIntStr) -> UResult<Vec<NativeIntString>> {
Expand Down Expand Up @@ -543,9 +603,23 @@ impl EnvAppData {

apply_specified_env_vars(&opts);

// Apply signal handling in the correct order:
// 1. Reset signals to default
// 2. Set signals to ignore
// 3. Block signals
// 4. List signal handling (if requested)
#[cfg(unix)]
apply_default_signal(&opts)?;

#[cfg(unix)]
apply_ignore_signal(&opts)?;

#[cfg(unix)]
apply_block_signal(&opts)?;

#[cfg(unix)]
list_signal_handling(&opts)?;

if opts.program.is_empty() {
// no program provided, so just dump all env vars to stdout
print_env(opts.line_ending);
Expand Down Expand Up @@ -705,12 +779,32 @@ fn make_options(matches: &clap::ArgMatches) -> UResult<Options<'_>> {
argv0,
#[cfg(unix)]
ignore_signal: vec![],
#[cfg(unix)]
default_signal: vec![],
#[cfg(unix)]
block_signal: vec![],
#[cfg(unix)]
list_signal_handling: matches.get_flag(options::LIST_SIGNAL_HANDLING),
};

#[cfg(unix)]
if let Some(iter) = matches.get_many::<OsString>("ignore-signal") {
if let Some(iter) = matches.get_many::<OsString>(options::IGNORE_SIGNAL) {
for opt in iter {
parse_signal_opt_or_all(&mut opts.ignore_signal, opt)?;
}
}

#[cfg(unix)]
if let Some(iter) = matches.get_many::<OsString>(options::DEFAULT_SIGNAL) {
for opt in iter {
parse_signal_opt(&mut opts, opt)?;
parse_signal_opt_or_all(&mut opts.default_signal, opt)?;
}
}

#[cfg(unix)]
if let Some(iter) = matches.get_many::<OsString>(options::BLOCK_SIGNAL) {
for opt in iter {
parse_signal_opt_or_all(&mut opts.block_signal, opt)?;
}
}

Expand Down Expand Up @@ -821,25 +915,58 @@ fn apply_specified_env_vars(opts: &Options<'_>) {
#[cfg(unix)]
fn apply_ignore_signal(opts: &Options<'_>) -> UResult<()> {
for &sig_value in &opts.ignore_signal {
let sig: Signal = (sig_value as i32)
.try_into()
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
ignore_signal(sig_value).map_err(|err| {
USimpleError::new(
125,
translate!("env-error-failed-set-signal-action", "signal" => sig_value, "error" => err.desc()),
)
})?;
}
Ok(())
}

ignore_signal(sig)?;
#[cfg(unix)]
fn apply_default_signal(opts: &Options<'_>) -> UResult<()> {
for &sig_value in &opts.default_signal {
reset_signal(sig_value).map_err(|err| {
USimpleError::new(
125,
translate!("env-error-failed-set-signal-action", "signal" => sig_value, "error" => err.desc()),
)
})?;
}
Ok(())
}

#[cfg(unix)]
fn ignore_signal(sig: Signal) -> 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) };
if let Err(err) = result {
return Err(USimpleError::new(
125,
translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()),
));
fn apply_block_signal(opts: &Options<'_>) -> UResult<()> {
block_signals(&opts.block_signal)
.map_err(|err| USimpleError::new(125, format!("failed to block signals: {}", err.desc())))
}

#[cfg(unix)]
fn list_signal_handling(opts: &Options<'_>) -> UResult<()> {
if !opts.list_signal_handling {
return Ok(());
}

let stderr = io::stderr();
let mut stderr = stderr.lock();

// Check each signal that was modified
for &sig_value in &opts.ignore_signal {
if let Some(name) = signal_name_by_value(sig_value) {
if let Ok(status) = get_signal_status(sig_value) {
let handler_str = match status {
SignalStatus::Ignore => "IGNORE",
SignalStatus::Default => "DEFAULT",
SignalStatus::Custom => "HANDLER",
};
writeln!(stderr, "{name:<10} (): {handler_str}").ok();
}
}
}

Ok(())
}

Expand All @@ -848,9 +975,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Rust ignores SIGPIPE (see https://github.com/rust-lang/rust/issues/62569).
// We restore its default action here.
#[cfg(unix)]
unsafe {
libc::signal(libc::SIGPIPE, libc::SIG_DFL);
}
uucore::signals::enable_pipe_errors().ok();
EnvAppData::default().run_env(args)
}

Expand Down
69 changes: 67 additions & 2 deletions src/uucore/src/lib/features/signals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 (vars/api) fcntl setrlimit setitimer rubout pollable sysconf pgrp
// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable sysconf pgrp sigset sigprocmask sigaction Sigmask
// spell-checker:ignore (vars/signals) ABRT ALRM CHLD SEGV SIGABRT SIGALRM SIGBUS SIGCHLD SIGCONT SIGDANGER SIGEMT SIGFPE SIGHUP SIGILL SIGINFO SIGINT SIGIO SIGIOT SIGKILL SIGMIGRATE SIGMSG SIGPIPE SIGPRE SIGPROF SIGPWR SIGQUIT SIGSEGV SIGSTOP SIGSYS SIGTALRM SIGTERM SIGTRAP SIGTSTP SIGTHR SIGTTIN SIGTTOU SIGURG SIGUSR SIGVIRT SIGVTALRM SIGWINCH SIGXCPU SIGXFSZ STKFLT PWR THR TSTP TTIN TTOU VIRT VTALRM XCPU XFSZ SIGCLD SIGPOLL SIGWAITING SIGAIOCANCEL SIGLWP SIGFREEZE SIGTHAW SIGCANCEL SIGLOST SIGXRES SIGJVM SIGRTMIN SIGRT SIGRTMAX TALRM AIOCANCEL XRES RTMIN RTMAX LTOSTOP

//! This module provides a way to handle signals in a platform-independent way.
Expand All @@ -13,10 +13,16 @@
#[cfg(unix)]
use nix::errno::Errno;
#[cfg(unix)]
use nix::libc;
#[cfg(unix)]
use nix::sys::signal::{
SigHandler::SigDfl, SigHandler::SigIgn, Signal::SIGINT, Signal::SIGPIPE, signal,
SigHandler::SigDfl, SigHandler::SigIgn, SigSet, SigmaskHow, Signal, Signal::SIGINT,
Signal::SIGPIPE, signal, sigprocmask,
};

#[cfg(unix)]
pub use nix::libc::{SIGKILL, SIGSTOP};

/// The default signal value.
pub static DEFAULT_SIGNAL: usize = 15;

Expand Down Expand Up @@ -426,6 +432,65 @@ pub fn ignore_interrupts() -> Result<(), Errno> {
unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
}

/// Ignore a signal by its number.
#[cfg(unix)]
pub fn ignore_signal(sig: usize) -> Result<(), Errno> {
let sig = Signal::try_from(sig as i32).map_err(|_| Errno::EINVAL)?;
// SAFETY: Setting to SigIgn is safe - no custom handler.
unsafe { signal(sig, SigIgn) }.map(|_| ())
}

/// Reset a signal to its default handler.
#[cfg(unix)]
pub fn reset_signal(sig: usize) -> Result<(), Errno> {
let sig = Signal::try_from(sig as i32).map_err(|_| Errno::EINVAL)?;
// SAFETY: Setting to SigDfl is safe - no custom handler.
unsafe { signal(sig, SigDfl) }.map(|_| ())
}

/// Block delivery of a set of signals.
#[cfg(unix)]
pub fn block_signals(signals: &[usize]) -> Result<(), Errno> {
if signals.is_empty() {
return Ok(());
}
let mut sigset = SigSet::empty();
for &sig in signals {
let sig = Signal::try_from(sig as i32).map_err(|_| Errno::EINVAL)?;
sigset.add(sig);
}
sigprocmask(SigmaskHow::SIG_BLOCK, Some(&sigset), None)
}

/// Status of a signal handler.
#[cfg(unix)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignalStatus {
/// Default signal handler.
Default,
/// Signal is ignored.
Ignore,
/// Custom signal handler installed.
Custom,
}

/// Query the current handler status for a signal.
#[cfg(unix)]
pub fn get_signal_status(sig: usize) -> Result<SignalStatus, Errno> {
let mut current = std::mem::MaybeUninit::<libc::sigaction>::uninit();
// SAFETY: We pass null for new action, so this only queries the current handler.
if unsafe { libc::sigaction(sig as i32, std::ptr::null(), current.as_mut_ptr()) } != 0 {
return Err(Errno::last());
}
// SAFETY: sigaction succeeded, so current is initialized.
let handler = unsafe { current.assume_init() }.sa_sigaction;
Ok(match handler {
h if h == libc::SIG_IGN => SignalStatus::Ignore,
h if h == libc::SIG_DFL => SignalStatus::Default,
_ => SignalStatus::Custom,
})
}

#[test]
fn signal_by_value() {
assert_eq!(signal_by_name_or_value("0"), Some(0));
Expand Down
Loading
Loading