Skip to main content

yash_env/trap/
state.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2023 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//! Items that manage the state of a single signal
18
19#[cfg(doc)]
20use super::TrapSet;
21use super::{Condition, SignalSystem};
22use crate::source::Location;
23use crate::system::{Disposition, Errno};
24use std::collections::btree_map::{Entry, VacantEntry};
25use std::rc::Rc;
26use thiserror::Error;
27
28/// Action performed when a [`Condition`] is met
29#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
30pub enum Action {
31    /// Performs the default action.
32    ///
33    /// For signal conditions, the behavior depends on the signal delivered.
34    /// For other conditions, this is equivalent to `Ignore`.
35    #[default]
36    Default,
37
38    /// Pretends as if the condition was not met.
39    Ignore,
40
41    /// Executes a command string.
42    Command(Rc<str>),
43}
44
45impl From<&Action> for Disposition {
46    fn from(trap: &Action) -> Self {
47        match trap {
48            Action::Default => Disposition::Default,
49            Action::Ignore => Disposition::Ignore,
50            Action::Command(_) => Disposition::Catch,
51        }
52    }
53}
54
55/// Error that may happen in [`TrapSet::set_action`]
56#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
57pub enum SetActionError {
58    /// Attempt to set a trap that has been ignored since the shell startup.
59    #[error("the signal has been ignored since startup")]
60    InitiallyIgnored,
61
62    /// Attempt to set a trap for the `SIGKILL` signal.
63    #[error("cannot set a trap for SIGKILL")]
64    SIGKILL,
65
66    /// Attempt to set a trap for the `SIGSTOP` signal.
67    #[error("cannot set a trap for SIGSTOP")]
68    SIGSTOP,
69
70    /// Error from the underlying system interface.
71    #[error(transparent)]
72    SystemError(#[from] Errno),
73}
74
75/// Origin of the current trap action
76///
77/// The `Origin` enum indicates how the current trap action was set.
78#[derive(Clone, Debug, Default, Eq, PartialEq)]
79pub enum Origin {
80    /// The current trap action was inherited from the previous process that
81    /// `exec`ed the shell.
82    ///
83    /// This is the default value.
84    #[default]
85    Inherited,
86
87    /// The current trap action was set by the shell when entering a subshell.
88    Subshell,
89
90    /// The current trap action was set by the user.
91    ///
92    /// The location indicates the simple command that invoked the trap built-in
93    /// that set the current action.
94    User(Location),
95}
96
97/// State of the trap action for a condition
98#[derive(Clone, Debug, Default, Eq, PartialEq)]
99pub struct TrapState {
100    /// Action taken when the condition is met
101    pub action: Action,
102
103    /// Origin of the current action
104    pub origin: Origin,
105
106    /// True iff a signal specified by the condition has been caught and the
107    /// action command has not yet executed.
108    pub pending: bool,
109}
110
111impl TrapState {
112    fn from_initial_disposition(disposition: Disposition) -> Self {
113        let action = match disposition {
114            Disposition::Default => Action::Default,
115            Disposition::Ignore => Action::Ignore,
116            // The Rust runtime installs a handler for SIGSEGV and SIGBUS before
117            // the shell is initialized. We should treat these signals as if
118            // they have the default disposition.
119            // Disposition::Catch => panic!("initial disposition cannot be `Catch`"),
120            Disposition::Catch => Action::Default,
121        };
122        TrapState {
123            action,
124            origin: Origin::Inherited,
125            pending: false,
126        }
127    }
128}
129
130/// Option for [`GrandState::enter_subshell`]
131#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub enum EnterSubshellOption {
133    /// Keeps the current internal disposition configuration.
134    KeepInternalDisposition,
135    /// Resets the internal disposition configuration to the default.
136    ClearInternalDisposition,
137    /// Resets the internal disposition configuration to the default and sets the
138    /// signal disposition to `Ignore`.
139    Ignore,
140}
141
142/// Whole configuration and state for a trap condition
143#[derive(Clone, Debug)]
144pub struct GrandState {
145    /// Current trap state
146    current_state: TrapState,
147
148    /// Trap state that was effective in the parent environment
149    parent_state: Option<TrapState>,
150
151    /// Current internal disposition
152    ///
153    /// The internal disposition is the signal disposition that is set by the
154    /// shell itself, not by the user. This is used to handle some specific
155    /// signals like `SIGCHLD` and `SIGTSTP`. When the user sets a trap for
156    /// these signals, the actual signal disposition registered in the system
157    /// is computed as the maximum of the user-defined disposition and the
158    /// internal disposition.
159    internal_disposition: Disposition,
160}
161
162impl GrandState {
163    /// Returns the current trap state.
164    #[inline]
165    #[must_use]
166    pub fn current_state(&self) -> &TrapState {
167        &self.current_state
168    }
169
170    /// Returns the parent trap state.
171    ///
172    /// This is the trap state that was effective in the parent environment
173    /// before the current environment was created as a subshell of the parent.
174    /// Returns `None` if the current environment is not a subshell or the parent
175    /// state was [cleared](Self::clear_parent_state).
176    #[inline]
177    #[must_use]
178    pub fn parent_state(&self) -> Option<&TrapState> {
179        self.parent_state.as_ref()
180    }
181
182    /// Clears the parent trap state.
183    pub fn clear_parent_state(&mut self) {
184        self.parent_state = None;
185    }
186
187    /// Inserts a new entry if the entry is vacant.
188    ///
189    /// If the condition is a signal, the new entry is initialized with the
190    /// current signal disposition obtained from the system.
191    pub fn insert_from_system_if_vacant<'a, S: SignalSystem>(
192        system: &S,
193        entry: Entry<'a, Condition, GrandState>,
194    ) -> Result<&'a GrandState, Errno> {
195        match entry {
196            Entry::Vacant(vacant) => {
197                let disposition = match *vacant.key() {
198                    Condition::Signal(signal) => system.get_disposition(signal)?,
199                    Condition::Exit => Disposition::Default,
200                };
201                let state = GrandState {
202                    current_state: TrapState::from_initial_disposition(disposition),
203                    parent_state: None,
204                    internal_disposition: Disposition::Default,
205                };
206                Ok(vacant.insert(state))
207            }
208
209            Entry::Occupied(occupied) => Ok(occupied.into_mut()),
210        }
211    }
212
213    /// Updates the entry with the new action.
214    pub fn set_action<S: SignalSystem>(
215        system: &mut S,
216        entry: Entry<Condition, GrandState>,
217        action: Action,
218        origin: Location,
219        override_ignore: bool,
220    ) -> Result<(), SetActionError> {
221        let cond = *entry.key();
222        let disposition = (&action).into();
223        let new_state = TrapState {
224            action,
225            origin: Origin::User(origin),
226            pending: false,
227        };
228
229        match entry {
230            Entry::Vacant(vacant) => {
231                if let Condition::Signal(signal) = cond {
232                    if !override_ignore {
233                        let initial_disposition =
234                            system.set_disposition(signal, Disposition::Ignore)?;
235                        if initial_disposition == Disposition::Ignore {
236                            vacant.insert(GrandState {
237                                current_state: TrapState::from_initial_disposition(
238                                    initial_disposition,
239                                ),
240                                parent_state: None,
241                                internal_disposition: Disposition::Default,
242                            });
243                            return Err(SetActionError::InitiallyIgnored);
244                        }
245                    }
246
247                    if override_ignore || disposition != Disposition::Ignore {
248                        system.set_disposition(signal, disposition)?;
249                    }
250                }
251
252                vacant.insert(GrandState {
253                    current_state: new_state,
254                    parent_state: None,
255                    internal_disposition: Disposition::Default,
256                });
257            }
258
259            Entry::Occupied(mut occupied) => {
260                let state = occupied.get_mut();
261                if !override_ignore
262                    && state.current_state.action == Action::Ignore
263                    && state.current_state.origin == Origin::Inherited
264                {
265                    return Err(SetActionError::InitiallyIgnored);
266                }
267
268                if let Condition::Signal(signal) = cond {
269                    let internal = state.internal_disposition;
270                    let old_disposition = internal.max((&state.current_state.action).into());
271                    let new_disposition = internal.max(disposition);
272                    if old_disposition != new_disposition {
273                        system.set_disposition(signal, new_disposition)?;
274                    }
275                }
276
277                state.current_state = new_state;
278            }
279        }
280
281        Ok(())
282    }
283
284    /// Returns the current internal disposition.
285    #[must_use]
286    pub fn internal_disposition(&self) -> Disposition {
287        self.internal_disposition
288    }
289
290    /// Sets the internal disposition.
291    ///
292    /// The condition of the given entry must be a signal, or this function
293    /// panics.
294    pub fn set_internal_disposition<S: SignalSystem>(
295        system: &mut S,
296        entry: Entry<Condition, GrandState>,
297        disposition: Disposition,
298    ) -> Result<(), Errno> {
299        let signal = match *entry.key() {
300            Condition::Signal(signal) => signal,
301            Condition::Exit => panic!("exit condition cannot have an internal disposition"),
302        };
303
304        match entry {
305            Entry::Vacant(_) if disposition == Disposition::Default => (),
306
307            Entry::Vacant(vacant) => {
308                let initial_disposition = system.set_disposition(signal, disposition)?;
309                vacant.insert(GrandState {
310                    current_state: TrapState::from_initial_disposition(initial_disposition),
311                    parent_state: None,
312                    internal_disposition: disposition,
313                });
314            }
315
316            Entry::Occupied(mut occupied) => {
317                let state = occupied.get_mut();
318                let setting = (&state.current_state.action).into();
319                let old_disposition = state.internal_disposition.max(setting);
320                let new_disposition = disposition.max(setting);
321                if old_disposition != new_disposition {
322                    system.set_disposition(signal, new_disposition)?;
323                }
324                state.internal_disposition = disposition;
325            }
326        }
327
328        Ok(())
329    }
330
331    /// Updates the trap states and gets ready for executing the body a
332    /// subshell.
333    ///
334    /// If the current state has a user-specified command
335    /// (`Action::Command(_)`), it is saved in the parent state and reset to the
336    /// default. Additionally, the signal disposition is updated depending on the
337    /// `option`.
338    pub fn enter_subshell<S: SignalSystem>(
339        &mut self,
340        system: &mut S,
341        cond: Condition,
342        option: EnterSubshellOption,
343    ) -> Result<(), Errno> {
344        let old_setting = (&self.current_state.action).into();
345        let old_disposition = self.internal_disposition.max(old_setting);
346
347        if matches!(self.current_state.action, Action::Command(_)) {
348            self.parent_state = Some(std::mem::replace(
349                &mut self.current_state,
350                TrapState {
351                    action: Action::Default,
352                    origin: Origin::Subshell,
353                    pending: false,
354                },
355            ));
356        }
357        if option == EnterSubshellOption::Ignore {
358            self.current_state.action = Action::Ignore;
359        }
360
361        let new_setting = (&self.current_state.action).into();
362        let new_disposition = match option {
363            EnterSubshellOption::KeepInternalDisposition => {
364                self.internal_disposition.max(new_setting)
365            }
366            EnterSubshellOption::ClearInternalDisposition => new_setting,
367            EnterSubshellOption::Ignore => Disposition::Ignore,
368        };
369        if old_disposition != new_disposition {
370            if let Condition::Signal(signal) = cond {
371                system.set_disposition(signal, new_disposition)?;
372            }
373        }
374        self.internal_disposition = match option {
375            EnterSubshellOption::KeepInternalDisposition => self.internal_disposition,
376            EnterSubshellOption::ClearInternalDisposition | EnterSubshellOption::Ignore => {
377                Disposition::Default
378            }
379        };
380        Ok(())
381    }
382
383    /// Sets the disposition to `Ignore` for the given signal condition.
384    ///
385    /// This function creates a new `GrandState` entry with the current state
386    /// having `Action::Ignore`. If the signal disposition is default, the shell
387    /// sets the signal disposition to `Ignore` and the origin to `Subshell`. If
388    /// the signal disposition is already `Ignore`, the origin is set to
389    /// `Inherited` to disallow changing the action in a non-interactive shell.
390    ///
391    /// You should call this function in place of [`Self::enter_subshell`] if
392    /// there is no entry for the condition yet.
393    ///
394    /// This function panics if the condition is not a signal.
395    pub fn ignore<S: SignalSystem>(
396        system: &mut S,
397        vacant: VacantEntry<Condition, GrandState>,
398    ) -> Result<(), Errno> {
399        let signal = match *vacant.key() {
400            Condition::Signal(signal) => signal,
401            Condition::Exit => panic!("exit condition cannot be ignored"),
402        };
403        let initial_disposition = system.set_disposition(signal, Disposition::Ignore)?;
404        let origin = match initial_disposition {
405            Disposition::Default => Origin::Subshell,
406            Disposition::Ignore => Origin::Inherited,
407            // The Rust runtime installs a handler for SIGSEGV and SIGBUS before
408            // the shell is initialized. We should treat these signals as if
409            // they have the default disposition (though the ignore function is
410            // normally not called for these signals).
411            // Disposition::Catch => panic!("initial disposition cannot be `Catch`"),
412            Disposition::Catch => Origin::Subshell,
413        };
414        vacant.insert(GrandState {
415            current_state: TrapState {
416                action: Action::Ignore,
417                origin,
418                pending: false,
419            },
420            parent_state: None,
421            internal_disposition: Disposition::Default,
422        });
423        Ok(())
424    }
425
426    /// Marks this signal as caught.
427    pub fn mark_as_caught(&mut self) {
428        self.current_state.pending = true;
429    }
430
431    /// Clears the mark of this signal being caught and returns the trap state.
432    pub fn handle_if_caught(&mut self) -> Option<&TrapState> {
433        if self.current_state.pending {
434            self.current_state.pending = false;
435            Some(&self.current_state)
436        } else {
437            None
438        }
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::super::tests::DummySystem;
445    use super::*;
446    use crate::system::Signals;
447    use crate::system::r#virtual::{
448        SIGABRT, SIGALRM, SIGBUS, SIGCHLD, SIGCONT, SIGFPE, SIGHUP, SIGILL, SIGINT, SIGIOT,
449        SIGKILL, SIGPIPE, SIGPROF, SIGQUIT, SIGSEGV, SIGSTOP, SIGSYS, SIGTERM, SIGTRAP, SIGTSTP,
450        SIGTTIN, SIGTTOU, SIGURG, SIGUSR1, SIGUSR2, SIGVTALRM, SIGWINCH, SIGXCPU, SIGXFSZ,
451    };
452    use assert_matches::assert_matches;
453    use std::borrow::Cow;
454    use std::{collections::BTreeMap, ops::RangeInclusive};
455
456    struct UnusedSystem;
457
458    impl Signals for UnusedSystem {
459        const SIGABRT: crate::signal::Number = SIGABRT;
460        const SIGALRM: crate::signal::Number = SIGALRM;
461        const SIGBUS: crate::signal::Number = SIGBUS;
462        const SIGCHLD: crate::signal::Number = SIGCHLD;
463        const SIGCLD: Option<crate::signal::Number> = None;
464        const SIGCONT: crate::signal::Number = SIGCONT;
465        const SIGEMT: Option<crate::signal::Number> = None;
466        const SIGFPE: crate::signal::Number = SIGFPE;
467        const SIGHUP: crate::signal::Number = SIGHUP;
468        const SIGILL: crate::signal::Number = SIGILL;
469        const SIGINFO: Option<crate::signal::Number> = None;
470        const SIGINT: crate::signal::Number = SIGINT;
471        const SIGIO: Option<crate::signal::Number> = None;
472        const SIGIOT: crate::signal::Number = SIGIOT;
473        const SIGKILL: crate::signal::Number = SIGKILL;
474        const SIGLOST: Option<crate::signal::Number> = None;
475        const SIGPIPE: crate::signal::Number = SIGPIPE;
476        const SIGPOLL: Option<crate::signal::Number> = None;
477        const SIGPROF: crate::signal::Number = SIGPROF;
478        const SIGPWR: Option<crate::signal::Number> = None;
479        const SIGQUIT: crate::signal::Number = SIGQUIT;
480        const SIGSEGV: crate::signal::Number = SIGSEGV;
481        const SIGSTKFLT: Option<crate::signal::Number> = None;
482        const SIGSTOP: crate::signal::Number = SIGSTOP;
483        const SIGSYS: crate::signal::Number = SIGSYS;
484        const SIGTERM: crate::signal::Number = SIGTERM;
485        const SIGTHR: Option<crate::signal::Number> = None;
486        const SIGTRAP: crate::signal::Number = SIGTRAP;
487        const SIGTSTP: crate::signal::Number = SIGTSTP;
488        const SIGTTIN: crate::signal::Number = SIGTTIN;
489        const SIGTTOU: crate::signal::Number = SIGTTOU;
490        const SIGURG: crate::signal::Number = SIGURG;
491        const SIGUSR1: crate::signal::Number = SIGUSR1;
492        const SIGUSR2: crate::signal::Number = SIGUSR2;
493        const SIGVTALRM: crate::signal::Number = SIGVTALRM;
494        const SIGWINCH: crate::signal::Number = SIGWINCH;
495        const SIGXCPU: crate::signal::Number = SIGXCPU;
496        const SIGXFSZ: crate::signal::Number = SIGXFSZ;
497
498        fn sigrt_range(&self) -> Option<RangeInclusive<crate::signal::Number>> {
499            unreachable!()
500        }
501        fn iter_sigrt(&self) -> impl DoubleEndedIterator<Item = crate::signal::Number> + use<> {
502            unreachable!() as std::iter::Empty<_>
503        }
504        fn sig2str<S: Into<crate::signal::RawNumber>>(
505            &self,
506            signal: S,
507        ) -> Option<Cow<'static, str>> {
508            unreachable!("sig2str({:?})", signal.into())
509        }
510        fn str2sig(&self, name: &str) -> Option<crate::signal::Number> {
511            unreachable!("str2sig({name:?})")
512        }
513        fn validate_signal(
514            &self,
515            number: crate::signal::RawNumber,
516        ) -> Option<(crate::signal::Name, crate::signal::Number)> {
517            unreachable!("validate_signal({number:?})")
518        }
519        fn signal_name_from_number(&self, number: crate::signal::Number) -> crate::signal::Name {
520            unreachable!("signal_name_from_number({number:?})")
521        }
522        fn signal_number_from_name(
523            &self,
524            name: crate::signal::Name,
525        ) -> Option<crate::signal::Number> {
526            unreachable!("signal_number_from_name({name:?})")
527        }
528    }
529
530    impl SignalSystem for UnusedSystem {
531        fn get_disposition(&self, signal: crate::signal::Number) -> Result<Disposition, Errno> {
532            unreachable!("get_disposition({signal})")
533        }
534        fn set_disposition(
535            &mut self,
536            signal: crate::signal::Number,
537            disposition: Disposition,
538        ) -> Result<Disposition, Errno> {
539            unreachable!("set_disposition({signal}, {disposition:?})")
540        }
541    }
542
543    #[test]
544    fn insertion_with_default_inherited_disposition() {
545        let system = DummySystem::default();
546        let mut map = BTreeMap::new();
547        let entry = map.entry(SIGCHLD.into());
548        let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
549        assert_eq!(
550            state.current_state(),
551            &TrapState {
552                action: Action::Default,
553                origin: Origin::Inherited,
554                pending: false,
555            }
556        );
557        assert_eq!(state.parent_state(), None);
558        assert_eq!(state.internal_disposition(), Disposition::Default);
559    }
560
561    #[test]
562    fn insertion_with_inherited_disposition_of_ignore() {
563        let mut system = DummySystem::default();
564        system.0.insert(SIGCHLD, Disposition::Ignore);
565        let mut map = BTreeMap::new();
566        let entry = map.entry(SIGCHLD.into());
567        let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
568        assert_eq!(
569            state.current_state(),
570            &TrapState {
571                action: Action::Ignore,
572                origin: Origin::Inherited,
573                pending: false,
574            }
575        );
576        assert_eq!(state.parent_state(), None);
577        assert_eq!(state.internal_disposition(), Disposition::Default);
578    }
579
580    #[test]
581    fn insertion_with_occupied_entry() {
582        let mut system = DummySystem::default();
583        let mut map = BTreeMap::new();
584        let entry = map.entry(SIGCHLD.into());
585        let origin = Location::dummy("origin");
586        let action = Action::Command("echo".into());
587        let _ = GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
588
589        // If the entry is occupied, the function should return the existing
590        // state without accessing the system.
591        let entry = map.entry(SIGCHLD.into());
592        let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
593        assert_eq!(
594            state.current_state(),
595            &TrapState {
596                action,
597                origin: Origin::User(origin),
598                pending: false,
599            }
600        );
601        assert_eq!(state.parent_state(), None);
602        assert_eq!(state.internal_disposition(), Disposition::Default);
603    }
604
605    #[test]
606    fn insertion_with_non_signal_condition() {
607        let mut map = BTreeMap::new();
608        let entry = map.entry(Condition::Exit);
609        let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
610        assert_eq!(
611            state.current_state(),
612            &TrapState {
613                action: Action::Default,
614                origin: Origin::Inherited,
615                pending: false,
616            }
617        );
618        assert_eq!(state.parent_state(), None);
619        assert_eq!(state.internal_disposition(), Disposition::Default);
620    }
621
622    #[test]
623    fn setting_trap_to_ignore_without_override_ignore() {
624        let mut system = DummySystem::default();
625        let mut map = BTreeMap::new();
626        let entry = map.entry(SIGCHLD.into());
627        let origin = Location::dummy("origin");
628
629        let result =
630            GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
631        assert_eq!(result, Ok(()));
632        assert_eq!(
633            map[&SIGCHLD.into()].current_state(),
634            &TrapState {
635                action: Action::Ignore,
636                origin: Origin::User(origin),
637                pending: false
638            }
639        );
640        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
641        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
642    }
643
644    #[test]
645    fn setting_trap_to_ignore_with_override_ignore() {
646        let mut system = DummySystem::default();
647        let mut map = BTreeMap::new();
648        let entry = map.entry(SIGCHLD.into());
649        let origin = Location::dummy("origin");
650
651        let result =
652            GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
653        assert_eq!(result, Ok(()));
654        assert_eq!(
655            map[&SIGCHLD.into()].current_state(),
656            &TrapState {
657                action: Action::Ignore,
658                origin: Origin::User(origin),
659                pending: false
660            }
661        );
662        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
663        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
664    }
665
666    #[test]
667    fn setting_trap_to_command() {
668        let mut system = DummySystem::default();
669        let mut map = BTreeMap::new();
670        let entry = map.entry(SIGCHLD.into());
671        let action = Action::Command("echo".into());
672        let origin = Location::dummy("origin");
673
674        let result =
675            GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
676        assert_eq!(result, Ok(()));
677        assert_eq!(
678            map[&SIGCHLD.into()].current_state(),
679            &TrapState {
680                action,
681                origin: Origin::User(origin),
682                pending: false
683            }
684        );
685        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
686        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
687    }
688
689    #[test]
690    fn setting_trap_to_default() {
691        let mut system = DummySystem::default();
692        let mut map = BTreeMap::new();
693        let entry = map.entry(SIGCHLD.into());
694        let origin = Location::dummy("foo");
695        GrandState::set_action(&mut system, entry, Action::Ignore, origin, false).unwrap();
696
697        let entry = map.entry(SIGCHLD.into());
698        let origin = Location::dummy("bar");
699        let result =
700            GrandState::set_action(&mut system, entry, Action::Default, origin.clone(), false);
701        assert_eq!(result, Ok(()));
702        assert_eq!(
703            map[&SIGCHLD.into()].current_state(),
704            &TrapState {
705                action: Action::Default,
706                origin: Origin::User(origin),
707                pending: false
708            }
709        );
710        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
711        assert_eq!(system.0[&SIGCHLD], Disposition::Default);
712    }
713
714    #[test]
715    fn resetting_trap_from_ignore_no_override() {
716        let mut system = DummySystem::default();
717        system.0.insert(SIGCHLD, Disposition::Ignore);
718        let mut map = BTreeMap::new();
719        let entry = map.entry(SIGCHLD.into());
720        let origin = Location::dummy("foo");
721        let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
722        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
723
724        // Idempotence
725        let entry = map.entry(SIGCHLD.into());
726        let origin = Location::dummy("bar");
727        let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
728        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
729
730        assert_eq!(
731            map[&SIGCHLD.into()].current_state(),
732            &TrapState {
733                action: Action::Ignore,
734                origin: Origin::Inherited,
735                pending: false
736            }
737        );
738        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
739        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
740    }
741
742    #[test]
743    fn resetting_trap_from_ignore_override() {
744        let mut system = DummySystem::default();
745        system.0.insert(SIGCHLD, Disposition::Ignore);
746        let mut map = BTreeMap::new();
747        let entry = map.entry(SIGCHLD.into());
748        let origin = Location::dummy("origin");
749        let result =
750            GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
751        assert_eq!(result, Ok(()));
752        assert_eq!(
753            map[&SIGCHLD.into()].current_state(),
754            &TrapState {
755                action: Action::Ignore,
756                origin: Origin::User(origin),
757                pending: false
758            }
759        );
760        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
761        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
762    }
763
764    #[test]
765    fn internal_disposition_ignore() {
766        let mut system = DummySystem::default();
767        let mut map = BTreeMap::new();
768        let entry = map.entry(SIGCHLD.into());
769
770        let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
771        assert_eq!(result, Ok(()));
772        assert_eq!(
773            map[&SIGCHLD.into()].internal_disposition(),
774            Disposition::Ignore
775        );
776        assert_eq!(
777            map[&SIGCHLD.into()].current_state(),
778            &TrapState {
779                action: Action::Default,
780                origin: Origin::Inherited,
781                pending: false
782            }
783        );
784        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
785        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
786    }
787
788    #[test]
789    fn internal_disposition_catch() {
790        let mut system = DummySystem::default();
791        let mut map = BTreeMap::new();
792        let entry = map.entry(SIGCHLD.into());
793
794        let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
795        assert_eq!(result, Ok(()));
796        assert_eq!(
797            map[&SIGCHLD.into()].internal_disposition(),
798            Disposition::Catch
799        );
800        assert_eq!(
801            map[&SIGCHLD.into()].current_state(),
802            &TrapState {
803                action: Action::Default,
804                origin: Origin::Inherited,
805                pending: false
806            }
807        );
808        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
809        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
810    }
811
812    #[test]
813    fn action_ignore_and_internal_disposition_catch() {
814        let mut system = DummySystem::default();
815        let mut map = BTreeMap::new();
816        let entry = map.entry(SIGCHLD.into());
817        let origin = Location::dummy("origin");
818        let _ = GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
819        let entry = map.entry(SIGCHLD.into());
820
821        let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
822        assert_eq!(result, Ok(()));
823        assert_eq!(
824            map[&SIGCHLD.into()].internal_disposition(),
825            Disposition::Catch
826        );
827        assert_eq!(
828            map[&SIGCHLD.into()].current_state(),
829            &TrapState {
830                action: Action::Ignore,
831                origin: Origin::User(origin),
832                pending: false
833            }
834        );
835        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
836        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
837    }
838
839    #[test]
840    fn action_catch_and_internal_disposition_ignore() {
841        let mut system = DummySystem::default();
842        let mut map = BTreeMap::new();
843        let entry = map.entry(SIGCHLD.into());
844        let origin = Location::dummy("origin");
845        let action = Action::Command("echo".into());
846        let _ = GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
847        let entry = map.entry(SIGCHLD.into());
848
849        let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
850        assert_eq!(result, Ok(()));
851        assert_eq!(
852            map[&SIGCHLD.into()].internal_disposition(),
853            Disposition::Ignore
854        );
855        assert_eq!(
856            map[&SIGCHLD.into()].current_state(),
857            &TrapState {
858                action,
859                origin: Origin::User(origin),
860                pending: false
861            }
862        );
863        assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
864        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
865    }
866
867    #[test]
868    fn set_internal_disposition_for_initially_defaulted_signal_then_allow_override() {
869        let mut system = DummySystem::default();
870        let mut map = BTreeMap::new();
871        let entry = map.entry(SIGTTOU.into());
872        let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
873        let entry = map.entry(SIGTTOU.into());
874        let origin = Location::dummy("origin");
875        let action = Action::Command("echo".into());
876
877        let result =
878            GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
879        assert_eq!(result, Ok(()));
880        assert_eq!(
881            map[&SIGTTOU.into()].internal_disposition(),
882            Disposition::Ignore
883        );
884        assert_eq!(
885            map[&SIGTTOU.into()].current_state(),
886            &TrapState {
887                action,
888                origin: Origin::User(origin),
889                pending: false
890            }
891        );
892        assert_eq!(map[&SIGTTOU.into()].parent_state(), None);
893        assert_eq!(system.0[&SIGTTOU], Disposition::Catch);
894    }
895
896    #[test]
897    fn set_internal_disposition_for_initially_ignored_signal_then_reject_override() {
898        let mut system = DummySystem::default();
899        system.0.insert(SIGTTOU, Disposition::Ignore);
900        let mut map = BTreeMap::new();
901        let cond = SIGTTOU.into();
902        let entry = map.entry(cond);
903        let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
904        let entry = map.entry(cond);
905        let origin = Location::dummy("origin");
906        let action = Action::Command("echo".into());
907
908        let result = GrandState::set_action(&mut system, entry, action, origin, false);
909        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
910        assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
911        assert_eq!(
912            map[&cond].current_state(),
913            &TrapState {
914                action: Action::Ignore,
915                origin: Origin::Inherited,
916                pending: false
917            }
918        );
919        assert_eq!(map[&cond].parent_state(), None);
920        assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
921    }
922
923    #[test]
924    fn enter_subshell_with_internal_disposition_keeping_internal_disposition() {
925        let mut system = DummySystem::default();
926        let mut map = BTreeMap::new();
927        let cond = SIGCHLD.into();
928        GrandState::set_internal_disposition(&mut system, map.entry(cond), Disposition::Catch)
929            .unwrap();
930
931        let result = map.get_mut(&cond).unwrap().enter_subshell(
932            &mut system,
933            cond,
934            EnterSubshellOption::KeepInternalDisposition,
935        );
936        assert_eq!(result, Ok(()));
937        assert_eq!(map[&cond].internal_disposition(), Disposition::Catch);
938        assert_eq!(
939            map[&cond].current_state(),
940            &TrapState {
941                action: Action::Default,
942                origin: Origin::Inherited,
943                pending: false
944            }
945        );
946        assert_eq!(map[&cond].parent_state(), None);
947        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
948    }
949
950    #[test]
951    fn enter_subshell_with_internal_disposition_clearing_internal_disposition() {
952        let mut system = DummySystem::default();
953        let mut map = BTreeMap::new();
954        let cond = SIGCHLD.into();
955        let entry = map.entry(cond);
956        GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
957
958        let result = map.get_mut(&cond).unwrap().enter_subshell(
959            &mut system,
960            cond,
961            EnterSubshellOption::ClearInternalDisposition,
962        );
963        assert_eq!(result, Ok(()));
964        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
965        assert_eq!(
966            map[&cond].current_state(),
967            &TrapState {
968                action: Action::Default,
969                origin: Origin::Inherited,
970                pending: false
971            }
972        );
973        assert_eq!(map[&cond].parent_state(), None);
974        assert_eq!(system.0[&SIGCHLD], Disposition::Default);
975    }
976
977    #[test]
978    fn enter_subshell_with_ignore_and_no_internal_disposition() {
979        let mut system = DummySystem::default();
980        let mut map = BTreeMap::new();
981        let cond = SIGCHLD.into();
982        let entry = map.entry(cond);
983        let origin = Location::dummy("foo");
984        GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
985
986        let result = map.get_mut(&cond).unwrap().enter_subshell(
987            &mut system,
988            cond,
989            EnterSubshellOption::KeepInternalDisposition,
990        );
991        assert_eq!(result, Ok(()));
992        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
993        assert_eq!(
994            map[&cond].current_state(),
995            &TrapState {
996                action: Action::Ignore,
997                origin: Origin::User(origin),
998                pending: false
999            }
1000        );
1001        assert_eq!(map[&cond].parent_state(), None);
1002        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
1003    }
1004
1005    #[test]
1006    fn enter_subshell_with_ignore_clearing_internal_disposition() {
1007        let mut system = DummySystem::default();
1008        let mut map = BTreeMap::new();
1009        let cond = SIGCHLD.into();
1010        let entry = map.entry(cond);
1011        let origin = Location::dummy("foo");
1012        GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
1013        let entry = map.entry(cond);
1014        GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
1015
1016        let result = map.get_mut(&cond).unwrap().enter_subshell(
1017            &mut system,
1018            cond,
1019            EnterSubshellOption::ClearInternalDisposition,
1020        );
1021        assert_eq!(result, Ok(()));
1022        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1023        assert_eq!(
1024            map[&cond].current_state(),
1025            &TrapState {
1026                action: Action::Ignore,
1027                origin: Origin::User(origin),
1028                pending: false
1029            }
1030        );
1031        assert_eq!(map[&cond].parent_state(), None);
1032        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
1033    }
1034
1035    #[test]
1036    fn enter_subshell_with_command_and_no_internal_disposition() {
1037        let mut system = DummySystem::default();
1038        let mut map = BTreeMap::new();
1039        let cond = SIGCHLD.into();
1040        let entry = map.entry(cond);
1041        let origin = Location::dummy("foo");
1042        let action = Action::Command("echo".into());
1043        GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1044
1045        let result = map.get_mut(&cond).unwrap().enter_subshell(
1046            &mut system,
1047            cond,
1048            EnterSubshellOption::ClearInternalDisposition,
1049        );
1050        assert_eq!(result, Ok(()));
1051        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1052        assert_eq!(
1053            map[&cond].current_state(),
1054            &TrapState {
1055                action: Action::Default,
1056                origin: Origin::Subshell,
1057                pending: false
1058            }
1059        );
1060        assert_eq!(
1061            map[&cond].parent_state(),
1062            Some(&TrapState {
1063                action,
1064                origin: Origin::User(origin),
1065                pending: false
1066            })
1067        );
1068        assert_eq!(system.0[&SIGCHLD], Disposition::Default);
1069    }
1070
1071    #[test]
1072    fn enter_subshell_with_command_keeping_internal_disposition() {
1073        let mut system = DummySystem::default();
1074        let mut map = BTreeMap::new();
1075        let cond = SIGTSTP.into();
1076        let entry = map.entry(cond);
1077        let origin = Location::dummy("foo");
1078        let action = Action::Command("echo".into());
1079        GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1080        let entry = map.entry(cond);
1081        GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
1082
1083        let result = map.get_mut(&cond).unwrap().enter_subshell(
1084            &mut system,
1085            cond,
1086            EnterSubshellOption::KeepInternalDisposition,
1087        );
1088        assert_eq!(result, Ok(()));
1089        assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
1090        assert_eq!(
1091            map[&cond].current_state(),
1092            &TrapState {
1093                action: Action::Default,
1094                origin: Origin::Subshell,
1095                pending: false
1096            }
1097        );
1098        assert_eq!(
1099            map[&cond].parent_state(),
1100            Some(&TrapState {
1101                action,
1102                origin: Origin::User(origin),
1103                pending: false
1104            })
1105        );
1106        assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
1107    }
1108
1109    #[test]
1110    fn enter_subshell_with_command_clearing_internal_disposition() {
1111        let mut system = DummySystem::default();
1112        let mut map = BTreeMap::new();
1113        let cond = SIGTSTP.into();
1114        let entry = map.entry(cond);
1115        let origin = Location::dummy("foo");
1116        let action = Action::Command("echo".into());
1117        GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1118        let entry = map.entry(cond);
1119        GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
1120
1121        let result = map.get_mut(&cond).unwrap().enter_subshell(
1122            &mut system,
1123            cond,
1124            EnterSubshellOption::ClearInternalDisposition,
1125        );
1126        assert_eq!(result, Ok(()));
1127        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1128        assert_eq!(
1129            map[&cond].current_state(),
1130            &TrapState {
1131                action: Action::Default,
1132                origin: Origin::Subshell,
1133                pending: false
1134            }
1135        );
1136        assert_eq!(
1137            map[&cond].parent_state(),
1138            Some(&TrapState {
1139                action,
1140                origin: Origin::User(origin),
1141                pending: false
1142            })
1143        );
1144        assert_eq!(system.0[&SIGTSTP], Disposition::Default);
1145    }
1146
1147    #[test]
1148    fn enter_subshell_with_command_ignoring() {
1149        let mut system = DummySystem::default();
1150        let mut map = BTreeMap::new();
1151        let cond = SIGQUIT.into();
1152        let entry = map.entry(cond);
1153        let origin = Location::dummy("foo");
1154        let action = Action::Command("echo".into());
1155        GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1156
1157        let result = map.get_mut(&cond).unwrap().enter_subshell(
1158            &mut system,
1159            cond,
1160            EnterSubshellOption::Ignore,
1161        );
1162        assert_eq!(result, Ok(()));
1163        assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1164        assert_eq!(
1165            map[&cond].current_state(),
1166            &TrapState {
1167                action: Action::Ignore,
1168                origin: Origin::Subshell,
1169                pending: false
1170            }
1171        );
1172        assert_eq!(
1173            map[&cond].parent_state(),
1174            Some(&TrapState {
1175                action,
1176                origin: Origin::User(origin),
1177                pending: false
1178            })
1179        );
1180        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1181    }
1182
1183    #[test]
1184    fn ignoring_initially_defaulted_signal() {
1185        let mut system = DummySystem::default();
1186        let mut map = BTreeMap::new();
1187        let cond = SIGQUIT.into();
1188        let entry = map.entry(cond);
1189        let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
1190
1191        let result = GrandState::ignore(&mut system, vacant);
1192        assert_eq!(result, Ok(()));
1193        assert_eq!(
1194            map[&cond].current_state(),
1195            &TrapState {
1196                action: Action::Ignore,
1197                origin: Origin::Subshell,
1198                pending: false
1199            }
1200        );
1201        assert_eq!(map[&cond].parent_state(), None);
1202        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1203
1204        let entry = map.entry(cond);
1205        let origin = Location::dummy("foo");
1206        let action = Action::Command("echo".into());
1207        let result = GrandState::set_action(&mut system, entry, action, origin, false);
1208        assert_eq!(result, Ok(()));
1209    }
1210
1211    #[test]
1212    fn ignoring_initially_ignored_signal() {
1213        let mut system = DummySystem::default();
1214        system.0.insert(SIGQUIT, Disposition::Ignore);
1215        let mut map = BTreeMap::new();
1216        let cond = SIGQUIT.into();
1217        let entry = map.entry(cond);
1218        let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
1219
1220        let result = GrandState::ignore(&mut system, vacant);
1221        assert_eq!(result, Ok(()));
1222        assert_eq!(
1223            map[&cond].current_state(),
1224            &TrapState {
1225                action: Action::Ignore,
1226                origin: Origin::Inherited,
1227                pending: false
1228            }
1229        );
1230        assert_eq!(map[&cond].parent_state(), None);
1231        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1232
1233        let entry = map.entry(cond);
1234        let origin = Location::dummy("foo");
1235        let action = Action::Command("echo".into());
1236        let result = GrandState::set_action(&mut system, entry, action, origin, false);
1237        assert_eq!(result, Err(SetActionError::InitiallyIgnored));
1238    }
1239
1240    #[test]
1241    fn clearing_parent_setting() {
1242        let mut system = DummySystem::default();
1243        let mut map = BTreeMap::new();
1244        let cond = SIGCHLD.into();
1245        let entry = map.entry(cond);
1246        let origin = Location::dummy("foo");
1247        let action = Action::Command("echo".into());
1248        GrandState::set_action(&mut system, entry, action, origin, false).unwrap();
1249        let state = map.get_mut(&cond).unwrap();
1250        state
1251            .enter_subshell(
1252                &mut system,
1253                cond,
1254                EnterSubshellOption::ClearInternalDisposition,
1255            )
1256            .unwrap();
1257
1258        state.clear_parent_state();
1259        assert_eq!(
1260            state.current_state(),
1261            &TrapState {
1262                action: Action::Default,
1263                origin: Origin::Subshell,
1264                pending: false
1265            }
1266        );
1267        assert_eq!(state.parent_state(), None);
1268    }
1269
1270    #[test]
1271    fn marking_as_caught_and_handling() {
1272        let mut system = DummySystem::default();
1273        let mut map = BTreeMap::new();
1274        let cond = SIGUSR1.into();
1275        let entry = map.entry(cond);
1276        let origin = Location::dummy("foo");
1277        let action = Action::Command("echo".into());
1278        GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1279
1280        let state = &mut map.get_mut(&cond).unwrap();
1281        state.mark_as_caught();
1282        let expected_trap = TrapState {
1283            action,
1284            origin: Origin::User(origin),
1285            pending: true,
1286        };
1287        assert_eq!(state.current_state(), &expected_trap);
1288        assert_eq!(state.parent_state(), None);
1289
1290        let trap = state.handle_if_caught();
1291        let expected_trap = TrapState {
1292            pending: false,
1293            ..expected_trap
1294        };
1295        assert_eq!(trap, Some(&expected_trap));
1296
1297        let trap = state.handle_if_caught();
1298        assert_eq!(trap, None);
1299    }
1300}