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