yash_semantics/command/simple_command.rs
1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Implementation of the simple command semantics.
18//!
19//! This module exports some utility functions that are used in implementing the
20//! simple command semantics and can be used in other modules. For the execution
21//! of simple commands, see the implementation of [`Command`] for
22//! [`syntax::SimpleCommand`].
23
24use crate::Handle;
25use crate::command::Command;
26use crate::command_search::classify;
27use crate::expansion::expand_word_with_mode;
28use crate::xtrace::XTrace;
29use std::ops::ControlFlow::Continue;
30use yash_env::Env;
31#[cfg(doc)]
32use yash_env::semantics::Divert;
33use yash_env::semantics::ExitStatus;
34use yash_env::semantics::Field;
35use yash_env::semantics::Result;
36#[cfg(doc)]
37use yash_env::variable::Context;
38use yash_env::variable::Scope;
39use yash_syntax::syntax;
40use yash_syntax::syntax::Assign;
41use yash_syntax::syntax::ExpansionMode;
42use yash_syntax::syntax::Word;
43
44/// Executes the simple command.
45///
46/// # Outline
47///
48/// The execution starts with the [expansion](crate::expansion) of the command
49/// words. Next, the [command search](crate::command_search) is performed to
50/// find an execution [target](crate::command_search::Target) named by the first
51/// [field](Field) of the expansion results. The target type defines how the
52/// target is executed. After the execution, the `ErrExit` option is applied
53/// with [`Env::apply_errexit`].
54///
55/// # Target types and their semantics
56///
57/// ## Absent target
58///
59/// If no fields resulted from the expansion, there is no target.
60///
61/// If the simple command has redirections and assignments, they are performed
62/// in a new subshell and the current shell environment, respectively.
63///
64/// If the redirections or assignments contain command substitutions, the [exit
65/// status](ExitStatus) of the simple command is taken from that of the last
66/// executed command substitution. Otherwise, the exit status will be zero.
67///
68/// ## Built-in
69///
70/// If the target is a built-in, the following steps are performed in the
71/// current shell environment.
72///
73/// First, if there are redirections, they are performed.
74///
75/// Next, if there are assignments, a temporary context is created to contain
76/// the assignment results. The context, as well as the assigned variables, are
77/// discarded when the execution finishes. If the target is a regular built-in,
78/// the variables are exported.
79///
80/// Lastly, the built-in is executed by calling its body with the remaining
81/// fields passed as arguments.
82///
83/// ## Function
84///
85/// If the target is a function, redirections are performed in the same way as a
86/// regular built-in. Then, assignments are performed in a
87/// [volatile](Context::Volatile) variable context and exported. Next, a
88/// [regular](Context::Regular) context is
89/// [pushed](yash_env::variable::VariableSet::push_context) to allow local
90/// variable assignment during the function execution. The remaining fields not
91/// used in the command search become positional parameters in the new context.
92/// After executing the function body, the contexts are
93/// [popped](yash_env::variable::VariableSet::pop_context).
94///
95/// If the execution results in a [`Divert::Return`], it is consumed, and its
96/// associated exit status, if any, is set as the exit status of the simple
97/// command.
98///
99/// ## External utility
100///
101/// If the target is an external utility, a subshell is created. Redirections
102/// and assignments, if any, are performed in the subshell. The assigned
103/// variables are exported. The subshell calls the
104/// [`execve`](yash_env::System::execve) function to invoke the external utility
105/// with all the fields passed as arguments.
106///
107/// If `execve` fails with an `ENOEXEC` error, it is re-called with the current
108/// executable file so that the restarted shell executes the external utility as
109/// a shell script.
110///
111/// ## Target not found
112///
113/// If the command search could not find a valid target, the execution proceeds
114/// in the same manner as an external utility except that it does not call
115/// `execve` and performs error handling as if it failed with `ENOENT`.
116///
117/// # Redirections
118///
119/// Redirections are performed in the order of appearance. The file descriptors
120/// modified by the redirections are restored after the target has finished
121/// except for external utilities executed in a subshell.
122///
123/// # Assignments
124///
125/// Assignments are performed in the order of appearance. For each assignment,
126/// the value is expanded and assigned to the variable.
127///
128/// # Errors
129///
130/// ## Expansion errors
131///
132/// If there is an error during the expansion, the execution aborts with a
133/// non-zero [exit status](ExitStatus) after printing an error message to the
134/// standard error.
135///
136/// Expansion errors may also occur when expanding an assignment value or a
137/// redirection operand.
138///
139/// ## Redirection errors
140///
141/// Any error happening in redirections causes the execution to abort with a
142/// non-zero exit status after printing an error message to the standard error.
143///
144/// ## Assignment errors
145///
146/// If an assignment tries to overwrite a read-only variable, the execution
147/// aborts with a non-zero exit status after printing an error message to the
148/// standard error.
149///
150/// ## External utility invocation failure
151///
152/// If the external utility could not be called, the subshell exits after
153/// printing an error message to the standard error.
154///
155/// # Portability
156///
157/// POSIX does not define the exit status when the `execve` system call fails
158/// for a reason other than `ENOEXEC`. In this implementation, the exit status
159/// is 127 for `ENOENT` and `ENOTDIR` and 126 for others.
160///
161/// POSIX leaves many aspects of the simple command execution unspecified. The
162/// detail semantics may differ in other shell implementations.
163impl Command for syntax::SimpleCommand {
164 async fn execute(&self, env: &mut Env) -> Result {
165 let (fields, exit_status) = match expand_words(env, &self.words).await {
166 Ok(result) => result,
167 Err(error) => return error.handle(env).await,
168 };
169
170 use crate::command_search::Target::{Builtin, External, Function};
171 if let Some(name) = fields.first() {
172 match classify(env, &name.value) {
173 Builtin { builtin, path: _ } => {
174 execute_builtin(env, builtin, &self.assigns, fields, &self.redirs).await
175 }
176 Function(function) => {
177 execute_function(env, function, &self.assigns, fields, &self.redirs).await
178 }
179 External { path: _ } => {
180 execute_external_utility(env, &self.assigns, fields, &self.redirs).await
181 }
182 }
183 } else {
184 let exit_status = exit_status.unwrap_or_default();
185 execute_absent_target(env, &self.assigns, &self.redirs, exit_status).await
186 }?;
187
188 env.apply_errexit()
189 }
190}
191
192async fn expand_words(
193 env: &mut Env,
194 words: &[(Word, ExpansionMode)],
195) -> crate::expansion::Result<(Vec<Field>, Option<ExitStatus>)> {
196 let mut fields = Vec::new();
197 let mut last_exit_status = None;
198 for (word, mode) in words {
199 let exit_status = expand_word_with_mode(env, word, *mode, &mut fields).await?;
200 if exit_status.is_some() {
201 last_exit_status = exit_status;
202 }
203 }
204 Ok((fields, last_exit_status))
205}
206
207async fn perform_assignments(
208 env: &mut Env,
209 assigns: &[Assign],
210 export: bool,
211 xtrace: Option<&mut XTrace>,
212) -> Result<Option<ExitStatus>> {
213 let scope = if export {
214 Scope::Volatile
215 } else {
216 Scope::Global
217 };
218 match crate::assign::perform_assignments(env, assigns, scope, export, xtrace).await {
219 Ok(exit_status) => Continue(exit_status),
220 Err(error) => {
221 error.handle(env).await?;
222 Continue(None)
223 }
224 }
225}
226
227mod absent;
228use absent::execute_absent_target;
229
230mod builtin;
231use builtin::execute_builtin;
232
233mod function;
234use function::execute_function;
235pub use function::execute_function_body;
236
237mod external;
238use external::execute_external_utility;
239pub use external::replace_current_process;
240pub use external::start_external_utility_in_subshell_and_wait;
241pub use external::to_c_strings;
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::tests::return_builtin;
247 use futures_util::FutureExt;
248 use std::ops::ControlFlow::Break;
249 use yash_env::option::Option::ErrExit;
250 use yash_env::option::State::On;
251 use yash_env::semantics::Divert;
252
253 #[test]
254 fn errexit_on_simple_command() {
255 let mut env = Env::new_virtual();
256 env.builtins.insert("return", return_builtin());
257 env.options.set(ErrExit, On);
258 let command: syntax::SimpleCommand = "return -n 93".parse().unwrap();
259 let result = command.execute(&mut env).now_or_never().unwrap();
260 assert_eq!(result, Break(Divert::Exit(None)));
261 assert_eq!(env.exit_status, ExitStatus(93));
262 }
263}