diff --git a/src/builtins/exec.rs b/src/builtins/exec.rs new file mode 100644 index 0000000000000000000000000000000000000000..9ae8a927d8cf9bb1ac50c653c42fa85dc75e8854 --- /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 6d3ca583e774eab3849b4a82490e6a8bf6f8f70f..7aa8740933d469991d8dd090d7adec6a795718f3 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 dafbbbc95f57d6f8f98ed481753c4d1811cff917..9c6be6e322f0ff14aa41858e907b7c5ecbe16a33 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 b76ab0ce30ab69221c9451562e474e14a04d99be..40af569cbf286b4a2c4abaad01d38b55594b98ef 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 919f51cdf483a1e2fa06f34c738a7a8789186987..5e950ff130428e18c39dbfca159b93c825d1559f 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 83abc799d7488869821bee60e23ebd0ef8abc958..5207ae09c1bb1d07e6d8f33566d121857ee5a77f 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];