1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
// This file is part of yash, an extended POSIX shell.
// Copyright (C) 2021 WATANABE Yuki
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//! Type definitions for command execution.
use crate::signal;
use crate::system::Errno;
use crate::system::System;
use nix::sys::signal::Signal;
use std::ffi::c_int;
use std::ops::ControlFlow;
use std::process::ExitCode;
use std::process::Termination;
use yash_syntax::source::Location;
/// Resultant string of word expansion.
///
/// A field is a string accompanied with the original word location.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Field {
/// String value of the field.
pub value: String,
/// Location of the word this field resulted from.
pub origin: Location,
}
impl Field {
/// Creates a new field with a dummy origin location.
///
/// The value of the resulting field will be `value.into()`.
/// The origin of the field will be created by [`Location::dummy`] with a
/// clone of the value.
#[inline]
pub fn dummy<S: Into<String>>(value: S) -> Field {
fn with_value(value: String) -> Field {
let origin = Location::dummy(value.clone());
Field { value, origin }
}
with_value(value.into())
}
/// Creates an array of fields with dummy origin locations.
///
/// This function calls [`dummy`](Self::dummy) to create the results.
pub fn dummies<I, S>(values: I) -> Vec<Field>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
values.into_iter().map(Self::dummy).collect()
}
}
impl std::fmt::Display for Field {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.value.fmt(f)
}
}
/// Number that summarizes the result of command execution.
///
/// An exit status is an integer returned from a utility (or command) when
/// executed. It usually is a summarized result of the execution. Many
/// utilities return an exit status of zero when successful and non-zero
/// otherwise.
///
/// In the shell language, the special parameter `$?` expands to the exit status
/// of the last executed command. Exit statuses also affect the behavior of some
/// compound commands.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct ExitStatus(pub c_int);
impl std::fmt::Display for ExitStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl From<c_int> for ExitStatus {
fn from(value: c_int) -> ExitStatus {
ExitStatus(value)
}
}
impl From<ExitStatus> for c_int {
fn from(exit_status: ExitStatus) -> c_int {
exit_status.0
}
}
/// Converts a signal number to the corresponding exit status.
///
/// POSIX requires the exit status to be greater than 128. The current
/// implementation returns `signal_number + 384`.
///
/// See [`ExitStatus::to_signal_number`] for the reverse conversion.
impl From<signal::Number> for ExitStatus {
fn from(number: signal::Number) -> Self {
Self::from(number.as_raw() + 0x180)
}
}
impl ExitStatus {
/// Returns the signal number corresponding to the exit status.
///
/// Basically, this function is the inverse of the `From<signal::Number>`
/// implementation for `ExitStatus`. It tries to find a signal number by
/// offsetting the exit status by 384. If the offsetting does not result in a
/// valid signal number, it additionally tries with 128 and 0.
#[must_use]
pub fn to_signal_number<S: System>(self, system: &S) -> Option<signal::Number> {
[0x180, 0x80, 0]
.into_iter()
.filter_map(|offset| self.0.checked_sub(offset))
.filter_map(|raw_number| system.validate_signal(raw_number))
.next()
.map(|(_name, number)| number)
}
}
/// Converts the exit status to `ExitCode`.
///
/// Note that `ExitCode` only supports exit statuses in the range of 0 to 255.
/// Only the lowest 8 bits of the exit status are used in the conversion.
impl Termination for ExitStatus {
fn report(self) -> ExitCode {
(self.0 as u8).into()
}
}
/// Converts an exit status to the corresponding signal.
///
/// If there is a signal such that
/// `exit_status == ExitStatus::from(signal)`,
/// the signal is returned.
/// The same if the exit status is the lowest 8 bits of such an exit status.
/// The signal is also returned if the exit status is a signal number itself.
/// Otherwise, an error is returned.
impl TryFrom<ExitStatus> for Signal {
type Error = Errno;
fn try_from(exit_status: ExitStatus) -> std::result::Result<Signal, Errno> {
Signal::try_from(exit_status.0 - 0x180)
.or_else(|_| Signal::try_from(exit_status.0 - 0x80))
.or_else(|_| Signal::try_from(exit_status.0))
.map_err(Errno::from)
}
}
impl ExitStatus {
/// Exit status of 0: success.
pub const SUCCESS: ExitStatus = ExitStatus(0);
/// Exit status of 1: failure.
pub const FAILURE: ExitStatus = ExitStatus(1);
/// Exit status of 2: error severer than failure.
pub const ERROR: ExitStatus = ExitStatus(2);
/// Exit Status of 126: command not executable.
pub const NOEXEC: ExitStatus = ExitStatus(126);
/// Exit status of 127: command not found.
pub const NOT_FOUND: ExitStatus = ExitStatus(127);
/// Returns true if and only if `self` is zero.
pub const fn is_successful(&self) -> bool {
self.0 == 0
}
}
/// Result of interrupted command execution.
///
/// `Divert` implements `Ord`. Values are ordered by severity.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Divert {
/// Continue the current loop.
Continue {
/// Number of loops to break before continuing.
///
/// `0` for continuing the innermost loop, `1` for one-level outer, and so on.
count: usize,
},
/// Break the current loop.
Break {
/// Number of loops to break.
///
/// `0` for breaking the innermost loop, `1` for one-level outer, and so on.
count: usize,
},
/// Return from the current function or script.
Return(Option<ExitStatus>),
/// Interrupt the current shell execution environment.
///
/// This is the same as `Exit` in a non-interactive shell: it makes the
/// shell exit after executing the EXIT trap, if any. If this is used inside
/// the EXIT trap, the shell will exit immediately.
///
/// In an interactive shell, this will abort the currently executed command
/// and resume prompting for a next command line.
Interrupt(Option<ExitStatus>),
/// Exit from the current shell execution environment.
///
/// This makes the shell exit after executing the EXIT trap, if any.
/// If this is used inside the EXIT trap, the shell will exit immediately.
Exit(Option<ExitStatus>),
/// Exit from the current shell execution environment immediately.
///
/// This makes the shell exit without executing the EXIT trap.
Abort(Option<ExitStatus>),
}
impl Divert {
/// Returns the exit status associated with the `Divert`.
///
/// Returns the variant's value if `self` is `Exit` or `Interrupt`;
/// otherwise, `None`.
pub fn exit_status(&self) -> Option<ExitStatus> {
use Divert::*;
match self {
Continue { .. } | Break { .. } => None,
Return(exit_status)
| Interrupt(exit_status)
| Exit(exit_status)
| Abort(exit_status) => *exit_status,
}
}
}
/// Result of command execution.
///
/// If the command was interrupted in the middle of execution, the result value
/// will be a `Break` having a [`Divert`] value which specifies what to execute
/// next.
pub type Result<T = ()> = ControlFlow<Divert, T>;
#[cfg(test)]
mod tests {
use super::*;
use crate::system::r#virtual::VirtualSystem;
use crate::system::r#virtual::{SIGINT, SIGTERM};
#[test]
fn exit_status_to_signal_number() {
let system = VirtualSystem::new();
assert_eq!(ExitStatus(0).to_signal_number(&system), None);
assert_eq!(
ExitStatus(SIGINT.as_raw()).to_signal_number(&system),
Some(SIGINT)
);
assert_eq!(
ExitStatus::from(SIGINT).to_signal_number(&system),
Some(SIGINT)
);
let mut exit_status = ExitStatus::from(SIGTERM);
assert_eq!(exit_status.to_signal_number(&system), Some(SIGTERM));
exit_status.0 &= 0xFF;
assert_eq!(exit_status.to_signal_number(&system), Some(SIGTERM));
exit_status.0 &= 0x7F;
assert_eq!(exit_status.to_signal_number(&system), Some(SIGTERM));
}
}