diff --git a/brush-core/src/builtins/exit.rs b/brush-core/src/builtins/exit.rs index 9c5b9d02..a57c3bb8 100644 --- a/brush-core/src/builtins/exit.rs +++ b/brush-core/src/builtins/exit.rs @@ -1,5 +1,7 @@ use clap::Parser; +use std::io::Write; + use crate::{builtins, commands}; /// Exit the shell. @@ -14,6 +16,18 @@ impl builtins::Command for ExitCommand { &self, context: commands::ExecutionContext<'_>, ) -> Result { + if !context.shell.jobs.jobs.is_empty() && context.shell.user_tried_exiting == 0 { + writeln!(context.stdout(), "brush: You have suspended jobs.")?; + + context.shell.user_tried_exiting = 2; // get's decreased this input too so next input will be 1 + + return Ok(builtins::ExitCode::Custom(1)) + } + + for job in &mut context.shell.jobs.jobs { + job.kill(Some(nix::sys::signal::SIGHUP))?; + } + let code_8bit: u8; #[allow(clippy::cast_sign_loss)] diff --git a/brush-core/src/builtins/kill.rs b/brush-core/src/builtins/kill.rs index 3a134104..b73c4d99 100644 --- a/brush-core/src/builtins/kill.rs +++ b/brush-core/src/builtins/kill.rs @@ -49,7 +49,7 @@ impl builtins::Command for KillCommand { if pid_or_job_spec.starts_with('%') { // It's a job spec. if let Some(job) = context.shell.jobs.resolve_job_spec(pid_or_job_spec) { - job.kill()?; + job.kill(None)?; } else { writeln!( context.stderr(), diff --git a/brush-core/src/jobs.rs b/brush-core/src/jobs.rs index 65f573b8..c511b056 100644 --- a/brush-core/src/jobs.rs +++ b/brush-core/src/jobs.rs @@ -408,9 +408,9 @@ impl Job { } /// Kills the job. - pub fn kill(&mut self) -> Result<(), error::Error> { + pub fn kill(&mut self, signal: Option) -> Result<(), error::Error> { if let Some(pid) = self.get_process_group_id() { - sys::signal::kill_process(pid) + sys::signal::kill_process(pid, signal) } else { Err(error::Error::FailedToSendSignal) } diff --git a/brush-core/src/shell.rs b/brush-core/src/shell.rs index 7e5db481..97feed9e 100644 --- a/brush-core/src/shell.rs +++ b/brush-core/src/shell.rs @@ -42,6 +42,9 @@ pub struct Shell { // Additional state /// The status of the last completed command. pub last_exit_status: u8, + + /// User tried exiting in the previous/current input if != 0. + pub user_tried_exiting: u8, /// Clone depth from the original ancestor shell. pub depth: usize, @@ -100,6 +103,7 @@ impl Clone for Shell { builtins: self.builtins.clone(), program_location_cache: self.program_location_cache.clone(), depth: self.depth + 1, + user_tried_exiting: self.user_tried_exiting } } } @@ -191,6 +195,7 @@ impl Shell { builtins: builtins::get_default_builtins(options), program_location_cache: pathcache::PathCache::default(), depth: 0, + user_tried_exiting: 0 }; // TODO: Without this a script that sets extglob will fail because we diff --git a/brush-core/src/sys/unix/signal.rs b/brush-core/src/sys/unix/signal.rs index 67cb1a6d..1e859c1e 100644 --- a/brush-core/src/sys/unix/signal.rs +++ b/brush-core/src/sys/unix/signal.rs @@ -7,8 +7,8 @@ pub(crate) fn continue_process(pid: sys::process::ProcessId) -> Result<(), error Ok(()) } -pub(crate) fn kill_process(pid: sys::process::ProcessId) -> Result<(), error::Error> { - nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), nix::sys::signal::SIGKILL) +pub(crate) fn kill_process(pid: sys::process::ProcessId, signal: Option) -> Result<(), error::Error> { + nix::sys::signal::kill(nix::unistd::Pid::from_raw(pid), signal.unwrap_or_else(|| nix::sys::signal::SIGKILL)) .map_err(|_errno| error::Error::FailedToSendSignal)?; Ok(()) diff --git a/brush-interactive/src/interactive_shell.rs b/brush-interactive/src/interactive_shell.rs index cfe0a360..bc6d6565 100644 --- a/brush-interactive/src/interactive_shell.rs +++ b/brush-interactive/src/interactive_shell.rs @@ -135,6 +135,10 @@ pub trait InteractiveShell { ReadResult::Input(read_result) => { let mut shell_mut = self.shell_mut(); + if shell_mut.as_mut().user_tried_exiting > 0 && !read_result.is_empty() { + shell_mut.as_mut().user_tried_exiting -= 1; + } + let precmd_prompt = shell_mut.as_mut().compose_precmd_prompt().await?; if !precmd_prompt.is_empty() { print!("{precmd_prompt}");