yash_env/
semantics.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//! Type definitions for command execution.
18
19use crate::Env;
20use crate::signal;
21use crate::source::Location;
22use crate::system::resource::{LimitPair, Resource, SetRlimit};
23use crate::system::r#virtual::SignalEffect;
24use crate::system::{Disposition, Exit, SendSignal, Sigaction, Sigmask, SigmaskOp, Signals};
25use std::cell::RefCell;
26use std::ffi::c_int;
27use std::ops::ControlFlow;
28use std::pin::Pin;
29use std::process::ExitCode;
30use std::process::Termination;
31
32/// Resultant string of word expansion.
33///
34/// A field is a string accompanied with the original word location.
35#[derive(Clone, Debug, Eq, PartialEq)]
36pub struct Field {
37    /// String value of the field.
38    pub value: String,
39    /// Location of the word this field resulted from.
40    pub origin: Location,
41}
42
43impl Field {
44    /// Creates a new field with a dummy origin location.
45    ///
46    /// The value of the resulting field will be `value.into()`.
47    /// The origin of the field will be created by [`Location::dummy`] with a
48    /// clone of the value.
49    #[inline]
50    pub fn dummy<S: Into<String>>(value: S) -> Field {
51        fn with_value(value: String) -> Field {
52            let origin = Location::dummy(value.clone());
53            Field { value, origin }
54        }
55        with_value(value.into())
56    }
57
58    /// Creates an array of fields with dummy origin locations.
59    ///
60    /// This function calls [`dummy`](Self::dummy) to create the results.
61    pub fn dummies<I, S>(values: I) -> Vec<Field>
62    where
63        I: IntoIterator<Item = S>,
64        S: Into<String>,
65    {
66        values.into_iter().map(Self::dummy).collect()
67    }
68}
69
70impl std::fmt::Display for Field {
71    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72        self.value.fmt(f)
73    }
74}
75
76/// Number that summarizes the result of command execution.
77///
78/// An exit status is an integer returned from a utility (or command) when
79/// executed. It usually is a summarized result of the execution.  Many
80/// utilities return an exit status of zero when successful and non-zero
81/// otherwise.
82///
83/// In the shell language, the special parameter `$?` expands to the exit status
84/// of the last executed command. Exit statuses also affect the behavior of some
85/// compound commands.
86#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
87pub struct ExitStatus(pub c_int);
88
89impl std::fmt::Display for ExitStatus {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        self.0.fmt(f)
92    }
93}
94
95impl From<c_int> for ExitStatus {
96    fn from(value: c_int) -> ExitStatus {
97        ExitStatus(value)
98    }
99}
100
101impl From<ExitStatus> for c_int {
102    fn from(exit_status: ExitStatus) -> c_int {
103        exit_status.0
104    }
105}
106
107/// Converts a signal number to the corresponding exit status.
108///
109/// POSIX requires the exit status to be greater than 128. The current
110/// implementation returns `signal_number + 384`.
111///
112/// See [`ExitStatus::to_signal`] for the reverse conversion.
113impl From<signal::Number> for ExitStatus {
114    fn from(number: signal::Number) -> Self {
115        Self::from(number.as_raw() + 0x180)
116    }
117}
118
119impl ExitStatus {
120    /// Returns the signal name and number corresponding to the exit status.
121    ///
122    /// This function is the inverse of the `From<signal::Number>` implementation
123    /// for `ExitStatus`. It tries to find a signal name and number by offsetting
124    /// the exit status by 384. If the offsetting does not result in a valid signal
125    /// name and number, it additionally tries with 128 and 0 unless `exact` is
126    /// `true`.
127    ///
128    /// If `self` is not a valid signal exit status, this function returns `None`.
129    #[must_use]
130    pub fn to_signal<S: Signals + ?Sized>(
131        self,
132        system: &S,
133        exact: bool,
134    ) -> Option<(signal::Name, signal::Number)> {
135        fn convert<S: Signals + ?Sized>(
136            exit_status: ExitStatus,
137            offset: c_int,
138            system: &S,
139        ) -> Option<(signal::Name, signal::Number)> {
140            let number = exit_status.0.checked_sub(offset)?;
141            system.validate_signal(number)
142        }
143
144        if let Some(signal) = convert(self, 0x180, system) {
145            return Some(signal);
146        }
147        if exact {
148            return None;
149        }
150        if let Some(signal) = convert(self, 0x80, system) {
151            return Some(signal);
152        }
153        if let Some(signal) = convert(self, 0, system) {
154            return Some(signal);
155        }
156        None
157    }
158}
159
160/// Converts the exit status to `ExitCode`.
161///
162/// Note that `ExitCode` only supports exit statuses in the range of 0 to 255.
163/// Only the lowest 8 bits of the exit status are used in the conversion.
164impl Termination for ExitStatus {
165    fn report(self) -> ExitCode {
166        (self.0 as u8).into()
167    }
168}
169
170impl ExitStatus {
171    /// Exit status of 0: success
172    pub const SUCCESS: ExitStatus = ExitStatus(0);
173
174    /// Exit status of 1: failure
175    pub const FAILURE: ExitStatus = ExitStatus(1);
176
177    /// Exit status of 2: error severer than failure
178    pub const ERROR: ExitStatus = ExitStatus(2);
179
180    /// Exit Status of 126: command not executable
181    pub const NOEXEC: ExitStatus = ExitStatus(126);
182
183    /// Exit status of 127: command not found
184    pub const NOT_FOUND: ExitStatus = ExitStatus(127);
185
186    /// Exit status of 128: unrecoverable read error
187    pub const READ_ERROR: ExitStatus = ExitStatus(128);
188
189    /// Returns true if and only if `self` is zero.
190    pub const fn is_successful(&self) -> bool {
191        self.0 == 0
192    }
193}
194
195/// Result of interrupted command execution.
196///
197/// `Divert` implements `Ord`. Values are ordered by severity.
198#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
199pub enum Divert {
200    /// Continue the current loop.
201    Continue {
202        /// Number of loops to break before continuing.
203        ///
204        /// `0` for continuing the innermost loop, `1` for one-level outer, and so on.
205        count: usize,
206    },
207
208    /// Break the current loop.
209    Break {
210        /// Number of loops to break.
211        ///
212        /// `0` for breaking the innermost loop, `1` for one-level outer, and so on.
213        count: usize,
214    },
215
216    /// Return from the current function or script.
217    Return(Option<ExitStatus>),
218
219    /// Interrupt the current shell execution environment.
220    ///
221    /// This is the same as `Exit` in a non-interactive shell: it makes the
222    /// shell exit after executing the EXIT trap, if any. If this is used inside
223    /// the EXIT trap, the shell will exit immediately.
224    ///
225    /// In an interactive shell, this will abort the currently executed command
226    /// and resume prompting for a next command line.
227    Interrupt(Option<ExitStatus>),
228
229    /// Exit from the current shell execution environment.
230    ///
231    /// This makes the shell exit after executing the EXIT trap, if any.
232    /// If this is used inside the EXIT trap, the shell will exit immediately.
233    Exit(Option<ExitStatus>),
234
235    /// Exit from the current shell execution environment immediately.
236    ///
237    /// This makes the shell exit without executing the EXIT trap.
238    Abort(Option<ExitStatus>),
239}
240
241impl Divert {
242    /// Returns the exit status associated with the `Divert`.
243    ///
244    /// Returns the variant's value if `self` is `Exit` or `Interrupt`;
245    /// otherwise, `None`.
246    pub fn exit_status(&self) -> Option<ExitStatus> {
247        use Divert::*;
248        match self {
249            Continue { .. } | Break { .. } => None,
250            Return(exit_status)
251            | Interrupt(exit_status)
252            | Exit(exit_status)
253            | Abort(exit_status) => *exit_status,
254        }
255    }
256}
257
258/// Result of command execution.
259///
260/// If the command was interrupted in the middle of execution, the result value
261/// will be a `Break` having a [`Divert`] value which specifies what to execute
262/// next.
263pub type Result<T = ()> = ControlFlow<Divert, T>;
264
265type PinFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
266
267/// Wrapper for running a read-eval-loop
268///
269/// This struct declares a function type for running a read-eval-loop. An
270/// implementation of this function type should be provided and stored in the
271/// environment's [`any`](Env::any) storage so that it can be used by modules
272/// depending on read-eval-loop execution.
273///
274/// The function takes two arguments. The first argument is a mutable reference
275/// to an environment wrapped in a [`RefCell`]. The second argument is a
276/// [configuration](crate::parser::Config) for the parser, which contains the
277/// [input function](crate::input::Input) and source information. The
278/// configuration will be used by the function to create a lexer for reading
279/// commands.
280///
281/// Note that the `RefCell` is passed as a shared reference. It can be shared
282/// with the input function, allowing the input function to access and modify
283/// the environment while reading commands. The input function must release
284/// the borrow on the `RefCell` before returning control to the caller so that
285/// the caller can borrow the `RefCell` mutably to execute commands read from
286/// the parser.
287///
288/// The function returns a future which resolves to a [`Result`] when awaited.
289/// The function should execute commands read from the lexer until the end of
290/// input or a [`Divert`] is encountered.
291///
292/// The function should set [`Env::exit_status`] appropriately after the loop
293/// ends. If the input contains no commands, the exit status should be set to
294/// `ExitStatus(0)`.
295///
296/// The function should also
297/// [update subshell statuses](Env::update_all_subshell_statuses) and handle
298/// traps during the loop execution as specified in the shell semantics.
299///
300/// The most standard implementation of this function type is provided in the
301/// [`yash-semantics` crate](https://crates.io/crates/yash-semantics):
302///
303/// ```
304/// # use yash_env::Env;
305/// # use yash_env::semantics::RunReadEvalLoop;
306/// fn register_read_eval_loop<S: yash_semantics::Runtime + 'static>(env: &mut Env<S>) {
307///     env.any.insert(Box::new(RunReadEvalLoop::<S>(|env, config| {
308///         Box::pin(async move {
309///             yash_semantics::read_eval_loop(env, &mut config.into()).await
310///         })
311///     })));
312/// }
313/// # register_read_eval_loop(&mut Env::new_virtual());
314/// ```
315pub struct RunReadEvalLoop<S>(
316    pub for<'a> fn(&'a RefCell<&mut Env<S>>, crate::parser::Config<'a>) -> PinFuture<'a, Result>,
317);
318
319// Not derived automatically because S may not implement Clone, Copy, or Debug
320impl<S> Clone for RunReadEvalLoop<S> {
321    fn clone(&self) -> Self {
322        *self
323    }
324}
325
326impl<S> Copy for RunReadEvalLoop<S> {}
327
328impl<S> std::fmt::Debug for RunReadEvalLoop<S> {
329    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
330        f.debug_tuple("RunReadEvalLoop").field(&self.0).finish()
331    }
332}
333
334pub mod command;
335pub mod expansion;
336
337/// Terminates the current process with the given exit status, possibly sending
338/// a signal to kill the process.
339///
340/// If the exit status represents a signal that killed the last executed
341/// command, this function sends the signal to the current process to propagate
342/// the termination status to the parent process. Otherwise, this function
343/// terminates the process with the given exit status.
344pub async fn exit_or_raise<S>(system: &S, exit_status: ExitStatus) -> !
345where
346    S: Signals + Sigmask + Sigaction + SendSignal + SetRlimit + Exit + ?Sized,
347{
348    async fn maybe_raise<S>(system: &S, exit_status: ExitStatus) -> crate::system::Result<()>
349    where
350        S: Signals + Sigmask + Sigaction + SendSignal + SetRlimit + ?Sized,
351    {
352        let Some(signal) = exit_status.to_signal(system, /* exact */ true) else {
353            return Ok(());
354        };
355        if !matches!(SignalEffect::of(signal.0), SignalEffect::Terminate { .. }) {
356            return Ok(());
357        }
358
359        // Disable core dump
360        system.setrlimit(Resource::CORE, LimitPair { soft: 0, hard: 0 })?;
361
362        if signal.0 != signal::Name::Kill {
363            // Reset signal disposition
364            system.sigaction(signal.1, Disposition::Default)?;
365        }
366
367        // Unblock the signal
368        system.sigmask(Some((SigmaskOp::Remove, &[signal.1])), None)?;
369
370        // Send the signal to the current process
371        system.raise(signal.1).await?;
372
373        Ok(())
374    }
375
376    maybe_raise(system, exit_status).await.ok();
377    match system.exit(exit_status).await {}
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    use crate::system::r#virtual::VirtualSystem;
384    use crate::system::r#virtual::{SIGINT, SIGTERM};
385
386    #[test]
387    fn exit_status_to_signal() {
388        let system = VirtualSystem::new();
389
390        assert_eq!(ExitStatus(0).to_signal(&system, false), None);
391        assert_eq!(ExitStatus(0).to_signal(&system, true), None);
392
393        assert_eq!(
394            ExitStatus(SIGINT.as_raw()).to_signal(&system, false),
395            Some((signal::Name::Int, SIGINT))
396        );
397        assert_eq!(ExitStatus(SIGINT.as_raw()).to_signal(&system, true), None);
398
399        assert_eq!(
400            ExitStatus(SIGINT.as_raw() + 0x80).to_signal(&system, false),
401            Some((signal::Name::Int, SIGINT))
402        );
403        assert_eq!(
404            ExitStatus(SIGINT.as_raw() + 0x80).to_signal(&system, true),
405            None
406        );
407
408        assert_eq!(
409            ExitStatus(SIGINT.as_raw() + 0x180).to_signal(&system, false),
410            Some((signal::Name::Int, SIGINT))
411        );
412        assert_eq!(
413            ExitStatus(SIGINT.as_raw() + 0x180).to_signal(&system, true),
414            Some((signal::Name::Int, SIGINT))
415        );
416
417        assert_eq!(
418            ExitStatus(SIGTERM.as_raw() + 0x180).to_signal(&system, false),
419            Some((signal::Name::Term, SIGTERM))
420        );
421        assert_eq!(
422            ExitStatus(SIGTERM.as_raw() + 0x180).to_signal(&system, true),
423            Some((signal::Name::Term, SIGTERM))
424        );
425    }
426}