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, ¶ms).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 ¶ms,
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}