diff --git a/cli/tests/cases/builtins/help.yaml b/cli/tests/cases/builtins/help.yaml index f4afe644..46565e0d 100644 --- a/cli/tests/cases/builtins/help.yaml +++ b/cli/tests/cases/builtins/help.yaml @@ -5,3 +5,9 @@ cases: ignore_stderr: true stdin: | help + + - name: "Topic-specific help" + ignore_stdout: true + ignore_stderr: true + stdin: | + help echo diff --git a/cli/tests/cases/builtins/trap.yaml b/cli/tests/cases/builtins/trap.yaml new file mode 100644 index 00000000..08c0eea4 --- /dev/null +++ b/cli/tests/cases/builtins/trap.yaml @@ -0,0 +1,29 @@ +name: "Builtins: trap" +cases: + - name: "trap registration" + stdin: | + trap "echo 1" SIGINT + trap "echo 2" SIGINT + trap + + trap "echo 3" int + trap + + trap "echo 4" 2 + trap + + - name: "trap EXIT" + known_failure: true + stdin: | + trap "echo [exit]" EXIT + trap + + - name: "trap DEBUG" + stdin: | + trap 'echo [command: ${BASH_COMMAND}]' DEBUG + trap + + - name: "trap ERR" + stdin: | + trap "echo [err]" ERR + trap diff --git a/shell/src/builtins/trap.rs b/shell/src/builtins/trap.rs index 094e4f3f..cc300b25 100644 --- a/shell/src/builtins/trap.rs +++ b/shell/src/builtins/trap.rs @@ -3,7 +3,7 @@ use std::{io::Write, str::FromStr}; use crate::{ builtin::{BuiltinCommand, BuiltinExitCode}, - error, + error, traps, }; #[derive(Parser)] @@ -77,12 +77,10 @@ impl TrapCommand { fn display_handlers_for( context: &crate::context::CommandExecutionContext<'_>, - signal_type: nix::sys::signal::Signal, + signal_type: traps::TrapSignal, ) -> Result<(), error::Error> { - if let Some(handlers) = context.shell.traps.handlers.get(&signal_type) { - for handler in handlers { - writeln!(context.stdout(), "trap -- '{handler}' {signal_type}")?; - } + if let Some(handler) = context.shell.traps.handlers.get(&signal_type) { + writeln!(context.stdout(), "trap -- '{handler}' {signal_type}")?; } Ok(()) } @@ -90,7 +88,7 @@ impl TrapCommand { #[allow(clippy::unnecessary_wraps)] fn remove_all_handlers( context: &mut crate::context::CommandExecutionContext<'_>, - signal: nix::sys::signal::Signal, + signal: traps::TrapSignal, ) -> Result<(), error::Error> { context.shell.traps.remove_handlers(signal); Ok(()) @@ -99,7 +97,7 @@ impl TrapCommand { #[allow(clippy::unnecessary_wraps)] fn register_handler( context: &mut crate::context::CommandExecutionContext<'_>, - signals: Vec, + signals: Vec, handler: &str, ) -> Result<(), error::Error> { for signal in signals { @@ -113,13 +111,15 @@ impl TrapCommand { } } -fn parse_signal(signal: &str) -> Result { +fn parse_signal(signal: &str) -> Result { if signal.chars().all(|c| c.is_ascii_digit()) { let digits = signal .parse::() .map_err(|_| error::Error::InvalidSignal)?; - nix::sys::signal::Signal::try_from(digits).map_err(|_| error::Error::InvalidSignal) + Ok(traps::TrapSignal::Signal( + nix::sys::signal::Signal::try_from(digits).map_err(|_| error::Error::InvalidSignal)?, + )) } else { let mut signal_to_parse = signal.to_ascii_uppercase(); @@ -127,7 +127,14 @@ fn parse_signal(signal: &str) -> Result signal_to_parse.insert_str(0, "SIG"); } - nix::sys::signal::Signal::from_str(signal_to_parse.as_str()) - .map_err(|_| error::Error::InvalidSignal) + match signal_to_parse { + s if s == "SIGDEBUG" => Ok(traps::TrapSignal::Debug), + s if s == "SIGERR" => Ok(traps::TrapSignal::Err), + s if s == "SIGEXIT" => Ok(traps::TrapSignal::Exit), + _ => Ok(traps::TrapSignal::Signal( + nix::sys::signal::Signal::from_str(signal_to_parse.as_str()) + .map_err(|_| error::Error::InvalidSignal)?, + )), + } } } diff --git a/shell/src/interp.rs b/shell/src/interp.rs index bf4f9466..d3ccc52b 100644 --- a/shell/src/interp.rs +++ b/shell/src/interp.rs @@ -19,7 +19,7 @@ use crate::shell::Shell; use crate::variables::{ ArrayLiteral, ShellValue, ShellValueLiteral, ShellValueUnsetType, ShellVariable, }; -use crate::{builtin, context, error, expansion, extendedtests, jobs, openfiles}; +use crate::{builtin, context, error, expansion, extendedtests, jobs, openfiles, traps}; #[derive(Debug, Default)] pub struct ExecutionResult { @@ -895,6 +895,43 @@ impl ExecuteInPipeline for ast::SimpleCommand { .trace_command(args.iter().map(|arg| arg.to_string()).join(" "))?; } + // TODO: This is adding more complexity here; should be factored out into an appropriate + // helper. + if context.shell.traps.handler_depth == 0 { + let debug_trap_handler = context + .shell + .traps + .handlers + .get(&traps::TrapSignal::Debug) + .cloned(); + if let Some(debug_trap_handler) = debug_trap_handler { + let params = ExecutionParameters { + open_files: open_files.clone(), + }; + + let full_cmd = args.iter().map(|arg| arg.to_string()).join(" "); + + // TODO: This shouldn't *just* be set in a trap situation. + context.shell.env.update_or_add( + "BASH_COMMAND", + ShellValueLiteral::Scalar(full_cmd), + |_| Ok(()), + EnvironmentLookup::Anywhere, + EnvironmentScope::Global, + )?; + + context.shell.traps.handler_depth += 1; + + // TODO: Discard result? + let _ = context + .shell + .run_string(debug_trap_handler.as_str(), ¶ms) + .await?; + + context.shell.traps.handler_depth -= 1; + } + } + let cmd_context = context::CommandExecutionContext { shell: context.shell, command_name: cmd_name, diff --git a/shell/src/traps.rs b/shell/src/traps.rs index 46462fd5..a3190d5a 100644 --- a/shell/src/traps.rs +++ b/shell/src/traps.rs @@ -1,20 +1,36 @@ -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Display}; + +#[derive(Clone, Copy, Eq, Hash, PartialEq)] +pub enum TrapSignal { + Signal(nix::sys::signal::Signal), + Debug, + Err, + Exit, +} + +impl Display for TrapSignal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TrapSignal::Signal(s) => s.fmt(f), + TrapSignal::Debug => write!(f, "DEBUG"), + TrapSignal::Err => write!(f, "ERR"), + TrapSignal::Exit => write!(f, "EXIT"), + } + } +} #[derive(Clone, Default)] pub struct TrapHandlerConfig { - pub handlers: HashMap>, + pub handlers: HashMap, + pub handler_depth: i32, } impl TrapHandlerConfig { - pub fn register_handler(&mut self, signal_type: nix::sys::signal::Signal, command: String) { - if let Some(handlers) = self.handlers.get_mut(&signal_type) { - handlers.push(command); - } else { - self.handlers.insert(signal_type, vec![command]); - } + pub fn register_handler(&mut self, signal_type: TrapSignal, command: String) { + let _ = self.handlers.insert(signal_type, command); } - pub fn remove_handlers(&mut self, signal_type: nix::sys::signal::Signal) { + pub fn remove_handlers(&mut self, signal_type: TrapSignal) { self.handlers.remove(&signal_type); } }