conch_runtime_pshaw/env/
executable.rs1use crate::env::SubEnvironment;
2use crate::error::CommandError;
3use crate::io::FileDesc;
4use crate::{ExitStatus, EXIT_ERROR};
5use futures_core::future::BoxFuture;
6use std::ffi::OsStr;
7use std::io::{Error as IoError, ErrorKind as IoErrorKind};
8use std::path::Path;
9use std::process::Stdio;
10use tokio::process::Command;
11
12#[derive(Debug, PartialEq, Eq)]
14pub struct ExecutableData<'a> {
15 pub name: &'a OsStr,
17 pub args: &'a [&'a OsStr],
19 pub env_vars: &'a [(&'a OsStr, &'a OsStr)],
23 pub current_dir: &'a Path,
25 pub stdin: Option<FileDesc>,
28 pub stdout: Option<FileDesc>,
31 pub stderr: Option<FileDesc>,
34}
35
36pub trait ExecutableEnvironment {
38 fn spawn_executable(
40 &self,
41 data: ExecutableData<'_>,
42 ) -> Result<BoxFuture<'static, ExitStatus>, CommandError>;
43}
44
45impl<'a, T: ExecutableEnvironment> ExecutableEnvironment for &'a T {
46 fn spawn_executable(
47 &self,
48 data: ExecutableData<'_>,
49 ) -> Result<BoxFuture<'static, ExitStatus>, CommandError> {
50 (**self).spawn_executable(data)
51 }
52}
53
54#[derive(Clone, Debug, Default)]
57#[allow(missing_copy_implementations)]
58pub struct TokioExecEnv(());
59
60impl SubEnvironment for TokioExecEnv {
61 fn sub_env(&self) -> Self {
62 self.clone()
63 }
64}
65
66impl TokioExecEnv {
67 pub fn new() -> Self {
69 Self(())
70 }
71}
72
73impl ExecutableEnvironment for TokioExecEnv {
74 fn spawn_executable(
75 &self,
76 data: ExecutableData<'_>,
77 ) -> Result<BoxFuture<'static, ExitStatus>, CommandError> {
78 let stdio = |fdes: Option<FileDesc>| fdes.map(Into::into).unwrap_or_else(Stdio::null);
79
80 let name = data.name;
81 let mut cmd = Command::new(&name);
82 cmd.args(data.args)
83 .kill_on_drop(true) .env_clear() .current_dir(&data.current_dir)
86 .stdin(stdio(data.stdin))
87 .stdout(stdio(data.stdout))
88 .stderr(stdio(data.stderr));
89
90 cmd.env("PATH", "");
93
94 for (k, v) in data.env_vars {
95 cmd.env(k, v);
96 }
97
98 let child = cmd
99 .spawn()
100 .map_err(|err| map_io_err(err, name.to_string_lossy().into_owned()))?;
101
102 Ok(Box::pin(async move {
103 child.await.map(ExitStatus::from).unwrap_or(EXIT_ERROR)
104 }))
105 }
106}
107
108fn map_io_err(err: IoError, name: String) -> CommandError {
109 #[cfg(unix)]
110 fn is_enoexec(err: &IoError) -> bool {
111 Some(::libc::ENOEXEC) == err.raw_os_error()
112 }
113
114 #[cfg(windows)]
115 fn is_enoexec(_err: &IoError) -> bool {
116 false
117 }
118
119 if IoErrorKind::NotFound == err.kind() {
120 CommandError::NotFound(name)
121 } else if is_enoexec(&err) {
122 CommandError::NotExecutable(name)
123 } else {
124 CommandError::Io(err, Some(name))
125 }
126}