use crate::env::SubEnvironment;
use crate::error::CommandError;
use crate::io::FileDesc;
use crate::{ExitStatus, EXIT_ERROR};
use futures_core::future::BoxFuture;
use std::ffi::OsStr;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use std::path::Path;
use std::process::Stdio;
use tokio::process::Command;
#[derive(Debug, PartialEq, Eq)]
pub struct ExecutableData<'a> {
pub name: &'a OsStr,
pub args: &'a [&'a OsStr],
pub env_vars: &'a [(&'a OsStr, &'a OsStr)],
pub current_dir: &'a Path,
pub stdin: Option<FileDesc>,
pub stdout: Option<FileDesc>,
pub stderr: Option<FileDesc>,
}
pub trait ExecutableEnvironment {
fn spawn_executable(
&self,
data: ExecutableData<'_>,
) -> Result<BoxFuture<'static, ExitStatus>, CommandError>;
}
impl<'a, T: ExecutableEnvironment> ExecutableEnvironment for &'a T {
fn spawn_executable(
&self,
data: ExecutableData<'_>,
) -> Result<BoxFuture<'static, ExitStatus>, CommandError> {
(**self).spawn_executable(data)
}
}
#[derive(Clone, Debug, Default)]
#[allow(missing_copy_implementations)]
pub struct TokioExecEnv(());
impl SubEnvironment for TokioExecEnv {
fn sub_env(&self) -> Self {
self.clone()
}
}
impl TokioExecEnv {
pub fn new() -> Self {
Self(())
}
}
impl ExecutableEnvironment for TokioExecEnv {
fn spawn_executable(
&self,
data: ExecutableData<'_>,
) -> Result<BoxFuture<'static, ExitStatus>, 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)
.kill_on_drop(true) .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);
}
let child = cmd
.spawn()
.map_err(|err| map_io_err(err, name.to_string_lossy().into_owned()))?;
Ok(Box::pin(async move {
child.await.map(ExitStatus::from).unwrap_or(EXIT_ERROR)
}))
}
}
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))
}
}