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
16 changes: 16 additions & 0 deletions .vscode/cspell.dictionaries/jargon.wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ setfacl
setfattr
shortcode
shortcodes
sigaction
siginfo
sigusr
strcasecmp
Expand Down Expand Up @@ -190,6 +191,21 @@ nofield
uninlined
nonminimal

# * poll/signal constants
GETFD
iopoll
isapipe
pollfd
POLLERR
POLLHUP
POLLNVAL
POLLRDBAND
revents
Sigmask
sigprocmask
sigset
SIGTTIN

# * CPU/hardware features
ASIMD
asimd
Expand Down
20 changes: 10 additions & 10 deletions fuzz/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

185 changes: 179 additions & 6 deletions src/uu/env/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,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 +100,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 +164,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 +188,33 @@ 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 (9) and SIGSTOP (19) which cannot be caught or ignored
for sig_val in 1..32 {
if sig_val == 9 || sig_val == 19 {
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 +335,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("set handling of SIG signal(s) to the default"),
)
.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("block delivery of SIG signal(s) to COMMAND"),
)
.arg(
Arg::new(options::LIST_SIGNAL_HANDLING)
.long(options::LIST_SIGNAL_HANDLING)
.action(ArgAction::SetTrue)
.help("list non default signal handling to stderr"),
)
}

pub fn parse_args_from_str(text: &NativeIntStr) -> UResult<Vec<NativeIntString>> {
Expand Down Expand Up @@ -543,9 +602,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 +778,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(&mut opts, opt)?;
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_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 @@ -843,6 +936,86 @@ fn ignore_signal(sig: Signal) -> UResult<()> {
Ok(())
}

#[cfg(unix)]
fn apply_default_signal(opts: &Options<'_>) -> UResult<()> {
use nix::sys::signal::{SigHandler::SigDfl, signal};

for &sig_value in &opts.default_signal {
let sig: Signal = (sig_value as i32)
.try_into()
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;

// SAFETY: Setting signal handler to default is safe
let result = unsafe { signal(sig, SigDfl) };
if let Err(err) = result {
return Err(USimpleError::new(
125,
translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()),
));
}
}
Ok(())
}

#[cfg(unix)]
fn apply_block_signal(opts: &Options<'_>) -> UResult<()> {
use nix::sys::signal::{SigSet, SigmaskHow, sigprocmask};

if opts.block_signal.is_empty() {
return Ok(());
}

let mut sigset = SigSet::empty();
for &sig_value in &opts.block_signal {
let sig: Signal = (sig_value as i32)
.try_into()
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
sigset.add(sig);
}

sigprocmask(SigmaskHow::SIG_BLOCK, Some(&sigset), None).map_err(|err| {
USimpleError::new(125, format!("failed to block signals: {}", err.desc()))
})?;

Ok(())
}

#[cfg(unix)]
fn list_signal_handling(opts: &Options<'_>) -> UResult<()> {
use std::mem::MaybeUninit;
use uucore::signals::signal_name_by_value;

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) {
// Get current signal handler
let mut current = MaybeUninit::<libc::sigaction>::uninit();
if unsafe { libc::sigaction(sig_value as i32, std::ptr::null(), current.as_mut_ptr()) }
== 0
{
let handler = unsafe { current.assume_init() }.sa_sigaction;
let handler_str = if handler == libc::SIG_IGN {
"IGNORE"
} else if handler == libc::SIG_DFL {
"DEFAULT"
} else {
"HANDLER"
};
writeln!(stderr, "{name:<10} (): {handler_str}").ok();
}
}
}

Ok(())
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
// Rust ignores SIGPIPE (see https://github.com/rust-lang/rust/issues/62569).
Expand Down
2 changes: 1 addition & 1 deletion src/uu/mktemp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ clap = { workspace = true }
rand = { workspace = true }
tempfile = { workspace = true }
thiserror = { workspace = true }
uucore = { workspace = true }
uucore = { workspace = true, features = ["signals"] }
fluent = { workspace = true }

[[bin]]
Expand Down
Loading
Loading