conch_runtime_pshaw/spawn/
simple.rs

1use crate::env::builtin::{BuiltinEnvironment, BuiltinUtility};
2use crate::env::{
3    AsyncIoEnvironment, EnvRestorer, ExecutableData, ExecutableEnvironment,
4    ExportedVariableEnvironment, FileDescEnvironment, FileDescOpener, FunctionEnvironment,
5    FunctionFrameEnvironment, RedirectEnvRestorer, SetArgumentsEnvironment,
6    UnsetVariableEnvironment, VarEnvRestorer, WorkingDirectoryEnvironment,
7};
8use crate::error::{CommandError, RedirectionError};
9use crate::eval::{
10    eval_redirects_or_cmd_words_with_restorer, eval_redirects_or_var_assignments_with_restorer,
11    EvalRedirectOrCmdWordError, EvalRedirectOrVarAssigError, RedirectEval, RedirectOrCmdWord,
12    RedirectOrVarAssig, WordEval,
13};
14use crate::io::FileDescWrapper;
15use crate::spawn::{function_body, Spawn};
16use crate::{
17    ExitStatus, EXIT_CMD_NOT_EXECUTABLE, EXIT_CMD_NOT_FOUND, EXIT_ERROR, EXIT_SUCCESS,
18    STDERR_FILENO, STDIN_FILENO, STDOUT_FILENO,
19};
20use futures_core::future::BoxFuture;
21use std::borrow::Borrow;
22use std::collections::VecDeque;
23use std::error::Error;
24use std::ffi::OsStr;
25
26/// Spawns a shell command (or function) after applying any redirects and
27/// environment variable assignments.
28pub async fn simple_command<'a, R, V, W, IV, IW, S, E>(
29    vars: IV,
30    words: IW,
31    env: &'a mut E,
32) -> Result<BoxFuture<'static, ExitStatus>, S::Error>
33where
34    IV: Iterator<Item = RedirectOrVarAssig<R, V, W>>,
35    IW: Iterator<Item = RedirectOrCmdWord<R, W>>,
36    R: RedirectEval<E, Handle = E::FileHandle>,
37    R::Error: 'static + Error + From<RedirectionError>,
38    W: WordEval<E>,
39    W::Error: 'static + Error,
40    E: ?Sized
41        + Send
42        + Sync
43        + AsyncIoEnvironment
44        + BuiltinEnvironment<BuiltinName = <E as FunctionEnvironment>::FnName>
45        + ExecutableEnvironment
46        + ExportedVariableEnvironment
47        + FileDescEnvironment
48        + FileDescOpener
49        + FunctionEnvironment<Fn = S>
50        + FunctionFrameEnvironment
51        + SetArgumentsEnvironment
52        + UnsetVariableEnvironment
53        + WorkingDirectoryEnvironment,
54    E::Builtin: BuiltinUtility<'a, Vec<W::EvalResult>, EnvRestorer<'a, E>, E>,
55    E::Arg: From<W::EvalResult>,
56    E::Args: From<VecDeque<E::Arg>>,
57    E::FileHandle: Send + Sync + Clone + FileDescWrapper + From<E::OpenedFileHandle>,
58    E::FnName: From<W::EvalResult>,
59    E::IoHandle: Send + Sync + From<E::FileHandle>,
60    E::VarName: Send + Sync + Clone + Borrow<String> + From<V>,
61    E::Var: Send + Sync + Clone + Borrow<String> + From<W::EvalResult>,
62    S: Spawn<E> + Clone,
63    S::Error: From<R::Error> + From<W::Error> + From<CommandError> + From<RedirectionError>,
64{
65    simple_command_with_restorer(vars, words, &mut EnvRestorer::new(env)).await
66}
67
68/// Spawns a shell command (or function) after applying any redirects and
69/// environment variable assignments.
70pub async fn simple_command_with_restorer<'a, R, V, W, IV, IW, RR, S, E>(
71    vars: IV,
72    words: IW,
73    restorer: &mut RR,
74) -> Result<BoxFuture<'static, ExitStatus>, S::Error>
75where
76    IV: Iterator<Item = RedirectOrVarAssig<R, V, W>>,
77    IW: Iterator<Item = RedirectOrCmdWord<R, W>>,
78    R: RedirectEval<E, Handle = E::FileHandle>,
79    R::Error: 'static + Error + From<RedirectionError>,
80    W: WordEval<E>,
81    W::Error: 'static + Error,
82    RR: ?Sized
83        + Send
84        + Sync
85        + AsyncIoEnvironment
86        + FileDescOpener
87        + ExportedVariableEnvironment
88        + RedirectEnvRestorer<'a, E>
89        + VarEnvRestorer<'a, E>,
90    RR::FileHandle: From<RR::OpenedFileHandle>,
91    RR::IoHandle: Send + From<RR::FileHandle>,
92    E: 'a
93        + ?Sized
94        + Send
95        + Sync
96        + BuiltinEnvironment<BuiltinName = <E as FunctionEnvironment>::FnName>
97        + ExecutableEnvironment
98        + ExportedVariableEnvironment
99        + FileDescEnvironment
100        + FunctionEnvironment<Fn = S>
101        + FunctionFrameEnvironment
102        + SetArgumentsEnvironment
103        + WorkingDirectoryEnvironment,
104    E::Builtin: BuiltinUtility<'a, Vec<W::EvalResult>, RR, E>,
105    E::Arg: From<W::EvalResult>,
106    E::Args: From<VecDeque<E::Arg>>,
107    E::FileHandle: Clone + FileDescWrapper,
108    E::FnName: From<W::EvalResult>,
109    E::VarName: Borrow<String> + From<V>,
110    E::Var: Borrow<String> + From<W::EvalResult>,
111    S: Spawn<E> + Clone,
112    S::Error: From<R::Error> + From<W::Error> + From<CommandError> + From<RedirectionError>,
113{
114    let ret = do_simple_command_with_restorer(vars, words, restorer).await;
115    restorer.restore_vars();
116    restorer.restore_redirects();
117    ret
118}
119
120async fn do_simple_command_with_restorer<'a, R, V, W, IV, IW, RR, S, E>(
121    vars: IV,
122    mut words: IW,
123    restorer: &mut RR,
124) -> Result<BoxFuture<'static, ExitStatus>, S::Error>
125where
126    IV: Iterator<Item = RedirectOrVarAssig<R, V, W>>,
127    IW: Iterator<Item = RedirectOrCmdWord<R, W>>,
128    R: RedirectEval<E, Handle = E::FileHandle>,
129    R::Error: 'static + Error + From<RedirectionError>,
130    W: WordEval<E>,
131    W::Error: 'static + Error,
132    RR: ?Sized
133        + Send
134        + Sync
135        + AsyncIoEnvironment
136        + FileDescOpener
137        + ExportedVariableEnvironment
138        + RedirectEnvRestorer<'a, E>
139        + VarEnvRestorer<'a, E>,
140    RR::FileHandle: From<RR::OpenedFileHandle>,
141    RR::IoHandle: Send + From<RR::FileHandle>,
142    E: 'a
143        + ?Sized
144        + Send
145        + Sync
146        + BuiltinEnvironment<BuiltinName = <E as FunctionEnvironment>::FnName>
147        + ExecutableEnvironment
148        + ExportedVariableEnvironment
149        + FileDescEnvironment
150        + FunctionEnvironment<Fn = S>
151        + FunctionFrameEnvironment
152        + SetArgumentsEnvironment
153        + WorkingDirectoryEnvironment,
154    E::Builtin: BuiltinUtility<'a, Vec<W::EvalResult>, RR, E>,
155    E::Arg: From<W::EvalResult>,
156    E::Args: From<VecDeque<E::Arg>>,
157    E::FileHandle: Clone + FileDescWrapper,
158    E::FnName: From<W::EvalResult>,
159    E::VarName: Borrow<String> + From<V>,
160    E::Var: Borrow<String> + From<W::EvalResult>,
161    S: Spawn<E> + Clone,
162    S::Error: From<R::Error> + From<W::Error> + From<CommandError> + From<RedirectionError>,
163{
164    // Any other redirects encountered before we found a command word
165    let mut other_redirects = Vec::new();
166    let mut first_word = None;
167
168    while let Some(w) = words.next() {
169        match w {
170            w @ RedirectOrCmdWord::CmdWord(_) => {
171                first_word = Some(w);
172                break;
173            }
174            RedirectOrCmdWord::Redirect(r) => {
175                other_redirects.push(RedirectOrVarAssig::Redirect(r));
176            }
177        }
178    }
179
180    // Setting local vars for commands or functions should
181    // behave as if the variables were exported. Otherwise
182    // variables should maintain an exported status if they had one
183    // or default to non-exported!
184    let export_vars = first_word.as_ref().map(|_| true);
185
186    let vars = vars.chain(other_redirects.into_iter());
187    let words = first_word.into_iter().chain(words);
188
189    eval_redirects_or_var_assignments_with_restorer(export_vars, vars, restorer)
190        .await
191        .map_err(|e| match e {
192            EvalRedirectOrVarAssigError::Redirect(e) => S::Error::from(e),
193            EvalRedirectOrVarAssigError::VarAssig(e) => S::Error::from(e),
194        })?;
195
196    let mut words = eval_redirects_or_cmd_words_with_restorer(restorer, words)
197        .await
198        .map_err(|e| match e {
199            EvalRedirectOrCmdWordError::Redirect(e) => S::Error::from(e),
200            EvalRedirectOrCmdWordError::CmdWord(e) => S::Error::from(e),
201        })?;
202
203    let cmd_name = if words.is_empty() {
204        // "Empty" command which is probably just assigning variables.
205        // Any redirect side effects have already been applied, but ensure
206        // we keep the actual variable values.
207        restorer.clear_vars();
208        return Ok(Box::pin(async { EXIT_SUCCESS }));
209    } else {
210        words.remove(0)
211    };
212
213    {
214        let cmd_name = cmd_name.clone().into();
215        let env = restorer.get_mut();
216
217        if let Some(func) = env.function(&cmd_name).cloned() {
218            let args = words.into_iter().map(Into::into).collect();
219            return Ok(function_body(func, args, env).await?);
220        } else if let Some(builtin) = env.builtin(&cmd_name) {
221            return Ok(builtin.spawn_builtin(words, restorer).await);
222        }
223    }
224
225    // FIXME: inherit all open file descriptors on UNIX systems
226    let (stdin, stdout, stderr) = {
227        let env = restorer.get();
228        (
229            env.file_desc(STDIN_FILENO).map(|(fdes, _)| fdes).cloned(),
230            env.file_desc(STDOUT_FILENO).map(|(fdes, _)| fdes).cloned(),
231            env.file_desc(STDERR_FILENO).map(|(fdes, _)| fdes).cloned(),
232        )
233    };
234
235    // Now that we've got all the redirections we care about having the
236    // child inherit, we can do the environment cleanup right now.
237    restorer.restore_redirects();
238
239    // Now that we've restore the environment's redirects, hopefully most of
240    // the Rc/Arc counts should be just one here and we can cheaply unwrap
241    // the handles. Otherwise, we're forced to duplicate the actual handle
242    // (which is a pretty unfortunate "limitation" of std::process::Command)
243    let get_io = move |fd, fdes: Option<E::FileHandle>| match fdes {
244        None => Ok(None),
245        Some(fdes_wrapper) => match fdes_wrapper.try_unwrap() {
246            Ok(fdes) => Ok(Some(fdes)),
247            Err(err) => {
248                let msg = format!("file descriptor {}", fd);
249                Err(RedirectionError::Io(err, Some(msg)))
250            }
251        },
252    };
253
254    let env = restorer.get();
255    let args = words
256        .iter()
257        .map(|a| OsStr::new(a.borrow()))
258        .collect::<Vec<_>>();
259    let env_vars = env
260        .env_vars()
261        .iter()
262        .map(|&(ref key, ref val)| {
263            let key = OsStr::new((*key).borrow());
264            let val = OsStr::new((*val).borrow());
265            (key, val)
266        })
267        .collect::<Vec<_>>();
268
269    let cur_dir = env.current_working_dir().to_path_buf();
270
271    let data = ExecutableData {
272        name: OsStr::new(cmd_name.borrow()),
273        args: &args,
274        env_vars: &env_vars,
275        current_dir: &cur_dir,
276        stdin: get_io(STDIN_FILENO, stdin)?,
277        stdout: get_io(STDOUT_FILENO, stdout)?,
278        stderr: get_io(STDERR_FILENO, stderr)?,
279    };
280
281    let child = env.spawn_executable(data);
282
283    // Once the child is fully bootstrapped (and we are no longer borrowing
284    // env vars) we can do the var cleanup.
285    restorer.restore_vars();
286
287    match child {
288        Ok(ret) => Ok(ret),
289        Err(e) => {
290            if let Some(e) = find_root_cause(&e).downcast_ref::<CommandError>() {
291                let status = match e {
292                    CommandError::NotExecutable(_) => EXIT_CMD_NOT_EXECUTABLE,
293                    CommandError::NotFound(_) => EXIT_CMD_NOT_FOUND,
294                    CommandError::Io(_, _) => EXIT_ERROR,
295                };
296
297                Ok(Box::pin(async move { status }))
298            } else {
299                Err(S::Error::from(e))
300            }
301        }
302    }
303}
304
305fn find_root_cause<'a>(mut err: &'a (dyn Error + 'static)) -> &'a (dyn Error + 'static) {
306    while let Some(e) = err.source() {
307        err = e;
308    }
309
310    err
311}