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