diff --git a/Cargo.lock b/Cargo.lock
index cafe8afb8e525a245abf8f2194d9d359c77d4acb..e285b13c93869f3a37cd8797f3ef8a9e8d3e8157 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -122,16 +122,6 @@ dependencies = [
  "serde 1.0.24 (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.34 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "failure"
 version = "0.1.1"
@@ -184,7 +174,6 @@ dependencies = [
  "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "calculate 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -491,7 +480,6 @@ source = "git+https://github.com/whitequark/rust-xdg#a1c9249f78a5395c2ee3dc8688a
 "checksum clap 2.29.0 (registry+https://github.com/rust-lang/crates.io-index)" = "110d43e343eb29f4f51c1db31beb879d546db27998577e5715270a54bcf41d3f"
 "checksum dbghelp-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97590ba53bcb8ac28279161ca943a924d1fd4a8fb3fa63302591647c4fc5b850"
 "checksum decimal 2.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e6458723bc760383275fbc02f4c769b2e5f3de782abaf5e7e0b9b7f0368a63ed"
-"checksum errno 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b2c858c42ac0b88532f48fca88b0ed947cad4f1f64d904bcd6c9f138f7b95d70"
 "checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82"
 "checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b"
 "checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
diff --git a/Cargo.toml b/Cargo.toml
index 3adafca0557d799cdad229d279673ccfb0bfe4c8..c6ad323c2b9a0aa99703306118185a4de475b824 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,7 +48,6 @@ path = "src/lib/lib.rs"
 lto = true
 panic = "abort"
 [target."cfg(all(unix, not(target_os = \"redox\")))".dependencies]
-errno = "0.2.3"
 libc = "0.2"
 libloading = "0.4"
 users = "0.5.1"
diff --git a/examples/script_exec.ion b/examples/script_exec.ion
new file mode 100644
index 0000000000000000000000000000000000000000..92cf7d03349b6accd79c5e83fadc1990263a11b8
--- /dev/null
+++ b/examples/script_exec.ion
@@ -0,0 +1,3 @@
+for script in examples/a* examples/b*
+    target/debug/ion $script
+end
\ No newline at end of file
diff --git a/examples/script_exec.out b/examples/script_exec.out
new file mode 100644
index 0000000000000000000000000000000000000000..61461fe254ed46e604a10cc44328e68102a6fb50
--- /dev/null
+++ b/examples/script_exec.out
@@ -0,0 +1,104 @@
+one one
+one twoone
+o n e t w o
+111 110 101 116 119 111
+o n e t w o
+firstline secondline
+3 2 1
+a
+3 2 1
+1
+2
+3
+4
+5
+6
+1
+2
+3
+4
+5
+6
+a
+b
+c
+d
+e
+a
+b
+c
+d
+e
+1
+2
+3
+4
+5
+6
+a
+b
+c
+d
+e
+1 2 3 4 5 6
+a b c d e
+1 2 3 4 5 6 a b c d e
+1
+2
+3
+1
+2
+3
+1
+2
+3
+1
+1 2
+1 2 3
+4 5
+1 2 3 4 5
+😉
+😉
+😉
+1 2 3 4 5
+pass
+1A1 1A2 1B1 1B2
+0abc2def5g 0abc2def5h 0abc2def5i 0abc2def6g 0abc2def6h 0abc2def6i 0abc2def7g 0abc2def7h 0abc2def7i 0abc3def5g 0abc3def5h 0abc3def5i 0abc3def6g 0abc3def6h 0abc3def6i 0abc3def7g 0abc3def7h 0abc3def7i 0abc4def5g 0abc4def5h 0abc4def5i 0abc4def6g 0abc4def6h 0abc4def6i 0abc4def7g 0abc4def7h 0abc4def7i 1abc2def5g 1abc2def5h 1abc2def5i 1abc2def6g 1abc2def6h 1abc2def6i 1abc2def7g 1abc2def7h 1abc2def7i 1abc3def5g 1abc3def5h 1abc3def5i 1abc3def6g 1abc3def6h 1abc3def6i 1abc3def7g 1abc3def7h 1abc3def7i 1abc4def5g 1abc4def5h 1abc4def5i 1abc4def6g 1abc4def6h 1abc4def6i 1abc4def7g 1abc4def7h 1abc4def7i
+-1 0 1
+2 1 0
+a b c
+d c b
+A B C
+D C B
+-1 0 1
+2 1 0
+a b c
+d c b
+A B C
+D C B
+0 2 4
+a c e
+A C E
+0 -2 -4
+e c a
+E C A
+0 2 4
+a c e
+A C E
+0 -2 -4
+e c
+E C
+1
+2
+3
+4
+5
+1
+2
+3
+4
+5
+true
+false
+foo
+bar
diff --git a/src/lib/builtins/exec.rs b/src/lib/builtins/exec.rs
index 9ae8a927d8cf9bb1ac50c653c42fa85dc75e8854..749e92d007d55536bd1b22147c1326733082a65d 100644
--- a/src/lib/builtins/exec.rs
+++ b/src/lib/builtins/exec.rs
@@ -28,10 +28,7 @@ pub(crate) fn exec(shell: &mut Shell, args: &[&str]) -> Result<(), String> {
                 &[]
             };
             shell.prep_for_exit();
-            match execve(argument, args, (flags & CLEAR_ENV) == 1) {
-                Ok(_) => Ok(()),
-                Err(err) => Err(err.description().to_owned()),
-            }
+            Err(execve(argument, args, (flags & CLEAR_ENV) == 1).description().to_owned())
         }
         None => Err("no command provided".to_owned()),
     }
diff --git a/src/lib/lib.rs b/src/lib/lib.rs
index dc3f9e852769c0645095a7d6d4816fb5efccaa9b..ab1ededfcf7c6693fd2b2f880ba8d07c9d15087f 100644
--- a/src/lib/lib.rs
+++ b/src/lib/lib.rs
@@ -8,8 +8,6 @@
 #[macro_use]
 extern crate bitflags;
 extern crate calc;
-#[cfg(all(unix, not(target_os = "redox")))]
-extern crate errno;
 extern crate failure;
 #[macro_use]
 extern crate failure_derive;
diff --git a/src/lib/shell/mod.rs b/src/lib/shell/mod.rs
index 1ca66d6da52d0a9d965d4e4aa2435cbd506577ed..3a7522e80e2dc8f6c562b441bebd56cd76c7ad12 100644
--- a/src/lib/shell/mod.rs
+++ b/src/lib/shell/mod.rs
@@ -84,9 +84,6 @@ pub struct Shell {
     pub(crate) previous_job: u32,
     /// Contains all the boolean flags that control shell behavior.
     pub flags: u8,
-    /// A temporary field for storing foreground PIDs used by the pipeline
-    /// execution.
-    foreground: Vec<u32>,
     /// Contains information on all of the active background processes that are being managed
     /// by the shell.
     pub(crate) background: Arc<Mutex<Vec<BackgroundProcess>>>,
@@ -170,7 +167,6 @@ impl<'a> Shell {
             previous_job:        !0,
             previous_status:     0,
             flags:               0,
-            foreground:          Vec::new(),
             background:          Arc::new(Mutex::new(Vec::new())),
             is_background_shell: false,
             is_library,
diff --git a/src/lib/shell/pipe_exec/job_control.rs b/src/lib/shell/pipe_exec/job_control.rs
index 854fd97a1c15fd046996fde25cf88ec7007adc8c..5fcd3d086b3c2cf433b78d7985e0aee2926e7079 100644
--- a/src/lib/shell/pipe_exec/job_control.rs
+++ b/src/lib/shell/pipe_exec/job_control.rs
@@ -31,7 +31,6 @@ pub(crate) trait JobControl {
     fn set_bg_task_in_foreground(&self, pid: u32, cont: bool) -> i32;
     fn resume_stopped(&mut self);
     fn handle_signal(&self, signal: i32) -> bool;
-    fn foreground_send(&self, signal: i32);
     fn background_send(&self, signal: i32);
     fn watch_foreground(&mut self, pid: i32, command: &str) -> i32;
     fn send_to_background(&mut self, child: u32, state: ProcessState, command: String);
@@ -152,13 +151,6 @@ impl JobControl for Shell {
         self_sys::watch_foreground(self, pid, command)
     }
 
-    /// Send a kill signal to all running foreground tasks.
-    fn foreground_send(&self, signal: i32) {
-        for &process in self.foreground.iter() {
-            let _ = sys::killpg(process, signal);
-        }
-    }
-
     /// Resumes all stopped background jobs
     fn resume_stopped(&mut self) {
         for process in self.background.lock().unwrap().iter() {
diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs
index 639f70cd9c9151a3598087cdb6c9b519ff0c26df..7300fd697387e3576afebf1bda85426f063ea4a6 100644
--- a/src/lib/shell/pipe_exec/mod.rs
+++ b/src/lib/shell/pipe_exec/mod.rs
@@ -414,8 +414,6 @@ pub(crate) trait PipelineExecution {
 
 impl PipelineExecution for Shell {
     fn execute_pipeline(&mut self, pipeline: &mut Pipeline) -> i32 {
-        // Remove any leftover foreground tasks from the last execution.
-        self.foreground.clear();
         // If the supplied pipeline is a background, a string representing the command
         // and a boolean representing whether it should be disowned is stored here.
         let possible_background_name =
@@ -711,42 +709,31 @@ impl PipelineExecution for Shell {
         stdout: &Option<File>,
         stderr: &Option<File>,
     ) -> i32 {
-        return match unsafe { sys::fork() } {
-            Ok(0) => {
-                if let Some(ref file) = *stdin {
-                    redir(file.as_raw_fd(), sys::STDIN_FILENO);
-                    let _ = sys::close(file.as_raw_fd());
-                }
-
-                if let Some(ref file) = *stdout {
-                    redir(file.as_raw_fd(), sys::STDOUT_FILENO);
-                    let _ = sys::close(file.as_raw_fd());
+        let result = sys::fork_and_exec(
+                name,
+                &args,
+                if let Some(ref f) = *stdin { Some(f.as_raw_fd()) } else { None },
+                if let Some(ref f) = *stdout { Some(f.as_raw_fd()) } else { None },
+                if let Some(ref f) = *stderr { Some(f.as_raw_fd()) } else { None },
+                false,
+                || prepare_child(false)
+            );
+
+            match result {
+                Ok(pid) => {
+                    self.watch_foreground(pid as i32, "")
                 }
-
-                if let Some(ref file) = *stderr {
-                    redir(file.as_raw_fd(), sys::STDERR_FILENO);
-                    let _ = sys::close(file.as_raw_fd());
+                Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
+                    if !command_not_found(self, &name) {
+                        eprintln!("ion: command not found: {}", name);
+                    }
+                    NO_SUCH_COMMAND
                 }
-
-                prepare_child(false);
-                if let Err(_why) = sys::execve(name, &args, false) {
-                    command_not_found(self, name);
-                    sys::fork_exit(NO_SUCH_COMMAND);
+                Err(ref err) => {
+                    eprintln!("ion: command exec error: {}", err);
+                    FAILURE
                 }
-                unreachable!()
-            },
-            Ok(pid) => {
-                close(stdin);
-                close(stdout);
-                close(stderr);
-                // TODO: get long string
-                self.watch_foreground(pid as i32, "")
             }
-            Err(why) => {
-                eprintln!("ion: failed to fork: {}", why);
-                COULD_NOT_EXEC
-            }
-        }
     }
 }
 
@@ -960,37 +947,28 @@ fn spawn_proc(
     match cmd {
         RefinedJob::External { ref name, ref args, ref stdout, ref stderr, ref stdin} => {
             let args: Vec<&str> = args.iter().skip(1).map(|x| x as &str).collect();
-            match unsafe { sys::fork() } {
-                Ok(0) => {
-                    if let Some(ref file) = *stdin {
-                        redir(file.as_raw_fd(), sys::STDIN_FILENO);
-                        let _ = sys::close(file.as_raw_fd());
-                    }
-                    if let Some(ref file) = *stdout {
-                        redir(file.as_raw_fd(), sys::STDOUT_FILENO);
-                        let _ = sys::close(file.as_raw_fd());
-                    }
-                    if let Some(ref file) = *stderr {
-                        redir(file.as_raw_fd(), sys::STDERR_FILENO);
-                        let _ = sys::close(file.as_raw_fd());
-                    }
-
-                    prepare_child(child_blocked);
-                    if let Err(_why) = sys::execve(&name, &args, false) {
-                        command_not_found(shell, name);
-                        sys::fork_exit(NO_SUCH_COMMAND);
-                    }
-                },
+            let result = sys::fork_and_exec(
+                name,
+                &args,
+                if let Some(ref f) = *stdin { Some(f.as_raw_fd()) } else { None },
+                if let Some(ref f) = *stdout { Some(f.as_raw_fd()) } else { None },
+                if let Some(ref f) = *stderr { Some(f.as_raw_fd()) } else { None },
+                false,
+                || prepare_child(child_blocked)
+            );
+
+            match result {
                 Ok(pid) => {
-                    close(stdin);
-                    close(stdout);
-                    close(stderr);
-                    shell.foreground.push(pid);
                     *last_pid = *current_pid;
                     *current_pid = pid;
-                },
-                Err(e) => {
-                    eprintln!("ion: failed to fork {}: {}", short, e);
+                }
+                Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
+                    if !command_not_found(shell, &name) {
+                        eprintln!("ion: command not found: {}", name);
+                    }
+                }
+                Err(ref err) => {
+                    eprintln!("ion: command exec error: {}", err);
                 }
             }
         }
@@ -1008,7 +986,6 @@ fn spawn_proc(
                 Ok(pid) => {
                     close(stdout);
                     close(stderr);
-                    shell.foreground.push(pid);
                     *last_pid = *current_pid;
                     *current_pid = pid;
                 },
@@ -1031,7 +1008,6 @@ fn spawn_proc(
                 Ok(pid) => {
                     close(stdout);
                     close(stderr);
-                    shell.foreground.push(pid);
                     *last_pid = *current_pid;
                     *current_pid = pid;
                 },
@@ -1052,7 +1028,6 @@ fn spawn_proc(
                 }
                 Ok(pid) => {
                     close(stdout);
-                    shell.foreground.push(pid);
                     *last_pid = *current_pid;
                     *current_pid = pid;
                 }
@@ -1073,7 +1048,6 @@ fn spawn_proc(
                 Ok(pid) => {
                     close(stdout);
                     close(stderr);
-                    shell.foreground.push(pid);
                     *last_pid = *current_pid;
                     *current_pid = pid;
                 }
diff --git a/src/lib/sys/redox.rs b/src/lib/sys/redox.rs
index 1b19fd5ace7a561e22768f88468cf5cb013a68e8..95b652fe019e8dfbf209a037ac22470d0bf0b35c 100644
--- a/src/lib/sys/redox.rs
+++ b/src/lib/sys/redox.rs
@@ -84,7 +84,15 @@ pub(crate) fn setpgid(pid: u32, pgid: u32) -> io::Result<()> {
     cvt(syscall::setpgid(pid as usize, pgid as usize)).and(Ok(()))
 }
 
-pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<()> {
+pub(crate) fn fork_and_exec<F: Fn()>(
+    prog: &str,
+    args: &[&str],
+    stdin: Option<RawFd>,
+    stdout: Option<RawFd>,
+    stderr: Option<RawFd>,
+    clear_env: bool,
+    before_exec: F
+) -> io::Result<u32> {
     // Construct a valid set of arguments to pass to execve. Ensure
     // that the program is the first argument.
     let mut cvt_args: Vec<[usize; 2]> = Vec::new();
@@ -125,14 +133,104 @@ pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<(
     }
 
     if let Some(prog) = prog {
-        // If we found the program. Run it!
-        cvt(syscall::execve(prog.as_os_str().as_bytes(), &cvt_args)).and(Ok(()))
+        unsafe {
+            match fork()? {
+                0 => {
+                    if let Some(stdin) = stdin {
+                        let _ = dup2(stdin, STDIN_FILENO);
+                        let _ = close(stdin);
+                    }
+
+                    if let Some(stdout) = stdout {
+                        let _ = dup2(stdout, STDOUT_FILENO);
+                        let _ = close(stdout);
+                    }
+
+                    if let Some(stderr) = stderr {
+                        let _ = dup2(stderr, STDERR_FILENO);
+                        let _ = close(stderr);
+                    }
+
+                    before_exec();
+
+                    let error = syscall::execve(prog.as_os_str().as_bytes(), &cvt_args);
+                    let error = io::Error::from_raw_os_error(error.err().unwrap().errno);
+                    eprintln!("ion: command exec: {}", error);
+                    fork_exit(1);
+                }
+                pid => {
+                    if let Some(stdin) = stdin {
+                        let _ = close(stdin);
+                    }
+
+                    if let Some(stdout) = stdout {
+                        let _ = close(stdout);
+                    }
+
+                    if let Some(stderr) = stderr {
+                        let _ = close(stderr);
+                    }
+
+                    Ok(pid)
+                }
+            }
+        }
     } else {
         // The binary was not found.
         Err(io::Error::from_raw_os_error(syscall::ENOENT))
     }
 }
 
+pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Error {
+    // Construct a valid set of arguments to pass to execve. Ensure
+    // that the program is the first argument.
+    let mut cvt_args: Vec<[usize; 2]> = Vec::new();
+    cvt_args.push([prog.as_ptr() as usize, prog.len()]);
+    for arg in args {
+        cvt_args.push([arg.as_ptr() as usize, arg.len()]);
+    }
+
+    // Get the PathBuf of the program if it exists.
+    let prog = if prog.contains(':') || prog.contains('/') {
+        // This is a fully specified scheme or path to an
+        // executable.
+        Some(PathBuf::from(prog))
+    } else if let Ok(paths) = env::var("PATH") {
+        // This is not a fully specified scheme or path.
+        // Iterate through the possible paths in the
+        // env var PATH that this executable may be found
+        // in and return the first one found.
+        env::split_paths(&paths)
+            .filter_map(|mut path| {
+                path.push(prog);
+                if path.exists() {
+                    Some(path)
+                } else {
+                    None
+                }
+            })
+            .next()
+    } else {
+        None
+    };
+
+    // If clear_env set, clear the env.
+    if clear_env {
+        for (key, _) in env::vars() {
+            env::remove_var(key);
+        }
+    }
+
+    if let Some(prog) = prog {
+        // If we found the program. Run it!
+        let error = syscall::execve(prog.as_os_str().as_bytes(), &cvt_args);
+        io::Error::from_raw_os_error(error.err().unwrap().errno)
+    } else {
+        // The binary was not found.
+        io::Error::from_raw_os_error(syscall::ENOENT)
+    }
+}
+
 #[allow(dead_code)]
 pub(crate) fn signal(signal: i32, handler: extern "C" fn(i32)) -> io::Result<()> {
     let new = SigAction {
@@ -209,7 +307,7 @@ pub mod job_control {
     use std::os::unix::process::ExitStatusExt;
     use std::process::ExitStatus;
     use std::sync::{Arc, Mutex};
-    use syscall::{ECHILD, waitpid};
+    use syscall::{self, ECHILD, waitpid};
     use super::{SIGINT, SIGPIPE};
 
     pub(crate) fn watch_background(
@@ -234,9 +332,11 @@ pub mod job_control {
             }
         }
 
+        let pgid = get_pid_value(pid);
+
         loop {
             status = 0;
-            let result = waitpid(get_pid_value(pid), &mut status, 0);
+            let result = waitpid(pgid, &mut status, 0);
             match result {
                 Err(error) => {
                     match error.errno {
@@ -257,7 +357,7 @@ pub mod job_control {
                             eprintln!("ion: process ended by signal {}", signal);
                             match signal {
                                 SIGINT => {
-                                    shell.foreground_send(signal as i32);
+                                    let _ = syscall::kill(pgid, signal as usize);
                                     shell.break_flow = true;
                                 }
                                 _ => {
diff --git a/src/lib/sys/unix/job_control.rs b/src/lib/sys/unix/job_control.rs
index fee78255e074f5ec1e6f32c1af70a85ceb7448bc..fdfc879e9bd1c816e732e4d53e631061cbc1650e 100644
--- a/src/lib/sys/unix/job_control.rs
+++ b/src/lib/sys/unix/job_control.rs
@@ -1,4 +1,3 @@
-use errno::errno;
 use libc::*;
 use shell::Shell;
 use shell::foreground::ForegroundSignals;
@@ -7,6 +6,7 @@ use shell::status::{FAILURE, TERMINATED};
 use std::sync::{Arc, Mutex};
 use std::thread::sleep;
 use std::time::Duration;
+use super::{errno, write_errno};
 
 pub(crate) fn watch_background(
     fg: Arc<ForegroundSignals>,
@@ -88,12 +88,11 @@ pub(crate) fn watch_foreground(shell: &mut Shell, pid: i32, command: &str) -> i3
             status = 0;
             match waitpid(pid, &mut status, WUNTRACED) {
                 -1 => {
-                    let error = errno();
-                    match error.0 {
+                    match errno() {
                         ECHILD if signaled == 0 => break exit_status,
                         ECHILD => break signaled,
-                        _ => {
-                            eprintln!("ion: waitpid error: {}", error);
+                        errno => {
+                            write_errno("ion: waitpid error: ", errno);
                             break FAILURE;
                         }
                     }
@@ -108,7 +107,7 @@ pub(crate) fn watch_foreground(shell: &mut Shell, pid: i32, command: &str) -> i3
                     eprintln!("ion: process ended by signal {}", signal);
                     match signal {
                         SIGINT => {
-                            shell.foreground_send(signal as i32);
+                            let _ = kill(pid, signal as i32);
                             shell.break_flow = true;
                         }
                         _ => {
diff --git a/src/lib/sys/unix/mod.rs b/src/lib/sys/unix/mod.rs
index 073abbc38b2b9fe538c644b947fa582f396dfcbf..f10fa882babf3da9aadc0bf76ad3f86ab82257ca 100644
--- a/src/lib/sys/unix/mod.rs
+++ b/src/lib/sys/unix/mod.rs
@@ -3,10 +3,10 @@ extern crate libc;
 pub mod job_control;
 pub mod signals;
 
-use libc::{c_char, c_int, pid_t, sighandler_t, waitpid, ECHILD, EINTR, WEXITSTATUS, WUNTRACED};
-use std::{io, ptr};
-use std::env;
-use std::ffi::CString;
+use libc::{c_char, c_int, pid_t, sighandler_t, strerror, waitpid, ECHILD, EINTR, WEXITSTATUS, WUNTRACED};
+use std::{io, ptr, env};
+use std::io::Write;
+use std::ffi::{CStr, CString};
 use std::os::unix::io::RawFd;
 
 pub(crate) const PATH_SEPARATOR: &str = ":";
@@ -27,6 +27,14 @@ pub(crate) const STDIN_FILENO: i32 = libc::STDIN_FILENO;
 
 fn errno() -> i32 { unsafe { *libc::__errno_location() } }
 
+fn write_errno(msg: &str, errno: i32) {
+    let stderr = io::stderr();
+    let mut stderr = stderr.lock();
+    let _ = stderr.write(msg.as_bytes());
+    let _ = stderr.write(unsafe { CStr::from_ptr(strerror(errno)) }.to_bytes());
+    let _ = stderr.write_all(b"\n");
+}
+
 pub(crate) fn geteuid() -> io::Result<u32> { Ok(unsafe { libc::geteuid() } as u32) }
 
 pub(crate) fn getuid() -> io::Result<u32> { Ok(unsafe { libc::getuid() } as u32) }
@@ -78,28 +86,36 @@ 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 execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<()> {
-    // Prepare the program string
+pub(crate) fn fork_and_exec<F: Fn()>(
+    prog: &str,
+    args: &[&str],
+    stdin: Option<RawFd>,
+    stdout: Option<RawFd>,
+    stderr: Option<RawFd>,
+    clear_env: bool,
+    before_exec: F,
+) -> io::Result<u32> {
     let prog_str = match CString::new(prog) {
-        Ok(prog_str) => prog_str,
+        Ok(prog) => prog,
         Err(_) => {
             return Err(io::Error::last_os_error());
         }
     };
 
-    // Create the arguments vector
+    // Create a vector of null-terminated strings.
     let mut cvt_args: Vec<CString> = Vec::new();
     cvt_args.push(prog_str.clone());
-    for arg in args.iter() {
-        match CString::new(*arg) {
+    for &arg in args.iter() {
+        match CString::new(arg) {
             Ok(arg) => cvt_args.push(arg),
             Err(_) => {
                 return Err(io::Error::last_os_error());
             }
         }
     }
+
+    // Create a null-terminated array of pointers to those strings.
     let mut arg_ptrs: Vec<*const c_char> = cvt_args.iter().map(|x| x.as_ptr()).collect();
-    // NULL terminate the argv array
     arg_ptrs.push(ptr::null());
 
     // Get the PathBuf of the program if it exists.
@@ -115,12 +131,8 @@ pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<(
             .filter_map(|mut path| {
                 path.push(prog);
                 match (path.exists(), path.to_str()) {
-                    (false, _) => None,
-                    (true, Some(path)) => match CString::new(path) {
-                        Ok(prog_str) => Some(prog_str),
-                        Err(_) => None,
-                    },
-                    (true, None) => None,
+                    (true, Some(path)) => CString::new(path).ok(),
+                    _ => None,
                 }
             })
             .next()
@@ -130,6 +142,7 @@ pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<(
 
     let mut env_ptrs: Vec<*const c_char> = Vec::new();
     let mut env_vars: Vec<CString> = Vec::new();
+
     // If clear_env is not specified build envp
     if !clear_env {
         for (key, value) in env::vars() {
@@ -144,13 +157,123 @@ pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Result<(
     }
     env_ptrs.push(ptr::null());
 
+    if let Some(prog) = prog {
+        unsafe {
+            match fork()? {
+                0 => {
+                    if let Some(stdin) = stdin {
+                        let _ = dup2(stdin, STDIN_FILENO);
+                        let _ = close(stdin);
+                    }
+
+                    if let Some(stdout) = stdout {
+                        let _ = dup2(stdout, STDOUT_FILENO);
+                        let _ = close(stdout);
+                    }
+
+                    if let Some(stderr) = stderr {
+                        let _ = dup2(stderr, STDERR_FILENO);
+                        let _ = close(stderr);
+                    }
+
+                    before_exec();
+
+                    libc::execve(prog.as_ptr(), arg_ptrs.as_ptr(), env_ptrs.as_ptr());
+                    eprintln!("ion: command exec: {}", io::Error::last_os_error());
+                    fork_exit(1);
+                }
+                pid => {
+                    if let Some(stdin) = stdin {
+                        let _ = close(stdin);
+                    }
+
+                    if let Some(stdout) = stdout {
+                        let _ = close(stdout);
+                    }
+
+                    if let Some(stderr) = stderr {
+                        let _ = close(stderr);
+                    }
+
+                    Ok(pid)
+                }
+            }
+        }
+    } else {
+        Err(io::Error::from_raw_os_error(libc::ENOENT))
+    }
+}
+
+pub(crate) fn execve(prog: &str, args: &[&str], clear_env: bool) -> io::Error {
+    let prog_str = match CString::new(prog) {
+        Ok(prog) => prog,
+        Err(_) => {
+            return io::Error::last_os_error();
+        }
+    };
+
+    // Create a vector of null-terminated strings.
+    let mut cvt_args: Vec<CString> = Vec::new();
+    cvt_args.push(prog_str.clone());
+    for &arg in args.iter() {
+        match CString::new(arg) {
+            Ok(arg) => cvt_args.push(arg),
+            Err(_) => {
+                return io::Error::last_os_error();
+            }
+        }
+    }
+
+    // Create a null-terminated array of pointers to those strings.
+    let mut arg_ptrs: Vec<*const c_char> = cvt_args.iter().map(|x| x.as_ptr()).collect();
+    arg_ptrs.push(ptr::null());
+
+    // Get the PathBuf of the program if it exists.
+    let prog = if prog.contains('/') {
+        // This is a fully specified path to an executable.
+        Some(prog_str)
+    } else if let Ok(paths) = env::var("PATH") {
+        // This is not a fully specified scheme or path.
+        // Iterate through the possible paths in the
+        // env var PATH that this executable may be found
+        // in and return the first one found.
+        env::split_paths(&paths)
+            .filter_map(|mut path| {
+                path.push(prog);
+                match (path.exists(), path.to_str()) {
+                    (true, Some(path)) => CString::new(path).ok(),
+                    _ => None,
+                }
+            })
+            .next()
+    } else {
+        None
+    };
+
+    let mut env_ptrs: Vec<*const c_char> = Vec::new();
+    let mut env_vars: Vec<CString> = Vec::new();
+
+    // If clear_env is not specified build envp
+    if !clear_env {
+        for (key, value) in env::vars() {
+            match CString::new(format!("{}={}", key, value)) {
+                Ok(var) => env_vars.push(var),
+                Err(_) => {
+                    return io::Error::last_os_error();
+                }
+            }
+        }
+        env_ptrs = env_vars.iter().map(|x| x.as_ptr()).collect();
+    }
+    env_ptrs.push(ptr::null());
+
     if let Some(prog) = prog {
         // If we found the program. Run it!
-        cvt(unsafe { libc::execve(prog.as_ptr(), arg_ptrs.as_ptr(), env_ptrs.as_ptr()) })
-            .and(Ok(()))
+        unsafe { libc::execve(prog.as_ptr(), arg_ptrs.as_ptr(), env_ptrs.as_ptr()) };
+        io::Error::last_os_error()
     } else {
         // The binary was not found.
-        Err(io::Error::from_raw_os_error(libc::ENOENT))
+        io::Error::from_raw_os_error(libc::ENOENT)
     }
 }