use crate::Env;
use crate::signal;
use crate::source::Location;
use crate::system::resource::{LimitPair, Resource, SetRlimit};
use crate::system::r#virtual::SignalEffect;
use crate::system::{Disposition, Exit, SendSignal, Sigaction, Sigmask, SigmaskOp, Signals};
use std::borrow::Cow;
use std::cell::RefCell;
use std::ffi::c_int;
use std::num::NonZero;
use std::ops::ControlFlow;
use std::pin::Pin;
use std::process::ExitCode;
use std::process::Termination;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Field {
pub value: String,
pub origin: Location,
}
impl Field {
#[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())
}
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)
}
}
#[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
}
}
impl From<signal::Number> for ExitStatus {
fn from(number: signal::Number) -> Self {
Self::from(number.as_raw() + 0x180)
}
}
impl ExitStatus {
#[must_use]
pub fn to_signal<S: Signals + ?Sized>(
self,
system: &S,
exact: bool,
) -> Option<(Cow<'static, str>, signal::Number)> {
fn convert<S: Signals + ?Sized>(
exit_status: ExitStatus,
offset: c_int,
system: &S,
) -> Option<(Cow<'static, str>, signal::Number)> {
let number = exit_status.0.checked_sub(offset)?;
let number = signal::Number::from_raw_unchecked(NonZero::new(number)?);
let name = system.sig2str(number)?;
Some((name, number))
}
if let Some(signal) = convert(self, 0x180, system) {
return Some(signal);
}
if exact {
return None;
}
if let Some(signal) = convert(self, 0x80, system) {
return Some(signal);
}
if let Some(signal) = convert(self, 0, system) {
return Some(signal);
}
None
}
}
impl Termination for ExitStatus {
fn report(self) -> ExitCode {
(self.0 as u8).into()
}
}
impl ExitStatus {
pub const SUCCESS: ExitStatus = ExitStatus(0);
pub const FAILURE: ExitStatus = ExitStatus(1);
pub const ERROR: ExitStatus = ExitStatus(2);
pub const NOEXEC: ExitStatus = ExitStatus(126);
pub const NOT_FOUND: ExitStatus = ExitStatus(127);
pub const READ_ERROR: ExitStatus = ExitStatus(128);
pub const fn is_successful(&self) -> bool {
self.0 == 0
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Divert {
Continue {
count: usize,
},
Break {
count: usize,
},
Return(Option<ExitStatus>),
Interrupt(Option<ExitStatus>),
Exit(Option<ExitStatus>),
Abort(Option<ExitStatus>),
}
impl Divert {
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,
}
}
}
pub type Result<T = ()> = ControlFlow<Divert, T>;
type PinFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
pub struct RunReadEvalLoop<S>(
pub for<'a> fn(&'a RefCell<&mut Env<S>>, crate::parser::Config<'a>) -> PinFuture<'a, Result>,
);
impl<S> Clone for RunReadEvalLoop<S> {
fn clone(&self) -> Self {
*self
}
}
impl<S> Copy for RunReadEvalLoop<S> {}
impl<S> std::fmt::Debug for RunReadEvalLoop<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("RunReadEvalLoop").field(&self.0).finish()
}
}
pub mod command;
pub mod expansion;
pub async fn exit_or_raise<S>(system: &S, exit_status: ExitStatus) -> !
where
S: Signals + Sigmask + Sigaction + SendSignal + SetRlimit + Exit + ?Sized,
{
async fn maybe_raise<S>(system: &S, exit_status: ExitStatus) -> crate::system::Result<()>
where
S: Signals + Sigmask + Sigaction + SendSignal + SetRlimit + ?Sized,
{
let Some(signal) = exit_status.to_signal(system, true) else {
return Ok(());
};
let Ok(name) = signal.0.parse() else {
return Ok(());
};
if !matches!(SignalEffect::of(name), SignalEffect::Terminate { .. }) {
return Ok(());
}
system.setrlimit(Resource::CORE, LimitPair { soft: 0, hard: 0 })?;
if signal.1 != S::SIGKILL {
system.sigaction(signal.1, Disposition::Default)?;
}
system
.sigmask(Some((SigmaskOp::Remove, &[signal.1])), None)
.await?;
system.raise(signal.1).await?;
Ok(())
}
maybe_raise(system, exit_status).await.ok();
match system.exit(exit_status).await {}
}
#[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() {
let system = VirtualSystem::new();
assert_eq!(ExitStatus(0).to_signal(&system, false), None);
assert_eq!(ExitStatus(0).to_signal(&system, true), None);
assert_eq!(
ExitStatus(SIGINT.as_raw()).to_signal(&system, false),
Some(("INT".into(), SIGINT))
);
assert_eq!(ExitStatus(SIGINT.as_raw()).to_signal(&system, true), None);
assert_eq!(
ExitStatus(SIGINT.as_raw() + 0x80).to_signal(&system, false),
Some(("INT".into(), SIGINT))
);
assert_eq!(
ExitStatus(SIGINT.as_raw() + 0x80).to_signal(&system, true),
None
);
assert_eq!(
ExitStatus(SIGINT.as_raw() + 0x180).to_signal(&system, false),
Some(("INT".into(), SIGINT))
);
assert_eq!(
ExitStatus(SIGINT.as_raw() + 0x180).to_signal(&system, true),
Some(("INT".into(), SIGINT))
);
assert_eq!(
ExitStatus(SIGTERM.as_raw() + 0x180).to_signal(&system, false),
Some(("TERM".into(), SIGTERM))
);
assert_eq!(
ExitStatus(SIGTERM.as_raw() + 0x180).to_signal(&system, true),
Some(("TERM".into(), SIGTERM))
);
}
}