From 9e5b39c0791ac8955a9461a7760dccb28046dfac Mon Sep 17 00:00:00 2001
From: Michael Aaron Murphy <mmstickman@gmail.com>
Date: Thu, 26 Oct 2017 14:03:31 -0400
Subject: [PATCH] Remove Dependency On nix Crate

Closes #547

This will remove all dependency upon the nix crate, as we will be
using `libc` directly for the `waitpid` functionality on *nix
systems. This should also make it easier to integrate with Redox.
---
 Cargo.lock                  |  37 ++---
 Cargo.toml                  |  76 ++++------
 src/lib.rs                  |   6 +-
 src/main.rs                 |   6 +-
 src/shell/pipe_exec/fork.rs |   1 +
 src/sys/redox.rs            |  25 +---
 src/sys/unix.rs             | 289 ------------------------------------
 src/sys/unix/job_control.rs | 153 +++++++++++++++++++
 src/sys/unix/mod.rs         | 121 +++++++++++++++
 src/sys/unix/signals.rs     |  34 +++++
 10 files changed, 360 insertions(+), 388 deletions(-)
 delete mode 100644 src/sys/unix.rs
 create mode 100644 src/sys/unix/job_control.rs
 create mode 100644 src/sys/unix/mod.rs
 create mode 100644 src/sys/unix/signals.rs

diff --git a/Cargo.lock b/Cargo.lock
index 6f6311ec..48504811 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6,13 +6,13 @@ dependencies = [
  "app_dirs 1.1.1 (git+https://github.com/redox-os/app-dirs-rs.git)",
  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "calculate 0.3.0 (git+https://github.com/redox-os/calc.git)",
+ "errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "libloading 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "liner 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "nix 0.8.1 (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.31 (registry+https://github.com/rust-lang/crates.io-index)",
  "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -47,11 +47,6 @@ dependencies = [
  "xdg 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "bitflags"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "bitflags"
 version = "0.9.1"
@@ -75,11 +70,6 @@ dependencies = [
  "decimal 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "cfg-if"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
 [[package]]
 name = "decimal"
 version = "2.0.0"
@@ -93,6 +83,16 @@ dependencies = [
  "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
+[[package]]
+name = "errno"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
 [[package]]
 name = "fnv"
 version = "1.0.5"
@@ -155,17 +155,6 @@ dependencies = [
  "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "nix"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
- "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "ole32-sys"
 version = "0.2.0"
@@ -331,13 +320,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
 "checksum app_dirs 1.1.1 (git+https://github.com/redox-os/app-dirs-rs.git)" = "<none>"
-"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
 "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
 "checksum bitflags 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f5cde24d1b2e2216a726368b2363a273739c91f4e3eb4e0dd12d672d396ad989"
 "checksum bytecount 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "4bbeb7c30341fce29f6078b4bdf876ea4779600866e98f5b2d203a534f195050"
 "checksum calculate 0.3.0 (git+https://github.com/redox-os/calc.git)" = "<none>"
-"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de"
 "checksum decimal 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8e3bfe070d7876527c8f901afc27b4190a715b6e52c8961f72fb35430960e80"
+"checksum errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2c858c42ac0b88532f48fca88b0ed947cad4f1f64d904bcd6c9f138f7b95d70"
 "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
 "checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb"
 "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
@@ -347,7 +335,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 "checksum libloading 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3f92926a9a4ba7aeeb01f5fba3f0d577147243b6e7fa8261c219cd1d6fbe3b1c"
 "checksum liner 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aff4455f523eb06630ad00ea139701ba51a5425d0a8163935e7bb1841c81e6e8"
 "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4"
-"checksum nix 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "47e49f6982987135c5e9620ab317623e723bd06738fd85377e8d55f57c8b6487"
 "checksum ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2c49021782e5233cd243168edfa8037574afed4eba4bbaf538b3d8d1789d8c"
 "checksum ord_subset 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bdb8b8c275824bc609c8294f20a55d37d808a1b071cec596da746ce4df0405e8"
 "checksum permutate 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53b7d5b19a715ffab38693a9dd44b067fdfa2b18eef65bd93562dfe507022fae"
diff --git a/Cargo.toml b/Cargo.toml
index 85c63b5a..87c359c4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,76 +1,54 @@
 [package]
-name = "ion-shell"
-description = "The Ion Shell"
-repository = "https://github.com/redox-os/ion"
-version = "1.0.5"
-license-file = "LICENSE"
-readme = "README.md"
 authors = [
-  "Michael Aaron Murphy <mmstickman@gmail.com>",
-  "Hunter Goldstein <hunter.d.goldstein@gmail.com>",
-  "Skyler Berg <skylertheberg@gmail.com>",
-  "Jeremy Soller <jackpot51@gmail.com>",
-  "Michael Gattozzi <mgattozzi@gmail.com>",
-  "Łukasz Niemier <lukasz@niemier.pl>",
+    "Michael Aaron Murphy <mmstickman@gmail.com>",
+    "Hunter Goldstein <hunter.d.goldstein@gmail.com>",
+    "Skyler Berg <skylertheberg@gmail.com>",
+    "Jeremy Soller <jackpot51@gmail.com>",
+    "Michael Gattozzi <mgattozzi@gmail.com>",
+    "Łukasz Niemier <lukasz@niemier.pl>",
 ]
 build = "build.rs"
+description = "The Ion Shell"
+license-file = "LICENSE"
+name = "ion-shell"
+readme = "README.md"
+repository = "https://github.com/redox-os/ion"
+version = "1.0.5"
 
 [[bin]]
 name = "ion"
 path = "src/main.rs"
 
-## Shared Dependencies
+[build-dependencies]
+ansi_term = "0.9"
+version_check = "0.1.3"
 
 [dependencies]
-# Provides XDG app directory support
-app_dirs = {git = "https://github.com/redox-os/app-dirs-rs.git"}
-# Provides methods for bitwise flag operations
 bitflags = "0.9.1"
-# Provides inline arithmetic expression and `calc` functionality
-calculate = {git = "https://github.com/redox-os/calc.git"}
-# A faster hashing algorithm for the hash maps in the shell.
 fnv = "1.0"
-# Performs globbing on words that are detected to be potentially globbable.
 glob = "0.2"
-# Provides a macro for lazily-evalulated statics
 lazy_static = "0.2"
-# Provides the line editor / prompt for the shell
 liner = "0.4"
-# Provides permutations of strings in brace expansions
 permutate = "0.3"
-# Enables strings to be stored inline on the stack, when possible.
+regex = "0.2"
 smallstring = "0.1"
-# Same as the above, but for vectors.
 smallvec = "0.4"
-# Provides grapheme-based string iterators.
 unicode-segmentation = "1.2"
-# Rusts regex crate
-regex = "0.2"
 
-## Redox Dependencies
+[dependencies.app_dirs]
+git = "https://github.com/redox-os/app-dirs-rs.git"
 
-[target.'cfg(target_os = "redox")'.dependencies]
-# Provides access to Redox syscalls for signals/job control.
-redox_syscall = "0.1"
+[dependencies.calculate]
+git = "https://github.com/redox-os/calc.git"
 
-## *nix Dependencies (Linux, Mac OS, BSDs)
+[profile.release]
+panic = "abort"
 
-[target.'cfg(all(unix, not(target_os = "redox")))'.dependencies]
-# Required to access some *nix-specific syscalls for signals/job control.
+[target."cfg(all(unix, not(target_os = \"redox\")))".dependencies]
+errno = "0.2.3"
 libc = "0.2"
-# A higher level abstraction of libc-acquired syscalls for signals/job control.
-nix = "0.8"
-# Obtains user directories
-users = "0.5.1"
-# Enables loading plugins
 libloading = "0.4"
+users = "0.5.1"
 
-[build-dependencies]
-ansi_term = "0.9"
-version_check = "0.1.3"
-
-[profile.release]
-# debug = true
-# rustflags = [ "-C", "target-cpu=native"]
-# lto = true
-panic = "abort"
+[target."cfg(target_os = \"redox\")".dependencies]
+redox_syscall = "0.1"
diff --git a/src/lib.rs b/src/lib.rs
index b758479a..6f277b14 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -7,6 +7,8 @@ extern crate app_dirs;
 #[macro_use]
 extern crate bitflags;
 extern crate calc;
+#[cfg(all(unix, not(target_os = "redox")))]
+extern crate errno;
 extern crate fnv;
 extern crate glob;
 #[macro_use]
@@ -16,8 +18,6 @@ extern crate libc;
 #[cfg(all(unix, not(target_os = "redox")))]
 extern crate libloading;
 extern crate liner;
-#[cfg(all(unix, not(target_os = "redox")))]
-extern crate nix;
 extern crate regex;
 extern crate smallstring;
 extern crate smallvec;
@@ -32,7 +32,7 @@ extern crate users as users_unix;
 mod sys;
 
 #[cfg(unix)]
-#[path = "sys/unix.rs"]
+#[path = "sys/unix/mod.rs"]
 mod sys;
 
 #[macro_use]
diff --git a/src/main.rs b/src/main.rs
index 36928eb3..cd28846c 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -11,6 +11,8 @@ extern crate app_dirs;
 #[macro_use]
 extern crate bitflags;
 extern crate calc;
+#[cfg(all(unix, not(target_os = "redox")))]
+extern crate errno;
 extern crate fnv;
 extern crate glob;
 #[macro_use]
@@ -20,8 +22,6 @@ extern crate libc;
 #[cfg(all(unix, not(target_os = "redox")))]
 extern crate libloading;
 extern crate liner;
-#[cfg(all(unix, not(target_os = "redox")))]
-extern crate nix;
 extern crate regex;
 extern crate smallstring;
 extern crate smallvec;
@@ -36,7 +36,7 @@ extern crate users as users_unix;
 mod sys;
 
 #[cfg(unix)]
-#[path = "sys/unix.rs"]
+#[path = "sys/unix/mod.rs"]
 mod sys;
 
 #[macro_use]
diff --git a/src/shell/pipe_exec/fork.rs b/src/shell/pipe_exec/fork.rs
index 476b0d23..5ac83622 100644
--- a/src/shell/pipe_exec/fork.rs
+++ b/src/shell/pipe_exec/fork.rs
@@ -25,6 +25,7 @@ pub(crate) fn fork_pipe(
             let _ = sys::reset_signal(sys::SIGTERM);
             // This ensures that the child fork has a unique PGID.
             create_process_group(0);
+            sys::close_stdin();
             // After execution of it's commands, exit with the last command's status.
             exit(pipe(shell, commands, false));
         }
diff --git a/src/sys/redox.rs b/src/sys/redox.rs
index a314db42..0c812180 100644
--- a/src/sys/redox.rs
+++ b/src/sys/redox.rs
@@ -83,6 +83,10 @@ pub(crate) fn dup2(old: RawFd, new: RawFd) -> io::Result<RawFd> {
 
 pub(crate) fn close(fd: RawFd) -> io::Result<()> { cvt(syscall::close(fd)).and(Ok(())) }
 
+pub(crate) fn close_stdin() {
+    syscall::close(STDIN_FILENO);
+}
+
 pub(crate) fn isatty(fd: RawFd) -> bool {
     if let Ok(tfd) = syscall::dup(fd, b"termios") {
         let _ = syscall::close(tfd);
@@ -99,29 +103,12 @@ fn cvt(result: Result<usize, syscall::Error>) -> io::Result<usize> {
 
 // TODO
 pub mod signals {
-    pub(crate) fn block()
-    // fn block()
-    // fn block()
-    // fn block()
-    // fn block()
-    // fn block() // fn block() // fn block() // fn block() // fn block() //
-    // fn block()
-    {
-    }
+    pub(crate) fn block() { }
 
     /// Unblocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so children processes can be
     /// controlled
     /// by the shell.
-    pub(crate) fn unblock()
-    // fn unblock()
-    // fn unblock()
-    // fn unblock()
-    // fn unblock()
-    // fn unblock()
-    // fn unblock() // fn unblock() // fn unblock() // fn unblock() // fn
-    // unblock()
-    {
-    }
+    pub(crate) fn unblock() { }
 }
 
 pub mod job_control {
diff --git a/src/sys/unix.rs b/src/sys/unix.rs
deleted file mode 100644
index d287f4bc..00000000
--- a/src/sys/unix.rs
+++ /dev/null
@@ -1,289 +0,0 @@
-extern crate libc;
-
-use libc::{c_int, pid_t, sighandler_t};
-use std::io;
-use std::os::unix::io::RawFd;
-
-pub(crate) const PATH_SEPARATOR: &str = ":";
-
-pub(crate) const O_CLOEXEC: usize = libc::O_CLOEXEC as usize;
-pub(crate) const SIGHUP: i32 = libc::SIGHUP;
-pub(crate) const SIGINT: i32 = libc::SIGINT;
-pub(crate) const SIGTERM: i32 = libc::SIGTERM;
-pub(crate) const SIGCONT: i32 = libc::SIGCONT;
-pub(crate) const SIGSTOP: i32 = libc::SIGSTOP;
-pub(crate) const SIGTSTP: i32 = libc::SIGTSTP;
-
-pub(crate) const STDOUT_FILENO: i32 = libc::STDOUT_FILENO;
-pub(crate) const STDERR_FILENO: i32 = libc::STDERR_FILENO;
-pub(crate) const STDIN_FILENO: i32 = libc::STDIN_FILENO;
-
-pub(crate) fn is_root() -> bool { unsafe { libc::geteuid() == 0 } }
-
-pub unsafe fn fork() -> io::Result<u32> { cvt(libc::fork()).map(|pid| pid as u32) }
-
-pub(crate) fn getpid() -> io::Result<u32> { cvt(unsafe { libc::getpid() }).map(|pid| pid as u32) }
-
-pub(crate) fn kill(pid: u32, signal: i32) -> io::Result<()> {
-    cvt(unsafe { libc::kill(pid as pid_t, signal as c_int) }).and(Ok(()))
-}
-
-pub(crate) fn killpg(pgid: u32, signal: i32) -> io::Result<()> {
-    cvt(unsafe { libc::kill(-(pgid as pid_t), signal as c_int) }).and(Ok(()))
-}
-
-pub(crate) fn pipe2(flags: usize) -> io::Result<(RawFd, RawFd)> {
-    let mut fds = [0; 2];
-
-    #[cfg(not(target_os = "macos"))]
-    cvt(unsafe { libc::pipe2(fds.as_mut_ptr(), flags as c_int) })?;
-
-    #[cfg(target_os = "macos")]
-    cvt(unsafe { libc::pipe(fds.as_mut_ptr()) })?;
-
-    Ok((fds[0], fds[1]))
-}
-
-pub(crate) fn setpgid(pid: u32, pgid: u32) -> io::Result<()> {
-    cvt(unsafe { libc::setpgid(pid as pid_t, pgid as pid_t) }).and(Ok(()))
-}
-
-#[allow(dead_code)]
-pub(crate) fn signal(signal: i32, handler: extern "C" fn(i32)) -> io::Result<()> {
-    if unsafe { libc::signal(signal as c_int, handler as sighandler_t) } == libc::SIG_ERR {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(())
-    }
-}
-
-pub(crate) fn reset_signal(signal: i32) -> io::Result<()> {
-    if unsafe { libc::signal(signal as c_int, libc::SIG_DFL) } == libc::SIG_ERR {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(())
-    }
-}
-
-pub(crate) fn tcsetpgrp(fd: RawFd, pgrp: u32) -> io::Result<()> {
-    cvt(unsafe { libc::tcsetpgrp(fd as c_int, pgrp as pid_t) }).and(Ok(()))
-}
-
-pub(crate) fn dup(fd: RawFd) -> io::Result<RawFd> { cvt(unsafe { libc::dup(fd) }) }
-
-pub(crate) fn dup2(old: RawFd, new: RawFd) -> io::Result<RawFd> {
-    cvt(unsafe { libc::dup2(old, new) })
-}
-
-pub(crate) fn close(fd: RawFd) -> io::Result<()> { cvt(unsafe { libc::close(fd) }).and(Ok(())) }
-
-pub(crate) fn isatty(fd: RawFd) -> bool { unsafe { libc::isatty(fd) == 1 } }
-
-// Support functions for converting libc return values to io errors {
-trait IsMinusOne {
-    fn is_minus_one(&self) -> bool;
-}
-
-macro_rules! impl_is_minus_one {
-        ($($t:ident)*) => ($(impl IsMinusOne for $t {
-            fn is_minus_one(&self) -> bool {
-                *self == -1
-            }
-        })*)
-    }
-
-impl_is_minus_one! { i8 i16 i32 i64 isize }
-
-fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
-    if t.is_minus_one() {
-        Err(io::Error::last_os_error())
-    } else {
-        Ok(t)
-    }
-}
-// } End of support functions
-
-pub mod signals {
-    /// Blocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so that the shell never receives
-    /// them.
-    pub(crate) 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);
-        }
-    }
-
-    /// Unblocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so children processes can be
-    /// controlled
-    /// by the shell.
-    pub(crate) 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);
-        }
-    }
-}
-
-pub mod job_control {
-    use shell::job_control::*;
-
-    use libc::{self, pid_t};
-    use shell::Shell;
-    use shell::foreground::ForegroundSignals;
-    use shell::status::{FAILURE, TERMINATED};
-    use std::sync::{Arc, Mutex};
-    use std::thread::sleep;
-    use std::time::Duration;
-
-    use nix::sys::wait::{waitpid, WaitStatus, WNOHANG, WUNTRACED};
-    #[cfg(not(target_os = "macos"))]
-    use nix::sys::wait::WCONTINUED;
-
-    use nix::{Errno, Error};
-    use nix::sys::signal::Signal;
-
-    pub(crate) fn watch_background(
-        fg: Arc<ForegroundSignals>,
-        processes: Arc<Mutex<Vec<BackgroundProcess>>>,
-        pid: u32,
-        njob: usize,
-    ) {
-        let mut fg_was_grabbed = false;
-        loop {
-            if !fg_was_grabbed {
-                if fg.was_grabbed(pid) {
-                    fg_was_grabbed = true;
-                }
-            }
-
-            #[cfg(not(target_os = "macos"))]
-            let opts = Some(WUNTRACED | WCONTINUED | WNOHANG);
-            #[cfg(target_os = "macos")]
-            let opts = Some(WUNTRACED | WNOHANG);
-
-            match waitpid(-(pid as pid_t), opts) {
-                Ok(WaitStatus::Exited(_, 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, _)) => {
-                    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)) => {
-                    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;
-                }
-                Ok(_) => (),
-                Err(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;
-                    if fg_was_grabbed {
-                        fg.errored();
-                    }
-                    break;
-                }
-            }
-            sleep(Duration::from_millis(100));
-        }
-    }
-
-    pub(crate) fn watch_foreground<'a, F, D>(
-        shell: &mut Shell<'a>,
-        _pid: u32,
-        last_pid: u32,
-        get_command: F,
-        mut drop_command: D,
-    ) -> i32
-        where F: FnOnce() -> String,
-              D: FnMut(i32)
-    {
-        let mut exit_status = 0;
-        loop {
-            match waitpid(-1, Some(WUNTRACED)) {
-                Ok(WaitStatus::Exited(pid, status)) => if pid == (last_pid as i32) {
-                    break status as i32;
-                } else {
-                    drop_command(pid);
-                    exit_status = status;
-                },
-                Ok(WaitStatus::Signaled(_, signal, _)) => {
-                    eprintln!("ion: process ended by signal");
-                    if signal == Signal::SIGTERM {
-                        shell.handle_signal(libc::SIGTERM);
-                        shell.exit(TERMINATED);
-                    } else if signal == Signal::SIGHUP {
-                        shell.handle_signal(libc::SIGHUP);
-                        shell.exit(TERMINATED);
-                    } else if signal == Signal::SIGINT {
-                        shell.foreground_send(libc::SIGINT as i32);
-                        shell.break_flow = true;
-                    }
-                    break TERMINATED;
-                }
-                Ok(WaitStatus::Stopped(pid, _)) => {
-                    shell.send_to_background(pid as u32, ProcessState::Stopped, get_command());
-                    shell.break_flow = true;
-                    break TERMINATED;
-                }
-                Ok(_) => (),
-                // ECHILD signifies that all children have exited
-                Err(Error::Sys(Errno::ECHILD)) => break exit_status as i32,
-                Err(why) => {
-                    eprintln!("ion: process doesn't exist: {}", why);
-                    break FAILURE;
-                }
-            }
-        }
-    }
-}
-
-pub mod variables {
-    use users_unix::get_user_by_name;
-    use users_unix::os::unix::UserExt;
-
-    pub(crate) fn get_user_home(username: &str) -> Option<String> {
-        match get_user_by_name(username) {
-            Some(user) => Some(user.home_dir().to_string_lossy().into_owned()),
-            None => None,
-        }
-    }
-}
diff --git a/src/sys/unix/job_control.rs b/src/sys/unix/job_control.rs
new file mode 100644
index 00000000..922ebc65
--- /dev/null
+++ b/src/sys/unix/job_control.rs
@@ -0,0 +1,153 @@
+use errno::errno;
+use libc::*;
+use shell::Shell;
+use shell::foreground::ForegroundSignals;
+use shell::job_control::*;
+use shell::status::{FAILURE, TERMINATED};
+use std::sync::{Arc, Mutex};
+use std::thread::sleep;
+use std::time::Duration;
+
+pub(crate) fn watch_background(
+    fg: Arc<ForegroundSignals>,
+    processes: Arc<Mutex<Vec<BackgroundProcess>>>,
+    pid: u32,
+    njob: usize,
+) {
+    let mut fg_was_grabbed = false;
+    loop {
+        if !fg_was_grabbed {
+            if fg.was_grabbed(pid) {
+                fg_was_grabbed = true;
+            }
+        }
+
+        let opts = WUNTRACED | WCONTINUED | WNOHANG;
+        let mut status = 0;
+
+        unsafe {
+            let pid = waitpid(-(pid as pid_t), &mut status, opts);
+            match pid {
+                -1 => {
+                    eprintln!("ion: ([{}] {}) errored: {}", njob, pid, errno());
+                    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;
+                }
+                0 => (),
+                _ if WIFEXITED(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(WEXITSTATUS(status) as i8);
+                    }
+                    break;
+                }
+                _ if WIFSTOPPED(status) => {
+                    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;
+                }
+                _ if WIFCONTINUED(status) => {
+                    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;
+                }
+                _ => (),
+            }
+        }
+        sleep(Duration::from_millis(100));
+    }
+}
+
+const FIRST: u8 = 1;
+const LAST: u8 = 2;
+
+pub(crate) fn watch_foreground<'a, F, D>(
+    shell: &mut Shell<'a>,
+    first_pid: u32,
+    last_pid: u32,
+    get_command: F,
+    mut drop_command: D,
+) -> i32
+    where F: FnOnce() -> String,
+          D: FnMut(i32)
+{
+    let mut exit_status = 0;
+    let mut found = 0;
+    loop {
+        unsafe {
+            let mut status = 0;
+            let pid = waitpid(-1, &mut status, WUNTRACED);
+            match pid {
+                -1 => {
+                    let error = errno();
+                    match error.0 {
+                        ECHILD => break exit_status,
+                        _ => {
+                            eprintln!("ion: {}", error);
+                            break FAILURE;
+                        }
+                    }
+                }
+                0 => (),
+                _ if WIFEXITED(status) => {
+                    let status = WEXITSTATUS(status) as i32;
+                    if pid == (last_pid as i32) {
+                        found |= LAST;
+                    }
+
+                    if pid == (first_pid as i32) {
+                        found |= FIRST;
+                    }
+
+                    if found == FIRST + LAST {
+                        break status;
+                    } else {
+                        drop_command(pid);
+                        exit_status = status;
+                    }
+                }
+                _ if WIFSIGNALED(status) => {
+                    eprintln!("ion: process ended by signal");
+                    let signal = WTERMSIG(status);
+                    match signal {
+                        SIGINT => {
+                            shell.foreground_send(signal as i32);
+                            shell.break_flow = true;
+                        }
+                        _ => {
+                            shell.handle_signal(signal);
+                            shell.exit(TERMINATED);
+                        }
+                    }
+                    break TERMINATED;
+                }
+                _ if WIFSTOPPED(status) => {
+                    shell.send_to_background(pid as u32, ProcessState::Stopped, get_command());
+                    shell.break_flow = true;
+                    break TERMINATED;
+                }
+                _ => (),
+            }
+        }
+    }
+}
diff --git a/src/sys/unix/mod.rs b/src/sys/unix/mod.rs
new file mode 100644
index 00000000..898717ff
--- /dev/null
+++ b/src/sys/unix/mod.rs
@@ -0,0 +1,121 @@
+extern crate libc;
+
+pub mod job_control;
+pub mod signals;
+
+use libc::{c_int, pid_t, sighandler_t};
+use std::io;
+use std::os::unix::io::RawFd;
+
+pub(crate) const PATH_SEPARATOR: &str = ":";
+
+pub(crate) const O_CLOEXEC: usize = libc::O_CLOEXEC as usize;
+pub(crate) const SIGHUP: i32 = libc::SIGHUP;
+pub(crate) const SIGINT: i32 = libc::SIGINT;
+pub(crate) const SIGTERM: i32 = libc::SIGTERM;
+pub(crate) const SIGCONT: i32 = libc::SIGCONT;
+pub(crate) const SIGSTOP: i32 = libc::SIGSTOP;
+pub(crate) const SIGTSTP: i32 = libc::SIGTSTP;
+
+pub(crate) const STDOUT_FILENO: i32 = libc::STDOUT_FILENO;
+pub(crate) const STDERR_FILENO: i32 = libc::STDERR_FILENO;
+pub(crate) const STDIN_FILENO: i32 = libc::STDIN_FILENO;
+
+pub(crate) fn is_root() -> bool { unsafe { libc::geteuid() == 0 } }
+
+pub unsafe fn fork() -> io::Result<u32> { cvt(libc::fork()).map(|pid| pid as u32) }
+
+pub(crate) fn getpid() -> io::Result<u32> { cvt(unsafe { libc::getpid() }).map(|pid| pid as u32) }
+
+pub(crate) fn kill(pid: u32, signal: i32) -> io::Result<()> {
+    cvt(unsafe { libc::kill(pid as pid_t, signal as c_int) }).and(Ok(()))
+}
+
+pub(crate) fn killpg(pgid: u32, signal: i32) -> io::Result<()> {
+    cvt(unsafe { libc::kill(-(pgid as pid_t), signal as c_int) }).and(Ok(()))
+}
+
+pub(crate) fn pipe2(flags: usize) -> io::Result<(RawFd, RawFd)> {
+    let mut fds = [0; 2];
+
+    #[cfg(not(target_os = "macos"))]
+    cvt(unsafe { libc::pipe2(fds.as_mut_ptr(), flags as c_int) })?;
+
+    #[cfg(target_os = "macos")]
+    cvt(unsafe { libc::pipe(fds.as_mut_ptr()) })?;
+
+    Ok((fds[0], fds[1]))
+}
+
+pub(crate) fn setpgid(pid: u32, pgid: u32) -> io::Result<()> {
+    cvt(unsafe { libc::setpgid(pid as pid_t, pgid as pid_t) }).and(Ok(()))
+}
+
+#[allow(dead_code)]
+pub(crate) fn signal(signal: i32, handler: extern "C" fn(i32)) -> io::Result<()> {
+    if unsafe { libc::signal(signal as c_int, handler as sighandler_t) } == libc::SIG_ERR {
+        Err(io::Error::last_os_error())
+    } else {
+        Ok(())
+    }
+}
+
+pub(crate) fn reset_signal(signal: i32) -> io::Result<()> {
+    if unsafe { libc::signal(signal as c_int, libc::SIG_DFL) } == libc::SIG_ERR {
+        Err(io::Error::last_os_error())
+    } else {
+        Ok(())
+    }
+}
+
+pub(crate) fn tcsetpgrp(fd: RawFd, pgrp: u32) -> io::Result<()> {
+    cvt(unsafe { libc::tcsetpgrp(fd as c_int, pgrp as pid_t) }).and(Ok(()))
+}
+
+pub(crate) fn dup(fd: RawFd) -> io::Result<RawFd> { cvt(unsafe { libc::dup(fd) }) }
+
+pub(crate) fn dup2(old: RawFd, new: RawFd) -> io::Result<RawFd> {
+    cvt(unsafe { libc::dup2(old, new) })
+}
+
+pub(crate) fn close(fd: RawFd) -> io::Result<()> { cvt(unsafe { libc::close(fd) }).and(Ok(())) }
+
+pub(crate) fn close_stdin() {
+    unsafe { libc::close(STDIN_FILENO); }
+}
+
+pub(crate) fn isatty(fd: RawFd) -> bool { unsafe { libc::isatty(fd) == 1 } }
+
+trait IsMinusOne {
+    fn is_minus_one(&self) -> bool;
+}
+
+macro_rules! impl_is_minus_one {
+        ($($t:ident)*) => ($(impl IsMinusOne for $t {
+            fn is_minus_one(&self) -> bool {
+                *self == -1
+            }
+        })*)
+    }
+
+impl_is_minus_one! { i8 i16 i32 i64 isize }
+
+fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
+    if t.is_minus_one() {
+        Err(io::Error::last_os_error())
+    } else {
+        Ok(t)
+    }
+}
+
+pub mod variables {
+    use users_unix::get_user_by_name;
+    use users_unix::os::unix::UserExt;
+
+    pub(crate) fn get_user_home(username: &str) -> Option<String> {
+        match get_user_by_name(username) {
+            Some(user) => Some(user.home_dir().to_string_lossy().into_owned()),
+            None => None,
+        }
+    }
+}
diff --git a/src/sys/unix/signals.rs b/src/sys/unix/signals.rs
new file mode 100644
index 00000000..659384f0
--- /dev/null
+++ b/src/sys/unix/signals.rs
@@ -0,0 +1,34 @@
+use libc::*;
+use std::mem;
+use std::ptr;
+
+/// Blocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so that the shell never receives
+/// them.
+pub(crate) fn block() // fn block() // fn block()
+{
+    unsafe {
+        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);
+    }
+}
+
+/// Unblocks the SIGTSTP/SIGTTOU/SIGTTIN/SIGCHLD signals so children processes can be
+/// controlled
+/// by the shell.
+pub(crate) fn unblock() // fn unblock() // fn unblock()
+{
+    unsafe {
+        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);
+    }
+}
-- 
GitLab