use ExitStatus;
use env::SubEnvironment;
use error::CommandError;
use futures::{Async, Future, IntoFuture, Poll};
use futures::sync::oneshot;
use io::FileDesc;
use std::borrow::Cow;
use std::ffi::OsStr;
use std::fmt;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use std::path::Path;
use std::process::{self, Command, Stdio};
use tokio_core::reactor::{Handle, Remote};
use tokio_process::{CommandExt, StatusAsync2};
#[derive(Debug, PartialEq, Eq)]
pub struct ExecutableData<'a> {
pub name: Cow<'a, OsStr>,
pub args: Vec<Cow<'a, OsStr>>,
pub env_vars: Vec<(Cow<'a, OsStr>, Cow<'a, OsStr>)>,
pub current_dir: Cow<'a, Path>,
pub stdin: Option<FileDesc>,
pub stdout: Option<FileDesc>,
pub stderr: Option<FileDesc>,
}
impl<'a> ExecutableData<'a> {
pub fn into_owned(self) -> ExecutableData<'static> {
let args = self.args.into_iter()
.map(Cow::into_owned)
.map(Cow::Owned)
.collect();
let env_vars = self.env_vars.into_iter()
.map(|(k, v)| (Cow::Owned(k.into_owned()), Cow::Owned(v.into_owned())))
.collect();
ExecutableData {
name: Cow::Owned(self.name.into_owned()),
args: args,
env_vars: env_vars,
current_dir: Cow::Owned(self.current_dir.into_owned()),
stdin: self.stdin,
stdout: self.stdout,
stderr: self.stderr,
}
}
}
pub trait ExecutableEnvironment {
type Future: Future<Item = ExitStatus, Error = CommandError>;
fn spawn_executable(&mut self, data: ExecutableData) -> Result<Self::Future, CommandError>;
}
#[derive(Clone)]
pub struct ExecEnv {
remote: Remote,
}
impl SubEnvironment for ExecEnv {
fn sub_env(&self) -> Self {
self.clone()
}
}
impl fmt::Debug for ExecEnv {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
fmt.debug_struct("ExecEnv")
.field("remote", &self.remote.id())
.finish()
}
}
impl ExecEnv {
pub fn new(remote: Remote) -> Self {
ExecEnv {
remote: remote,
}
}
}
fn spawn_child<'a>(data: ExecutableData<'a>, handle: &Handle)
-> Result<StatusAsync2, CommandError>
{
let stdio = |fdes: Option<FileDesc>| fdes.map(Into::into).unwrap_or_else(Stdio::null);
let name = data.name;
let mut cmd = Command::new(&name);
cmd.args(data.args)
.env_clear() .current_dir(&data.current_dir)
.stdin(stdio(data.stdin))
.stdout(stdio(data.stdout))
.stderr(stdio(data.stderr));
cmd.env("PATH", "");
for (k, v) in data.env_vars {
cmd.env(k, v);
}
cmd.status_async2(handle).map_err(|err| map_io_err(err, convert_to_string(name)))
}
fn convert_to_string(os_str: Cow<OsStr>) -> String {
match os_str {
Cow::Borrowed(s) => s.to_string_lossy().into_owned(),
Cow::Owned(string) => string.into_string().unwrap_or_else(|s| {
s.as_os_str().to_string_lossy().into_owned()
}),
}
}
fn map_io_err(err: IoError, name: String) -> CommandError {
#[cfg(unix)]
fn is_enoexec(err: &IoError) -> bool {
Some(::libc::ENOEXEC) == err.raw_os_error()
}
#[cfg(windows)]
fn is_enoexec(_err: &IoError) -> bool {
false
}
if IoErrorKind::NotFound == err.kind() {
CommandError::NotFound(name)
} else if is_enoexec(&err) {
CommandError::NotExecutable(name)
} else {
CommandError::Io(err, Some(name))
}
}
impl ExecutableEnvironment for ExecEnv {
type Future = Child;
fn spawn_executable(&mut self, data: ExecutableData) -> Result<Self::Future, CommandError> {
let inner = match self.remote.handle() {
Some(handle) => Inner::Child(Box::new(try!(spawn_child(data, &handle)))),
None => {
let (tx, rx) = oneshot::channel();
let data = data.into_owned();
self.remote.spawn(move |handle| {
spawn_child(data, handle).into_future()
.and_then(|child| child.map_err(|err| CommandError::Io(err, None)))
.then(|status| {
tx.send(status).map_err(|_| ())
})
});
Inner::Remote(rx)
},
};
Ok(Child {
inner: inner,
})
}
}
#[must_use = "futures do nothing unless polled"]
#[derive(Debug)]
pub struct Child {
inner: Inner,
}
enum Inner {
Child(Box<StatusAsync2>),
Remote(oneshot::Receiver<Result<process::ExitStatus, CommandError>>),
}
impl fmt::Debug for Inner {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
Inner::Child(ref inner) => {
fmt.debug_tuple("Inner::Child")
.field(&inner)
.finish()
},
Inner::Remote(ref rx) => {
fmt.debug_tuple("Inner::Remote")
.field(rx)
.finish()
},
}
}
}
impl Future for Child {
type Item = ExitStatus;
type Error = CommandError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
let result = match self.inner {
Inner::Child(ref mut inner) => match inner.poll() {
Ok(Async::Ready(status)) => Ok(status),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(err) => Err(err),
},
Inner::Remote(ref mut rx) => match rx.poll() {
Ok(Async::Ready(status)) => Ok(try!(status)),
Ok(Async::NotReady) => return Ok(Async::NotReady),
Err(cancelled) => Err(IoError::new(IoErrorKind::Other, cancelled)),
},
};
result.map(ExitStatus::from)
.map(Async::Ready)
.map_err(|err| CommandError::Io(err, None))
}
}