diff --git a/Cargo.toml b/Cargo.toml index 53cf8c40..efc0edd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT/Apache-2.0" name = "exec" readme = "README.md" repository = "https://github.com/faradayio/exec-rs" -version = "0.3.1" +version = "0.4.0" [dependencies] errno = "0.2" diff --git a/examples/exec.rs b/examples/exec.rs index 636794e2..c60c9df3 100644 --- a/examples/exec.rs +++ b/examples/exec.rs @@ -12,7 +12,7 @@ fn main() { // Exec the specified program. If all goes well, this will never // return. If it does return, it will always retun an error. - let err = exec::Command::new(&argv[0]).args(&argv).exec(); + let err = exec::Command::new(&argv[0]).args(&argv[1..]).exec(); println!("Error: {}", err); process::exit(1); } diff --git a/src/lib.rs b/src/lib.rs index ca2fed54..7703913f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,12 +11,10 @@ extern crate libc; use errno::{Errno, errno}; use std::error; -use std::error::Error as ErrorTrait; // Include for methods, not name. -use std::ffi::{CString, NulError, OsStr, OsString}; +use std::ffi::{OsStr, OsString}; use std::iter::{IntoIterator, Iterator}; use std::fmt; use std::ptr; -use std::os::unix::ffi::OsStrExt; /// Represents an error calling `exec`. /// @@ -29,41 +27,27 @@ use std::os::unix::ffi::OsStrExt; pub enum Error { /// One of the strings passed to `execv` contained an internal null byte /// and can't be passed correctly to C. - BadArgument(NulError), + NullByteInArgument, /// An error was returned by the system. Errno(Errno), } -impl error::Error for Error { - fn description(&self) -> &str { - match self { - &Error::BadArgument(_) => "bad argument to exec", - &Error::Errno(_) => "couldn't exec process", - } - } - fn cause(&self) -> Option<&error::Error> { - match self { - &Error::BadArgument(ref err) => Some(err), - &Error::Errno(_) => None, - } - } -} +impl error::Error for Error {} impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - &Error::BadArgument(ref err) => - write!(f, "{}: {}", self.description(), err), - &Error::Errno(err) => - write!(f, "{}: {}", self.description(), err), + Error::NullByteInArgument => + write!(f, "interior NUL byte in string argument to exec"), + Error::Errno(err) => + write!(f, "couldn't exec process: {}", err), } } } -impl From for Error { - /// Convert a `NulError` into an `ExecError`. - fn from(err: NulError) -> Error { - Error::BadArgument(err) +impl From for Error { + fn from(err: Errno) -> Error { + Error::Errno(err) } } @@ -97,16 +81,33 @@ macro_rules! exec_try { pub fn execvp(program: S, args: I) -> Error where S: AsRef, I: IntoIterator, I::Item: AsRef { + execvp_impl(program, args) +} + +#[cfg(unix)] +fn execvp_impl(program: S, args: I) -> Error + where S: AsRef, I: IntoIterator, I::Item: AsRef +{ + use std::ffi::CString; + use std::os::unix::ffi::OsStrExt; + // Add null terminations to our strings and our argument array, // converting them into a C-compatible format. - let program_cstring = - exec_try!(CString::new(program.as_ref().as_bytes())); - let arg_cstrings = exec_try!(args.into_iter().map(|arg| { - CString::new(arg.as_ref().as_bytes()) - }).collect::, _>>()); - let mut arg_charptrs: Vec<_> = arg_cstrings.iter().map(|arg| { - arg.as_ptr() - }).collect(); + let program_cstring = exec_try!( + CString::new(program.as_ref().as_bytes()) + .map_err(|_| Error::NullByteInArgument) + ); + let arg_cstrings = exec_try!( + args.into_iter() + .map(|arg| { + CString::new(arg.as_ref().as_bytes()) + .map_err(|_| Error::NullByteInArgument) + }) + .collect::, _>>() + ); + let mut arg_charptrs: Vec<_> = arg_cstrings.iter() + .map(|arg| arg.as_ptr()) + .collect(); arg_charptrs.push(ptr::null()); // Use an `unsafe` block so that we can call directly into C. @@ -123,6 +124,51 @@ pub fn execvp(program: S, args: I) -> Error } } +#[cfg(windows)] +extern "C" { + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/execvp-wexecvp + pub fn _wexecvp(cmdname: *const libc::wchar_t, argv: *const *const libc::wchar_t) + -> libc::intptr_t; +} + +#[cfg(windows)] +fn execvp_impl(program: S, args: I) -> Error + where S: AsRef, I: IntoIterator, I::Item: AsRef +{ + use std::os::windows::ffi::OsStrExt; + + let wcstring = |s: &OsStr| -> Result, Error> { + let mut vec: Vec = s.encode_wide().collect(); + if vec.iter().any(|&x| x == 0) { + // We have an interior null. + // The Unix impl includes a NulError, but that's only constructible using CString. + Err(Error::NullByteInArgument) + } else { + vec.push(0); // append null terminator + Ok(vec) + } + }; + + let program_wide = exec_try!(wcstring(program.as_ref())); + let args_wide = exec_try!(args.into_iter() + .map(|arg| wcstring(arg.as_ref())) + .collect::, _>>()); + let mut arg_ptrs: Vec<_> = args_wide.iter().map(|arg| arg.as_ptr()).collect(); + arg_ptrs.push(ptr::null()); + + let res = unsafe { + _wexecvp(program_wide.as_ptr(), arg_ptrs.as_ptr()) + }; + + // Handle our error result. + if res < 0 { + Error::Errno(errno()) + } else { + // Should never happen. + panic!("_wexecvp returned unexpectedly") + } +} + /// Build a command to execute. This has an API which is deliberately /// similar to `std::process::Command`. ///