Skip to main content

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