From a2c5e5d4e1ca68c3ce77cb2135291e3ad410ba7c Mon Sep 17 00:00:00 2001
From: Dan Robertson <dan.robertson@anidata.org>
Date: Wed, 29 Nov 2017 23:34:41 +0000
Subject: [PATCH] Add the exec builtin

 - Add the necessary functions to builtin
 - Add the necessary interfaces for the execve syscall
   - Add sys::unix::execve
   - Add sys::redox::execve
---
 src/builtins/exec.rs      | 38 ++++++++++++++++++
 src/builtins/man_pages.rs | 16 ++++++++
 src/builtins/mod.rs       | 18 ++++++++-
 src/shell/mod.rs          |  7 +++-
 src/sys/redox.rs          | 52 ++++++++++++++++++++++++
 src/sys/unix/mod.rs       | 83 ++++++++++++++++++++++++++++++++++++++-
 6 files changed, 209 insertions(+), 5 deletions(-)
 create mode 100644 src/builtins/exec.rs

diff --git a/src/builtins/exec.rs b/src/builtins/exec.rs
new file mode 100644
index 00000000..9ae8a927
--- /dev/null
+++ b/src/builtins/exec.rs
@@ -0,0 +1,38 @@
+use builtins::man_pages::{check_help, MAN_EXEC};
+use shell::Shell;
+use std::error::Error;
+use sys::execve;
+
+/// Executes the givent commmand.
+pub(crate) fn exec(shell: &mut Shell, args: &[&str]) -> Result<(), String> {
+    const CLEAR_ENV: u8 = 1;
+
+    let mut flags = 0u8;
+    let mut idx = 0;
+    for &arg in args.iter() {
+        match arg {
+            "-c" => flags |= CLEAR_ENV,
+            _ if check_help(args, MAN_EXEC) => {
+                return Ok(());
+            }
+            _ => break,
+        }
+        idx += 1;
+    }
+
+    match args.get(idx) {
+        Some(argument) => {
+            let args = if args.len() > idx + 1 {
+                &args[idx + 1..]
+            } else {
+                &[]
+            };
+            shell.prep_for_exit();
+            match execve(argument, args, (flags & CLEAR_ENV) == 1) {
+                Ok(_) => Ok(()),
+                Err(err) => Err(err.description().to_owned()),
+            }
+        }
+        None => Err("no command provided".to_owned()),
+    }
+}
diff --git a/src/builtins/man_pages.rs b/src/builtins/man_pages.rs
index 6d3ca583..7aa87409 100644
--- a/src/builtins/man_pages.rs
+++ b/src/builtins/man_pages.rs
@@ -197,6 +197,22 @@ DESCRIPTION
     all arguments are joined using a space as a separator.
 "#;
 
+pub(crate) const MAN_EXEC: &'static str = r#"NAME
+    exec - Replace the shell with the given command.
+
+SYNOPSIS
+    exec [-ch] [--help] [command [arguments ...]]
+
+DESCRIPTION
+    Execute <command>, replacing the shell with the specified program.
+    The <arguments> following the command become the arguments to
+    <command>.
+
+OPTIONS
+    -c  Execute command with an empty environment.
+"#;
+
+
 pub(crate) const MAN_HISTORY: &'static str = r#"NAME
     history - print command history
 
diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs
index dafbbbc9..9c6be6e3 100644
--- a/src/builtins/mod.rs
+++ b/src/builtins/mod.rs
@@ -12,11 +12,13 @@ mod echo;
 mod set;
 mod status;
 mod exists;
+mod exec;
 mod ion;
 mod is;
 
 use self::conditionals::{contains, ends_with, starts_with};
 use self::echo::echo;
+use self::exec::exec;
 use self::exists::exists;
 use self::functions::fn_;
 use self::ion::ion_docs;
@@ -80,7 +82,8 @@ pub const BUILTINS: &'static BuiltinMap = &map!(
     "drop" => builtin_drop : "Delete a variable",
     "echo" => builtin_echo : "Display a line of text",
     "ends-with" => ends_with :"Evaluates if the supplied argument ends with a given string",
-    "eval" => builtin_eval : "evaluates the evaluated expression",
+    "eval" => builtin_eval : "Evaluates the evaluated expression",
+    "exec" => builtin_exec : "Replace the shell with the given command.",
     "exists" => builtin_exists : "Performs tests on files and text",
     "exit" => builtin_exit : "Exits the current session",
     "false" => builtin_false : "Do nothing, unsuccessfully",
@@ -499,6 +502,19 @@ fn builtin_exit(args: &[&str], shell: &mut Shell) -> i32 {
     )
 }
 
+fn builtin_exec(args: &[&str], shell: &mut Shell) -> i32 {
+    match exec(shell, &args[1..]) {
+        // Shouldn't ever hit this case.
+        Ok(()) => SUCCESS,
+        Err(err) => {
+            let stderr = io::stderr();
+            let mut stderr = stderr.lock();
+            let _ = writeln!(stderr, "ion: exec: {}", err);
+            FAILURE
+        }
+    }
+}
+
 use regex::Regex;
 fn builtin_matches(args: &[&str], _: &mut Shell) -> i32 {
     if check_help(args, MAN_MATCHES) {
diff --git a/src/shell/mod.rs b/src/shell/mod.rs
index b76ab0ce..40af569c 100644
--- a/src/shell/mod.rs
+++ b/src/shell/mod.rs
@@ -164,7 +164,7 @@ impl<'a> Shell {
         }
     }
 
-    pub(crate) fn exit(&mut self, status: i32) -> ! {
+    pub(crate) fn prep_for_exit(&mut self) {
         // The context has two purposes: if it exists, this is an interactive shell; and the
         // context will also be sent a signal to commit all changes to the history file,
         // and waiting for the history thread in the background to finish.
@@ -174,9 +174,12 @@ impl<'a> Shell {
                 self.background_send(sys::SIGHUP);
             }
             let context = self.context.as_mut().unwrap();
-            context.history.commit_history()
+            context.history.commit_history();
         }
+    }
 
+    pub(crate) fn exit(&mut self, status: i32) -> ! {
+        self.prep_for_exit();
         process::exit(status);
     }
 
diff --git a/src/sys/redox.rs b/src/sys/redox.rs
index 919f51cd..5e950ff1 100644
--- a/src/sys/redox.rs
+++ b/src/sys/redox.rs
@@ -1,7 +1,10 @@
 extern crate syscall;
 
 use std::{io, mem, slice};
+use std::env;
+use std::os::unix::ffi::OsStrExt;
 use std::os::unix::io::RawFd;
+use std::path::PathBuf;
 
 use syscall::SigAction;
 
@@ -49,6 +52,55 @@ 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<()> {
+    // 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!
+        cvt(syscall::execve(prog.as_os_str().as_bytes(), &cvt_args)).and(Ok(()))
+    } else {
+        // The binary was not found.
+        Err(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 {
diff --git a/src/sys/unix/mod.rs b/src/sys/unix/mod.rs
index 83abc799..5207ae09 100644
--- a/src/sys/unix/mod.rs
+++ b/src/sys/unix/mod.rs
@@ -3,8 +3,10 @@ extern crate libc;
 pub mod job_control;
 pub mod signals;
 
-use libc::{c_int, pid_t, sighandler_t};
-use std::io;
+use libc::{c_char, c_int, pid_t, sighandler_t};
+use std::{io, ptr};
+use std::env;
+use std::ffi::CString;
 use std::os::unix::io::RawFd;
 
 pub(crate) const PATH_SEPARATOR: &str = ":";
@@ -41,6 +43,83 @@ 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
+    let prog_str = match CString::new(prog) {
+        Ok(prog_str) => prog_str,
+        Err(_) => { return Err(io::Error::last_os_error()); }
+    };
+
+    // Create the arguments vector
+    let mut cvt_args: Vec<CString> = Vec::new();
+    cvt_args.push(prog_str);
+    for arg in args.iter() {
+        match CString::new(*arg) {
+            Ok(arg) => cvt_args.push(arg),
+            Err(_) => {
+                return Err(io::Error::last_os_error());
+            }
+        }
+    }
+    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.
+    let prog = if prog.contains('/') {
+        // This is a fully specified path to an executable.
+        match CString::new(prog) {
+            Ok(prog_str) => Some(prog_str),
+            Err(_) => None,
+        }
+    } 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()) {
+                    (false, _) => None,
+                    (true, Some(path)) => match CString::new(path) {
+                        Ok(prog_str) => Some(prog_str),
+                        Err(_) => None,
+                    },
+                    (true, None) => None,
+                }
+            })
+            .next()
+    } else {
+        None
+    };
+
+    let mut env_ptrs: Vec<*const c_char> = Vec::new();
+    // If clear_env is not specified build envp
+    if !clear_env {
+        let mut env_vars: Vec<CString> = Vec::new();
+        for (key, value) in env::vars() {
+            match CString::new(format!("{}={}", key, value)) {
+                Ok(var) => env_vars.push(var),
+                Err(_) => {
+                    return Err(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(()))
+    } else {
+        // The binary was not found.
+        Err(io::Error::from_raw_os_error(libc::ENOENT))
+    }
+}
+
 pub(crate) fn pipe2(flags: usize) -> io::Result<(RawFd, RawFd)> {
     let mut fds = [0; 2];
 
-- 
GitLab