Skip to main content

shrub_core/shell/
execution.rs

1//! Execution support for shell.
2
3use std::io::Read;
4use std::path::Path;
5
6use crate::arithmetic::Evaluatable as _;
7use crate::interp::Execute as _;
8use crate::{
9    ExecutionControlFlow, ExecutionParameters, ExecutionResult, ProcessGroupPolicy, SourceInfo,
10    callstack, error, openfiles, trace_categories,
11};
12
13impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
14    /// Returns the default execution parameters for this shell.
15    pub fn default_exec_params(&self) -> ExecutionParameters {
16        let mut params = ExecutionParameters::default();
17
18        params.process_group_policy = if self.options.enable_job_control {
19            ProcessGroupPolicy::NewProcessGroup
20        } else {
21            ProcessGroupPolicy::SameProcessGroup
22        };
23
24        params
25    }
26
27    pub(super) async fn source_if_exists(
28        &mut self,
29        path: impl AsRef<Path>,
30        params: &ExecutionParameters,
31    ) -> Result<bool, error::Error> {
32        let path = path.as_ref();
33        if path.exists() {
34            self.source_script(path, std::iter::empty::<String>(), params)
35                .await?;
36            Ok(true)
37        } else {
38            tracing::debug!("skipping non-existent file: {}", path.display());
39            Ok(false)
40        }
41    }
42
43    /// Source the given file as a shell script, returning the execution result.
44    ///
45    /// # Arguments
46    ///
47    /// * `path` - The path to the file to source.
48    /// * `args` - The arguments to pass to the script as positional parameters.
49    /// * `params` - Execution parameters.
50    ///
51    /// # Errors
52    ///
53    /// - [`crate::error::ErrorKind::FailedSourcingFile`] if the file cannot be
54    ///   opened or is a directory.
55    pub async fn source_script<S: Into<String>, P: AsRef<Path>, I: Iterator<Item = S>>(
56        &mut self,
57        path: P,
58        args: I,
59        params: &ExecutionParameters,
60    ) -> Result<ExecutionResult, error::Error> {
61        self.parse_and_execute_script_file(
62            path.as_ref(),
63            args,
64            params,
65            callstack::ScriptCallType::Source,
66        )
67        .await
68    }
69
70    /// Parse and execute the given file as a shell script, returning the
71    /// execution result.
72    ///
73    /// # Arguments
74    ///
75    /// * `path` - The path to the file to source.
76    /// * `args` - The arguments to pass to the script as positional parameters.
77    /// * `params` - Execution parameters.
78    /// * `call_type` - The type of script call being made.
79    async fn parse_and_execute_script_file<
80        S: Into<String>,
81        P: AsRef<Path>,
82        I: Iterator<Item = S>,
83    >(
84        &mut self,
85        path: P,
86        args: I,
87        params: &ExecutionParameters,
88        call_type: callstack::ScriptCallType,
89    ) -> Result<ExecutionResult, error::Error> {
90        let path = path.as_ref();
91        tracing::debug!("sourcing: {}", path.display());
92
93        let mut options = std::fs::File::options();
94        options.read(true);
95
96        let opened_file: openfiles::OpenFile = self
97            .open_file(&options, path, params)
98            .map_err(|e| error::ErrorKind::FailedSourcingFile(path.to_owned(), e))?;
99
100        if opened_file.is_dir() {
101            return Err(error::ErrorKind::FailedSourcingFile(
102                path.to_owned(),
103                std::io::Error::from(std::io::ErrorKind::IsADirectory),
104            )
105            .into());
106        }
107
108        let source_info = crate::SourceInfo::from(path.to_owned());
109
110        let mut result = self
111            .source_file(opened_file, &source_info, args, params, call_type)
112            .await?;
113
114        // Handle control flow at script execution boundary. If execution
115        // completed with a `return`, we need to clear it since it's
116        // already been "used". All other control flow types are
117        // preserved.
118        if matches!(
119            result.next_control_flow,
120            ExecutionControlFlow::ReturnFromFunctionOrScript
121        ) {
122            result.next_control_flow = ExecutionControlFlow::Normal;
123        }
124
125        Ok(result)
126    }
127
128    /// Source the given file as a shell script, returning the execution result.
129    ///
130    /// # Arguments
131    ///
132    /// * `file` - The file to source.
133    /// * `source_info` - Information about the source of the script.
134    /// * `args` - The arguments to pass to the script as positional parameters.
135    /// * `params` - Execution parameters.
136    /// * `call_type` - The type of script call being made.
137    async fn source_file<F: Read, S: Into<String>, I: Iterator<Item = S>>(
138        &mut self,
139        file: F,
140        source_info: &crate::SourceInfo,
141        args: I,
142        params: &ExecutionParameters,
143        call_type: callstack::ScriptCallType,
144    ) -> Result<ExecutionResult, error::Error> {
145        let mut reader = std::io::BufReader::new(file);
146        let mut parser = shrub_parser::Parser::new(&mut reader, &self.parser_options());
147
148        tracing::debug!(target: trace_categories::PARSE, "Parsing sourced file: {}", source_info.source);
149        let parse_result = parser.parse_program();
150
151        let script_positional_args = args.map(Into::into);
152
153        self.enter_script(call_type, source_info, script_positional_args)
154            .run_parsed_result(parse_result, source_info, params)
155            .await
156    }
157
158    /// Executes the given string as a shell program, returning the resulting
159    /// exit status.
160    ///
161    /// # Arguments
162    ///
163    /// * `command` - The command to execute.
164    /// * `source_info` - Information about the source of the command text.
165    /// * `params` - Execution parameters.
166    ///
167    /// # Errors
168    ///
169    /// Parse and execution errors are not returned. They are displayed on the
170    /// shell's stderr and folded into the exit status of the returned result.
171    pub async fn run_string<S: Into<String>>(
172        &mut self,
173        command: S,
174        source_info: &crate::SourceInfo,
175        params: &ExecutionParameters,
176    ) -> Result<ExecutionResult, error::Error> {
177        let parse_result = self.parse_string(command);
178        self.run_parsed_result(parse_result, source_info, params)
179            .await
180    }
181
182    /// Executes the given command, provided to a shell executable on the
183    /// command line (i.e., via `-c`).
184    ///
185    /// It is expected that the shell will not be used for any further execution
186    /// after this command; this function will perform any necessary shell exit
187    /// handling.
188    ///
189    /// # Arguments
190    ///
191    /// * `command` - The command to execute.
192    ///
193    /// # Errors
194    ///
195    /// Returns [`crate::error::ErrorKind::NotExecutingCommandString`] if the
196    /// command string frame is no longer the current call stack frame once the
197    /// command finishes. Errors from the command itself are handled as in
198    /// [`Self::run_string`].
199    pub async fn run_dash_c_command<S: Into<String>>(
200        &mut self,
201        command: S,
202    ) -> Result<ExecutionResult, error::Error> {
203        self.start_command_string_mode();
204
205        // Execute the command string.
206        let params = self.default_exec_params();
207        let source_info = SourceInfo::from("-c");
208        let result = self.run_string(command, &source_info, &params).await?;
209
210        self.end_command_string_mode()?;
211
212        // Give the shell a chance to run on-exit tasks, but ignore the result.
213        let _ = self.on_exit().await;
214
215        Ok(result)
216    }
217
218    /// Executes the given script file, returning the resulting exit status.
219    ///
220    /// It is expected that the shell will not be used for any further execution
221    /// after this command; this function will perform any necessary shell exit
222    /// handling.
223    ///
224    /// # Arguments
225    ///
226    /// * `script_path` - The path to the script file to execute.
227    /// * `args` - The arguments to pass to the script as positional parameters.
228    ///
229    /// # Errors
230    ///
231    /// - [`crate::error::ErrorKind::FailedSourcingFile`] if the file cannot be
232    ///   opened or is a directory.
233    /// - Errors from the script itself are reported on stderr and folded into
234    ///   the returned exit status.
235    pub async fn run_script<S: Into<String>, P: AsRef<Path>, I: Iterator<Item = S>>(
236        &mut self,
237        script_path: P,
238        args: I,
239    ) -> Result<ExecutionResult, error::Error> {
240        let params = self.default_exec_params();
241        let result = self
242            .parse_and_execute_script_file(
243                script_path.as_ref(),
244                args,
245                &params,
246                callstack::ScriptCallType::Run,
247            )
248            .await?;
249
250        // Give the shell a chance to run on-exit tasks, but ignore the result.
251        let _ = self.on_exit().await;
252
253        Ok(result)
254    }
255
256    pub(crate) async fn run_parsed_result(
257        &mut self,
258        parse_result: Result<shrub_parser::ast::Program, shrub_parser::ParseError>,
259        source_info: &crate::SourceInfo,
260        params: &ExecutionParameters,
261    ) -> Result<ExecutionResult, error::Error> {
262        // If parsing succeeded, run the program. If there's a parse error, it's
263        // fatal (per spec).
264        let result = match parse_result {
265            Ok(prog) => self.run_program(prog, params).await,
266            Err(parse_err) => Err(error::Error::from(error::ErrorKind::ParseError(
267                parse_err,
268                source_info.clone(),
269            ))
270            .into_fatal()),
271        };
272
273        // Report any errors.
274        match result {
275            Ok(result) => Ok(result),
276            Err(err) => {
277                let _ = self.display_error(&mut params.stderr(self), &err);
278
279                let result = err.into_result(self);
280                self.set_last_exit_status(result.exit_code.into());
281
282                Ok(result)
283            }
284        }
285    }
286
287    /// Executes the given parsed shell program, returning the resulting exit
288    /// status.
289    ///
290    /// # Arguments
291    ///
292    /// * `program` - The program to execute.
293    /// * `params` - Execution parameters.
294    ///
295    /// # Errors
296    ///
297    /// Returns the error that aborts execution, such as a failed expansion or
298    /// redirection. Unlike [`Self::run_string`], the error is neither displayed
299    /// nor converted to an exit status.
300    pub async fn run_program(
301        &mut self,
302        program: shrub_parser::ast::Program,
303        params: &ExecutionParameters,
304    ) -> Result<ExecutionResult, error::Error> {
305        program.execute(self, params).await
306    }
307
308    /// Evaluate the given arithmetic expression, returning the result.
309    ///
310    /// # Errors
311    ///
312    /// Returns [`crate::error::ErrorKind::EvalError`] if evaluation fails, for
313    /// example on division by zero, a negative exponent, or an unset variable
314    /// under `set -u`.
315    pub fn eval_arithmetic(
316        &mut self,
317        expr: &shrub_parser::ast::ArithmeticExpr,
318    ) -> Result<i64, error::Error> {
319        Ok(expr.eval(self)?)
320    }
321}