diff --git a/src/lib.rs b/src/lib.rs index 03dcd86a58..6bec7409a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 + Deref; + + 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 { + inner: [Option], +} + +impl TerminatedSlice { + /// 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]) -> 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]) -> 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]) -> &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]) -> &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 Deref for TerminatedSlice { + type Target = [Option]; + + fn deref(&self) -> &Self::Target { + &self.inner[..self.inner.len() - 1] + } +} + +impl DerefMut for TerminatedSlice { + fn deref_mut(&mut self) -> &mut Self::Target { + let len = self.inner.len(); + &mut self.inner[..len - 1] + } +} + +impl AsRef> for TerminatedSlice { + fn as_ref(&self) -> &Self { + self + } +} + +/// Owned variant of `TerminatedSlice`. +pub struct TerminatedVec { + inner: Vec>, +} + +impl TerminatedVec { + /// 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 { + 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>) -> Self { + TerminatedVec { + inner: vec, + } + } + + /// Consume `self` to return the inner wrapped `Vec`. + pub fn into_inner(self) -> Vec> { + self.inner + } +} + +impl<'a> TerminatedVec<&'a c_char> { + fn terminate + 'a, I: IntoIterator>(iter: I) -> Self { + fn cstr_char<'a, S: AsRef + '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 Deref for TerminatedVec { + type Target = TerminatedSlice; + + fn deref(&self) -> &Self::Target { + unsafe { + TerminatedSlice::from_slice_unchecked(&self.inner) + } + } +} + +impl DerefMut for TerminatedVec { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { + TerminatedSlice::from_slice_mut_unchecked(&mut self.inner) + } + } +} + +impl AsRef> for TerminatedVec { + fn as_ref(&self) -> &TerminatedSlice { + 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 + 'a, I: IntoIterator> IntoRef<'a, TerminatedSlice<&'a c_char>> for I { + type Target = TerminatedVec<&'a c_char>; + + fn into_ref(self) -> Self::Target { + TerminatedVec::terminate(self) + } +} diff --git a/src/unistd.rs b/src/unistd.rs index c3c78414ae..6881988c2d 100644 --- a/src/unistd.rs +++ b/src/unistd.rs @@ -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, @@ -552,12 +552,6 @@ pub fn chown(path: &P, owner: Option, group: Option 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)). /// @@ -565,12 +559,12 @@ fn to_exec_array(args: &[CString]) -> Vec<*const c_char> { /// 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 { - 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 { + 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())) } @@ -589,13 +583,13 @@ pub fn execv(path: &CString, argv: &[CString]) -> Result { /// 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 { - 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 { + 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())) } @@ -610,12 +604,14 @@ pub fn execve(path: &CString, args: &[CString], env: &[CString]) -> Result /// 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 { - 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 { + 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())) } @@ -633,9 +629,9 @@ pub fn execvp(filename: &CString, args: &[CString]) -> Result { #[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 { - 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 { + let args_p = args.into_ref(); + let env_p = env.into_ref(); unsafe { libc::fexecve(fd, args_p.as_ptr(), env_p.as_ptr()) @@ -656,10 +652,10 @@ pub fn fexecve(fd: RawFd, args: &[CString], env: &[CString]) -> Result { /// 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 { - 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 { + let args_p = args.into_ref(); + let env_p = env.into_ref(); unsafe { libc::syscall(libc::SYS_execveat, dirfd, pathname.as_ptr(),