Skip to content

Commit b1f259d

Browse files
committed
Staging changes to validate that signal handler implementation works with all signal tests
1 parent e5d26e4 commit b1f259d

20 files changed

Lines changed: 699 additions & 121 deletions

File tree

.vscode/cspell.dictionaries/jargon.wordlist.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,21 @@ nofield
191191
uninlined
192192
nonminimal
193193

194+
# * poll/signal constants
195+
GETFD
196+
iopoll
197+
isapipe
198+
pollfd
199+
POLLERR
200+
POLLHUP
201+
POLLNVAL
202+
POLLRDBAND
203+
revents
204+
Sigmask
205+
sigprocmask
206+
sigset
207+
SIGTTIN
208+
194209
# * CPU/hardware features
195210
ASIMD
196211
asimd

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

fuzz/Cargo.lock

Lines changed: 10 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uu/env/src/env.rs

Lines changed: 179 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ mod options {
8484
pub const SPLIT_STRING: &str = "split-string";
8585
pub const ARGV0: &str = "argv0";
8686
pub const IGNORE_SIGNAL: &str = "ignore-signal";
87+
pub const DEFAULT_SIGNAL: &str = "default-signal";
88+
pub const BLOCK_SIGNAL: &str = "block-signal";
89+
pub const LIST_SIGNAL_HANDLING: &str = "list-signal-handling";
8790
}
8891

8992
struct Options<'a> {
@@ -97,6 +100,12 @@ struct Options<'a> {
97100
argv0: Option<&'a OsStr>,
98101
#[cfg(unix)]
99102
ignore_signal: Vec<usize>,
103+
#[cfg(unix)]
104+
default_signal: Vec<usize>,
105+
#[cfg(unix)]
106+
block_signal: Vec<usize>,
107+
#[cfg(unix)]
108+
list_signal_handling: bool,
100109
}
101110

102111
/// print `name=value` env pairs on screen
@@ -155,11 +164,11 @@ fn parse_signal_value(signal_name: &str) -> UResult<usize> {
155164
}
156165

157166
#[cfg(unix)]
158-
fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
167+
fn parse_signal_opt(signal_vec: &mut Vec<usize>, opt: &OsStr) -> UResult<()> {
159168
if opt.is_empty() {
160169
return Ok(());
161170
}
162-
let signals: Vec<&'a OsStr> = opt
171+
let signals: Vec<&OsStr> = opt
163172
.as_bytes()
164173
.split(|&b| b == b',')
165174
.map(OsStr::from_bytes)
@@ -179,14 +188,33 @@ fn parse_signal_opt<'a>(opts: &mut Options<'a>, opt: &'a OsStr) -> UResult<()> {
179188
));
180189
};
181190
let sig_val = parse_signal_value(sig_str)?;
182-
if !opts.ignore_signal.contains(&sig_val) {
183-
opts.ignore_signal.push(sig_val);
191+
if !signal_vec.contains(&sig_val) {
192+
signal_vec.push(sig_val);
184193
}
185194
}
186195

187196
Ok(())
188197
}
189198

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

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

544603
apply_specified_env_vars(&opts);
545604

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

616+
#[cfg(unix)]
617+
apply_block_signal(&opts)?;
618+
619+
#[cfg(unix)]
620+
list_signal_handling(&opts)?;
621+
549622
if opts.program.is_empty() {
550623
// no program provided, so just dump all env vars to stdout
551624
print_env(opts.line_ending);
@@ -705,12 +778,32 @@ fn make_options(matches: &clap::ArgMatches) -> UResult<Options<'_>> {
705778
argv0,
706779
#[cfg(unix)]
707780
ignore_signal: vec![],
781+
#[cfg(unix)]
782+
default_signal: vec![],
783+
#[cfg(unix)]
784+
block_signal: vec![],
785+
#[cfg(unix)]
786+
list_signal_handling: matches.get_flag(options::LIST_SIGNAL_HANDLING),
708787
};
709788

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

@@ -843,6 +936,86 @@ fn ignore_signal(sig: Signal) -> UResult<()> {
843936
Ok(())
844937
}
845938

939+
#[cfg(unix)]
940+
fn apply_default_signal(opts: &Options<'_>) -> UResult<()> {
941+
use nix::sys::signal::{SigHandler::SigDfl, signal};
942+
943+
for &sig_value in &opts.default_signal {
944+
let sig: Signal = (sig_value as i32)
945+
.try_into()
946+
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
947+
948+
// SAFETY: Setting signal handler to default is safe
949+
let result = unsafe { signal(sig, SigDfl) };
950+
if let Err(err) = result {
951+
return Err(USimpleError::new(
952+
125,
953+
translate!("env-error-failed-set-signal-action", "signal" => (sig as i32), "error" => err.desc()),
954+
));
955+
}
956+
}
957+
Ok(())
958+
}
959+
960+
#[cfg(unix)]
961+
fn apply_block_signal(opts: &Options<'_>) -> UResult<()> {
962+
use nix::sys::signal::{SigSet, SigmaskHow, sigprocmask};
963+
964+
if opts.block_signal.is_empty() {
965+
return Ok(());
966+
}
967+
968+
let mut sigset = SigSet::empty();
969+
for &sig_value in &opts.block_signal {
970+
let sig: Signal = (sig_value as i32)
971+
.try_into()
972+
.map_err(|e| io::Error::from_raw_os_error(e as i32))?;
973+
sigset.add(sig);
974+
}
975+
976+
sigprocmask(SigmaskHow::SIG_BLOCK, Some(&sigset), None).map_err(|err| {
977+
USimpleError::new(125, format!("failed to block signals: {}", err.desc()))
978+
})?;
979+
980+
Ok(())
981+
}
982+
983+
#[cfg(unix)]
984+
fn list_signal_handling(opts: &Options<'_>) -> UResult<()> {
985+
use std::mem::MaybeUninit;
986+
use uucore::signals::signal_name_by_value;
987+
988+
if !opts.list_signal_handling {
989+
return Ok(());
990+
}
991+
992+
let stderr = io::stderr();
993+
let mut stderr = stderr.lock();
994+
995+
// Check each signal that was modified
996+
for &sig_value in &opts.ignore_signal {
997+
if let Some(name) = signal_name_by_value(sig_value) {
998+
// Get current signal handler
999+
let mut current = MaybeUninit::<libc::sigaction>::uninit();
1000+
if unsafe { libc::sigaction(sig_value as i32, std::ptr::null(), current.as_mut_ptr()) }
1001+
== 0
1002+
{
1003+
let handler = unsafe { current.assume_init() }.sa_sigaction;
1004+
let handler_str = if handler == libc::SIG_IGN {
1005+
"IGNORE"
1006+
} else if handler == libc::SIG_DFL {
1007+
"DEFAULT"
1008+
} else {
1009+
"HANDLER"
1010+
};
1011+
writeln!(stderr, "{name:<10} (): {handler_str}").ok();
1012+
}
1013+
}
1014+
}
1015+
1016+
Ok(())
1017+
}
1018+
8461019
#[uucore::main]
8471020
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
8481021
// Rust ignores SIGPIPE (see https://github.com/rust-lang/rust/issues/62569).

src/uu/mktemp/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ clap = { workspace = true }
2222
rand = { workspace = true }
2323
tempfile = { workspace = true }
2424
thiserror = { workspace = true }
25-
uucore = { workspace = true }
25+
uucore = { workspace = true, features = ["signals"] }
2626
fluent = { workspace = true }
2727

2828
[[bin]]

0 commit comments

Comments
 (0)