From 744693a89d2080aa6d953fe3fc14557d90b5a91e Mon Sep 17 00:00:00 2001 From: pagedmov Date: Thu, 19 Feb 2026 16:39:18 -0500 Subject: [PATCH] implemented the trap builtin --- src/builtin/echo.rs | 2 +- src/builtin/mod.rs | 5 +- src/builtin/read.rs | 1 + src/builtin/trap.rs | 162 +++++++++++++++++++++++++++++ src/jobs.rs | 8 +- src/main.rs | 8 +- src/parse/execute.rs | 14 ++- src/parse/mod.rs | 21 +++- src/prompt/readline/term.rs | 12 ++- src/signal.rs | 201 ++++++++++++++++++------------------ src/state.rs | 35 +++++-- 11 files changed, 340 insertions(+), 129 deletions(-) create mode 100644 src/builtin/trap.rs diff --git a/src/builtin/echo.rs b/src/builtin/echo.rs index 2a230a7..1b03d6e 100644 --- a/src/builtin/echo.rs +++ b/src/builtin/echo.rs @@ -48,7 +48,7 @@ pub fn echo(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<( flags.contains(EchoFlags::USE_PROMPT) )?.join(" "); - if !flags.contains(EchoFlags::NO_NEWLINE) { + if !flags.contains(EchoFlags::NO_NEWLINE) && !echo_output.ends_with('\n') { echo_output.push('\n') } diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index c3931e3..cb2f1a9 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -22,12 +22,13 @@ pub mod source; pub mod test; // [[ ]] thing pub mod read; pub mod zoltraak; +pub mod trap; -pub const BUILTINS: [&str; 20] = [ +pub const BUILTINS: [&str; 21] = [ "echo", "cd", "read", "export", "pwd", "source", "shift", "jobs", "fg", "bg", "alias", "unalias", "return", "break", "continue", "exit", "zoltraak", - "shopt", "builtin", "command", + "shopt", "builtin", "command", "trap" ]; /// Sets up a builtin command diff --git a/src/builtin/read.rs b/src/builtin/read.rs index 71c9423..c7f0961 100644 --- a/src/builtin/read.rs +++ b/src/builtin/read.rs @@ -155,6 +155,7 @@ pub fn read_builtin(node: Node, _io_stack: &mut IoStack, job: &mut JobBldr) -> S } } + state::set_status(0); Ok(()) } diff --git a/src/builtin/trap.rs b/src/builtin/trap.rs new file mode 100644 index 0000000..2ab3a08 --- /dev/null +++ b/src/builtin/trap.rs @@ -0,0 +1,162 @@ +use std::{fmt::Display, str::FromStr}; + +use nix::{libc::{STDERR_FILENO, STDOUT_FILENO}, sys::signal::Signal, unistd::write}; + +use crate::{builtin::setup_builtin, jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, procio::{IoStack, borrow_fd}, state::{self, read_logic, write_logic}}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub enum TrapTarget { + Exit, + Error, + Signal(Signal) +} + +impl FromStr for TrapTarget { + type Err = ShErr; + fn from_str(s: &str) -> Result { + match s { + "EXIT" => Ok(TrapTarget::Exit), + "ERR" => Ok(TrapTarget::Error), + + "INT" => Ok(TrapTarget::Signal(Signal::SIGINT)), + "QUIT" => Ok(TrapTarget::Signal(Signal::SIGQUIT)), + "ILL" => Ok(TrapTarget::Signal(Signal::SIGILL)), + "TRAP" => Ok(TrapTarget::Signal(Signal::SIGTRAP)), + "ABRT" => Ok(TrapTarget::Signal(Signal::SIGABRT)), + "BUS" => Ok(TrapTarget::Signal(Signal::SIGBUS)), + "FPE" => Ok(TrapTarget::Signal(Signal::SIGFPE)), + "KILL" => Ok(TrapTarget::Signal(Signal::SIGKILL)), + "USR1" => Ok(TrapTarget::Signal(Signal::SIGUSR1)), + "SEGV" => Ok(TrapTarget::Signal(Signal::SIGSEGV)), + "USR2" => Ok(TrapTarget::Signal(Signal::SIGUSR2)), + "PIPE" => Ok(TrapTarget::Signal(Signal::SIGPIPE)), + "ALRM" => Ok(TrapTarget::Signal(Signal::SIGALRM)), + "TERM" => Ok(TrapTarget::Signal(Signal::SIGTERM)), + "STKFLT" => Ok(TrapTarget::Signal(Signal::SIGSTKFLT)), + "CHLD" => Ok(TrapTarget::Signal(Signal::SIGCHLD)), + "CONT" => Ok(TrapTarget::Signal(Signal::SIGCONT)), + "STOP" => Ok(TrapTarget::Signal(Signal::SIGSTOP)), + "TSTP" => Ok(TrapTarget::Signal(Signal::SIGTSTP)), + "TTIN" => Ok(TrapTarget::Signal(Signal::SIGTTIN)), + "TTOU" => Ok(TrapTarget::Signal(Signal::SIGTTOU)), + "URG" => Ok(TrapTarget::Signal(Signal::SIGURG)), + "XCPU" => Ok(TrapTarget::Signal(Signal::SIGXCPU)), + "XFSZ" => Ok(TrapTarget::Signal(Signal::SIGXFSZ)), + "VTALRM" => Ok(TrapTarget::Signal(Signal::SIGVTALRM)), + "PROF" => Ok(TrapTarget::Signal(Signal::SIGPROF)), + "WINCH" => Ok(TrapTarget::Signal(Signal::SIGWINCH)), + "IO" => Ok(TrapTarget::Signal(Signal::SIGIO)), + "PWR" => Ok(TrapTarget::Signal(Signal::SIGPWR)), + "SYS" => Ok(TrapTarget::Signal(Signal::SIGSYS)), + _ => { + return Err(ShErr::simple( + ShErrKind::ExecFail, + format!("invalid trap target '{}'", s), + )) + } + } + } +} + +impl Display for TrapTarget { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TrapTarget::Exit => write!(f, "EXIT"), + TrapTarget::Error => write!(f, "ERR"), + TrapTarget::Signal(s) => { + match s { + Signal::SIGHUP => write!(f, "HUP"), + Signal::SIGINT => write!(f, "INT"), + Signal::SIGQUIT => write!(f, "QUIT"), + Signal::SIGILL => write!(f, "ILL"), + Signal::SIGTRAP => write!(f, "TRAP"), + Signal::SIGABRT => write!(f, "ABRT"), + Signal::SIGBUS => write!(f, "BUS"), + Signal::SIGFPE => write!(f, "FPE"), + Signal::SIGKILL => write!(f, "KILL"), + Signal::SIGUSR1 => write!(f, "USR1"), + Signal::SIGSEGV => write!(f, "SEGV"), + Signal::SIGUSR2 => write!(f, "USR2"), + Signal::SIGPIPE => write!(f, "PIPE"), + Signal::SIGALRM => write!(f, "ALRM"), + Signal::SIGTERM => write!(f, "TERM"), + Signal::SIGSTKFLT => write!(f, "STKFLT"), + Signal::SIGCHLD => write!(f, "CHLD"), + Signal::SIGCONT => write!(f, "CONT"), + Signal::SIGSTOP => write!(f, "STOP"), + Signal::SIGTSTP => write!(f, "TSTP"), + Signal::SIGTTIN => write!(f, "TTIN"), + Signal::SIGTTOU => write!(f, "TTOU"), + Signal::SIGURG => write!(f, "URG"), + Signal::SIGXCPU => write!(f, "XCPU"), + Signal::SIGXFSZ => write!(f, "XFSZ"), + Signal::SIGVTALRM => write!(f, "VTALRM"), + Signal::SIGPROF => write!(f, "PROF"), + Signal::SIGWINCH => write!(f, "WINCH"), + Signal::SIGIO => write!(f, "IO"), + Signal::SIGPWR => write!(f, "PWR"), + Signal::SIGSYS => write!(f, "SYS"), + + _ => { + log::warn!("TrapTarget::fmt() : unrecognized signal {}", s); + Err(std::fmt::Error) + } + } + } + } + } +} + +pub fn trap(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { + let span = node.get_span(); + let NdRule::Command { + assignments: _, + argv, + } = node.class + else { + unreachable!() + }; + + let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?; + + if argv.is_empty() { + let stdout = borrow_fd(STDOUT_FILENO); + + return read_logic(|l| -> ShResult<()> { + for l in l.traps() { + let target = l.0; + let command = l.1; + write(stdout, format!("trap -- '{command}' {target}\n").as_bytes())?; + } + Ok(()) + }); + } + + if argv.len() == 1 { + let stderr = borrow_fd(STDERR_FILENO); + write(stderr, b"usage: trap [SIGNAL...]\n")?; + state::set_status(1); + return Ok(()) + } + + let mut args = argv.into_iter(); + + let command = args.next().unwrap().0; + let mut targets = vec![]; + + while let Some((arg, _)) = args.next() { + let target = arg.parse::()?; + targets.push(target); + } + + for target in targets { + if &command == "-" { + write_logic(|l| l.remove_trap(target)) + } else { + write_logic(|l| l.insert_trap(target, command.clone())) + } + } + + state::set_status(0); + Ok(()) +} diff --git a/src/jobs.rs b/src/jobs.rs index 7db2da8..007d4be 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -2,7 +2,7 @@ use crate::{ libsh::{ error::ShResult, term::{Style, Styled}, - }, prelude::*, procio::{IoMode, borrow_fd}, signal::{disable_reaping, enable_reaping}, state::{self, set_status, write_jobs} + }, prelude::*, procio::{IoMode, borrow_fd}, signal::{disable_reaping, enable_reaping}, state::{self, set_status, read_jobs, write_jobs} }; pub const SIG_EXIT_OFFSET: i32 = 128; @@ -610,11 +610,7 @@ impl Job { let job_stat = *self.get_stats().get(i).unwrap(); let fmt_stat = DisplayWaitStatus(job_stat).to_string(); - let mut stat_line = if init { - "".to_string() - } else { - fmt_stat.clone() - }; + let mut stat_line = fmt_stat.clone(); stat_line = format!("{}{} ", pid, stat_line); stat_line = format!("{} {}", stat_line, cmd); stat_line = match job_stat { diff --git a/src/main.rs b/src/main.rs index bc82e3e..d7e2df3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ use nix::poll::{PollFd, PollFlags, PollTimeout, poll}; use nix::unistd::read; use nix::errno::Errno; +use crate::builtin::trap::TrapTarget; use crate::libsh::error::{ShErr, ShErrKind, ShResult}; use crate::parse::execute::exec_input; use crate::prelude::*; @@ -34,7 +35,7 @@ use crate::prompt::get_prompt; use crate::prompt::readline::term::raw_mode; use crate::prompt::readline::{FernVi, ReadlineEvent}; use crate::signal::{QUIT_CODE, check_signals, sig_setup, signals_pending}; -use crate::state::{source_rc, write_meta}; +use crate::state::{read_logic, source_rc, write_meta}; use clap::Parser; use state::{read_vars, write_vars}; @@ -85,6 +86,11 @@ fn main() -> ExitCode { eprintln!("fern: {e}"); }; + if let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Exit)) + && let Err(e) = exec_input(trap, None, false) { + eprintln!("fern: error running EXIT trap: {e}"); + } + ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8) } diff --git a/src/parse/execute.rs b/src/parse/execute.rs index 102b472..b1195c7 100644 --- a/src/parse/execute.rs +++ b/src/parse/execute.rs @@ -2,7 +2,7 @@ use std::collections::{HashSet, VecDeque}; use crate::{ builtin::{ - alias::{alias, unalias}, cd::cd, echo::echo, export::export, flowctl::flowctl, jobctl::{JobBehavior, continue_job, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, zoltraak::zoltraak + alias::{alias, unalias}, cd::cd, echo::echo, export::export, flowctl::flowctl, jobctl::{JobBehavior, continue_job, jobs}, pwd::pwd, read::read_builtin, shift::shift, shopt::shopt, source::source, test::double_bracket_test, trap::{TrapTarget, trap}, zoltraak::zoltraak }, expand::expand_aliases, jobs::{ChildProc, JobStack, dispatch_job}, @@ -126,7 +126,16 @@ pub fn exec_input(input: String, io_stack: Option, interactive: bool) - if let Some(mut stack) = io_stack { dispatcher.io_stack.extend(stack.drain(..)); } - dispatcher.begin_dispatch() + let result = dispatcher.begin_dispatch(); + + if state::get_status() != 0 + && let Some(trap) = read_logic(|l| l.get_trap(TrapTarget::Error)) { + let saved_status = state::get_status(); + exec_input(trap, None, false)?; + state::set_status(saved_status); + } + + result } pub struct Dispatcher { @@ -602,6 +611,7 @@ impl Dispatcher { "zoltraak" => zoltraak(cmd, io_stack_mut, curr_job_mut), "shopt" => shopt(cmd, io_stack_mut, curr_job_mut), "read" => read_builtin(cmd, io_stack_mut, curr_job_mut), + "trap" => trap(cmd, io_stack_mut, curr_job_mut), _ => unimplemented!( "Have not yet added support for builtin '{}'", cmd_raw.span.as_str() diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 48ef9a7..45ef8c3 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -1238,11 +1238,18 @@ impl ParseStream { fn parse_pipeln(&mut self) -> ShResult> { let mut cmds = vec![]; let mut node_tks = vec![]; + let mut flags = NdFlags::empty(); + while let Some(cmd) = self.parse_block(false)? { let is_punctuated = node_is_punctuated(&cmd.tokens); node_tks.append(&mut cmd.tokens.clone()); cmds.push(cmd); - if *self.next_tk_class() != TkRule::Pipe || is_punctuated { + if *self.next_tk_class() == TkRule::Bg { + let tk = self.next_tk().unwrap(); + node_tks.push(tk.clone()); + flags |= NdFlags::BACKGROUND; + break; + } else if *self.next_tk_class() != TkRule::Pipe || is_punctuated { break; } else if let Some(pipe) = self.next_tk() { node_tks.push(pipe) @@ -1259,7 +1266,7 @@ impl ParseStream { cmds, pipe_err: false, }, - flags: NdFlags::empty(), + flags, redirs: vec![], tokens: node_tks, })) @@ -1271,6 +1278,7 @@ impl ParseStream { let mut node_tks = vec![]; let mut redirs = vec![]; let mut argv = vec![]; + let mut flags = NdFlags::empty(); let mut assignments = vec![]; while let Some(prefix_tk) = tk_iter.next() { @@ -1316,15 +1324,18 @@ impl ParseStream { return Ok(Some(Node { class: NdRule::Command { assignments, argv }, tokens: node_tks, - flags: NdFlags::empty(), + flags, redirs, })); } } while let Some(tk) = tk_iter.next() { + if *self.next_tk_class() == TkRule::Bg { + break; + } match tk.class { - TkRule::EOI | TkRule::Pipe | TkRule::And | TkRule::BraceGrpEnd | TkRule::Or => break, + TkRule::EOI | TkRule::Pipe | TkRule::And | TkRule::BraceGrpEnd | TkRule::Or | TkRule::Bg => break, TkRule::Sep => { node_tks.push(tk.clone()); break; @@ -1370,7 +1381,7 @@ impl ParseStream { Ok(Some(Node { class: NdRule::Command { assignments, argv }, tokens: node_tks, - flags: NdFlags::empty(), + flags, redirs, })) } diff --git a/src/prompt/readline/term.rs b/src/prompt/readline/term.rs index 116eeb6..9299532 100644 --- a/src/prompt/readline/term.rs +++ b/src/prompt/readline/term.rs @@ -17,7 +17,7 @@ use unicode_segmentation::UnicodeSegmentation; use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; use vte::{Parser, Perform}; -use crate::{prelude::*, procio::borrow_fd}; +use crate::{prelude::*, procio::borrow_fd, state::{read_meta, write_meta}}; use crate::{ libsh::error::{ShErr, ShErrKind, ShResult}, prompt::readline::keys::{KeyCode, ModKeys}, @@ -914,8 +914,16 @@ impl LineWriter for TermWriter { let end = new_layout.end; let cursor = new_layout.cursor; + if read_meta(|m| m.system_msg_pending()) { + let mut system_msg = String::new(); + while let Some(msg) = write_meta(|m| m.pop_system_message()) { + writeln!(system_msg, "{msg}").map_err(err)?; + } + self.buffer.push_str(&system_msg); + } + self.buffer.push_str(prompt); - self.buffer.push_str(&line.to_string()); + self.buffer.push_str(line); if end.col == 0 && end.row > 0 && !self.buffer.ends_with('\n') { // The line has wrapped. We need to use our own line break. diff --git a/src/signal.rs b/src/signal.rs index 018bd80..63c1740 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -1,47 +1,84 @@ -use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; +use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering}; use nix::sys::signal::{SaFlags, SigAction, sigaction}; use crate::{ - jobs::{JobCmdFlags, JobID, take_term}, - libsh::error::{ShErr, ShErrKind, ShResult}, - prelude::*, - state::{read_jobs, write_jobs}, + builtin::trap::TrapTarget, jobs::{JobCmdFlags, JobID, take_term}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::execute::exec_input, prelude::*, state::{read_jobs, read_logic, write_jobs, write_meta} }; -static GOT_SIGINT: AtomicBool = AtomicBool::new(false); -static GOT_SIGHUP: AtomicBool = AtomicBool::new(false); -static GOT_SIGTSTP: AtomicBool = AtomicBool::new(false); -static GOT_SIGCHLD: AtomicBool = AtomicBool::new(false); -static REAPING_ENABLED: AtomicBool = AtomicBool::new(true); +static SIGNALS: AtomicU64 = AtomicU64::new(0); +pub static REAPING_ENABLED: AtomicBool = AtomicBool::new(true); pub static SHOULD_QUIT: AtomicBool = AtomicBool::new(false); pub static QUIT_CODE: AtomicI32 = AtomicI32::new(0); +const MISC_SIGNALS: [Signal;22] = [ + Signal::SIGILL, + Signal::SIGTRAP, + Signal::SIGABRT, + Signal::SIGBUS, + Signal::SIGFPE, + Signal::SIGUSR1, + Signal::SIGSEGV, + Signal::SIGUSR2, + Signal::SIGPIPE, + Signal::SIGALRM, + Signal::SIGTERM, + Signal::SIGSTKFLT, + Signal::SIGCONT, + Signal::SIGURG, + Signal::SIGXCPU, + Signal::SIGXFSZ, + Signal::SIGVTALRM, + Signal::SIGPROF, + Signal::SIGWINCH, + Signal::SIGIO, + Signal::SIGPWR, + Signal::SIGSYS, +]; + pub fn signals_pending() -> bool { - GOT_SIGINT.load(Ordering::SeqCst) - || GOT_SIGHUP.load(Ordering::SeqCst) - || GOT_SIGTSTP.load(Ordering::SeqCst) - || (REAPING_ENABLED.load(Ordering::SeqCst) - && GOT_SIGCHLD.load(Ordering::SeqCst)) - || SHOULD_QUIT.load(Ordering::SeqCst) + SIGNALS.load(Ordering::SeqCst) != 0 || SHOULD_QUIT.load(Ordering::SeqCst) } pub fn check_signals() -> ShResult<()> { - if GOT_SIGINT.swap(false, Ordering::SeqCst) { + let pending = SIGNALS.swap(0, Ordering::SeqCst); + let got_signal = |sig: Signal| -> bool { pending & (1 << sig as u64) != 0 }; + let run_trap = |sig: Signal| -> ShResult<()> { + if let Some(command) = read_logic(|l| l.get_trap(TrapTarget::Signal(sig))) { + exec_input(command, None, false)?; + } + Ok(()) + }; + + if got_signal(Signal::SIGINT) { interrupt()?; + run_trap(Signal::SIGINT)?; return Err(ShErr::simple(ShErrKind::ClearReadline, "")); } - if GOT_SIGHUP.swap(false, Ordering::SeqCst) { + if got_signal(Signal::SIGHUP) { + run_trap(Signal::SIGHUP)?; hang_up(0); } - if GOT_SIGTSTP.swap(false, Ordering::SeqCst) { + if got_signal(Signal::SIGQUIT) { + run_trap(Signal::SIGQUIT)?; + hang_up(0); + } + if got_signal(Signal::SIGTSTP) { + run_trap(Signal::SIGTSTP)?; terminal_stop()?; } - if REAPING_ENABLED.load(Ordering::SeqCst) && GOT_SIGCHLD.swap(false, Ordering::SeqCst) { + if got_signal(Signal::SIGCHLD) && REAPING_ENABLED.load(Ordering::SeqCst) { + run_trap(Signal::SIGCHLD)?; wait_child()?; - } else if GOT_SIGCHLD.load(Ordering::SeqCst) { } + + for sig in MISC_SIGNALS { + if got_signal(sig) { + run_trap(sig)?; + } + } + if SHOULD_QUIT.load(Ordering::SeqCst) { let code = QUIT_CODE.load(Ordering::SeqCst); return Err(ShErr::simple(ShErrKind::CleanExit(code), "exit")); @@ -59,78 +96,52 @@ pub fn enable_reaping() { pub fn sig_setup() { let flags = SaFlags::empty(); - let actions = [ - SigAction::new( - SigHandler::Handler(handle_sigchld), - flags, - SigSet::empty(), - ), - SigAction::new( - SigHandler::Handler(handle_sigquit), - flags, - SigSet::empty(), - ), - SigAction::new( - SigHandler::Handler(handle_sigtstp), - flags, - SigSet::empty(), - ), - SigAction::new( - SigHandler::Handler(handle_sighup), - flags, - SigSet::empty(), - ), - SigAction::new( - SigHandler::Handler(handle_sigint), - flags, - SigSet::empty(), - ), - SigAction::new( // SIGTTIN - SigHandler::SigIgn, - flags, - SigSet::empty(), - ), - SigAction::new( // SIGTTOU - SigHandler::SigIgn, - flags, - SigSet::empty(), - ), - SigAction::new( - SigHandler::Handler(handle_sigwinch), - flags, - SigSet::empty(), - ), - ]; + let action = SigAction::new(SigHandler::Handler(handle_signal), flags, SigSet::empty()); + let ignore = SigAction::new(SigHandler::SigIgn, flags, SigSet::empty()); + unsafe { - sigaction(Signal::SIGCHLD, &actions[0]).unwrap(); - sigaction(Signal::SIGQUIT, &actions[1]).unwrap(); - sigaction(Signal::SIGTSTP, &actions[2]).unwrap(); - sigaction(Signal::SIGHUP, &actions[3]).unwrap(); - sigaction(Signal::SIGINT, &actions[4]).unwrap(); - sigaction(Signal::SIGTTIN, &actions[5]).unwrap(); - sigaction(Signal::SIGTTOU, &actions[6]).unwrap(); - sigaction(Signal::SIGWINCH, &actions[7]).unwrap(); + sigaction(Signal::SIGTTIN, &ignore).unwrap(); + sigaction(Signal::SIGTTOU, &ignore).unwrap(); + + sigaction(Signal::SIGCHLD, &action).unwrap(); + sigaction(Signal::SIGHUP, &action).unwrap(); + sigaction(Signal::SIGINT, &action).unwrap(); + sigaction(Signal::SIGQUIT, &action).unwrap(); + sigaction(Signal::SIGILL, &action).unwrap(); + sigaction(Signal::SIGTRAP, &action).unwrap(); + sigaction(Signal::SIGABRT, &action).unwrap(); + sigaction(Signal::SIGBUS, &action).unwrap(); + sigaction(Signal::SIGFPE, &action).unwrap(); + sigaction(Signal::SIGUSR1, &action).unwrap(); + sigaction(Signal::SIGSEGV, &action).unwrap(); + sigaction(Signal::SIGUSR2, &action).unwrap(); + sigaction(Signal::SIGPIPE, &action).unwrap(); + sigaction(Signal::SIGALRM, &action).unwrap(); + sigaction(Signal::SIGTERM, &action).unwrap(); + sigaction(Signal::SIGSTKFLT, &action).unwrap(); + sigaction(Signal::SIGCONT, &action).unwrap(); + sigaction(Signal::SIGTSTP, &action).unwrap(); + sigaction(Signal::SIGURG, &action).unwrap(); + sigaction(Signal::SIGXCPU, &action).unwrap(); + sigaction(Signal::SIGXFSZ, &action).unwrap(); + sigaction(Signal::SIGVTALRM, &action).unwrap(); + sigaction(Signal::SIGPROF, &action).unwrap(); + sigaction(Signal::SIGWINCH, &action).unwrap(); + sigaction(Signal::SIGIO, &action).unwrap(); + sigaction(Signal::SIGPWR, &action).unwrap(); + sigaction(Signal::SIGSYS, &action).unwrap(); } } -extern "C" fn handle_sigwinch(_: libc::c_int) { - /* do nothing - * this exists for the sole purpose of interrupting readline - * readline will be refreshed after the interruption, - * which will cause window size calculations to be re-run - * and we get window resize handling for free as a result - */ -} - -extern "C" fn handle_sighup(_: libc::c_int) { - GOT_SIGHUP.store(true, Ordering::SeqCst); - SHOULD_QUIT.store(true, Ordering::SeqCst); - QUIT_CODE.store(128 + libc::SIGHUP, Ordering::SeqCst); +extern "C" fn handle_signal(sig: libc::c_int) { + SIGNALS.fetch_or(1 << sig, Ordering::SeqCst); } pub fn hang_up(_: libc::c_int) { + SHOULD_QUIT.store(true, Ordering::SeqCst); + QUIT_CODE.store(1, Ordering::SeqCst); write_jobs(|j| { for job in j.jobs_mut().iter_mut().flatten() { job.killpg(Signal::SIGTERM).ok(); @@ -138,10 +149,6 @@ pub fn hang_up(_: libc::c_int) { }); } -extern "C" fn handle_sigtstp(_: libc::c_int) { - GOT_SIGTSTP.store(true, Ordering::SeqCst); -} - pub fn terminal_stop() -> ShResult<()> { write_jobs(|j| { if let Some(job) = j.get_fg_mut() { @@ -153,10 +160,6 @@ pub fn terminal_stop() -> ShResult<()> { // TODO: It seems like there is supposed to be a take_term() call here } -extern "C" fn handle_sigint(_: libc::c_int) { - GOT_SIGINT.store(true, Ordering::SeqCst); -} - pub fn interrupt() -> ShResult<()> { write_jobs(|j| { if let Some(job) = j.get_fg_mut() { @@ -167,20 +170,11 @@ pub fn interrupt() -> ShResult<()> { }) } -extern "C" fn handle_sigquit(_: libc::c_int) { - SHOULD_QUIT.store(true, Ordering::SeqCst); - QUIT_CODE.store(128 + libc::SIGQUIT, Ordering::SeqCst); -} - -extern "C" fn handle_sigchld(_: libc::c_int) { - GOT_SIGCHLD.store(true, Ordering::SeqCst); -} - pub fn wait_child() -> ShResult<()> { let flags = WtFlag::WNOHANG | WtFlag::WSTOPPED; while let Ok(status) = waitpid(None, Some(flags)) { match status { - WtStat::Exited(pid, code) => { + WtStat::Exited(pid, _) => { child_exited(pid, status)?; } WtStat::Signaled(pid, signal, _) => { @@ -284,7 +278,8 @@ pub fn child_exited(pid: Pid, status: WtStat) -> ShResult<()> { let job_order = read_jobs(|j| j.order().to_vec()); let result = read_jobs(|j| j.query(JobID::Pgid(pgid)).cloned()); if let Some(job) = result { - println!("{}", job.display(&job_order, JobCmdFlags::PIDS)) + let job_complete_msg = job.display(&job_order, JobCmdFlags::PIDS).to_string(); + write_meta(|m| m.post_system_message(job_complete_msg)) } } } diff --git a/src/state.rs b/src/state.rs index d1999dd..b8e5974 100644 --- a/src/state.rs +++ b/src/state.rs @@ -5,15 +5,10 @@ use std::{ use nix::unistd::{gethostname, getppid, User}; use crate::{ - exec_input, - jobs::JobTab, - libsh::{ + builtin::trap::TrapTarget, exec_input, jobs::JobTab, libsh::{ error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt, - }, - parse::{ConjunctNode, NdRule, Node, ParsedSrc}, - prelude::*, - shopt::ShOpts, + }, parse::{ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*, shopt::ShOpts }; pub struct Fern { @@ -292,6 +287,7 @@ impl Deref for ShFunc { pub struct LogTab { functions: HashMap, aliases: HashMap, + traps: HashMap, } impl LogTab { @@ -301,6 +297,18 @@ impl LogTab { pub fn insert_func(&mut self, name: &str, src: ShFunc) { self.functions.insert(name.into(), src); } + pub fn insert_trap(&mut self, target: TrapTarget, command: String) { + self.traps.insert(target, command); + } + pub fn get_trap(&self, target: TrapTarget) -> Option { + self.traps.get(&target).cloned() + } + pub fn remove_trap(&mut self, target: TrapTarget) { + self.traps.remove(&target); + } + pub fn traps(&self) -> &HashMap { + &self.traps + } pub fn get_func(&self, name: &str) -> Option { self.functions.get(name).cloned() } @@ -682,8 +690,12 @@ impl VarTab { /// A table of metadata for the shell #[derive(Default, Debug)] pub struct MetaTab { + // command running duration runtime_start: Option, runtime_stop: Option, + + // pending system messages + system_msg: Vec } impl MetaTab { @@ -703,6 +715,15 @@ impl MetaTab { None } } + pub fn post_system_message(&mut self, message: String) { + self.system_msg.push(message); + } + pub fn pop_system_message(&mut self) -> Option { + self.system_msg.pop() + } + pub fn system_msg_pending(&self) -> bool { + !self.system_msg.is_empty() + } } /// Read from the job table