Skip to main content

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