From 8fc7729400b7ba8b2bad8e5248be61b9a39472eb Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Sun, 9 Jul 2017 01:48:48 -0400
Subject: [PATCH] Finally Complete Job Control Work: Fg Works Now!

- Job control is now complete for non-redox unix systems.
- The fg command now works

We need to do a lot of refactoring now to clean this up though.
---
 README.md                   |  10 +-
 src/builtins/job_control.rs |  69 ++----------
 src/builtins/mod.rs         |   6 +-
 src/main.rs                 |  23 +---
 src/shell/job_control.rs    | 204 +++++++++++++++++++++++++++++-------
 src/shell/mod.rs            |   5 +-
 src/shell/pipe.rs           |  32 ++----
 src/shell/signals.rs        |  40 +++++++
 8 files changed, 237 insertions(+), 152 deletions(-)
 create mode 100644 src/shell/signals.rs

diff --git a/README.md b/README.md
index 4eedf34a..cf43e54a 100644
--- a/README.md
+++ b/README.md
@@ -32,13 +32,13 @@ Below is an overview of features that Ion has either already implemented, or aim
     - [x] Process Expansions
         - [x] String-based Command Substitution (**$()**)
         - [x] Array-based Command Substitution (**@()**)
-    - [ ] Arithmetic Expansions
+    - [x] Arithmetic Expansions
 - [x] Flow Control
     - [x] For Loops
     - [ ] Foreach Loops
     - [x] While Loops
     - [x] If Conditionals
-    - [ ] Match Statements
+    - [x] Match Statements
 - [x] Functions
     - [x] Optionally-typed Function Parameters
 - [x] Script Execution
@@ -72,7 +72,7 @@ Below is an overview of features that Ion has either already implemented, or aim
 - [x] Signal Handling
 - [x] **&&** and **||** Conditionals
 - [x] Redirecting Stdout / Stderr
-- [ ] Redirecting Stdout & Stderr
+- [x] Redirecting Stdout & Stderr
 - [x] Piping Builtins
 - [x] Background Jobs
 - [ ] Piping Functions
@@ -116,8 +116,8 @@ Unlike Bash, job arguments are their specified job IDs.
 This area is still a work in progress. When a foreground task is stopped with the **Ctrl+Z** signal, that process will
 be added to the background process list as a stopped job. When a supplied command ends with the **&** operator, this
 will specify to run the task the background as a running job. To resume a stopped job, executing the `bg <job_id>`
-command will send a `SIGCONT` to the specified job ID, hence resuming the job. The `fg` command doesn't work at the
-moment though (coming soon).
+command will send a `SIGCONT` to the specified job ID, hence resuming the job. The `fg` command will similarly do the
+same, but also set that task as the foreground process.
 
 #### Exiting the Shell
 
diff --git a/src/builtins/job_control.rs b/src/builtins/job_control.rs
index b53b10f0..35ee8d6d 100644
--- a/src/builtins/job_control.rs
+++ b/src/builtins/job_control.rs
@@ -1,35 +1,7 @@
 use shell::Shell;
-use shell::job_control::{JobControl, ProcessState};
+use shell::job_control::{JobControl, ProcessState, resume};
 use shell::status::*;
 use std::io::{stderr, Write};
-#[cfg(all(unix, not(target_os = "redox")))] use libc::pid_t;
-#[cfg(not(target_os = "redox"))] use nix::sys::signal::{self, Signal};
-#[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
-}
-
-#[cfg(all(unix, not(target_os = "redox")))]
-/// Suspends a given process by it's process ID.
-pub fn suspend(pid: u32) {
-    let _ = signal::kill(-(pid as pid_t), Some(Signal::SIGSTOP));
-}
-
-#[cfg(all(unix, not(target_os = "redox")))]
-/// Resumes a given process by it's process ID.
-fn resume(pid: u32) {
-    let _ = signal::kill(-(pid as pid_t), Some(Signal::SIGCONT));
-}
 
 pub fn disown(shell: &mut Shell, args: &[&str]) -> i32 {
     let stderr = stderr();
@@ -94,19 +66,6 @@ pub fn disown(shell: &mut Shell, args: &[&str]) -> i32 {
     SUCCESS
 }
 
-
-#[cfg(target_os = "redox")]
-pub fn suspend(pid: u32) {
-    use syscall;
-    let _ = syscall::kill(pid as usize, syscall::SIGSTOP);
-}
-
-#[cfg(target_os = "redox")]
-fn resume(pid: u32) {
-    use syscall;
-    let _ = syscall::kill(pid as usize, syscall::SIGCONT);
-}
-
 /// Display a list of all jobs running in the background.
 pub fn jobs(shell: &mut Shell) {
     let stderr = stderr();
@@ -133,24 +92,16 @@ pub fn fg(shell: &mut Shell, args: &[&str]) -> i32 {
                 continue
             }
 
-            match job.state {
-                ProcessState::Running => {
-                    set_foreground(njob);
-                    // TODO: This doesn't work
-                    status = shell.watch_foreground(njob)
-                },
-                ProcessState::Stopped => {
-                    resume(job.pid);
-                    set_foreground(njob);
-                    // TODO: This doesn't work
-                    status = shell.watch_foreground(njob);
-                },
+            // Bring the process into the foreground and wait for it to finish.
+            status = match job.state {
+                ProcessState::Running => shell.set_bg_task_in_foreground(job.pid, false),
+                ProcessState::Stopped => shell.set_bg_task_in_foreground(job.pid, true),
                 ProcessState::Empty => {
                     let stderr = stderr();
                     let _ = writeln!(stderr.lock(), "ion: fg: job {} does not exist", njob);
-                    status = FAILURE;
+                    FAILURE
                 }
-            }
+            };
         } else {
             let stderr = stderr();
             let _ = writeln!(stderr.lock(), "ion: fg: {} is not a valid job number", arg);
@@ -171,11 +122,7 @@ pub fn bg(shell: &mut Shell, args: &[&str]) -> i32 {
                         let _ = writeln!(stderr, "ion: bg: job {} is already running", njob);
                         error = true;
                     },
-                    ProcessState::Stopped => {
-                        resume(job.pid);
-                        job.state = ProcessState::Running;
-                        let _ = writeln!(stderr, "[{}] {} Running", njob, job.pid);
-                    },
+                    ProcessState::Stopped => resume(job.pid),
                     ProcessState::Empty => {
                         let _ = writeln!(stderr, "ion: bg: job {} does not exist", njob);
                         error = true;
diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs
index 75076252..32ae0830 100644
--- a/src/builtins/mod.rs
+++ b/src/builtins/mod.rs
@@ -22,7 +22,7 @@ use std::error::Error;
 
 use parser::QuoteTerminator;
 use shell::job_control::{JobControl, ProcessState};
-use shell::{Shell, FlowLogic, ShellHistory};
+use shell::{self, Shell, FlowLogic, ShellHistory};
 use shell::status::*;
 
 #[cfg(target_os = "redox")]
@@ -261,8 +261,8 @@ impl Builtin {
         commands.insert("suspend", Builtin {
             name: "suspend",
             help: "Suspends the shell with a SIGTSTOP signal",
-            main: Box::new(|args: &[&str], shell: &mut Shell| -> i32 {
-                job_control::suspend(0);
+            main: Box::new(|_: &[&str], _: &mut Shell| -> i32 {
+                shell::job_control::suspend(0);
                 SUCCESS
             })
         });
diff --git a/src/main.rs b/src/main.rs
index 6b604eff..bc6c8c0f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -83,7 +83,7 @@ fn main() {
 
     // Block the SIGTSTP signal -- prevents the shell from being stopped
     // when the foreground group is changed during command execution.
-    block_signals();
+    shell::signals::unix::block();
 
     // Create a stream that will select over SIGINT, SIGTERM, and SIGHUP signals.
     let signals = Signal::new(unix_signal::SIGINT, &handle).flatten_stream()
@@ -103,24 +103,3 @@ fn main() {
     let (_, signals_rx) = mpsc::channel();
     inner_main(signals_rx);
 }
-
-#[cfg(all(unix, not(target_os = "redox")))]
-fn block_signals() {
-    unsafe {
-        use libc::*;
-        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);
-        sigaddset(&mut sigset as *mut sigset_t, SIGTTOU);
-        sigaddset(&mut sigset as *mut sigset_t, SIGTTIN);
-        sigaddset(&mut sigset as *mut sigset_t, SIGCHLD);
-        sigprocmask(SIG_BLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
-    }
-}
-
-#[cfg(target_os = "redox")]
-fn block_signals() {
-    // TODO
-}
diff --git a/src/shell/job_control.rs b/src/shell/job_control.rs
index 6ea85775..7313ecc6 100644
--- a/src/shell/job_control.rs
+++ b/src/shell/job_control.rs
@@ -1,15 +1,117 @@
 #[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};
+#[cfg(all(unix, not(target_os = "redox")))] use nix::sys::signal::{self, Signal};
+#[cfg(all(unix, not(target_os = "redox")))] use super::signals::unix as signals;
+#[cfg(all(unix, not(target_os = "redox")))] use nix::unistd;
+#[cfg(target_os = "redox")] use super::signals::unix as signals;
 use std::fmt;
 use std::thread::{sleep, spawn};
 use std::time::Duration;
 use std::process;
 use std::sync::{Arc, Mutex};
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
 use super::status::*;
 use super::Shell;
+use super::pipe::crossplat::get_pid;
+
+#[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_as(pid: u32) {
+    signals::block();
+    let _ = unistd::tcsetpgrp(0, pid as i32);
+    signals::unblock();
+}
+
+#[cfg(all(unix, not(target_os = "redox")))]
+/// Suspends a given process by it's process ID.
+pub fn suspend(pid: u32) {
+    let _ = signal::kill(-(pid as pid_t), Some(Signal::SIGSTOP));
+}
+
+#[cfg(all(unix, not(target_os = "redox")))]
+/// Resumes a given process by it's process ID.
+pub fn resume(pid: u32) {
+    let _ = signal::kill(-(pid as pid_t), Some(Signal::SIGCONT));
+}
+
+#[cfg(target_os = "redox")]
+pub fn suspend(pid: u32) {
+    use syscall;
+    let _ = syscall::kill(pid as usize, syscall::SIGSTOP);
+}
+
+#[cfg(target_os = "redox")]
+pub fn resume(pid: u32) {
+    use syscall;
+    let _ = syscall::kill(pid as usize, syscall::SIGCONT);
+}
+
+#[cfg(target_os = "redox")]
+pub fn set_foreground_as(pid: u32) {
+    // TODO
+}
+
+pub enum BackgroundResult {
+    Errored,
+    Status(u8)
+}
+
+pub struct ForegroundSignals {
+    grab:    AtomicUsize, // TODO: Use AtomicU32 when stable
+    status:  AtomicUsize, // TODO: Use AtomicU8 when stable
+    reply:   AtomicBool,
+    errored: AtomicBool   // TODO: Combine with reply when U8 is stable
+}
+
+impl ForegroundSignals {
+    pub fn new() -> ForegroundSignals {
+        ForegroundSignals {
+            grab: AtomicUsize::new(0),
+            status: AtomicUsize::new(0),
+            reply: AtomicBool::new(false),
+            errored: AtomicBool::new(false)
+        }
+    }
+
+    pub fn signal_to_grab(&self, pid: u32) {
+        self.grab.store(pid as usize, Ordering::Relaxed);
+    }
+
+    pub fn reply_with(&self, status: i8) {
+        self.grab.store(0, Ordering::Relaxed);
+        self.reply.store(true, Ordering::Relaxed);
+        self.status.store(status as usize, Ordering::Relaxed);
+    }
+
+    pub fn errored(&self) {
+        self.errored.store(true, Ordering::Relaxed);
+    }
+
+    pub fn was_processed(&self) -> Option<BackgroundResult> {
+        if self.reply.load(Ordering::Relaxed) {
+            self.reply.store(false, Ordering::Relaxed);
+            if self.errored.load(Ordering::Relaxed) {
+                self.errored.store(false, Ordering::Relaxed);
+                Some(BackgroundResult::Errored)
+            } else {
+                Some(BackgroundResult::Status(self.status.load(Ordering::Relaxed) as u8))
+            }
+        } else {
+            None
+        }
+    }
+
+    pub fn was_grabbed(&self, pid: u32) -> bool {
+        self.grab.load(Ordering::Relaxed) == pid as usize
+    }
+}
 
 pub trait JobControl {
+    /// Waits for background jobs to finish before returning.
     fn wait_for_background(&mut self);
+    /// Takes a background tasks's PID and whether or not it needs to be continued; resumes the task
+    /// and sets it as the foreground process. Once the task exits or stops, the exit status 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 handle_signal(&self, signal: i32);
     fn foreground_send(&self, signal: i32);
     fn background_send(&self, signal: i32);
@@ -36,34 +138,55 @@ impl fmt::Display for ProcessState {
 }
 
 #[cfg(target_os = "redox")]
-pub fn watch_background_pid(processes: Arc<Mutex<Vec<BackgroundProcess>>>, pid: u32, njob: usize) {
+pub fn watch_background (
+    fg: Arc<ForegroundSignals>,
+    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_background_pid (
+pub fn watch_background (
+    fg: Arc<ForegroundSignals>,
     processes: Arc<Mutex<Vec<BackgroundProcess>>>,
     pid: u32,
-    njob: usize)
-{
+    njob: usize
+) {
     use nix::sys::wait::*;
+    let mut fg_was_grabbed = false;
     loop {
-        match waitpid(-(pid as pid_t), Some(WUNTRACED)) {
+        if !fg_was_grabbed {
+            if fg.was_grabbed(pid) { fg_was_grabbed = true; }
+        }
+        match waitpid(-(pid as pid_t), Some(WUNTRACED | WCONTINUED | WNOHANG)) {
             Ok(WaitStatus::Exited(_, status)) => {
-                eprintln!("ion: ([{}] {}) exited with {}", njob, pid, status);
+                if !fg_was_grabbed {
+                    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;
+                if fg_was_grabbed { fg.reply_with(status); }
                 break
             },
             Ok(WaitStatus::Stopped(pid, _)) => {
-                eprintln!("ion: ([{}] {}) Stopped", njob, pid);
+                if !fg_was_grabbed {
+                    eprintln!("ion: ([{}] {}) Stopped", njob, pid);
+                }
                 let mut processes = processes.lock().unwrap();
                 let process = &mut processes.iter_mut().nth(njob).unwrap();
+                if fg_was_grabbed {
+                    fg.reply_with(TERMINATED as i8);
+                    fg_was_grabbed = false;
+                }
                 process.state = ProcessState::Stopped;
             },
             Ok(WaitStatus::Continued(pid)) => {
-                eprintln!("ion: ([{}] {}) Running", njob, pid);
+                if !fg_was_grabbed {
+                    eprintln!("ion: ([{}] {}) Running", njob, pid);
+                }
                 let mut processes = processes.lock().unwrap();
                 let process = &mut processes.iter_mut().nth(njob).unwrap();
                 process.state = ProcessState::Running;
@@ -74,9 +197,11 @@ pub fn watch_background_pid (
                 let mut processes = processes.lock().unwrap();
                 let process = &mut processes.iter_mut().nth(njob).unwrap();
                 process.state = ProcessState::Empty;
+                if fg_was_grabbed { fg.errored(); }
                 break
             }
         }
+        sleep(Duration::from_millis(100));
     }
 }
 
@@ -86,30 +211,20 @@ pub fn add_to_background (
     state: ProcessState
 ) -> usize {
     let mut processes = processes.lock().unwrap();
-    match (*processes).iter().position(|x| {
-        if let ProcessState::Empty = x.state { true } else { false }
-    }) {
+    match (*processes).iter().position(|x| x.state == ProcessState::Empty) {
         Some(id) => {
-            (*processes)[id] = BackgroundProcess {
-                pid: pid,
-                ignore_sighup: false,
-                state: state
-            };
+            (*processes)[id] = BackgroundProcess { pid: pid, ignore_sighup: false, state: state };
             id
         },
         None => {
             let njobs = (*processes).len();
-            (*processes).push(BackgroundProcess {
-                pid: pid,
-                ignore_sighup: false,
-                state: state
-            });
+            (*processes).push(BackgroundProcess { pid: pid, ignore_sighup: false, state: state });
             njobs
         }
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 /// 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
 /// as the process ID, state that the process is in, and the command that the
@@ -123,6 +238,28 @@ pub struct BackgroundProcess {
 }
 
 impl<'a> JobControl for Shell<'a> {
+    fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> i32 {
+        // Resume the background task, if needed.
+        if cont { resume(pid); }
+        // Pass the TTY to the background job
+        set_foreground_as(pid);
+        // Signal the background thread that is waiting on this process to stop waiting.
+        self.foreground_signals.signal_to_grab(pid);
+        let status = loop {
+            // When the background thread that is monitoring the task receives an exit/stop signal,
+            // the status of that process will be communicated back. To avoid consuming CPU cycles,
+            // we wait 25 ms between polls.
+            match self.foreground_signals.was_processed() {
+                Some(BackgroundResult::Status(stat)) => break stat as i32,
+                Some(BackgroundResult::Errored) => break TERMINATED,
+                None => sleep(Duration::from_millis(25))
+            }
+        };
+        // Have the shell reclaim the TTY
+        set_foreground_as(get_pid());
+        status
+    }
+
     #[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.
@@ -151,13 +288,11 @@ impl<'a> JobControl for Shell<'a> {
 
     #[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::wait::{waitpid, WaitStatus, WUNTRACED};
         use nix::sys::signal::Signal;
         loop {
-            match waitpid(-(pid as pid_t), Some(WUNTRACED | WNOHANG)) {
-                Ok(WaitStatus::Exited(_, status)) => {
-                    break status as i32
-                },
+            match waitpid(-(pid as pid_t), Some(WUNTRACED)) {
+                Ok(WaitStatus::Exited(_, status)) => break status as i32,
                 Ok(WaitStatus::Signaled(_, signal, _)) => {
                     eprintln!("ion: process ended by signal");
                     if signal == Signal::SIGTERM {
@@ -180,7 +315,6 @@ impl<'a> JobControl for Shell<'a> {
                     break FAILURE
                 }
             }
-            sleep(Duration::from_millis(1));
         }
     }
 
@@ -194,7 +328,7 @@ impl<'a> JobControl for Shell<'a> {
 
         loop {
             let mut status_raw = 0;
-            match syscall::waitpid(pid as usize, &mut status_raw, WNOHANG) {
+            match syscall::waitpid(pid as usize, &mut status_raw, 0) {
                 Ok(0) => (),
                 Ok(_pid) => {
                     let status = ExitStatus::from_raw(status_raw as i32);
@@ -214,7 +348,6 @@ impl<'a> JobControl for Shell<'a> {
                     break 100 // TODO what should we return here?
                 }
             }
-            sleep(Duration::from_millis(1));
         }
     }
 
@@ -222,7 +355,7 @@ impl<'a> JobControl for Shell<'a> {
     /// 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), Signal::from_c_int(signal as c_int).ok());
         }
     }
 
@@ -237,13 +370,13 @@ impl<'a> JobControl for Shell<'a> {
         if signal == libc::SIGHUP {
             for process in self.background.lock().unwrap().iter() {
                 if !process.ignore_sighup {
-                    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), Signal::from_c_int(signal as c_int).ok());
                 }
             }
         } else {
             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), Signal::from_c_int(signal as c_int).ok());
                 }
             }
         }
@@ -256,10 +389,11 @@ impl<'a> JobControl for Shell<'a> {
 
     fn send_to_background(&mut self, pid: u32, state: ProcessState) {
         let processes = self.background.clone();
+        let fg_signals = self.foreground_signals.clone();
         let _ = spawn(move || {
             let njob = add_to_background(processes.clone(), pid, state);
             eprintln!("ion: bg [{}] {}", njob, pid);
-            watch_background_pid(processes, pid, njob);
+            watch_background(fg_signals, processes, pid, njob);
         });
     }
 
diff --git a/src/shell/mod.rs b/src/shell/mod.rs
index ccb49228..8cdff2aa 100644
--- a/src/shell/mod.rs
+++ b/src/shell/mod.rs
@@ -8,6 +8,7 @@ mod history;
 pub mod job_control;
 mod job;
 mod pipe;
+pub mod signals;
 pub mod status;
 pub mod variables;
 
@@ -36,7 +37,7 @@ use smallstring::SmallString;
 use self::completer::{MultiCompleter, IonFileCompleter};
 use self::directory_stack::DirectoryStack;
 use self::flow_control::{FlowControl, Function, FunctionArgument, Statement, Type};
-use self::job_control::{JobControl, BackgroundProcess};
+use self::job_control::{JobControl, BackgroundProcess, ForegroundSignals};
 use self::variables::Variables;
 use self::status::*;
 use self::pipe::PipelineExecution;
@@ -64,6 +65,7 @@ pub struct Shell<'a> {
     foreground: Vec<u32>,
     pub background: Arc<Mutex<Vec<BackgroundProcess>>>,
     pub received_sigtstp: bool,
+    pub foreground_signals: Arc<ForegroundSignals>
 }
 
 impl<'a> Shell<'a> {
@@ -85,6 +87,7 @@ impl<'a> Shell<'a> {
             foreground: Vec::new(),
             background: Arc::new(Mutex::new(Vec::new())),
             received_sigtstp: false,
+            foreground_signals: Arc::new(ForegroundSignals::new())
         }
     }
 
diff --git a/src/shell/pipe.rs b/src/shell/pipe.rs
index 7dee66a6..2923adf2 100644
--- a/src/shell/pipe.rs
+++ b/src/shell/pipe.rs
@@ -13,12 +13,15 @@ use super::{JobKind, Shell};
 use super::status::*;
 use parser::peg::{Pipeline, RedirectFrom};
 
+#[cfg(all(unix, not(target_os = "redox")))] use super::signals::unix as signals;
+#[cfg(target_os = "redox")] use super::signals::redox as signals;
+
 use self::crossplat::*;
 
 /// The `crossplat` module contains components that are meant to be abstracted across
 /// different platforms
 #[cfg(not(target_os = "redox"))]
-mod crossplat {
+pub mod crossplat {
     use libc;
     use nix::{fcntl, unistd};
     use parser::peg::{RedirectFrom};
@@ -44,21 +47,6 @@ mod crossplat {
         }
     }
 
-    pub fn unblock_signals() {
-        unsafe {
-            use libc::*;
-            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);
-            sigaddset(&mut sigset as *mut sigset_t, SIGTTOU);
-            sigaddset(&mut sigset as *mut sigset_t, SIGTTIN);
-            sigaddset(&mut sigset as *mut sigset_t, SIGCHLD);
-            sigprocmask(SIG_UNBLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
-        }
-    }
-
     /// When given a process ID, that process will be assigned to a new process group.
     pub fn create_process_group() {
         let _ = unistd::setpgid(0, 0);
@@ -67,8 +55,6 @@ mod crossplat {
     /// 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);
     }
 
     pub fn get_pid() -> u32 {
@@ -130,10 +116,6 @@ mod crossplat {
         }
     }
 
-    pub fn unblock_signals() {
-        // TODO
-    }
-
     pub fn create_process_group() {
         // TODO
     }
@@ -296,7 +278,7 @@ fn fork_pipe(shell: &mut Shell, commands: Vec<(Command, JobKind)>) -> i32 {
             SUCCESS
         },
         Ok(Fork::Child) => {
-            unblock_signals();
+            signals::unblock();
             create_process_group();
             exit(pipe(shell, commands, false));
         },
@@ -345,7 +327,7 @@ fn pipe (
                     macro_rules! spawn_proc {
                         ($cmd:expr) => {{
                             let child = $cmd.before_exec(move || {
-                                unblock_signals();
+                                signals::unblock();
                                 create_process_group();
                                 Ok(())
                             }).spawn();
@@ -419,7 +401,7 @@ fn terminate_fg(shell: &mut Shell) {
 
 fn execute_command(shell: &mut Shell, command: &mut Command, foreground: bool) -> i32 {
     match command.before_exec(move || {
-        unblock_signals();
+        signals::unblock();
         create_process_group();
         Ok(())
     }).spawn() {
diff --git a/src/shell/signals.rs b/src/shell/signals.rs
new file mode 100644
index 00000000..1b32fa46
--- /dev/null
+++ b/src/shell/signals.rs
@@ -0,0 +1,40 @@
+#[cfg(all(unix, not(target_os = "redox")))]
+pub mod unix {
+    pub fn block() {
+        unsafe {
+            use libc::*;
+            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);
+            sigaddset(&mut sigset as *mut sigset_t, SIGTTOU);
+            sigaddset(&mut sigset as *mut sigset_t, SIGTTIN);
+            sigaddset(&mut sigset as *mut sigset_t, SIGCHLD);
+            sigprocmask(SIG_BLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
+        }
+    }
+
+    pub fn unblock() {
+        unsafe {
+            use libc::*;
+            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);
+            sigaddset(&mut sigset as *mut sigset_t, SIGTTOU);
+            sigaddset(&mut sigset as *mut sigset_t, SIGTTIN);
+            sigaddset(&mut sigset as *mut sigset_t, SIGCHLD);
+            sigprocmask(SIG_UNBLOCK, &sigset as *const sigset_t, ptr::null_mut() as *mut sigset_t);
+        }
+    }
+}
+
+// TODO
+#[cfg(target_os = "redox")]
+pub mod redox {
+    pub fn block() { }
+
+    pub fn unblock() { }
+}
-- 
GitLab