Skip to content

Commit 8670fcc

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

File tree

4 files changed

+317
-29
lines changed

4 files changed

+317
-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: 150 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,15 @@ 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::signals::{
30+
SignalStatus, block_signals, get_signal_status, ignore_signal, reset_signal,
31+
signal_by_name_or_value, signal_name_by_value,
32+
};
3233

3334
use uucore::display::Quotable;
3435
use uucore::error::{ExitCode, UError, UResult, USimpleError, UUsageError};
3536
use uucore::line_ending::LineEnding;
36-
#[cfg(unix)]
37-
use uucore::signals::signal_by_name_or_value;
3837
use uucore::translate;
3938
use uucore::{format_usage, show_warning};
4039

@@ -84,6 +83,9 @@ mod options {
8483
pub const SPLIT_STRING: &str = "split-string";
8584
pub const ARGV0: &str = "argv0";
8685
pub const IGNORE_SIGNAL: &str = "ignore-signal";
86+
pub const DEFAULT_SIGNAL: &str = "default-signal";
87+
pub const BLOCK_SIGNAL: &str = "block-signal";
88+
pub const LIST_SIGNAL_HANDLING: &str = "list-signal-handling";
8789
}
8890

8991
struct Options<'a> {
@@ -97,6 +99,12 @@ struct Options<'a> {
9799
argv0: Option<&'a OsStr>,
98100
#[cfg(unix)]
99101
ignore_signal: Vec<usize>,
102+
#[cfg(unix)]
103+
default_signal: Vec<usize>,
104+
#[cfg(unix)]
105+
block_signal: Vec<usize>,
106+
#[cfg(unix)]
107+
list_signal_handling: bool,
100108
}
101109

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

157165
#[cfg(unix)]
158-
fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
166+
fn parse_signal_opt(signal_vec: &mut Vec<usize>, opt: &OsStr) -> UResult<()> {
159167
if opt.is_empty() {
160168
return Ok(());
161169
}
162-
let signals: Vec<&'a OsStr> = opt
170+
let signals: Vec<&OsStr> = opt
163171
.as_bytes()
164172
.split(|&b| b == b',')
165173
.map(OsStr::from_bytes)
@@ -179,14 +187,33 @@ fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
179187
));
180188
};
181189
let sig_val = parse_signal_value(sig_str)?;
182-
if !opts.ignore_signal.contains(&sig_val) {
183-
opts.ignore_signal.push(sig_val);
190+
if !signal_vec.contains(&sig_val) {
191+
signal_vec.push(sig_val);
184192
}
185193
}
186194

187195
Ok(())
188196
}
189197

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

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

544602
apply_specified_env_vars(&opts);
545603

604+
// Apply signal handling in the correct order:
605+
// 1. Reset signals to default
606+
// 2. Set signals to ignore
607+
// 3. Block signals
608+
// 4. List signal handling (if requested)
609+
#[cfg(unix)]
610+
apply_default_signal(&opts)?;
611+
546612
#[cfg(unix)]
547613
apply_ignore_signal(&opts)?;
548614

615+
#[cfg(unix)]
616+
apply_block_signal(&opts)?;
617+
618+
#[cfg(unix)]
619+
list_signal_handling(&opts)?;
620+
549621
if opts.program.is_empty() {
550622
// no program provided, so just dump all env vars to stdout
551623
print_env(opts.line_ending);
@@ -705,12 +777,32 @@ fn make_options(matches: &clap::ArgMatches) -> UResult<Options<'_>> {
705777
argv0,
706778
#[cfg(unix)]
707779
ignore_signal: vec![],
780+
#[cfg(unix)]
781+
default_signal: vec![],
782+
#[cfg(unix)]
783+
block_signal: vec![],
784+
#[cfg(unix)]
785+
list_signal_handling: matches.get_flag(options::LIST_SIGNAL_HANDLING),
708786
};
709787

710788
#[cfg(unix)]
711-
if let Some(iter) = matches.get_many::<OsString>("ignore-signal") {
789+
if let Some(iter) = matches.get_many::<OsString>(options::IGNORE_SIGNAL) {
790+
for opt in iter {
791+
parse_signal_opt_or_all(&mut opts.ignore_signal, opt)?;
792+
}
793+
}
794+
795+
#[cfg(unix)]
796+
if let Some(iter) = matches.get_many::<OsString>(options::DEFAULT_SIGNAL) {
712797
for opt in iter {
713-
parse_signal_opt(&mut opts, opt)?;
798+
parse_signal_opt_or_all(&mut opts.default_signal, opt)?;
799+
}
800+
}
801+
802+
#[cfg(unix)]
803+
if let Some(iter) = matches.get_many::<OsString>(options::BLOCK_SIGNAL) {
804+
for opt in iter {
805+
parse_signal_opt_or_all(&mut opts.block_signal, opt)?;
714806
}
715807
}
716808

@@ -821,25 +913,58 @@ fn apply_specified_env_vars(opts: &Options<'_>) {
821913
#[cfg(unix)]
822914
fn apply_ignore_signal(opts: &Options<'_>) -> UResult<()> {
823915
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))?;
916+
ignore_signal(sig_value).map_err(|err| {
917+
USimpleError::new(
918+
125,
919+
translate!("env-error-failed-set-signal-action", "signal" => sig_value, "error" => err.desc()),
920+
)
921+
})?;
922+
}
923+
Ok(())
924+
}
827925

828-
ignore_signal(sig)?;
926+
#[cfg(unix)]
927+
fn apply_default_signal(opts: &Options<'_>) -> UResult<()> {
928+
for &sig_value in &opts.default_signal {
929+
reset_signal(sig_value).map_err(|err| {
930+
USimpleError::new(
931+
125,
932+
translate!("env-error-failed-set-signal-action", "signal" => sig_value, "error" => err.desc()),
933+
)
934+
})?;
829935
}
830936
Ok(())
831937
}
832938

833939
#[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-
));
940+
fn apply_block_signal(opts: &Options<'_>) -> UResult<()> {
941+
block_signals(&opts.block_signal)
942+
.map_err(|err| USimpleError::new(125, format!("failed to block signals: {}", err.desc())))
943+
}
944+
945+
#[cfg(unix)]
946+
fn list_signal_handling(opts: &Options<'_>) -> UResult<()> {
947+
if !opts.list_signal_handling {
948+
return Ok(());
842949
}
950+
951+
let stderr = io::stderr();
952+
let mut stderr = stderr.lock();
953+
954+
// Check each signal that was modified
955+
for &sig_value in &opts.ignore_signal {
956+
if let Some(name) = signal_name_by_value(sig_value) {
957+
if let Ok(status) = get_signal_status(sig_value) {
958+
let handler_str = match status {
959+
SignalStatus::Ignore => "IGNORE",
960+
SignalStatus::Default => "DEFAULT",
961+
SignalStatus::Custom => "HANDLER",
962+
};
963+
writeln!(stderr, "{name:<10} (): {handler_str}").ok();
964+
}
965+
}
966+
}
967+
843968
Ok(())
844969
}
845970

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

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)