From 3387390a717a81029f48e7dd887f2f60ba05ee6f Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Thu, 6 Jul 2017 20:27:01 -0400
Subject: [PATCH] Implement Process Groups

Almost everything is working now. The target/debug/ion command doesn't work yet though.
---
 src/builtins/job_control.rs |  63 ++++-----
 src/main.rs                 |  32 ++++-
 src/shell/job_control.rs    |  92 ++++++++++--
 src/shell/pipe.rs           | 271 +++++++++++++++++-------------------
 4 files changed, 260 insertions(+), 198 deletions(-)

diff --git a/src/builtins/job_control.rs b/src/builtins/job_control.rs
index ba542fe9..af0aea9d 100644
--- a/src/builtins/job_control.rs
+++ b/src/builtins/job_control.rs
@@ -2,10 +2,21 @@ use shell::Shell;
 use shell::job_control::{JobControl, ProcessState};
 use shell::status::*;
 use std::io::{stderr, Write};
-use std::thread::sleep;
-use std::time::Duration;
 #[cfg(not(target_os = "redox"))] use nix::sys::signal::{self, Signal};
-#[cfg(not(target_os = "redox"))] use libc::{self, pid_t};
+#[cfg(not(target_os = "redox"))] use nix::unistd;
+
+#[cfg(all(unix, not(target_os = "redox")))]
+/// When given a process ID, that process's group will be assigned as the foreground process group.
+pub fn set_foreground(pid: u32) {
+    let _ = unistd::tcsetpgrp(0, pid as i32);
+    let _ = unistd::tcsetpgrp(1, pid as i32);
+    let _ = unistd::tcsetpgrp(2, pid as i32);
+}
+
+#[cfg(target_os = "redox")]
+pub fn set_foreground(pid: u32) {
+    // TODO
+}
 
 /// Display a list of all jobs running in the background.
 pub fn jobs(shell: &mut Shell) {
@@ -19,32 +30,7 @@ pub fn jobs(shell: &mut Shell) {
     }
 }
 
-#[cfg(not(target_os = "redox"))]
-fn fg_listen(shell: &mut Shell, job: u32) {
-    loop {
-        sleep(Duration::from_millis(100));
-        let job = &mut (*shell.background.lock().unwrap())[job as usize];
-        if let ProcessState::Empty = job.state { break }
-        if let Ok(signal) = shell.signals.try_recv() {
-            match signal {
-                libc::SIGTSTP => {
-                    let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGTSTP));
-                    break
-                },
-                libc::SIGTERM => {
-                    shell.handle_signal(libc::SIGTERM);
-                },
-                libc::SIGINT => {
-                    let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGINT));
-                    break
-                },
-                _ => unimplemented!()
-            }
-        }
-    }
-}
-
-#[cfg(not(target_os = "redox"))]
+#[cfg(all(unix, not(target_os = "redox")))]
 pub fn fg(shell: &mut Shell, args: &[&str]) -> i32 {
     let mut status = 0;
     for arg in args {
@@ -61,13 +47,15 @@ pub fn fg(shell: &mut Shell, args: &[&str]) -> i32 {
 
             match job.state {
                 ProcessState::Running => {
-                    fg_listen(shell, njob);
-                    status = SUCCESS;
+                    set_foreground(njob);
+                    // TODO: This doesn't work
+                    status = shell.watch_foreground(njob)
                 },
                 ProcessState::Stopped => {
-                    let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGCONT));
-                    fg_listen(shell, njob);
-                    status = SUCCESS;
+                    let _ = signal::kill(-(job.pid as i32), Some(Signal::SIGCONT));
+                    set_foreground(njob);
+                    // TODO: This doesn't work
+                    status = shell.watch_foreground(njob);
                 },
                 ProcessState::Empty => {
                     let stderr = stderr();
@@ -91,7 +79,7 @@ pub fn fg(_: &mut Shell, _: &[&str]) -> i32 {
     0
 }
 
-#[cfg(not(target_os = "redox"))]
+#[cfg(all(unix, not(target_os = "redox")))]
 pub fn bg(shell: &mut Shell, args: &[&str]) -> i32 {
     let mut error = false;
     let stderr = stderr();
@@ -105,8 +93,9 @@ pub fn bg(shell: &mut Shell, args: &[&str]) -> i32 {
                         error = true;
                     },
                     ProcessState::Stopped => {
-                        let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGCONT));
-                        let _ = writeln!(stderr, "[{}] {} {}", njob, job.pid, job.state);
+                        let _ = signal::kill(-(job.pid as i32), Some(Signal::SIGCONT));
+                        job.state = ProcessState::Running;
+                        let _ = writeln!(stderr, "[{}] {} Running", njob, job.pid);
                     },
                     ProcessState::Empty => {
                         let _ = writeln!(stderr, "ion: bg: job {} does not exist", njob);
diff --git a/src/main.rs b/src/main.rs
index 64245edb..cec152a1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -72,7 +72,6 @@ fn inner_main(sigint_rx : mpsc::Receiver<i32>) {
    shell.execute();
 }
 
-
 #[cfg(not(target_os = "redox"))]
 fn main() {
     let (signals_tx, signals_rx) = mpsc::channel();
@@ -82,14 +81,17 @@ fn main() {
     let mut core = Core::new().unwrap();
     let handle = core.handle();
 
-    // Create a stream that will select over SIGINT, SIGTERM and SIGTSTP signals.
-    let signal_stream = Signal::new(unix_signal::SIGINT, &handle).flatten_stream()
-        .select(Signal::new(unix_signal::SIGTERM, &handle).flatten_stream())
-        .select(Signal::new(libc::SIGTSTP, &handle).flatten_stream());
+    // Mask the SIGTSTP signal -- prevents the shell from being stopped
+    // when the foreground group is changed during command execution.
+    mask_sigstp();
+
+    // Create a stream that will select over SIGINT and SIGTERM signals.
+    let signals = Signal::new(unix_signal::SIGINT, &handle).flatten_stream()
+        .select(Signal::new(unix_signal::SIGTERM, &handle).flatten_stream());
 
     // Execute the event loop that will listen for and transmit received
     // signals to the shell.
-    core.run(signal_stream.for_each(|signal| {
+    core.run(signals.for_each(|signal| {
         let _ = signals_tx.send(signal);
         Ok(())
     })).unwrap();
@@ -100,3 +102,21 @@ fn main() {
     let (_, signals_rx) = mpsc::channel();
     inner_main(signals_rx);
 }
+
+#[cfg(all(unix, not(target_os = "redox")))]
+fn mask_sigstp() {
+    unsafe {
+        use libc::{sigset_t, SIGTSTP, SIG_BLOCK, sigemptyset, sigaddset, sigprocmask};
+        use std::mem;
+        use std::ptr;
+        let mut sigset = mem::uninitialized::<sigset_t>();
+        sigemptyset(&mut sigset as *mut sigset_t);
+        sigaddset(&mut sigset as *mut sigset_t, SIGTSTP);
+        sigprocmask(SIG_BLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
+    }
+}
+
+#[cfg(target_os = "redox")]
+fn mask_sigstp() {
+    // TODO
+}
diff --git a/src/shell/job_control.rs b/src/shell/job_control.rs
index 0680c1a9..dde18351 100644
--- a/src/shell/job_control.rs
+++ b/src/shell/job_control.rs
@@ -14,7 +14,8 @@ pub trait JobControl {
     fn handle_signal(&self, signal: i32);
     fn foreground_send(&self, signal: i32);
     fn background_send(&self, signal: i32);
-    fn send_child_to_background(&mut self, child: u32, state: ProcessState);
+    fn watch_foreground(&mut self, pid: u32) -> i32;
+    fn send_to_background(&mut self, child: u32, state: ProcessState);
 }
 
 #[derive(Clone)]
@@ -36,45 +37,48 @@ impl fmt::Display for ProcessState {
 }
 
 #[cfg(target_os = "redox")]
-pub fn watch_pid(processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32, njob: usize) {
+pub fn watch_background_pid(processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32, njob: usize) {
     // TODO: Implement this using syscall::call::waitpid
 }
 
 #[cfg(all(unix, not(target_os = "redox")))]
-pub fn watch_pid (
+pub fn watch_background_pid (
     processes: Arc<Mutex<Vec<BackgroundProcess>>>,
     pid: u32,
     njob: usize)
 {
-    use nix::sys::wait::{waitpid, WaitStatus};
+    use nix::sys::wait::{waitpid, WaitStatus, WUNTRACED, WNOHANG};
     loop {
-        match waitpid(pid as pid_t, None) {
+        match waitpid(-(pid as pid_t), Some(WUNTRACED | WNOHANG)) {
             Ok(WaitStatus::Exited(_, status)) => {
-                eprintln!("ion: background process ([{}] {}) exited with {}", njob, pid, status);
+                eprintln!("ion: ([{}] {}) exited with {}", njob, pid, status);
                 let mut processes = processes.lock().unwrap();
                 let process = &mut processes.iter_mut().nth(njob).unwrap();
                 process.state = ProcessState::Empty;
                 break
             },
-            Ok(WaitStatus::Stopped(_, _)) => {
+            Ok(WaitStatus::Stopped(pid, _)) => {
+                eprintln!("ion: ([{}] {}) Stopped", njob, pid);
                 let mut processes = processes.lock().unwrap();
                 let process = &mut processes.iter_mut().nth(njob).unwrap();
                 process.state = ProcessState::Stopped;
             },
-            Ok(WaitStatus::Continued(_)) => {
+            Ok(WaitStatus::Continued(pid)) => {
+                eprintln!("ion: ([{}] {}) Running", njob, pid);
                 let mut processes = processes.lock().unwrap();
                 let process = &mut processes.iter_mut().nth(njob).unwrap();
                 process.state = ProcessState::Running;
             },
             Ok(_) => (),
             Err(why) => {
-                eprintln!("ion: background process ([{}] {}) errored: {}", njob, pid, why);
+                eprintln!("ion: ([{}] {}) errored: {}", njob, pid, why);
                 let mut processes = processes.lock().unwrap();
                 let process = &mut processes.iter_mut().nth(njob).unwrap();
                 process.state = ProcessState::Empty;
                 break
             }
         }
+        sleep(Duration::from_millis(100));
     }
 }
 
@@ -121,7 +125,7 @@ impl<'a> JobControl for Shell<'a> {
     #[cfg(all(unix, not(target_os = "redox")))]
     /// Suspends a given process by it's process ID.
     fn suspend(&mut self, pid: u32) {
-        let _ = signal::kill(pid as pid_t, Some(NixSignal::SIGTSTP));
+        let _ = signal::kill(-(pid as pid_t), Some(NixSignal::SIGTSTP));
     }
 
     #[cfg(target_os = "redox")]
@@ -155,11 +159,71 @@ impl<'a> JobControl for Shell<'a> {
         // TODO: Redox doesn't support signals yet.
     }
 
+    #[cfg(all(unix, not(target_os = "redox")))]
+    fn watch_foreground(&mut self, pid: u32) -> i32 {
+        use nix::sys::wait::{waitpid, WaitStatus, WUNTRACED, WNOHANG};
+        use nix::sys::signal::Signal;
+        loop {
+            match waitpid(-(pid as pid_t), Some(WUNTRACED | WNOHANG)) {
+                Ok(WaitStatus::Exited(_, status)) => {
+                    break status as i32
+                },
+                Ok(WaitStatus::Signaled(_, signal, _)) => {
+                    eprintln!("ion: process ended by signal");
+                    if signal == Signal::SIGTERM {
+                        self.handle_signal(libc::SIGTERM);
+                    } else if signal == Signal::SIGINT {
+                        self.foreground_send(libc::SIGINT as i32);
+                    }
+                    break TERMINATED;
+                },
+                Ok(WaitStatus::Stopped(pid, _)) => {
+                    self.send_to_background(pid as u32, ProcessState::Stopped);
+                    self.received_sigtstp = true;
+                    break TERMINATED
+                },
+                Ok(_) => (),
+                Err(why) => {
+                    eprintln!("ion: process doesn't exist: {}", why);
+                    break FAILURE
+                }
+            }
+            sleep(Duration::from_millis(1));
+        }
+    }
+
+    #[cfg(target_os = "redox")]
+    fn watch_foreground(&mut self, pid: u32) -> i32 {
+        loop {
+            match child.try_wait() {
+                Ok(Some(status)) => {
+                    if let Some(code) = status.code() {
+                        break code
+                    } else {
+                        let stderr = io::stderr();
+                        let mut stderr = stderr.lock();
+                        let _ = stderr.write_all(b"ion: child ended by signal\n");
+                        break TERMINATED
+                    }
+                },
+                Ok(None) => {
+                    thread::sleep(Duration::from_millis(1));
+                },
+                Err(err) => {
+                    let stderr = io::stderr();
+                    let mut stderr = stderr.lock();
+                    let _ = writeln!(stderr, "ion: failed to wait: {}", err);
+                    break 100 // TODO what should we return here?
+                }
+            }
+        }
+    }
+
     #[cfg(all(unix, not(target_os = "redox")))]
     /// Send a kill signal to all running foreground tasks.
     fn foreground_send(&self, signal: i32) {
         for process in self.foreground.iter() {
-            let _ = signal::kill(*process as pid_t, NixSignal::from_c_int(signal as c_int).ok());
+            let _ = signal::kill(-(*process as pid_t), NixSignal::from_c_int(signal as c_int).ok());
         }
     }
 
@@ -173,7 +237,7 @@ impl<'a> JobControl for Shell<'a> {
     fn background_send(&self, signal: i32) {
         for process in self.background.lock().unwrap().iter() {
             if let ProcessState::Running = process.state {
-                let _ = signal::kill(process.pid as pid_t, NixSignal::from_c_int(signal as c_int).ok());
+                let _ = signal::kill(-(process.pid as pid_t), NixSignal::from_c_int(signal as c_int).ok());
             }
         }
     }
@@ -183,12 +247,12 @@ impl<'a> JobControl for Shell<'a> {
         // TODO: Redox doesn't support signals yet
     }
 
-    fn send_child_to_background(&mut self, pid: u32, state: ProcessState) {
+    fn send_to_background(&mut self, pid: u32, state: ProcessState) {
         let processes = self.background.clone();
         let _ = spawn(move || {
             let njob = add_to_background(processes.clone(), pid, state);
             eprintln!("ion: bg [{}] {}", njob, pid);
-            watch_pid(processes, pid, njob);
+            watch_background_pid(processes, pid, njob);
         });
     }
 
diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs
index c3e6636d..cc4e2aa5 100644
--- a/src/shell/pipe.rs
+++ b/src/shell/pipe.rs
@@ -1,19 +1,100 @@
 #[cfg(all(unix, not(target_os = "redox")))] use libc;
-#[cfg(all(unix, not(target_os = "redox")))] use nix::unistd::{fork, ForkResult};
+#[cfg(all(unix, not(target_os = "redox")))] use nix::unistd::{self, ForkResult};
 #[cfg(all(unix, not(target_os = "redox")))] use nix::Error as NixError;
 #[cfg(target_os = "redox")] use syscall;
 use std::io::{self, Write};
 use std::process::{Stdio, Command, Child};
 use std::os::unix::io::{FromRawFd, AsRawFd, IntoRawFd};
+use std::os::unix::process::CommandExt;
 use std::fs::{File, OpenOptions};
 use std::process::exit;
-use std::thread;
-use std::time::Duration;
 use super::job_control::{JobControl, ProcessState};
 use super::{JobKind, Shell};
 use super::status::*;
 use parser::peg::{Pipeline, RedirectFrom};
 
+/// The purpose of the signal handler is to ignore signals when it is active, and then continue
+/// listening to signals once the handler is dropped.
+struct SignalHandler;
+
+impl SignalHandler {
+    #[cfg(all(unix, not(target_os = "redox")))]
+    pub fn new() -> SignalHandler {
+        unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_IGN); }
+        SignalHandler
+    }
+
+    #[cfg(target_os = "redox")]
+    pub fn new() -> SignalHandler {
+        // TODO
+        SignalHandler
+    }
+}
+
+impl Drop for SignalHandler {
+    #[cfg(all(unix, not(target_os = "redox")))]
+    fn drop(&mut self) {
+        unsafe { let _ = libc::signal(libc::SIGTTOU, libc::SIG_DFL); }
+    }
+
+    #[cfg(target_os = "redox")]
+    fn drop(&mut self) {
+        // TODO
+    }
+}
+
+#[cfg(all(unix, not(target_os = "redox")))]
+fn unmask_sigtstp() {
+    unsafe {
+        use libc::{sigset_t, SIG_UNBLOCK, SIGTSTP, sigemptyset, sigaddset, sigprocmask};
+        use std::mem;
+        use std::ptr;
+        let mut sigset = mem::uninitialized::<sigset_t>();
+        sigemptyset(&mut sigset as *mut sigset_t);
+        sigaddset(&mut sigset as *mut sigset_t, SIGTSTP);
+        sigprocmask(SIG_UNBLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
+    }
+}
+
+#[cfg(target_os = "redox")]
+fn unmask_sigtstp() {
+    // TODO
+}
+
+#[cfg(all(unix, not(target_os = "redox")))]
+/// When given a process ID, that process will be assigned to a new process group.
+fn create_process_group() {
+    let _ = unistd::setpgid(0, 0);
+}
+
+#[cfg(target_os = "redox")]
+fn create_process_group() {
+    // TODO
+}
+
+#[cfg(all(unix, not(target_os = "redox")))]
+/// When given a process ID, that process's group will be assigned as the foreground process group.
+pub fn set_foreground(pid: u32) {
+    let _ = unistd::tcsetpgrp(0, pid as i32);
+    let _ = unistd::tcsetpgrp(1, pid as i32);
+    let _ = unistd::tcsetpgrp(2, pid as i32);
+}
+
+#[cfg(target_os = "redox")]
+pub fn set_foreground(pid: u32) {
+    // TODO
+}
+
+#[cfg(all(unix, not(target_os = "redox")))]
+fn get_pid() -> u32 {
+    unistd::getpid() as u32
+}
+
+#[cfg(target_os = "redox")]
+fn get_pid() -> u32 {
+    // TODO
+}
+
 pub trait PipelineExecution {
     fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32;
 }
@@ -72,10 +153,19 @@ impl<'a> PipelineExecution for Shell<'a> {
         }
 
         self.foreground.clear();
+        // If the given pipeline is a background task, fork the shell.
         if piped_commands[piped_commands.len()-1].1 == JobKind::Background {
             fork_pipe(self, &mut piped_commands)
         } else {
-            pipe(self, &mut piped_commands)
+            // While active, the SIGTTOU signal will be ignored.
+            let sig_ignore = SignalHandler::new();
+            // Execute each command in the pipeline, giving each command the foreground.
+            let exit_status = pipe(self, &mut piped_commands, true);
+            // Set the shell as the foreground process again to regain the TTY.
+            set_foreground(get_pid());
+            // Dropping this will un-ignore the SIGTTOU signal.
+            drop(sig_ignore);
+            exit_status
         }
     }
 }
@@ -97,30 +187,32 @@ fn ion_fork() -> syscall::error::Result<Fork> {
 
 #[cfg(all(unix, not(target_os = "redox")))]
 fn ion_fork() -> Result<Fork, NixError> {
-    match fork()? {
-        ForkResult::Parent{ child: pid }  => Ok(Fork::Parent(pid as u32)),
-        ForkResult::Child                 => Ok(Fork::Child)
+    match unistd::fork()? {
+        ForkResult::Parent{ child: pid } => Ok(Fork::Parent(pid as u32)),
+        ForkResult::Child                => Ok(Fork::Child)
     }
 }
 
 fn fork_pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
     match ion_fork() {
         Ok(Fork::Parent(pid)) => {
-            shell.send_child_to_background(pid, ProcessState::Running);
+            shell.send_to_background(pid, ProcessState::Running);
             SUCCESS
         },
         Ok(Fork::Child) => {
-            exit(pipe(shell, commands));
+            unmask_sigtstp();
+            create_process_group();
+            exit(pipe(shell, commands, false));
         },
         Err(why) => {
-            eprintln!("ion: background job: {}", why);
+            eprintln!("ion: background fork failed: {}", why);
             FAILURE
         }
     }
 }
 
 /// This function will panic if called with an empty slice
-fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
+fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)], foreground: bool) -> i32 {
     let mut previous_status = SUCCESS;
     let mut previous_kind = JobKind::And;
     let mut commands = commands.iter_mut();
@@ -148,9 +240,14 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
                     RedirectFrom::Stdout => command.stdout(Stdio::piped()),
                 };
 
-                let child = command.spawn().ok();
+                let child = command.before_exec(move || {
+                    unmask_sigtstp();
+                    create_process_group();
+                    Ok(())
+                }).spawn().ok();
                 match child {
                     Some(child) => {
+                        if foreground { set_foreground(child.id()); }
                         shell.foreground.push(child.id());
                         children.push(Some(child))
                     },
@@ -191,9 +288,14 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
                             command.stdin(Stdio::null());
                         }
                     }
-                    let child = command.spawn().ok();
+                    let child = command.before_exec(move || {
+                        unmask_sigtstp();
+                        create_process_group();
+                        Ok(())
+                    }).spawn().ok();
                     match child {
                         Some(child) => {
+                            if foreground { set_foreground(child.id()); }
                             shell.foreground.push(child.id());
                             children.push(Some(child));
                         },
@@ -220,7 +322,7 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
                 }
             }
             _ => {
-                previous_status = execute_command(shell, command);
+                previous_status = execute_command(shell, command, foreground);
                 previous_kind = kind;
             }
         }
@@ -239,20 +341,13 @@ fn terminate_fg(shell: &mut Shell) {
     // TODO: Redox does not support signals
 }
 
-#[cfg(all(unix, not(target_os = "redox")))]
-fn is_sigtstp(signal: i32) -> bool {
-    signal == libc::SIGTSTP
-}
-
-#[cfg(target_os = "redox")]
-fn is_sigtstp(_: i32) -> bool {
-    // TODO: Redox does not support signals
-    false
-}
-
-fn execute_command(shell: &mut Shell, command: &mut Command) -> i32 {
-    match command.spawn() {
-        Ok(child) => wait_on_child(shell, child),
+fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) -> i32 {
+    match command.before_exec(move || {
+        unmask_sigtstp();
+        create_process_group();
+        Ok(())
+    }).spawn() {
+        Ok(child) => wait_on_child(shell, child, foreground),
         Err(_) => {
             let stderr = io::stderr();
             let mut stderr = stderr.lock();
@@ -262,131 +357,25 @@ fn execute_command(shell: &mut Shell, command: &mut Command) -> i32 {
     }
 }
 
-fn wait_on_child(shell: &mut Shell, mut child: Child) -> i32 {
-    loop {
-        match child.try_wait() {
-            Ok(Some(status)) => {
-                if let Some(code) = status.code() {
-                    break code
-                } else {
-                    let stderr = io::stderr();
-                    let mut stderr = stderr.lock();
-                    let _ = stderr.write_all(b"ion: child ended by signal\n");
-                    break TERMINATED
-                }
-            },
-            Ok(None) => {
-                if let Ok(signal) = shell.signals.try_recv() {
-                    if is_sigtstp(signal) {
-                        shell.received_sigtstp = true;
-                        let pid = child.id();
-                        shell.suspend(pid);
-                        shell.send_child_to_background(pid, ProcessState::Stopped);
-                        break SUCCESS
-                    } else {
-                        if let Err(why) = child.kill() {
-                            let stderr = io::stderr();
-                            let _ = writeln!(stderr.lock(), "ion: unable to kill child: {}", why);
-                        }
-                        shell.foreground_send(signal);
-                        shell.handle_signal(signal);
-                        break TERMINATED
-                    }
-                }
-                thread::sleep(Duration::from_millis(1));
-            },
-            Err(err) => {
-                let stderr = io::stderr();
-                let mut stderr = stderr.lock();
-                let _ = writeln!(stderr, "ion: failed to wait: {}", err);
-                break 100 // TODO what should we return here?
-            }
-        }
-    }
+fn wait_on_child(shell: &mut Shell, child: Child, foreground: bool) -> i32 {
+    if foreground { set_foreground(child.id()); }
+    shell.watch_foreground(child.id())
 }
 
 /// This function will panic if called with an empty vector
 fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 {
     let end = children.len() - 1;
     for child in children.drain(..end) {
-        if let Some(mut child) = child {
-            let status = loop {
-                match child.try_wait() {
-                    Ok(Some(status)) => {
-                        if let Some(code) = status.code() {
-                            break code
-                        } else {
-                            let stderr = io::stderr();
-                            let mut stderr = stderr.lock();
-                            let _ = stderr.write_all(b"ion: child ended by signal\n");
-                            break TERMINATED
-                        }
-                    },
-                    Ok(None) => {
-                        if let Ok(signal) = shell.signals.try_recv() {
-                            if is_sigtstp(signal) {
-                                shell.received_sigtstp = true;
-                                let pid = child.id();
-                                shell.suspend(pid);
-                                shell.send_child_to_background(pid, ProcessState::Stopped);
-                                break SUCCESS
-                            }
-                            shell.foreground_send(signal);
-                            shell.handle_signal(signal);
-                            break TERMINATED
-                        }
-                        thread::sleep(Duration::from_millis(1));
-                    },
-                    Err(err) => {
-                        let stderr = io::stderr();
-                        let mut stderr = stderr.lock();
-                        let _ = writeln!(stderr, "ion: failed to wait: {}", err);
-                        break 100 // TODO what should we return here?
-                    }
-                }
-            };
+        if let Some(child) = child {
+            let status = shell.watch_foreground(child.id());
             if status == TERMINATED {
                 return status
             }
         }
     }
 
-    if let Some(mut child) = children.pop().unwrap() {
-        loop {
-            match child.try_wait() {
-                Ok(Some(status)) => {
-                    if let Some(code) = status.code() {
-                        break code
-                    } else {
-                        let stderr = io::stderr();
-                        let mut stderr = stderr.lock();
-                        let _ = stderr.write_all(b"ion: child ended by signal\n");
-                        break TERMINATED
-                    }
-                },
-                Ok(None) => {
-                    if let Ok(signal) = shell.signals.try_recv() {
-                        if is_sigtstp(signal) {
-                            shell.received_sigtstp = true;
-                            let pid = child.id();
-                            shell.suspend(pid);
-                            shell.send_child_to_background(pid, ProcessState::Stopped);
-                            break SUCCESS
-                        }
-                        shell.foreground_send(signal);
-                        shell.handle_signal(signal);
-                        break TERMINATED
-                    }
-                    thread::sleep(Duration::from_millis(1));
-                },
-                Err(err) => {
-                    let stderr = io::stderr();
-                    let mut stderr = stderr.lock();
-                    let _ = writeln!(stderr, "ion: failed to wait: {}", err);
-                    break 100 // TODO what should we return here?
-                }
-            }
-        }
+    if let Some(child) = children.pop().unwrap() {
+        shell.watch_foreground(child.id())
     } else {
         NO_SUCH_COMMAND
     }
-- 
GitLab