From c3eb46cadffe4fe1eb6d51327f81d7c6c40aab48 Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Sat, 9 Dec 2017 00:21:48 -0500
Subject: [PATCH] Implement huponexit shell option

To enable this option, use `set -o huponexit`. This will send **SIGHUP** to all background jobs when
exiting the shell. If a background job is stopped, that job will be resumed with a **SIGCONT**
before being sent a **SIGHUP**.
---
 src/builtins/set.rs                | 30 ++++++++++++++++++------------
 src/shell/flags.rs                 |  1 +
 src/shell/mod.rs                   | 13 +++++++++++--
 src/shell/pipe_exec/job_control.rs | 10 ++++++++++
 4 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/src/builtins/set.rs b/src/builtins/set.rs
index c48049a6..e97fe16f 100644
--- a/src/builtins/set.rs
+++ b/src/builtins/set.rs
@@ -1,7 +1,6 @@
 use liner::KeyBindings;
 use shell::Shell;
 use shell::flags::*;
-use std::io::{self, Write};
 use std::iter;
 
 enum PositionalArgs {
@@ -12,7 +11,6 @@ enum PositionalArgs {
 use self::PositionalArgs::*;
 
 pub(crate) fn set(args: &[&str], shell: &mut Shell) -> i32 {
-    let stderr = io::stderr();
     let mut args_iter = args.iter();
     let mut positionals = None;
 
@@ -32,22 +30,19 @@ pub(crate) fn set(args: &[&str], shell: &mut Shell) -> i32 {
                 match flag {
                     b'e' => shell.flags |= ERR_EXIT,
                     b'o' => match args_iter.next() {
-                        Some(&mode) if mode == "vi" => {
-                            if let Some(context) = shell.context.as_mut() {
-                                context.key_bindings = KeyBindings::Vi;
-                            }
+                        Some(&"vi") => if let Some(context) = shell.context.as_mut() {
+                            context.key_bindings = KeyBindings::Vi;
                         }
-                        Some(&mode) if mode == "emacs" => {
-                            if let Some(context) = shell.context.as_mut() {
-                                context.key_bindings = KeyBindings::Emacs;
-                            }
+                        Some(&"emacs") => if let Some(context) = shell.context.as_mut() {
+                            context.key_bindings = KeyBindings::Emacs;
                         }
+                        Some(&"huponexit") => shell.flags |= HUPONEXIT,
                         Some(_) => {
-                            let _ = stderr.lock().write_all(b"set: invalid keymap\n");
+                            eprintln!("ion: set: invalid option");
                             return 0;
                         }
                         None => {
-                            let _ = stderr.lock().write_all(b"set: no keymap given\n");
+                            eprintln!("ion: set: no option given");
                             return 0;
                         }
                     },
@@ -60,6 +55,17 @@ pub(crate) fn set(args: &[&str], shell: &mut Shell) -> i32 {
                 match flag {
                     b'e' => shell.flags &= 255 ^ ERR_EXIT,
                     b'x' => shell.flags &= 255 ^ PRINT_COMMS,
+                    b'o' => match args_iter.next() {
+                        Some(&"huponexit") => shell.flags &= 255 ^ HUPONEXIT,
+                        Some(_) => {
+                            eprintln!("ion: set: invalid option");
+                            return 0;
+                        }
+                        None => {
+                            eprintln!("ion: set: no option given");
+                            return 0;
+                        }
+                    }
                     _ => return 0,
                 }
             }
diff --git a/src/shell/flags.rs b/src/shell/flags.rs
index b5de908e..9dcb63b9 100644
--- a/src/shell/flags.rs
+++ b/src/shell/flags.rs
@@ -1,3 +1,4 @@
 pub const ERR_EXIT: u8 = 1;
 pub const PRINT_COMMS: u8 = 2;
 pub const NO_EXEC: u8 = 4;
+pub const HUPONEXIT: u8 = 8;
\ No newline at end of file
diff --git a/src/shell/mod.rs b/src/shell/mod.rs
index 88e36656..00397a61 100644
--- a/src/shell/mod.rs
+++ b/src/shell/mod.rs
@@ -165,9 +165,18 @@ impl<'a> Shell {
     }
 
     pub(crate) fn exit(&mut self, status: i32) -> ! {
-        if let Some(context) = self.context.as_mut() {
-            context.history.commit_history();
+        // The context has two purposes: if it exists, this is an interactive shell; and the
+        // context will also be sent a signal to commit all changes to the history file,
+        // and waiting for the history thread in the background to finish.
+        if self.context.is_some() {
+            if self.flags & HUPONEXIT != 0 {
+                self.resume_stopped();
+                self.background_send(sys::SIGHUP);
+            }
+            let context = self.context.as_mut().unwrap();
+            context.history.commit_history()
         }
+        
         process::exit(status);
     }
 
diff --git a/src/shell/pipe_exec/job_control.rs b/src/shell/pipe_exec/job_control.rs
index ebc03148..aab408fe 100644
--- a/src/shell/pipe_exec/job_control.rs
+++ b/src/shell/pipe_exec/job_control.rs
@@ -29,6 +29,7 @@ pub(crate) trait JobControl {
     /// will
     /// be returned, and ownership of the TTY given back to the shell.
     fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> i32;
+    fn resume_stopped(&mut self);
     fn handle_signal(&self, signal: i32) -> bool;
     fn foreground_send(&self, signal: i32);
     fn background_send(&self, signal: i32);
@@ -177,6 +178,15 @@ impl JobControl for Shell {
         }
     }
 
+    /// Resumes all stopped background jobs
+    fn resume_stopped(&mut self) {
+        for process in self.background.lock().unwrap().iter() {
+            if process.state == ProcessState::Stopped {
+                signals::resume(process.pid);
+            }
+        }
+    }
+
     /// Send a kill signal to all running background tasks.
     fn background_send(&self, signal: i32) {
         if signal == sys::SIGHUP {
-- 
GitLab