diff --git a/src/builtin/eval.rs b/src/builtin/eval.rs new file mode 100644 index 0000000..d38d294 --- /dev/null +++ b/src/builtin/eval.rs @@ -0,0 +1,34 @@ +use nix::{errno::Errno, unistd::execvpe}; + +use crate::{ + builtin::setup_builtin, + jobs::JobBldr, + libsh::error::ShResult, + parse::{NdRule, Node, execute::exec_input}, + procio::IoStack, + state, +}; + +pub fn eval(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { + let NdRule::Command { + assignments: _, + argv, + } = node.class + else { + unreachable!() + }; + + let (expanded_argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?; + + if expanded_argv.is_empty() { + state::set_status(0); + return Ok(()); + } + + let joined_argv = expanded_argv.into_iter() + .map(|(s, _)| s) + .collect::>() + .join(" "); + + exec_input(joined_argv, None, false) +} diff --git a/src/builtin/export.rs b/src/builtin/export.rs index f7fb020..afea5cf 100644 --- a/src/builtin/export.rs +++ b/src/builtin/export.rs @@ -4,7 +4,7 @@ use crate::{ parse::{NdRule, Node}, prelude::*, procio::{IoStack, borrow_fd}, - state::{self, VarFlags, write_vars}, + state::{self, VarFlags, read_vars, write_vars}, }; use super::setup_builtin; @@ -45,3 +45,42 @@ pub fn export(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult state::set_status(0); Ok(()) } + +pub fn local(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { + 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() { + // Display the local variables + let vars_output = read_vars(|v| { + let mut vars = v.flatten_vars() + .into_iter() + .map(|(k, v)| format!("{}={}", k, v)) + .collect::>(); + vars.sort(); + let mut vars_joined = vars.join("\n"); + vars_joined.push('\n'); + vars_joined + }); + + let stdout = borrow_fd(STDOUT_FILENO); + write(stdout, vars_output.as_bytes())?; // Write it + } else { + for (arg, _) in argv { + if let Some((var, val)) = arg.split_once('=') { + write_vars(|v| v.set_var(var, val, VarFlags::LOCAL)); + } else { + write_vars(|v| v.set_var(&arg, "", VarFlags::LOCAL)); // Create an uninitialized local variable + } + } + } + state::set_status(0); + Ok(()) +} diff --git a/src/builtin/jobctl.rs b/src/builtin/jobctl.rs index c23b7a8..fb0972e 100644 --- a/src/builtin/jobctl.rs +++ b/src/builtin/jobctl.rs @@ -179,3 +179,46 @@ pub fn jobs(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<( Ok(()) } + +pub fn disown(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> { + let blame = node.get_span().clone(); + let NdRule::Command { + assignments: _, + argv, + } = node.class + else { + unreachable!() + }; + + let (argv, _guard) = setup_builtin(argv, job, Some((io_stack, node.redirs)))?; + let mut argv = argv.into_iter(); + + let curr_job_id = if let Some(id) = read_jobs(|j| j.curr_job()) { + id + } else { + return Err(ShErr::full(ShErrKind::ExecFail, "disown: No jobs to disown", blame)); + }; + + let mut tabid = curr_job_id; + let mut nohup = false; + let mut disown_all = false; + + while let Some((arg, span)) = argv.next() { + match arg.as_str() { + "-h" => nohup = true, + "-a" => disown_all = true, + _ => { + tabid = parse_job_id(&arg, span.clone())?; + } + } + } + + if disown_all { + write_jobs(|j| j.disown_all(nohup))?; + } else { + write_jobs(|j| j.disown(JobID::TableID(tabid), nohup))?; + } + + state::set_status(0); + Ok(()) +} diff --git a/src/builtin/local.rs b/src/builtin/local.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/builtin/local.rs @@ -0,0 +1 @@ + diff --git a/src/builtin/mod.rs b/src/builtin/mod.rs index e05c959..563c7db 100644 --- a/src/builtin/mod.rs +++ b/src/builtin/mod.rs @@ -27,11 +27,12 @@ pub mod trap; pub mod zoltraak; pub mod dirstack; pub mod exec; +pub mod eval; -pub const BUILTINS: [&str; 25] = [ - "echo", "cd", "read", "export", "pwd", "source", "shift", "jobs", "fg", "bg", "alias", "unalias", +pub const BUILTINS: [&str; 28] = [ + "echo", "cd", "read", "export", "local", "pwd", "source", "shift", "jobs", "fg", "bg", "disown", "alias", "unalias", "return", "break", "continue", "exit", "zoltraak", "shopt", "builtin", "command", "trap", - "pushd", "popd", "dirs", "exec", + "pushd", "popd", "dirs", "exec", "eval" ]; /// Sets up a builtin command diff --git a/src/jobs.rs b/src/jobs.rs index 653e295..3e3d0ed 100644 --- a/src/jobs.rs +++ b/src/jobs.rs @@ -373,7 +373,7 @@ impl JobTab { if job .get_stats() .iter() - .all(|stat| matches!(stat, WtStat::Exited(_, _))) + .all(|stat| matches!(stat, WtStat::Exited(_, _) | WtStat::Signaled(_, _, _))) { jobs_to_remove.push(JobID::TableID(id)); } @@ -383,6 +383,41 @@ impl JobTab { } Ok(()) } + + pub fn hang_up(&mut self) { + for job in self.jobs_mut().iter_mut().flatten() { + if job.send_hup { + job.killpg(Signal::SIGHUP).ok(); + } + } + } + + pub fn disown(&mut self, id: JobID, nohup: bool) -> ShResult<()> { + if let Some(job) = self.query_mut(id.clone()) { + if nohup { + job.no_hup(); + } else { + self.remove_job(id); + } + } + Ok(()) + } + + pub fn disown_all(&mut self, nohup: bool) -> ShResult<()> { + let mut ids_to_remove = vec![]; + for job in self.jobs_mut().iter_mut().flatten() { + if nohup { + job.no_hup(); + } else { + ids_to_remove.push(JobID::TableID(job.tabid().unwrap())); + } + } + + for id in ids_to_remove { + self.remove_job(id); + } + Ok(()) + } } #[derive(Debug)] @@ -390,6 +425,7 @@ pub struct JobBldr { table_id: Option, pgid: Option, children: Vec, + send_hup: bool, } impl Default for JobBldr { @@ -404,6 +440,7 @@ impl JobBldr { table_id: None, pgid: None, children: vec![], + send_hup: true, } } pub fn with_id(self, id: usize) -> Self { @@ -411,6 +448,7 @@ impl JobBldr { table_id: Some(id), pgid: self.pgid, children: self.children, + send_hup: self.send_hup, } } pub fn with_pgid(self, pgid: Pid) -> Self { @@ -418,6 +456,7 @@ impl JobBldr { table_id: self.table_id, pgid: Some(pgid), children: self.children, + send_hup: self.send_hup, } } pub fn set_pgid(&mut self, pgid: Pid) { @@ -426,11 +465,16 @@ impl JobBldr { pub fn pgid(&self) -> Option { self.pgid } + pub fn no_hup(mut self) -> Self { + self.send_hup = false; + self + } pub fn with_children(self, children: Vec) -> Self { Self { table_id: self.table_id, pgid: self.pgid, children, + send_hup: self.send_hup, } } pub fn push_child(&mut self, child: ChildProc) { @@ -441,6 +485,7 @@ impl JobBldr { table_id: self.table_id, pgid: self.pgid.unwrap_or(Pid::from_raw(0)), children: self.children, + send_hup: self.send_hup, } } } @@ -469,12 +514,16 @@ pub struct Job { table_id: Option, pgid: Pid, children: Vec, + send_hup: bool, } impl Job { pub fn set_tabid(&mut self, id: usize) { self.table_id = Some(id) } + pub fn no_hup(&mut self) { + self.send_hup = false; + } pub fn running(&self) -> bool { !self.children.iter().all(|chld| chld.exited()) } @@ -520,8 +569,7 @@ impl Job { let stat = match sig { Signal::SIGTSTP => WtStat::Stopped(self.pgid, Signal::SIGTSTP), Signal::SIGCONT => WtStat::Continued(self.pgid), - Signal::SIGTERM => WtStat::Signaled(self.pgid, Signal::SIGTERM, false), - _ => unimplemented!("{}", sig), + sig => WtStat::Signaled(self.pgid, sig, false), }; self.set_stats(stat); Ok(killpg(self.pgid, sig)?) diff --git a/src/main.rs b/src/main.rs index 3ecd031..68b0bf9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,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::{read_logic, source_rc, write_meta}; +use crate::state::{read_logic, source_rc, write_jobs, write_meta}; use clap::Parser; use state::{read_vars, write_vars}; @@ -74,9 +74,25 @@ fn kickstart_lazy_evals() { read_vars(|_| {}); } +/// We need to make sure that even if we panic, our child processes get sighup +fn setup_panic_handler() { + let default_panic_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + let _ = state::FERN.try_with(|fern| { + if let Ok(mut jobs) = fern.jobs.try_borrow_mut() { + jobs.hang_up(); + } + }); + + default_panic_hook(info); + })); +} + fn main() -> ExitCode { env_logger::init(); kickstart_lazy_evals(); + setup_panic_handler(); + let mut args = FernArgs::parse(); if env::args().next().is_some_and(|a| a.starts_with('-')) { // first arg is '-fern' @@ -84,7 +100,7 @@ fn main() -> ExitCode { args.login_shell = true; } if args.version { - println!("fern {}", env!("CARGO_PKG_VERSION")); + println!("fern {} ({} {})", env!("CARGO_PKG_VERSION"), std::env::consts::ARCH, std::env::consts::OS); return ExitCode::SUCCESS; } @@ -104,6 +120,7 @@ fn main() -> ExitCode { eprintln!("fern: error running EXIT trap: {e}"); } + write_jobs(|j| j.hang_up()); ExitCode::from(QUIT_CODE.load(Ordering::SeqCst) as u8) } diff --git a/src/parse/execute.rs b/src/parse/execute.rs index a04b119..060e817 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, dirstack::{dirs, popd, pushd}, echo::echo, exec, 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 + alias::{alias, unalias}, cd::cd, dirstack::{dirs, popd, pushd}, echo::echo, eval, exec, export::{export, local}, flowctl::flowctl, jobctl::{JobBehavior, continue_job, disown, 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}, @@ -588,11 +588,13 @@ impl Dispatcher { "echo" => echo(cmd, io_stack_mut, curr_job_mut), "cd" => cd(cmd, curr_job_mut), "export" => export(cmd, io_stack_mut, curr_job_mut), + "local" => local(cmd, io_stack_mut, curr_job_mut), "pwd" => pwd(cmd, io_stack_mut, curr_job_mut), "source" => source(cmd, curr_job_mut), "shift" => shift(cmd, curr_job_mut), "fg" => continue_job(cmd, curr_job_mut, JobBehavior::Foregound), "bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background), + "disown" => disown(cmd, io_stack_mut, curr_job_mut), "jobs" => jobs(cmd, io_stack_mut, curr_job_mut), "alias" => alias(cmd, io_stack_mut, curr_job_mut), "unalias" => unalias(cmd, io_stack_mut, curr_job_mut), @@ -608,6 +610,7 @@ impl Dispatcher { "popd" => popd(cmd, io_stack_mut, curr_job_mut), "dirs" => dirs(cmd, io_stack_mut, curr_job_mut), "exec" => exec::exec_builtin(cmd, io_stack_mut, curr_job_mut), + "eval" => eval::eval(cmd, io_stack_mut, curr_job_mut), _ => unimplemented!( "Have not yet added support for builtin '{}'", cmd_raw.span.as_str() diff --git a/src/prompt/readline/mod.rs b/src/prompt/readline/mod.rs index 6952f58..ebb365c 100644 --- a/src/prompt/readline/mod.rs +++ b/src/prompt/readline/mod.rs @@ -677,15 +677,19 @@ pub fn get_insertions(input: &str) -> Vec<(usize, Marker)> { /// - Unimplemented features (comments, brace groups) pub fn marker_for(class: &TkRule) -> Option { match class { - TkRule::Pipe | TkRule::ErrPipe | TkRule::And | TkRule::Or | TkRule::Bg => { + TkRule::Pipe | + TkRule::ErrPipe | + TkRule::And | + TkRule::Or | + TkRule::Bg | + TkRule::BraceGrpStart | + TkRule::BraceGrpEnd => { Some(markers::OPERATOR) } TkRule::Sep => Some(markers::CMD_SEP), TkRule::Redir => Some(markers::REDIRECT), TkRule::CasePattern => Some(markers::CASE_PAT), - TkRule::BraceGrpStart => todo!(), - TkRule::BraceGrpEnd => todo!(), - TkRule::Comment => todo!(), + TkRule::Comment => Some(markers::COMMENT), TkRule::Expanded { exp: _ } | TkRule::EOI | TkRule::SOI | TkRule::Null | TkRule::Str => None, } } diff --git a/src/signal.rs b/src/signal.rs index dda561e..734247e 100644 --- a/src/signal.rs +++ b/src/signal.rs @@ -147,9 +147,7 @@ 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(); - } + j.hang_up(); }); }