From 4bae77d2df58dbf58cbedc9b33f5197851635bcb Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Tue, 4 Jul 2017 17:51:27 -0400
Subject: [PATCH] Monitor Child w/ waitpid & Fork Shell w/ BG Jobs

This change will modify the way that job statuses are monitored.
Rather than using the wait() method on a child, this will use
the lower level waitpid() method from the nix crate, enabling
the ability to detect and record changes to the child process
automatically.

This also implements a change that will fork the shell when a
command is executed that needs to be run in the background.
However, these commands still don't work at the moment due to a
parsing issue.
---
 Cargo.lock                  |   7 ++
 Cargo.toml                  |   9 +--
 src/builtins/job_control.rs |   5 --
 src/main.rs                 |  21 +++---
 src/shell/flow.rs           |   3 +-
 src/shell/job_control.rs    | 136 ++++++++++++++++++++++--------------
 src/shell/pipe.rs           |  78 ++++++++++++++++-----
 7 files changed, 166 insertions(+), 93 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 2e09e0b2..c293587e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -12,6 +12,7 @@ dependencies = [
  "nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "tokio-core 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -189,6 +190,11 @@ name = "quote"
 version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "redox_syscall"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "scoped-tls"
 version = "0.1.0"
@@ -335,6 +341,7 @@ dependencies = [
 "checksum peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36a474cba42744afe0f223e9d4263594b3387f172e512259c72d2011e477c4fb"
 "checksum permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53b7d5b19a715ffab38693a9dd44b067fdfa2b18eef65bd93562dfe507022fae"
 "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
+"checksum redox_syscall 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "e4a357d14a12e90a37d658725df0e6468504750b5948b9710f83f94a0c5818e8"
 "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d"
 "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
 "checksum smallstring 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30950abdb5b38f56a0e181ae56ed64a539b64fa77ea6325147203dc7faeb087f"
diff --git a/Cargo.toml b/Cargo.toml
index 0ea584f3..5b9b807e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,16 +27,17 @@ unicode-segmentation = "1.2"
 smallvec = "0.4"
 smallstring = "0.1"
 
-[target.'cfg(not(target_os = "redox"))'.dependencies]
+[target.'cfg(target_os = "redox")'.dependencies]
+redox_syscall = "0.1"
+
+[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
+users = "0.5.1"
 futures = "0.1"
 libc = "0.2"
 nix = "0.8"
 tokio-core = "0.1"
 tokio-signal = "0.1"
 
-[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
-users = "0.5.1"
-
 [build-dependencies]
 peg = "0.5"
 ansi_term = "0.9"
diff --git a/src/builtins/job_control.rs b/src/builtins/job_control.rs
index f4238a1b..ba542fe9 100644
--- a/src/builtins/job_control.rs
+++ b/src/builtins/job_control.rs
@@ -26,12 +26,9 @@ fn fg_listen(shell: &mut Shell, job: u32) {
         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() {
-            let stderr = stderr();
-            let _ = writeln!(stderr.lock(), "ion: fg_listen: signal {} ", signal);
             match signal {
                 libc::SIGTSTP => {
                     let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGTSTP));
-                    job.state = ProcessState::Stopped;
                     break
                 },
                 libc::SIGTERM => {
@@ -39,7 +36,6 @@ fn fg_listen(shell: &mut Shell, job: u32) {
                 },
                 libc::SIGINT => {
                     let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGINT));
-                    job.state = ProcessState::Empty;
                     break
                 },
                 _ => unimplemented!()
@@ -110,7 +106,6 @@ pub fn bg(shell: &mut Shell, args: &[&str]) -> i32 {
                     },
                     ProcessState::Stopped => {
                         let _ = signal::kill(job.pid as pid_t, Some(Signal::SIGCONT));
-                        job.state = ProcessState::Running;
                         let _ = writeln!(stderr, "[{}] {} {}", njob, job.pid, job.state);
                     },
                     ProcessState::Empty => {
diff --git a/src/main.rs b/src/main.rs
index 5f873fd4..9f8776fe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,14 +13,13 @@ extern crate liner;
 extern crate smallvec;
 extern crate smallstring;
 
-#[cfg(not(target_os = "redox"))] extern crate futures;
-#[cfg(not(target_os = "redox"))] extern crate libc;
-#[cfg(not(target_os = "redox"))] extern crate nix;
-#[cfg(not(target_os = "redox"))] extern crate tokio_core;
-#[cfg(not(target_os = "redox"))] extern crate tokio_signal;
-
-#[cfg(all(unix, not(target_os = "redox")))]
-extern crate users as users_unix;
+#[cfg(all(unix, not(target_os = "redox")))] extern crate futures;
+#[cfg(all(unix, not(target_os = "redox")))] extern crate libc;
+#[cfg(all(unix, not(target_os = "redox")))] extern crate nix;
+#[cfg(all(unix, not(target_os = "redox")))] extern crate tokio_core;
+#[cfg(all(unix, not(target_os = "redox")))] extern crate tokio_signal;
+#[cfg(all(unix, not(target_os = "redox")))] extern crate users as users_unix;
+#[cfg(target_os = "redox")] extern crate redox_syscall;
 
 #[macro_use] mod parser;
 mod builtins;
@@ -33,9 +32,9 @@ use std::io::{stderr, Write, ErrorKind};
 use builtins::Builtin;
 use shell::Shell;
 
-#[cfg(not(target_os = "redox"))] use tokio_core::reactor::Core;
-#[cfg(not(target_os = "redox"))] use futures::{Future, Stream};
-#[cfg(not(target_os = "redox"))] use tokio_signal::unix::{self as unix_signal, Signal};
+#[cfg(all(unix, not(target_os = "redox")))] use tokio_core::reactor::Core;
+#[cfg(all(unix, not(target_os = "redox")))] use futures::{Future, Stream};
+#[cfg(all(unix, not(target_os = "redox")))] use tokio_signal::unix::{self as unix_signal, Signal};
 
 use std::sync::mpsc;
 use std::thread;
diff --git a/src/shell/flow.rs b/src/shell/flow.rs
index baefa0ae..29c2b5f3 100644
--- a/src/shell/flow.rs
+++ b/src/shell/flow.rs
@@ -10,8 +10,7 @@ use parser::{ForExpression, StatementSplitter, check_statement, expand_string, S
 use parser::peg::Pipeline;
 use super::assignments::{let_assignment, export_variable};
 use types::Array;
-#[cfg(not(target_os = "redox"))]
-use libc;
+#[cfg(all(unix, not(target_os = "redox")))] use libc;
 
 pub enum Condition {
     Continue,
diff --git a/src/shell/job_control.rs b/src/shell/job_control.rs
index c41adcea..91f60711 100644
--- a/src/shell/job_control.rs
+++ b/src/shell/job_control.rs
@@ -1,10 +1,10 @@
-#[cfg(not(target_os = "redox"))] use libc::{self, pid_t, c_int};
-#[cfg(not(target_os = "redox"))] use nix::sys::signal::{self, Signal as NixSignal};
+#[cfg(all(unix, not(target_os = "redox")))] use libc::{self, pid_t, c_int};
+#[cfg(all(unix, not(target_os = "redox")))] use nix::sys::signal::{self, Signal as NixSignal};
 use std::fmt;
-use std::io::{stderr, Write};
 use std::thread::{sleep, spawn};
 use std::time::Duration;
-use std::process::{self, Child};
+use std::process;
+use std::sync::{Arc, Mutex};
 use super::status::*;
 use super::Shell;
 
@@ -14,7 +14,7 @@ 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: Child, state: ProcessState);
+    fn send_child_to_background(&mut self, child: u32, state: ProcessState);
 }
 
 #[derive(Clone)]
@@ -35,6 +35,76 @@ impl fmt::Display for ProcessState {
     }
 }
 
+#[cfg(target_os = "redox")]
+pub fn watch_pid(processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32) {
+    // TODO: Implement this using syscall::call::waitpid
+}
+
+#[cfg(all(unix, not(target_os = "redox")))]
+pub fn watch_pid (
+    processes: Arc<Mutex<Vec<BackgroundProcess>>>,
+    pid: u32,
+    njob: usize)
+{
+    use nix::sys::wait::{waitpid, WaitStatus};
+    loop {
+        match waitpid(pid as pid_t, None) {
+            Ok(WaitStatus::Exited(_, status)) => {
+                eprintln!("ion: background process ([{}] {}) 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(_, _)) => {
+                let mut processes = processes.lock().unwrap();
+                let process = &mut processes.iter_mut().nth(njob).unwrap();
+                process.state = ProcessState::Stopped;
+            },
+            Ok(WaitStatus::Continued(_)) => {
+                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);
+                let mut processes = processes.lock().unwrap();
+                let process = &mut processes.iter_mut().nth(njob).unwrap();
+                process.state = ProcessState::Empty;
+                break
+            }
+        }
+    }
+}
+
+pub fn add_to_background (
+    processes: Arc<Mutex<Vec<BackgroundProcess>>>,
+    pid: u32,
+    state: ProcessState
+) -> usize {
+    let mut processes = processes.lock().unwrap();
+    match (*processes).iter().position(|x| {
+        if let ProcessState::Empty = x.state { true } else { false }
+    }) {
+        Some(id) => {
+            (*processes)[id] = BackgroundProcess {
+                pid: pid,
+                state: state
+            };
+            id
+        },
+        None => {
+            let njobs = (*processes).len();
+            (*processes).push(BackgroundProcess {
+                pid: pid,
+                state: state
+            });
+            njobs
+        }
+    }
+}
+
 #[derive(Clone)]
 /// A background process is a process that is attached to, but not directly managed
 /// by the shell. The shell will only retain information about the process, such
@@ -48,7 +118,7 @@ pub struct BackgroundProcess {
 }
 
 impl<'a> JobControl for Shell<'a> {
-    #[cfg(not(target_os = "redox"))]
+    #[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));
@@ -59,7 +129,7 @@ impl<'a> JobControl for Shell<'a> {
         // TODO: Redox doesn't support signals yet.
     }
 
-    #[cfg(not(target_os = "redox"))]
+    #[cfg(all(unix, not(target_os = "redox")))]
     /// Waits until all running background tasks have completed, and listens for signals in the
     /// event that a signal is sent to kill the running tasks.
     fn wait_for_background(&mut self) {
@@ -85,7 +155,7 @@ impl<'a> JobControl for Shell<'a> {
         // TODO: Redox doesn't support signals yet.
     }
 
-    #[cfg(not(target_os = "redox"))]
+    #[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() {
@@ -98,7 +168,7 @@ impl<'a> JobControl for Shell<'a> {
         // TODO: Redox doesn't support signals yet
     }
 
-    #[cfg(not(target_os = "redox"))]
+    #[cfg(all(unix, not(target_os = "redox")))]
     /// Send a kill signal to all running background tasks.
     fn background_send(&self, signal: i32) {
         for process in self.background.lock().unwrap().iter() {
@@ -113,52 +183,12 @@ impl<'a> JobControl for Shell<'a> {
         // TODO: Redox doesn't support signals yet
     }
 
-    fn send_child_to_background(&mut self, mut child: Child, state: ProcessState) {
-        let pid = child.id();
+    fn send_child_to_background(&mut self, pid: u32, state: ProcessState) {
         let processes = self.background.clone();
         let _ = spawn(move || {
-            let njob;
-            {
-                let mut processes = processes.lock().unwrap();
-                njob = match (*processes).iter().position(|x| {
-                    if let ProcessState::Empty = x.state { true } else { false }
-                }) {
-                    Some(id) => {
-                        (*processes)[id] = BackgroundProcess {
-                            pid: pid,
-                            state: state
-                        };
-                        id
-                    },
-                    None => {
-                        let njobs = (*processes).len();
-                        (*processes).push(BackgroundProcess {
-                            pid: pid,
-                            state: state
-                        });
-                        njobs
-                    }
-                };
-
-                let stderr = stderr();
-                let _ = writeln!(stderr.lock(), "ion: bg: [{}] {}", njob, pid);
-            }
-
-            // Wait for the child to complete before removing it from the process list.
-            let status = child.wait();
-
-            // Notify the user that the background task has completed.
-            let stderr = stderr();
-            let mut stderr = stderr.lock();
-            let _ = match status {
-                Ok(status) => writeln!(stderr, "ion: bg: [{}] {} completed: {}", njob, pid, status),
-                Err(why)   => writeln!(stderr, "ion: bg: [{}] {} errored: {}", njob, pid, why)
-            };
-
-            // Remove the process from the background processes list.
-            let mut processes = processes.lock().unwrap();
-            let process = &mut processes.iter_mut().nth(njob).unwrap();
-            process.state = ProcessState::Empty;
+            let njob = add_to_background(processes.clone(), pid, state);
+            eprintln!("ion: bg [{}] {}", njob, pid);
+            watch_pid(processes, pid, njob);
         });
     }
 
diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs
index 525027e6..afc35ec2 100644
--- a/src/shell/pipe.rs
+++ b/src/shell/pipe.rs
@@ -1,8 +1,12 @@
-#[cfg(not(target_os = "redox"))] use libc;
+#[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::Error as NixError;
+#[cfg(target_os = "redox")] use std::error::Error;
 use std::io::{self, Write};
 use std::process::{Stdio, Command, Child};
 use std::os::unix::io::{FromRawFd, AsRawFd, IntoRawFd};
 use std::fs::{File, OpenOptions};
+use std::process::exit;
 use std::thread;
 use std::time::Duration;
 use super::job_control::{JobControl, ProcessState};
@@ -18,7 +22,9 @@ impl<'a> PipelineExecution for Shell<'a> {
     fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32 {
         // Generate a list of commands from the given pipeline
         let mut piped_commands: Vec<(Command, JobKind)> = pipeline.jobs
-            .drain(..).map(|mut job| (job.build_command(), job.kind)).collect();
+            .drain(..).map(|mut job| {
+                (job.build_command(), job.kind)
+            }).collect();
 
         if let Some(ref stdin) = pipeline.stdin {
             if let Some(command) = piped_commands.first_mut() {
@@ -66,7 +72,48 @@ impl<'a> PipelineExecution for Shell<'a> {
         }
 
         self.foreground.clear();
-        pipe(self, &mut piped_commands)
+        if piped_commands[piped_commands.len()-1].1 == JobKind::Background {
+            fork_pipe(self, &mut piped_commands)
+        } else {
+            pipe(self, &mut piped_commands)
+        }
+    }
+}
+
+enum Fork {
+    Parent(u32),
+    Child
+}
+
+#[cfg(target_os = "redox")]
+fn ion_fork() -> Result<Fork, Error> {
+    use redox_syscall::call::clone;
+    unsafe {
+        clone(0).map(|pid| if pid == 0 { Fork::Child } else { Fork::Parent(pid as u32)})?
+    }
+}
+
+#[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)
+    }
+}
+
+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);
+            SUCCESS
+        },
+        Ok(Fork::Child) => {
+            exit(pipe(shell, commands));
+        },
+        Err(why) => {
+            eprintln!("ion: background job: {}", why);
+            FAILURE
+        }
     }
 }
 
@@ -90,15 +137,6 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
         }
 
         match kind {
-            JobKind::Background => {
-                if let Err(_) = command.spawn()
-                    .map(|child| shell.send_child_to_background(child, ProcessState::Running))
-                {
-                    let stderr = io::stderr();
-                    let mut stderr = stderr.lock();
-                    let _ = writeln!(stderr, "ion: command not found: {}", get_command_name(command));
-                }
-            },
             JobKind::Pipe(mut from) => {
                 let mut children: Vec<Option<Child>> = Vec::new();
 
@@ -185,6 +223,7 @@ fn pipe(shell: &mut Shell, commands: &mut [(Command, JobKind)]) -> i32 {
             }
         }
     }
+
     previous_status
 }
 
@@ -217,8 +256,9 @@ fn wait_on_child(shell: &mut Shell, mut child: Child) -> i32 {
                 if let Ok(signal) = shell.signals.try_recv() {
                     if signal == libc::SIGTSTP {
                         shell.received_sigtstp = true;
-                        shell.suspend(child.id());
-                        shell.send_child_to_background(child, ProcessState::Stopped);
+                        let pid = child.id();
+                        shell.suspend(pid);
+                        shell.send_child_to_background(pid, ProcessState::Stopped);
                         break SUCCESS
                     } else {
                         if let Err(why) = child.kill() {
@@ -263,8 +303,9 @@ fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 {
                         if let Ok(signal) = shell.signals.try_recv() {
                             if signal == libc::SIGTSTP {
                                 shell.received_sigtstp = true;
-                                shell.suspend(child.id());
-                                shell.send_child_to_background(child, ProcessState::Stopped);
+                                let pid = child.id();
+                                shell.suspend(pid);
+                                shell.send_child_to_background(pid, ProcessState::Stopped);
                                 break SUCCESS
                             }
                             shell.foreground_send(signal);
@@ -304,8 +345,9 @@ fn wait(shell: &mut Shell, children: &mut Vec<Option<Child>>) -> i32 {
                     if let Ok(signal) = shell.signals.try_recv() {
                         if signal == libc::SIGTSTP {
                             shell.received_sigtstp = true;
-                            shell.suspend(child.id());
-                            shell.send_child_to_background(child, ProcessState::Stopped);
+                            let pid = child.id();
+                            shell.suspend(pid);
+                            shell.send_child_to_background(pid, ProcessState::Stopped);
                             break SUCCESS
                         }
                         shell.foreground_send(signal);
-- 
GitLab