Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds execvpe #6

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 136 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@
extern crate errno;
extern crate libc;

use errno::{Errno, errno};
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::iter::{IntoIterator, Iterator};
use std::fmt;
use std::ptr;
use std::iter::{IntoIterator, Iterator};
use std::os::unix::ffi::OsStrExt;
use std::ptr;

/// Represents an error calling `exec`.
///
Expand All @@ -41,7 +40,7 @@ impl error::Error for Error {
&Error::Errno(_) => "couldn't exec process",
}
}
fn cause(&self) -> Option<&error::Error> {
fn cause(&self) -> Option<&dyn error::Error> {
match self {
&Error::BadArgument(ref err) => Some(err),
&Error::Errno(_) => None,
Expand All @@ -52,10 +51,8 @@ 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::BadArgument(ref err) => write!(f, "{}: {}", self.to_string(), err),
&Error::Errno(err) => write!(f, "{}: {}", self.to_string(), err),
}
}
}
Expand Down Expand Up @@ -95,23 +92,73 @@ macro_rules! exec_try {
/// println!("Error: {}", err);
/// ```
pub fn execvp<S, I>(program: S, args: I) -> Error
where S: AsRef<OsStr>, I: IntoIterator, I::Item: AsRef<OsStr>
where
S: AsRef<OsStr>,
I: IntoIterator,
I::Item: AsRef<OsStr>,
{
// Add null terminations to our strings and our argument array,
// converting them into a C-compatible format.
let program_cstring = exec_try!(to_program_cstring(program));
let argv = exec_try!(to_argv(args));

// Use an `unsafe` block so that we can call directly into C.
let res = unsafe { libc::execvp(program_cstring.as_ptr(), argv.char_ptrs.as_ptr()) };

// Handle our error result.
if res < 0 {
Error::Errno(errno())
} else {
// Should never happen.
panic!("execvp returned unexpectedly")
}
}

/// Run `program` with `args` and environment `envs`, completely replacing
/// the currently running program. If it returns at all, it always return
/// an error.
///
/// Note that `program` and the first element of `args` will normally be
/// identical. The former is the program we ask the operating system to
/// run, and the latter is the value that will show up in `argv[0]` when
/// the program executes. On POSIX systems, these can technically be
/// completely different, and we've preserved that much of the low-level
/// API here.
///
/// # Examples
///
/// ```no_run
/// use std::env::vars_os;
/// use std::ffi::OsString;
/// let err = execvpe(
/// "bash",
/// ["bash"],
/// vars_os().chain([(OsString::from("NAME"), OsString::from("VALUE"))]),
/// println!("Error: {}", err);
/// ```
#[cfg(not(target_os = "macos"))]
pub fn execvpe<S, I, J, N, V>(program: S, args: I, envs: J) -> Error
where
S: AsRef<OsStr>,
I: IntoIterator,
I::Item: AsRef<OsStr>,
J: IntoIterator<Item = (N, V)>,
N: AsRef<OsStr> + std::fmt::Debug,
V: AsRef<OsStr> + std::fmt::Debug,
{
// 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::<Result<Vec<_>, _>>());
let mut arg_charptrs: Vec<_> = arg_cstrings.iter().map(|arg| {
arg.as_ptr()
}).collect();
arg_charptrs.push(ptr::null());
let program_cstring = exec_try!(to_program_cstring(program));
let argv = exec_try!(to_argv(args));
let envp = exec_try!(to_envp(envs));

// Use an `unsafe` block so that we can call directly into C.
let res = unsafe {
libc::execvp(program_cstring.as_ptr(), arg_charptrs.as_ptr())
libc::execvpe(
program_cstring.as_ptr(),
argv.char_ptrs.as_ptr(),
envp.char_ptrs.as_ptr(),
)
};

// Handle our error result.
Expand All @@ -123,6 +170,74 @@ pub fn execvp<S, I>(program: S, args: I) -> Error
}
}

fn to_program_cstring<S>(program: S) -> std::result::Result<CString, NulError>
where
S: AsRef<OsStr>,
{
CString::new(program.as_ref().as_bytes())
}

// Struct ensures that cstrings have same lifetime as char_ptrs that points into them
struct Argv {
#[allow(dead_code)]
cstrings: Vec<CString>,
char_ptrs: Vec<*const i8>,
}

fn to_argv<I>(args: I) -> std::result::Result<Argv, NulError>
where
I: IntoIterator,
I::Item: AsRef<OsStr>,
{
let cstrings = args
.into_iter()
.map(|arg| CString::new(arg.as_ref().as_bytes()))
.collect::<Result<Vec<_>, _>>()?;

let mut char_ptrs = cstrings.iter().map(|arg| arg.as_ptr()).collect::<Vec<_>>();
char_ptrs.push(ptr::null());

Ok(Argv {
cstrings: cstrings,
char_ptrs: char_ptrs,
})
}

// Struct ensures that cstrings have same lifetime as char_ptrs that points into them
#[cfg(not(target_os = "macos"))]
struct Envp {
#[allow(dead_code)]
cstrings: Vec<CString>,
char_ptrs: Vec<*const i8>,
}

#[cfg(not(target_os = "macos"))]
fn to_envp<J, N, V>(envs: J) -> std::result::Result<Envp, NulError>
where
J: IntoIterator<Item = (N, V)>,
N: AsRef<OsStr> + std::fmt::Debug,
V: AsRef<OsStr> + std::fmt::Debug,
{
let cstrings = envs
.into_iter()
.map(|(n, v)| {
let mut temp: OsString = OsString::new();
temp.push(n);
temp.push("=");
temp.push(v);
CString::new(temp.as_bytes())
})
.collect::<std::result::Result<Vec<_>, _>>()?;

let mut char_ptrs = cstrings.iter().map(|x| x.as_ptr()).collect::<Vec<_>>();
char_ptrs.push(ptr::null());

Ok(Envp {
cstrings: cstrings,
char_ptrs: char_ptrs,
})
}

/// Build a command to execute. This has an API which is deliberately
/// similar to `std::process::Command`.
///
Expand All @@ -145,7 +260,7 @@ impl Command {
/// program will be searched for using the usual rules for `PATH`.
pub fn new<S: AsRef<OsStr>>(program: S) -> Command {
Command {
argv: vec!(program.as_ref().to_owned()),
argv: vec![program.as_ref().to_owned()],
}
}

Expand Down