diff --git a/src/lib/shell/pipe_exec/mod.rs b/src/lib/shell/pipe_exec/mod.rs index 36c52326fc03a49516677cd650a53a21d1d43d65..7300fd697387e3576afebf1bda85426f063ea4a6 100644 --- a/src/lib/shell/pipe_exec/mod.rs +++ b/src/lib/shell/pipe_exec/mod.rs @@ -709,50 +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()); + 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) = *stdout { - redir(file.as_raw_fd(), sys::STDOUT_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 } - - if let Some(ref file) = *stderr { - redir(file.as_raw_fd(), sys::STDERR_FILENO); - let _ = sys::close(file.as_raw_fd()); + Err(ref err) => { + eprintln!("ion: command exec error: {}", err); + FAILURE } - - prepare_child(false); - let code = match sys::execve(&name, &args, false) { - ref err if err.kind() == io::ErrorKind::NotFound => { - if !command_not_found(self, &name) { - eprintln!("ion: command not found: {}", name); - } - NO_SUCH_COMMAND - } - ref err => { - eprintln!("ion: command exec error: {}", err); - FAILURE - } - }; - sys::fork_exit(code); - }, - 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 } - } } } @@ -966,45 +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); - let code = match sys::execve(&name, &args, false) { - ref err if err.kind() == io::ErrorKind::NotFound => { - if !command_not_found(shell, &name) { - eprintln!("ion: command not found: {}", name); - } - NO_SUCH_COMMAND - } - ref err => { - eprintln!("ion: command exec error: {}", err); - FAILURE - } - }; - sys::fork_exit(code); - }, + 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); *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); } } } diff --git a/src/lib/sys/redox.rs b/src/lib/sys/redox.rs index 3889d783652d9c5a5a071fc206d5f40db66d3d86..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,11 +332,11 @@ pub mod job_control { } } - let pid = get_pid_value(pid); + let pgid = get_pid_value(pid); loop { status = 0; - let result = waitpid(pid, &mut status, 0); + let result = waitpid(pgid, &mut status, 0); match result { Err(error) => { match error.errno { @@ -259,7 +357,7 @@ pub mod job_control { eprintln!("ion: process ended by signal {}", signal); match signal { SIGINT => { - let _ = syscall::kill(pid, signal as usize); + let _ = syscall::kill(pgid, signal as usize); shell.break_flow = true; } _ => { diff --git a/src/lib/sys/unix/mod.rs b/src/lib/sys/unix/mod.rs index adf0926627a0ca386ee0619792f6a2aec322107d..f10fa882babf3da9aadc0bf76ad3f86ab82257ca 100644 --- a/src/lib/sys/unix/mod.rs +++ b/src/lib/sys/unix/mod.rs @@ -86,6 +86,124 @@ 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 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) => prog, + Err(_) => { + return Err(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 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(); + 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 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 { + 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,