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;
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 { builtin, path: _ } => {
175                    execute_builtin(env, builtin, &self.assigns, fields, &self.redirs).await
176                }
177                Function(function) => {
178                    execute_function(env, function, &self.assigns, fields, &self.redirs).await
179                }
180                External { path: _ } => {
181                    execute_external_utility(env, &self.assigns, fields, &self.redirs).await
182                }
183            }
184        } else {
185            let exit_status = exit_status.unwrap_or_default();
186            execute_absent_target(env, &self.assigns, &self.redirs, exit_status).await
187        }?;
188
189        env.apply_errexit()
190    }
191}
192
193async fn expand_words<S: Runtime + 'static>(
194    env: &mut Env<S>,
195    words: &[(Word, ExpansionMode)],
196) -> crate::expansion::Result<(Vec<Field>, Option<ExitStatus>)> {
197    let mut fields = Vec::new();
198    let mut last_exit_status = None;
199    for (word, mode) in words {
200        let exit_status = expand_word_with_mode(env, word, *mode, &mut fields).await?;
201        if exit_status.is_some() {
202            last_exit_status = exit_status;
203        }
204    }
205    Ok((fields, last_exit_status))
206}
207
208async fn perform_assignments<S: Runtime + 'static>(
209    env: &mut Env<S>,
210    assigns: &[Assign],
211    export: bool,
212    xtrace: Option<&mut XTrace>,
213) -> Result<Option<ExitStatus>> {
214    let scope = if export {
215        Scope::Volatile
216    } else {
217        Scope::Global
218    };
219    match crate::assign::perform_assignments(env, assigns, scope, export, xtrace).await {
220        Ok(exit_status) => Continue(exit_status),
221        Err(error) => {
222            error.handle(env).await?;
223            Continue(None)
224        }
225    }
226}
227
228mod absent;
229use absent::execute_absent_target;
230
231mod builtin;
232use builtin::execute_builtin;
233
234mod function;
235use function::execute_function;
236pub use function::execute_function_body;
237
238mod external;
239use external::execute_external_utility;
240pub use external::start_external_utility_in_subshell_and_wait;
241#[allow(deprecated)]
242pub use external::to_c_strings;
243
244#[doc(no_inline)]
245pub use yash_env::semantics::command::replace_current_process;
246
247#[cfg(test)]
248mod tests {
249    use super::*;
250    use crate::tests::return_builtin;
251    use futures_util::FutureExt;
252    use std::ops::ControlFlow::Break;
253    use yash_env::option::Option::ErrExit;
254    use yash_env::option::State::On;
255    use yash_env::semantics::Divert;
256
257    #[test]
258    fn errexit_on_simple_command() {
259        let mut env = Env::new_virtual();
260        env.builtins.insert("return", return_builtin());
261        env.options.set(ErrExit, On);
262        let command: syntax::SimpleCommand = "return -n 93".parse().unwrap();
263        let result = command.execute(&mut env).now_or_never().unwrap();
264        assert_eq!(result, Break(Divert::Exit(None)));
265        assert_eq!(env.exit_status, ExitStatus(93));
266    }
267}