Skip to content

Commit

Permalink
Null terminated slices for exec
Browse files Browse the repository at this point in the history
  • Loading branch information
arcnmx committed Dec 8, 2017
1 parent 2170b51 commit 9506b74
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 30 deletions.
185 changes: 185 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,188 @@ impl<'a, NP: ?Sized + NixPath> NixPath for Option<&'a NP> {
}
}
}

/*
*
* ===== Null terminated slices for exec =====
*
*/

use std::ops::{Deref, DerefMut};
use std::mem::transmute;
use std::iter;

/// A conversion trait that may borrow or allocate memory depending on the input.
/// Used to convert between terminated slices and `Vec`s.
pub trait IntoRef<'a, T: ?Sized> {
type Target: 'a + AsRef<T> + Deref<Target=T>;

fn into_ref(self) -> Self::Target;
}

/// A slice of references terminated by `None`. Used by API calls that accept
/// null-terminated arrays such as the `exec` family of functions.
pub struct TerminatedSlice<T> {
inner: [Option<T>],
}

impl<T> TerminatedSlice<T> {
/// Instantiate a `TerminatedSlice` from a slice ending in `None`. Returns
/// `None` if the provided slice is not properly terminated.
pub fn from_slice(slice: &[Option<T>]) -> Option<&Self> {
if slice.last().map(Option::is_none).unwrap_or(false) {
Some(unsafe { Self::from_slice_unchecked(slice) })
} else {
None
}
}

/// Instantiate a `TerminatedSlice` from a mutable slice ending in `None`.
/// Returns `None` if the provided slice is not properly terminated.
pub fn from_slice_mut(slice: &mut [Option<T>]) -> Option<&mut Self> {
if slice.last().map(Option::is_none).unwrap_or(false) {
Some(unsafe { Self::from_slice_mut_unchecked(slice) })
} else {
None
}
}

/// Instantiate a `TerminatedSlice` from a slice ending in `None`.
///
/// ## Unsafety
///
/// This assumes that the slice is properly terminated, and can cause
/// undefined behaviour if that invariant is not upheld.
pub unsafe fn from_slice_unchecked(slice: &[Option<T>]) -> &Self {
transmute(slice)
}

/// Instantiate a `TerminatedSlice` from a mutable slice ending in `None`.
///
/// ## Unsafety
///
/// This assumes that the slice is properly terminated, and can cause
/// undefined behaviour if that invariant is not upheld.
pub unsafe fn from_slice_mut_unchecked(slice: &mut [Option<T>]) -> &mut Self {
transmute(slice)
}
}

impl<'a, U: Sized> TerminatedSlice<&'a U> {
pub fn as_ptr(&self) -> *const *const U {
self.inner.as_ptr() as *const _
}
}

impl<T> Deref for TerminatedSlice<T> {
type Target = [Option<T>];

fn deref(&self) -> &Self::Target {
&self.inner[..self.inner.len() - 1]
}
}

impl<T> DerefMut for TerminatedSlice<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
let len = self.inner.len();
&mut self.inner[..len - 1]
}
}

impl<T> AsRef<TerminatedSlice<T>> for TerminatedSlice<T> {
fn as_ref(&self) -> &Self {
self
}
}

/// Owned variant of `TerminatedSlice`.
pub struct TerminatedVec<T> {
inner: Vec<Option<T>>,
}

impl<T> TerminatedVec<T> {
/// Instantiates a `TerminatedVec` from a `None` terminated `Vec`. Returns
/// `None` if the provided `Vec` is not properly terminated.
pub fn from_vec(vec: Vec<Option<T>>) -> Option<Self> {
if vec.last().map(Option::is_none).unwrap_or(false) {
Some(unsafe { Self::from_vec_unchecked(vec) })
} else {
None
}
}

/// Instantiates a `TerminatedVec` from a `None` terminated `Vec`.
///
/// ## Unsafety
///
/// This assumes that the `Vec` is properly terminated, and can cause
/// undefined behaviour if that invariant is not upheld.
pub unsafe fn from_vec_unchecked(vec: Vec<Option<T>>) -> Self {
TerminatedVec {
inner: vec,
}
}

/// Consume `self` to return the inner wrapped `Vec`.
pub fn into_inner(self) -> Vec<Option<T>> {
self.inner
}
}

impl<'a> TerminatedVec<&'a c_char> {
fn terminate<T: AsRef<CStr> + 'a, I: IntoIterator<Item=T>>(iter: I) -> Self {
fn cstr_char<'a, S: AsRef<CStr> + 'a>(s: S) -> &'a c_char {
unsafe {
&*s.as_ref().as_ptr()
}
}

let terminated = iter.into_iter()
.map(cstr_char)
.map(Some).chain(iter::once(None)).collect();

unsafe {
TerminatedVec::from_vec_unchecked(terminated)
}
}
}

impl<T> Deref for TerminatedVec<T> {
type Target = TerminatedSlice<T>;

fn deref(&self) -> &Self::Target {
unsafe {
TerminatedSlice::from_slice_unchecked(&self.inner)
}
}
}

impl<T> DerefMut for TerminatedVec<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe {
TerminatedSlice::from_slice_mut_unchecked(&mut self.inner)
}
}
}

impl<T> AsRef<TerminatedSlice<T>> for TerminatedVec<T> {
fn as_ref(&self) -> &TerminatedSlice<T> {
self
}
}

impl<'a, T: 'a> IntoRef<'a, TerminatedSlice<&'a T>> for &'a TerminatedSlice<&'a T> {
type Target = &'a TerminatedSlice<&'a T>;

fn into_ref(self) -> Self::Target {
self
}
}

impl<'a, T: AsRef<CStr> + 'a, I: IntoIterator<Item=T>> IntoRef<'a, TerminatedSlice<&'a c_char>> for I {
type Target = TerminatedVec<&'a c_char>;

fn into_ref(self) -> Self::Target {
TerminatedVec::terminate(self)
}
}
56 changes: 26 additions & 30 deletions src/unistd.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Safe wrappers around functions found in libc "unistd.h" header

use errno::{self, Errno};
use {Error, Result, NixPath};
use {Error, Result, NixPath, IntoRef, TerminatedSlice};
use fcntl::{fcntl, FdFlag, OFlag};
use fcntl::FcntlArg::F_SETFD;
use libc::{self, c_char, c_void, c_int, c_long, c_uint, size_t, pid_t, off_t,
Expand Down Expand Up @@ -552,25 +552,19 @@ pub fn chown<P: ?Sized + NixPath>(path: &P, owner: Option<Uid>, group: Option<Gi
Errno::result(res).map(drop)
}

fn to_exec_array(args: &[CString]) -> Vec<*const c_char> {
let mut args_p: Vec<*const c_char> = args.iter().map(|s| s.as_ptr()).collect();
args_p.push(ptr::null());
args_p
}

/// Replace the current process image with a new one (see
/// [exec(3)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html)).
///
/// See the `::nix::unistd::execve` system call for additional details. `execv`
/// performs the same action but does not allow for customization of the
/// environment for the new process.
#[inline]
pub fn execv(path: &CString, argv: &[CString]) -> Result<Void> {
let args_p = to_exec_array(argv);
pub fn execv<'a, P: ?Sized + NixPath, A: IntoRef<'a, TerminatedSlice<&'a c_char>>>(path: &P, argv: A) -> Result<Void> {
let args_p = argv.into_ref();

unsafe {
libc::execv(path.as_ptr(), args_p.as_ptr())
};
try!(path.with_nix_path(|cstr| {
unsafe { libc::execv(cstr.as_ptr(), args_p.as_ptr()) }
}));

Err(Error::Sys(Errno::last()))
}
Expand All @@ -589,13 +583,13 @@ pub fn execv(path: &CString, argv: &[CString]) -> Result<Void> {
/// in the `args` list is an argument to the new process. Each element in the
/// `env` list should be a string in the form "key=value".
#[inline]
pub fn execve(path: &CString, args: &[CString], env: &[CString]) -> Result<Void> {
let args_p = to_exec_array(args);
let env_p = to_exec_array(env);
pub fn execve<'a, 'e, P: ?Sized + NixPath, A: IntoRef<'a, TerminatedSlice<&'a c_char>>, E: IntoRef<'e, TerminatedSlice<&'e c_char>>>(path: &P, args: A, env: E) -> Result<Void> {
let args_p = args.into_ref();
let env_p = env.into_ref();

unsafe {
libc::execve(path.as_ptr(), args_p.as_ptr(), env_p.as_ptr())
};
try!(path.with_nix_path(|cstr| {
unsafe { libc::execve(cstr.as_ptr(), args_p.as_ptr(), env_p.as_ptr()) }
}));

Err(Error::Sys(Errno::last()))
}
Expand All @@ -610,12 +604,14 @@ pub fn execve(path: &CString, args: &[CString], env: &[CString]) -> Result<Void>
/// would not work if "bash" was specified for the path argument, but `execvp`
/// would assuming that a bash executable was on the system `PATH`.
#[inline]
pub fn execvp(filename: &CString, args: &[CString]) -> Result<Void> {
let args_p = to_exec_array(args);
pub fn execvp<'a, P: ?Sized + NixPath, A: IntoRef<'a, TerminatedSlice<&'a c_char>>>(filename: &P, args: A) -> Result<Void> {
let args_p = args.into_ref();

unsafe {
libc::execvp(filename.as_ptr(), args_p.as_ptr())
};
try!(filename.with_nix_path(|cstr| {
unsafe {
libc::execvp(cstr.as_ptr(), args_p.as_ptr())
}
}));

Err(Error::Sys(Errno::last()))
}
Expand All @@ -633,9 +629,9 @@ pub fn execvp(filename: &CString, args: &[CString]) -> Result<Void> {
#[cfg(any(target_os = "android", target_os = "dragonfly", target_os = "freebsd",
target_os = "netbsd", target_os = "openbsd", target_os = "linux"))]
#[inline]
pub fn fexecve(fd: RawFd, args: &[CString], env: &[CString]) -> Result<Void> {
let args_p = to_exec_array(args);
let env_p = to_exec_array(env);
pub fn fexecve<'a, 'e, A: IntoRef<'a, TerminatedSlice<&'a c_char>>, E: IntoRef<'e, TerminatedSlice<&'e c_char>>>(fd: RawFd, args: A, env: E) -> Result<Void> {
let args_p = args.into_ref();
let env_p = env.into_ref();

unsafe {
libc::fexecve(fd, args_p.as_ptr(), env_p.as_ptr())
Expand All @@ -656,10 +652,10 @@ pub fn fexecve(fd: RawFd, args: &[CString], env: &[CString]) -> Result<Void> {
/// is referenced as a file descriptor to the base directory plus a path.
#[cfg(any(target_os = "android", target_os = "linux"))]
#[inline]
pub fn execveat(dirfd: RawFd, pathname: &CString, args: &[CString],
env: &[CString], flags: super::fcntl::AtFlags) -> Result<Void> {
let args_p = to_exec_array(args);
let env_p = to_exec_array(env);
pub fn execveat<'a, 'e, A: IntoRef<'a, TerminatedSlice<&'a c_char>>, E: IntoRef<'e, TerminatedSlice<&'e c_char>>>(dirfd: RawFd, pathname: &CString, args: A,
env: E, flags: super::fcntl::AtFlags) -> Result<Void> {
let args_p = args.into_ref();
let env_p = env.into_ref();

unsafe {
libc::syscall(libc::SYS_execveat, dirfd, pathname.as_ptr(),
Expand Down

0 comments on commit 9506b74

Please sign in to comment.