Skip to main content

brush_core/shell/
execution.rs

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