#[cfg(doc)]
use super::TrapSet;
use super::{Condition, SignalSystem};
use crate::source::Location;
use crate::system::{Disposition, Errno};
use std::collections::btree_map::{Entry, VacantEntry};
use std::rc::Rc;
use thiserror::Error;
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
pub enum Action {
#[default]
Default,
Ignore,
Command(Rc<str>),
}
impl From<&Action> for Disposition {
fn from(trap: &Action) -> Self {
match trap {
Action::Default => Disposition::Default,
Action::Ignore => Disposition::Ignore,
Action::Command(_) => Disposition::Catch,
}
}
}
#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum SetActionError {
#[error("the signal has been ignored since startup")]
InitiallyIgnored,
#[error("cannot set a trap for SIGKILL")]
SIGKILL,
#[error("cannot set a trap for SIGSTOP")]
SIGSTOP,
#[error(transparent)]
SystemError(#[from] Errno),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub enum Origin {
#[default]
Inherited,
Subshell,
User(Location),
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct TrapState {
pub action: Action,
pub origin: Origin,
pub pending: bool,
}
impl TrapState {
fn from_initial_disposition(disposition: Disposition) -> Self {
let action = match disposition {
Disposition::Default => Action::Default,
Disposition::Ignore => Action::Ignore,
Disposition::Catch => Action::Default,
};
TrapState {
action,
origin: Origin::Inherited,
pending: false,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EnterSubshellOption {
KeepInternalDisposition,
ClearInternalDisposition,
Ignore,
}
#[derive(Clone, Debug)]
pub struct GrandState {
current_state: TrapState,
parent_state: Option<TrapState>,
internal_disposition: Disposition,
}
impl GrandState {
#[inline]
#[must_use]
pub fn current_state(&self) -> &TrapState {
&self.current_state
}
#[inline]
#[must_use]
pub fn parent_state(&self) -> Option<&TrapState> {
self.parent_state.as_ref()
}
pub fn clear_parent_state(&mut self) {
self.parent_state = None;
}
pub fn insert_from_system_if_vacant<'a, S: SignalSystem>(
system: &S,
entry: Entry<'a, Condition, GrandState>,
) -> Result<&'a GrandState, Errno> {
match entry {
Entry::Vacant(vacant) => {
let disposition = match *vacant.key() {
Condition::Signal(signal) => system.get_disposition(signal)?,
Condition::Exit => Disposition::Default,
};
let state = GrandState {
current_state: TrapState::from_initial_disposition(disposition),
parent_state: None,
internal_disposition: Disposition::Default,
};
Ok(vacant.insert(state))
}
Entry::Occupied(occupied) => Ok(occupied.into_mut()),
}
}
pub async fn set_action<S: SignalSystem>(
system: &S,
entry: Entry<'_, Condition, GrandState>,
action: Action,
origin: Location,
override_ignore: bool,
) -> Result<(), SetActionError> {
let cond = *entry.key();
let disposition = (&action).into();
let new_state = TrapState {
action,
origin: Origin::User(origin),
pending: false,
};
match entry {
Entry::Vacant(vacant) => {
if let Condition::Signal(signal) = cond {
if !override_ignore {
let initial_disposition =
system.set_disposition(signal, Disposition::Ignore).await?;
if initial_disposition == Disposition::Ignore {
vacant.insert(GrandState {
current_state: TrapState::from_initial_disposition(
initial_disposition,
),
parent_state: None,
internal_disposition: Disposition::Default,
});
return Err(SetActionError::InitiallyIgnored);
}
}
if override_ignore || disposition != Disposition::Ignore {
system.set_disposition(signal, disposition).await?;
}
}
vacant.insert(GrandState {
current_state: new_state,
parent_state: None,
internal_disposition: Disposition::Default,
});
}
Entry::Occupied(mut occupied) => {
let state = occupied.get_mut();
if !override_ignore
&& state.current_state.action == Action::Ignore
&& state.current_state.origin == Origin::Inherited
{
return Err(SetActionError::InitiallyIgnored);
}
if let Condition::Signal(signal) = cond {
let internal = state.internal_disposition;
let old_disposition = internal.max((&state.current_state.action).into());
let new_disposition = internal.max(disposition);
if old_disposition != new_disposition {
system.set_disposition(signal, new_disposition).await?;
}
}
state.current_state = new_state;
}
}
Ok(())
}
#[must_use]
pub fn internal_disposition(&self) -> Disposition {
self.internal_disposition
}
pub async fn set_internal_disposition<S: SignalSystem>(
system: &S,
entry: Entry<'_, Condition, GrandState>,
disposition: Disposition,
) -> Result<(), Errno> {
let signal = match *entry.key() {
Condition::Signal(signal) => signal,
Condition::Exit => panic!("exit condition cannot have an internal disposition"),
};
match entry {
Entry::Vacant(_) if disposition == Disposition::Default => (),
Entry::Vacant(vacant) => {
let initial_disposition = system.set_disposition(signal, disposition).await?;
vacant.insert(GrandState {
current_state: TrapState::from_initial_disposition(initial_disposition),
parent_state: None,
internal_disposition: disposition,
});
}
Entry::Occupied(mut occupied) => {
let state = occupied.get_mut();
let setting = (&state.current_state.action).into();
let old_disposition = state.internal_disposition.max(setting);
let new_disposition = disposition.max(setting);
if old_disposition != new_disposition {
system.set_disposition(signal, new_disposition).await?;
}
state.internal_disposition = disposition;
}
}
Ok(())
}
pub async fn enter_subshell<S: SignalSystem>(
&mut self,
system: &S,
cond: Condition,
option: EnterSubshellOption,
) -> Result<(), Errno> {
let old_setting = (&self.current_state.action).into();
let old_disposition = self.internal_disposition.max(old_setting);
if matches!(self.current_state.action, Action::Command(_)) {
self.parent_state = Some(std::mem::replace(
&mut self.current_state,
TrapState {
action: Action::Default,
origin: Origin::Subshell,
pending: false,
},
));
}
if option == EnterSubshellOption::Ignore {
self.current_state.action = Action::Ignore;
}
let new_setting = (&self.current_state.action).into();
let new_disposition = match option {
EnterSubshellOption::KeepInternalDisposition => {
self.internal_disposition.max(new_setting)
}
EnterSubshellOption::ClearInternalDisposition => new_setting,
EnterSubshellOption::Ignore => Disposition::Ignore,
};
if old_disposition != new_disposition {
if let Condition::Signal(signal) = cond {
system.set_disposition(signal, new_disposition).await?;
}
}
self.internal_disposition = match option {
EnterSubshellOption::KeepInternalDisposition => self.internal_disposition,
EnterSubshellOption::ClearInternalDisposition | EnterSubshellOption::Ignore => {
Disposition::Default
}
};
Ok(())
}
pub async fn ignore<S: SignalSystem>(
system: &S,
vacant: VacantEntry<'_, Condition, GrandState>,
) -> Result<(), Errno> {
let signal = match *vacant.key() {
Condition::Signal(signal) => signal,
Condition::Exit => panic!("exit condition cannot be ignored"),
};
let initial_disposition = system.set_disposition(signal, Disposition::Ignore).await?;
let origin = match initial_disposition {
Disposition::Default => Origin::Subshell,
Disposition::Ignore => Origin::Inherited,
Disposition::Catch => Origin::Subshell,
};
vacant.insert(GrandState {
current_state: TrapState {
action: Action::Ignore,
origin,
pending: false,
},
parent_state: None,
internal_disposition: Disposition::Default,
});
Ok(())
}
pub fn mark_as_caught(&mut self) {
self.current_state.pending = true;
}
pub fn handle_if_caught(&mut self) -> Option<&TrapState> {
if self.current_state.pending {
self.current_state.pending = false;
Some(&self.current_state)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::super::tests::DummySystem;
use super::*;
use crate::system::Signals;
use crate::system::r#virtual::{
SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIOT,
SIGKILL, SIGPIPE, SIGPROF, SIGQUIT, SIGSEGV, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP, SIGTSTP,
SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ,
};
use assert_matches::assert_matches;
use futures_util::FutureExt as _;
use std::borrow::Cow;
use std::{collections::BTreeMap, ops::RangeInclusive};
struct UnusedSystem;
impl Signals for UnusedSystem {
const SIGABRT: crate::signal::Number = SIGABRT;
const SIGALRM: crate::signal::Number = SIGALRM;
const SIGBUS: crate::signal::Number = SIGBUS;
const SIGCHLD: crate::signal::Number = SIGCHLD;
const SIGCLD: Option<crate::signal::Number> = None;
const SIGCONT: crate::signal::Number = SIGCONT;
const SIGEMT: Option<crate::signal::Number> = None;
const SIGFPE: crate::signal::Number = SIGFPE;
const SIGHUP: crate::signal::Number = SIGHUP;
const SIGILL: crate::signal::Number = SIGILL;
const SIGINFO: Option<crate::signal::Number> = None;
const SIGINT: crate::signal::Number = SIGINT;
const SIGIO: Option<crate::signal::Number> = None;
const SIGIOT: crate::signal::Number = SIGIOT;
const SIGKILL: crate::signal::Number = SIGKILL;
const SIGLOST: Option<crate::signal::Number> = None;
const SIGPIPE: crate::signal::Number = SIGPIPE;
const SIGPOLL: Option<crate::signal::Number> = None;
const SIGPROF: crate::signal::Number = SIGPROF;
const SIGPWR: Option<crate::signal::Number> = None;
const SIGQUIT: crate::signal::Number = SIGQUIT;
const SIGSEGV: crate::signal::Number = SIGSEGV;
const SIGSTKFLT: Option<crate::signal::Number> = None;
const SIGSTOP: crate::signal::Number = SIGSTOP;
const SIGSYS: crate::signal::Number = SIGSYS;
const SIGTERM: crate::signal::Number = SIGTERM;
const SIGTHR: Option<crate::signal::Number> = None;
const SIGTRAP: crate::signal::Number = SIGTRAP;
const SIGTSTP: crate::signal::Number = SIGTSTP;
const SIGTTIN: crate::signal::Number = SIGTTIN;
const SIGTTOU: crate::signal::Number = SIGTTOU;
const SIGURG: crate::signal::Number = SIGURG;
const SIGUSR1: crate::signal::Number = SIGUSR1;
const SIGUSR2: crate::signal::Number = SIGUSR2;
const SIGVTALRM: crate::signal::Number = SIGVTALRM;
const SIGWINCH: crate::signal::Number = SIGWINCH;
const SIGXCPU: crate::signal::Number = SIGXCPU;
const SIGXFSZ: crate::signal::Number = SIGXFSZ;
fn sigrt_range(&self) -> Option<RangeInclusive<crate::signal::Number>> {
unreachable!()
}
fn iter_sigrt(&self) -> impl DoubleEndedIterator<Item = crate::signal::Number> + use<> {
unreachable!() as std::iter::Empty<_>
}
fn sig2str<S: Into<crate::signal::RawNumber>>(
&self,
signal: S,
) -> Option<Cow<'static, str>> {
unreachable!("sig2str({:?})", signal.into())
}
fn str2sig(&self, name: &str) -> Option<crate::signal::Number> {
unreachable!("str2sig({name:?})")
}
fn validate_signal(
&self,
number: crate::signal::RawNumber,
) -> Option<(crate::signal::Name, crate::signal::Number)> {
unreachable!("validate_signal({number:?})")
}
fn signal_name_from_number(&self, number: crate::signal::Number) -> crate::signal::Name {
unreachable!("signal_name_from_number({number:?})")
}
fn signal_number_from_name(
&self,
name: crate::signal::Name,
) -> Option<crate::signal::Number> {
unreachable!("signal_number_from_name({name:?})")
}
}
impl SignalSystem for UnusedSystem {
fn get_disposition(&self, signal: crate::signal::Number) -> Result<Disposition, Errno> {
unreachable!("get_disposition({signal})")
}
fn set_disposition(
&self,
signal: crate::signal::Number,
disposition: Disposition,
) -> impl Future<Output = Result<Disposition, Errno>> + use<> {
unreachable!("set_disposition({signal}, {disposition:?})") as std::future::Ready<_>
}
}
#[test]
fn insertion_with_default_inherited_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
assert_eq!(
state.current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Inherited,
pending: false,
}
);
assert_eq!(state.parent_state(), None);
assert_eq!(state.internal_disposition(), Disposition::Default);
}
#[test]
fn insertion_with_inherited_disposition_of_ignore() {
let system = DummySystem::default();
system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
assert_eq!(
state.current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::Inherited,
pending: false,
}
);
assert_eq!(state.parent_state(), None);
assert_eq!(state.internal_disposition(), Disposition::Default);
}
#[test]
fn insertion_with_occupied_entry() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(SIGCHLD.into());
let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
assert_eq!(
state.current_state(),
&TrapState {
action,
origin: Origin::User(origin),
pending: false,
}
);
assert_eq!(state.parent_state(), None);
assert_eq!(state.internal_disposition(), Disposition::Default);
}
#[test]
fn insertion_with_non_signal_condition() {
let mut map = BTreeMap::new();
let entry = map.entry(Condition::Exit);
let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
assert_eq!(
state.current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Inherited,
pending: false,
}
);
assert_eq!(state.parent_state(), None);
assert_eq!(state.internal_disposition(), Disposition::Default);
}
#[test]
fn setting_trap_to_ignore_without_override_ignore() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn setting_trap_to_ignore_with_override_ignore() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), true)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn setting_trap_to_command() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let action = Action::Command("echo".into());
let origin = Location::dummy("origin");
let result = GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
}
#[test]
fn setting_trap_to_default() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("foo");
GrandState::set_action(&system, entry, Action::Ignore, origin, false)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("bar");
let result = GrandState::set_action(&system, entry, Action::Default, origin.clone(), false)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
}
#[test]
fn resetting_trap_from_ignore_no_override() {
let system = DummySystem::default();
system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("foo");
let result = GrandState::set_action(&system, entry, Action::Ignore, origin, false)
.now_or_never()
.unwrap();
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("bar");
let result = GrandState::set_action(&system, entry, Action::Ignore, origin, false)
.now_or_never()
.unwrap();
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::Inherited,
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn resetting_trap_from_ignore_override() {
let system = DummySystem::default();
system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), true)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn internal_disposition_ignore() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Ignore
);
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Inherited,
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn internal_disposition_catch() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Catch
);
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Inherited,
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
}
#[test]
fn action_ignore_and_internal_disposition_catch() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Catch
);
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
}
#[test]
fn action_catch_and_internal_disposition_ignore() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGCHLD.into());
let origin = Location::dummy("origin");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(SIGCHLD.into());
let result = GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGCHLD.into()].internal_disposition(),
Disposition::Ignore
);
assert_eq!(
map[&SIGCHLD.into()].current_state(),
&TrapState {
action,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
}
#[test]
fn set_internal_disposition_for_initially_defaulted_signal_then_allow_override() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let entry = map.entry(SIGTTOU.into());
GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(SIGTTOU.into());
let origin = Location::dummy("origin");
let action = Action::Command("echo".into());
let result = GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&SIGTTOU.into()].internal_disposition(),
Disposition::Ignore
);
assert_eq!(
map[&SIGTTOU.into()].current_state(),
&TrapState {
action,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&SIGTTOU.into()].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGTTOU], Disposition::Catch);
}
#[test]
fn set_internal_disposition_for_initially_ignored_signal_then_reject_override() {
let system = DummySystem::default();
system.0.borrow_mut().insert(SIGTTOU, Disposition::Ignore);
let mut map = BTreeMap::new();
let cond = SIGTTOU.into();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(cond);
let origin = Location::dummy("origin");
let action = Action::Command("echo".into());
let result = GrandState::set_action(&system, entry, action, origin, false)
.now_or_never()
.unwrap();
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::Inherited,
pending: false
}
);
assert_eq!(map[&cond].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGTTOU], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_internal_disposition_keeping_internal_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
GrandState::set_internal_disposition(&system, map.entry(cond), Disposition::Catch)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Catch);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Inherited,
pending: false
}
);
assert_eq!(map[&cond].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
}
#[test]
fn enter_subshell_with_internal_disposition_clearing_internal_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Inherited,
pending: false
}
);
assert_eq!(map[&cond].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
}
#[test]
fn enter_subshell_with_ignore_and_no_internal_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&cond].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_ignore_clearing_internal_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::User(origin),
pending: false
}
);
assert_eq!(map[&cond].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_command_and_no_internal_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Subshell,
pending: false
}
);
assert_eq!(
map[&cond].parent_state(),
Some(&TrapState {
action,
origin: Origin::User(origin),
pending: false
})
);
assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
}
#[test]
fn enter_subshell_with_command_keeping_internal_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGTSTP.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Subshell,
pending: false
}
);
assert_eq!(
map[&cond].parent_state(),
Some(&TrapState {
action,
origin: Origin::User(origin),
pending: false
})
);
assert_eq!(system.0.borrow()[&SIGTSTP], Disposition::Ignore);
}
#[test]
fn enter_subshell_with_command_clearing_internal_disposition() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGTSTP.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let entry = map.entry(cond);
GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Subshell,
pending: false
}
);
assert_eq!(
map[&cond].parent_state(),
Some(&TrapState {
action,
origin: Origin::User(origin),
pending: false
})
);
assert_eq!(system.0.borrow()[&SIGTSTP], Disposition::Default);
}
#[test]
fn enter_subshell_with_command_ignoring() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGQUIT.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let result = map
.get_mut(&cond)
.unwrap()
.enter_subshell(&system, cond, EnterSubshellOption::Ignore)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::Subshell,
pending: false
}
);
assert_eq!(
map[&cond].parent_state(),
Some(&TrapState {
action,
origin: Origin::User(origin),
pending: false
})
);
assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
}
#[test]
fn ignoring_initially_defaulted_signal() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGQUIT.into();
let entry = map.entry(cond);
let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
let result = GrandState::ignore(&system, vacant).now_or_never().unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::Subshell,
pending: false
}
);
assert_eq!(map[&cond].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
let result = GrandState::set_action(&system, entry, action, origin, false)
.now_or_never()
.unwrap();
assert_eq!(result, Ok(()));
}
#[test]
fn ignoring_initially_ignored_signal() {
let system = DummySystem::default();
system.0.borrow_mut().insert(SIGQUIT, Disposition::Ignore);
let mut map = BTreeMap::new();
let cond = SIGQUIT.into();
let entry = map.entry(cond);
let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
let result = GrandState::ignore(&system, vacant).now_or_never().unwrap();
assert_eq!(result, Ok(()));
assert_eq!(
map[&cond].current_state(),
&TrapState {
action: Action::Ignore,
origin: Origin::Inherited,
pending: false
}
);
assert_eq!(map[&cond].parent_state(), None);
assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
let result = GrandState::set_action(&system, entry, action, origin, false)
.now_or_never()
.unwrap();
assert_eq!(result, Err(SetActionError::InitiallyIgnored));
}
#[test]
fn clearing_parent_setting() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGCHLD.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action, origin, false)
.now_or_never()
.unwrap()
.unwrap();
let state = map.get_mut(&cond).unwrap();
state
.enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
.now_or_never()
.unwrap()
.unwrap();
state.clear_parent_state();
assert_eq!(
state.current_state(),
&TrapState {
action: Action::Default,
origin: Origin::Subshell,
pending: false
}
);
assert_eq!(state.parent_state(), None);
}
#[test]
fn marking_as_caught_and_handling() {
let system = DummySystem::default();
let mut map = BTreeMap::new();
let cond = SIGUSR1.into();
let entry = map.entry(cond);
let origin = Location::dummy("foo");
let action = Action::Command("echo".into());
GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
.now_or_never()
.unwrap()
.unwrap();
let state = &mut map.get_mut(&cond).unwrap();
state.mark_as_caught();
let expected_trap = TrapState {
action,
origin: Origin::User(origin),
pending: true,
};
assert_eq!(state.current_state(), &expected_trap);
assert_eq!(state.parent_state(), None);
let trap = state.handle_if_caught();
let expected_trap = TrapState {
pending: false,
..expected_trap
};
assert_eq!(trap, Some(&expected_trap));
let trap = state.handle_if_caught();
assert_eq!(trap, None);
}
}