Skip to content

Commit d34a4de

Browse files
committed
env: add --default-signal, --block-signal, and --list-signal-handling options
1 parent c085cd1 commit d34a4de

File tree

4 files changed

+321
-29
lines changed

4 files changed

+321
-29
lines changed

src/uu/env/locales/en-US.ftl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ env-help-debug = print verbose information for each processing step
1212
env-help-split-string = process and split S into separate arguments; used to pass multiple arguments on shebang lines
1313
env-help-argv0 = Override the zeroth argument passed to the command being executed. Without this option a default value of `command` is used.
1414
env-help-ignore-signal = set handling of SIG signal(s) to do nothing
15+
env-help-default-signal = set handling of SIG signal(s) to the default
16+
env-help-block-signal = block delivery of SIG signal(s) to COMMAND
17+
env-help-list-signal-handling = list non default signal handling to stderr
1518
1619
# Error messages
1720
env-error-missing-closing-quote = no terminating quote in -S string at position { $position } for quote '{ $quote }'

src/uu/env/src/env.rs

Lines changed: 154 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,6 @@ use ini::Ini;
1717
use native_int_str::{
1818
Convert, NCvt, NativeIntStr, NativeIntString, NativeStr, from_native_int_representation_owned,
1919
};
20-
#[cfg(unix)]
21-
use nix::libc;
22-
#[cfg(unix)]
23-
use nix::sys::signal::{SigHandler::SigIgn, Signal, signal};
2420
use std::borrow::Cow;
2521
use std::env;
2622
use std::ffi::{OsStr, OsString};
@@ -29,12 +25,17 @@ use std::io::{self, Write};
2925
use std::os::unix::ffi::OsStrExt;
3026
#[cfg(unix)]
3127
use std::os::unix::process::CommandExt;
28+
#[cfg(unix)]
29+
use uucore::libc::{SIGKILL, SIGSTOP};
30+
#[cfg(unix)]
31+
use uucore::signals::{
32+
SignalStatus, block_signals, get_signal_status, ignore_signal, reset_signal,
33+
signal_by_name_or_value, signal_name_by_value,
34+
};
3235

3336
use uucore::display::Quotable;
3437
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
3538
use uucore::line_ending::LineEnding;
36-
#[cfg(unix)]
37-
use uucore::signals::signal_by_name_or_value;
3839
use uucore::translate;
3940
use uucore::{format_usage, show_warning};
4041

@@ -84,6 +85,9 @@ mod options {
8485
pub const SPLIT_STRING: &str = "split-string";
8586
pub const ARGV0: &str = "argv0";
8687
pub const IGNORE_SIGNAL: &str = "ignore-signal";
88+
pub const DEFAULT_SIGNAL: &str = "default-signal";
89+
pub const BLOCK_SIGNAL: &str = "block-signal";
90+
pub const LIST_SIGNAL_HANDLING: &str = "list-signal-handling";
8791
}
8892

8993
struct Options<'a> {
@@ -97,6 +101,12 @@ struct Options<'a> {
97101
argv0: Option<&'a OsStr>,
98102
#[cfg(unix)]
99103
ignore_signal: Vec<usize>,
104+
#[cfg(unix)]
105+
default_signal: Vec<usize>,
106+
#[cfg(unix)]
107+
block_signal: Vec<usize>,
108+
#[cfg(unix)]
109+
list_signal_handling: bool,
100110
}
101111

102112
/// print `name=value` env pairs on screen
@@ -155,11 +165,11 @@ fn parse_signal_value(signal_name: &str) -> UResult<usize> {
155165
}
156166

157167
#[cfg(unix)]
158-
fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
168+
fn parse_signal_opt(signal_vec: &mut Vec<usize>, opt: &OsStr) -> UResult<()> {
159169
if opt.is_empty() {
160170
return Ok(());
161171
}
162-
let signals: Vec<&'a OsStr> = opt
172+
let signals: Vec<&OsStr> = opt
163173
.as_bytes()
164174
.split(|&b| b == b',')
165175
.map(OsStr::from_bytes)
@@ -179,14 +189,35 @@ fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
179189
));
180190
};
181191
let sig_val = parse_signal_value(sig_str)?;
182-
if !opts.ignore_signal.contains(&sig_val) {
183-
opts.ignore_signal.push(sig_val);
192+
if !signal_vec.contains(&sig_val) {
193+
signal_vec.push(sig_val);
184194
}
185195
}
186196

187197
Ok(())
188198
}
189199

200+
/// Parse signal option that can be empty (meaning all signals)
201+
#[cfg(unix)]
202+
fn parse_signal_opt_or_all(signal_vec: &mut Vec<usize>, opt: &OsStr) -> UResult<()> {
203+
if opt.is_empty() {
204+
// Empty means all signals - add all valid signal numbers (1-31 typically)
205+
// Skip SIGKILL and SIGSTOP which cannot be caught or ignored
206+
let sigkill = SIGKILL as usize;
207+
let sigstop = SIGSTOP as usize;
208+
for sig_val in 1..32 {
209+
if sig_val == sigkill || sig_val == sigstop {
210+
continue; // SIGKILL and SIGSTOP cannot be modified
211+
}
212+
if !signal_vec.contains(&sig_val) {
213+
signal_vec.push(sig_val);
214+
}
215+
}
216+
return Ok(());
217+
}
218+
parse_signal_opt(signal_vec, opt)
219+
}
220+
190221
fn load_config_file(opts: &mut Options) -> UResult<()> {
191222
// NOTE: config files are parsed using an INI parser b/c it's available and compatible with ".env"-style files
192223
// ... * but support for actual INI files, although working, is not intended, nor claimed
@@ -307,9 +338,40 @@ pub fn uu_app() -> Command {
307338
.long(options::IGNORE_SIGNAL)
308339
.value_name("SIG")
309340
.action(ArgAction::Append)
341+
.num_args(0..=1)
342+
.default_missing_value("")
343+
.require_equals(true)
310344
.value_parser(ValueParser::os_string())
311345
.help(translate!("env-help-ignore-signal")),
312346
)
347+
.arg(
348+
Arg::new(options::DEFAULT_SIGNAL)
349+
.long(options::DEFAULT_SIGNAL)
350+
.value_name("SIG")
351+
.action(ArgAction::Append)
352+
.num_args(0..=1)
353+
.default_missing_value("")
354+
.require_equals(true)
355+
.value_parser(ValueParser::os_string())
356+
.help(translate!("env-help-default-signal")),
357+
)
358+
.arg(
359+
Arg::new(options::BLOCK_SIGNAL)
360+
.long(options::BLOCK_SIGNAL)
361+
.value_name("SIG")
362+
.action(ArgAction::Append)
363+
.num_args(0..=1)
364+
.default_missing_value("")
365+
.require_equals(true)
366+
.value_parser(ValueParser::os_string())
367+
.help(translate!("env-help-block-signal")),
368+
)
369+
.arg(
370+
Arg::new(options::LIST_SIGNAL_HANDLING)
371+
.long(options::LIST_SIGNAL_HANDLING)
372+
.action(ArgAction::SetTrue)
373+
.help(translate!("env-help-list-signal-handling")),
374+
)
313375
}
314376

315377
pub fn parse_args_from_str(text: &NativeIntStr) -> UResult<Vec<NativeIntString>> {
@@ -543,9 +605,23 @@ impl EnvAppData {
543605

544606
apply_specified_env_vars(&opts);
545607

608+
// Apply signal handling in the correct order:
609+
// 1. Reset signals to default
610+
// 2. Set signals to ignore
611+
// 3. Block signals
612+
// 4. List signal handling (if requested)
613+
#[cfg(unix)]
614+
apply_default_signal(&opts)?;
615+
546616
#[cfg(unix)]
547617
apply_ignore_signal(&opts)?;
548618

619+
#[cfg(unix)]
620+
apply_block_signal(&opts)?;
621+
622+
#[cfg(unix)]
623+
list_signal_handling(&opts)?;
624+
549625
if opts.program.is_empty() {
550626
// no program provided, so just dump all env vars to stdout
551627
print_env(opts.line_ending);
@@ -705,12 +781,32 @@ fn make_options(matches: &clap::ArgMatches) -> UResult<Options<'_>> {
705781
argv0,
706782
#[cfg(unix)]
707783
ignore_signal: vec![],
784+
#[cfg(unix)]
785+
default_signal: vec![],
786+
#[cfg(unix)]
787+
block_signal: vec![],
788+
#[cfg(unix)]
789+
list_signal_handling: matches.get_flag(options::LIST_SIGNAL_HANDLING),
708790
};
709791

710792
#[cfg(unix)]
711-
if let Some(iter) = matches.get_many::<OsString>("ignore-signal") {
793+
if let Some(iter) = matches.get_many::<OsString>(options::IGNORE_SIGNAL) {
794+
for opt in iter {
795+
parse_signal_opt_or_all(&mut opts.ignore_signal, opt)?;
796+
}
797+
}
798+
799+
#[cfg(unix)]
800+
if let Some(iter) = matches.get_many::<OsString>(options::DEFAULT_SIGNAL) {
712801
for opt in iter {
713-
parse_signal_opt(&mut opts, opt)?;
802+
parse_signal_opt_or_all(&mut opts.default_signal, opt)?;
803+
}
804+
}
805+
806+
#[cfg(unix)]
807+
if let Some(iter) = matches.get_many::<OsString>(options::BLOCK_SIGNAL) {
808+
for opt in iter {
809+
parse_signal_opt_or_all(&mut opts.block_signal, opt)?;
714810
}
715811
}
716812

@@ -821,25 +917,58 @@ fn apply_specified_env_vars(opts: &Options<'_>) {
821917
#[cfg(unix)]
822918
fn apply_ignore_signal(opts: &Options<'_>) -> UResult<()> {
823919
for &sig_value in &opts.ignore_signal {
824-
let sig: Signal = (sig_value as i32)
825-
.try_into()
826-
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
920+
ignore_signal(sig_value).map_err(|err| {
921+
USimpleError::new(
922+
125,
923+
translate!("env-error-failed-set-signal-action", "signal" => sig_value, "error" => err.desc()),
924+
)
925+
})?;
926+
}
927+
Ok(())
928+
}
827929

828-
ignore_signal(sig)?;
930+
#[cfg(unix)]
931+
fn apply_default_signal(opts: &Options<'_>) -> UResult<()> {
932+
for &sig_value in &opts.default_signal {
933+
reset_signal(sig_value).map_err(|err| {
934+
USimpleError::new(
935+
125,
936+
translate!("env-error-failed-set-signal-action", "signal" => sig_value, "error" => err.desc()),
937+
)
938+
})?;
829939
}
830940
Ok(())
831941
}
832942

833943
#[cfg(unix)]
834-
fn ignore_signal(sig: Signal) -> UResult<()> {
835-
// 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.
836-
let result = unsafe { signal(sig, SigIgn) };
837-
if let Err(err) = result {
838-
return Err(USimpleError::new(
839-
125,
840-
translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()),
841-
));
944+
fn apply_block_signal(opts: &Options<'_>) -> UResult<()> {
945+
block_signals(&opts.block_signal)
946+
.map_err(|err| USimpleError::new(125, format!("failed to block signals: {}", err.desc())))
947+
}
948+
949+
#[cfg(unix)]
950+
fn list_signal_handling(opts: &Options<'_>) -> UResult<()> {
951+
if !opts.list_signal_handling {
952+
return Ok(());
842953
}
954+
955+
let stderr = io::stderr();
956+
let mut stderr = stderr.lock();
957+
958+
// Check each signal that was modified
959+
for &sig_value in &opts.ignore_signal {
960+
if let Some(name) = signal_name_by_value(sig_value) {
961+
if let Ok(status) = get_signal_status(sig_value) {
962+
let handler_str = match status {
963+
SignalStatus::Ignore => "IGNORE",
964+
SignalStatus::Default => "DEFAULT",
965+
SignalStatus::Custom => "HANDLER",
966+
};
967+
writeln!(stderr, "{name:<10} (): {handler_str}").ok();
968+
}
969+
}
970+
}
971+
843972
Ok(())
844973
}
845974

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

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

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable sysconf pgrp
6+
// spell-checker:ignore (vars/api) fcntl setrlimit setitimer rubout pollable sysconf pgrp sigset sigprocmask sigaction Sigmask
77
// 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
88

99
//! This module provides a way to handle signals in a platform-independent way.
@@ -13,8 +13,11 @@
1313
#[cfg(unix)]
1414
use nix::errno::Errno;
1515
#[cfg(unix)]
16+
use nix::libc;
17+
#[cfg(unix)]
1618
use nix::sys::signal::{
17-
SigHandler::SigDfl, SigHandler::SigIgn, Signal::SIGINT, Signal::SIGPIPE, signal,
19+
SigHandler::SigDfl, SigHandler::SigIgn, SigSet, SigmaskHow, Signal, Signal::SIGINT,
20+
Signal::SIGPIPE, signal, sigprocmask,
1821
};
1922

2023
/// The default signal value.
@@ -426,6 +429,65 @@ pub fn ignore_interrupts() -> Result<(), Errno> {
426429
unsafe { signal(SIGINT, SigIgn) }.map(|_| ())
427430
}
428431

432+
/// Ignore a signal by its number.
433+
#[cfg(unix)]
434+
pub fn ignore_signal(sig: usize) -> Result<(), Errno> {
435+
let sig = Signal::try_from(sig as i32).map_err(|_| Errno::EINVAL)?;
436+
// SAFETY: Setting to SigIgn is safe - no custom handler.
437+
unsafe { signal(sig, SigIgn) }.map(|_| ())
438+
}
439+
440+
/// Reset a signal to its default handler.
441+
#[cfg(unix)]
442+
pub fn reset_signal(sig: usize) -> Result<(), Errno> {
443+
let sig = Signal::try_from(sig as i32).map_err(|_| Errno::EINVAL)?;
444+
// SAFETY: Setting to SigDfl is safe - no custom handler.
445+
unsafe { signal(sig, SigDfl) }.map(|_| ())
446+
}
447+
448+
/// Block delivery of a set of signals.
449+
#[cfg(unix)]
450+
pub fn block_signals(signals: &[usize]) -> Result<(), Errno> {
451+
if signals.is_empty() {
452+
return Ok(());
453+
}
454+
let mut sigset = SigSet::empty();
455+
for &sig in signals {
456+
let sig = Signal::try_from(sig as i32).map_err(|_| Errno::EINVAL)?;
457+
sigset.add(sig);
458+
}
459+
sigprocmask(SigmaskHow::SIG_BLOCK, Some(&sigset), None)
460+
}
461+
462+
/// Status of a signal handler.
463+
#[cfg(unix)]
464+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465+
pub enum SignalStatus {
466+
/// Default signal handler.
467+
Default,
468+
/// Signal is ignored.
469+
Ignore,
470+
/// Custom signal handler installed.
471+
Custom,
472+
}
473+
474+
/// Query the current handler status for a signal.
475+
#[cfg(unix)]
476+
pub fn get_signal_status(sig: usize) -> Result<SignalStatus, Errno> {
477+
let mut current = std::mem::MaybeUninit::<libc::sigaction>::uninit();
478+
// SAFETY: We pass null for new action, so this only queries the current handler.
479+
if unsafe { libc::sigaction(sig as i32, std::ptr::null(), current.as_mut_ptr()) } != 0 {
480+
return Err(Errno::last());
481+
}
482+
// SAFETY: sigaction succeeded, so current is initialized.
483+
let handler = unsafe { current.assume_init() }.sa_sigaction;
484+
Ok(match handler {
485+
h if h == libc::SIG_IGN => SignalStatus::Ignore,
486+
h if h == libc::SIG_DFL => SignalStatus::Default,
487+
_ => SignalStatus::Custom,
488+
})
489+
}
490+
429491
#[test]
430492
fn signal_by_value() {
431493
assert_eq!(signal_by_name_or_value("0"), Some(0));

0 commit comments

Comments
 (0)