yash_env/
trap.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2021 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Signal and other event handling settings.
18//!
19//! The trap is a mechanism of the shell that allows you to configure event
20//! handlers for specific situations. A [`TrapSet`] is a mapping from [`Condition`]s to
21//! [`Action`]s. When the mapping is modified, it updates the corresponding signal
22//! disposition in the underlying system through a [`SignalSystem`] implementor.
23//! Methods of `TrapSet` expect they are passed the same system instance in
24//! every call to keep it in a correct state.
25//!
26//! `TrapSet` manages two types of signal handling configurations. One is
27//! user-defined traps, which the user explicitly configures with the trap
28//! built-in. The other is internal dispositions, which the shell implicitly
29//! installs to the system to implement additional actions it needs to perform.
30//! `TrapSet` merges the two configurations into a single [`Disposition`] for
31//! each signal and sets it to the system.
32//!
33//! No signal disposition is involved for conditions other than signals, and the
34//! trap set serves only as a storage for action settings.
35
36mod cond;
37mod state;
38
39pub use self::cond::Condition;
40pub use self::state::{Action, Origin, SetActionError, TrapState};
41use self::state::{EnterSubshellOption, GrandState};
42use crate::Env;
43use crate::signal;
44use crate::source::Location;
45use crate::system::{Disposition, Errno};
46#[cfg(doc)]
47use crate::system::{SharedSystem, System};
48use std::collections::BTreeMap;
49use std::collections::btree_map::Entry;
50use std::pin::Pin;
51
52/// System interface for signal handling configuration
53pub trait SignalSystem {
54    /// Returns the name of a signal from its number.
55    #[must_use]
56    fn signal_name_from_number(&self, number: signal::Number) -> signal::Name;
57
58    /// Returns the signal number from its name.
59    ///
60    /// This function returns the signal number corresponding to the signal name
61    /// in the system. If the signal name is not supported, it returns `None`.
62    ///
63    /// Note that the `TrapSet` implementation assumes that the system supports
64    /// all the following signals:
65    ///
66    /// - `SIGCHLD`
67    /// - `SIGINT`
68    /// - `SIGTERM`
69    /// - `SIGQUIT`
70    /// - `SIGTSTP`
71    /// - `SIGTTIN`
72    /// - `SIGTTOU`
73    ///
74    /// If this method returns `None` for any of these signals, `TrapSet` will
75    /// panic.
76    #[must_use]
77    fn signal_number_from_name(&self, name: signal::Name) -> Option<signal::Number>;
78
79    /// Returns the current disposition for a signal.
80    ///
81    /// This function returns the current disposition for the specified signal like
82    /// [`set_disposition`](Self::set_disposition) does, but does not change the
83    /// disposition.
84    fn get_disposition(&self, signal: signal::Number) -> Result<Disposition, Errno>;
85
86    /// Sets how a signal is handled.
87    ///
88    /// This function updates the signal blocking mask and the disposition for
89    /// the specified signal, and returns the previous disposition.
90    fn set_disposition(
91        &mut self,
92        signal: signal::Number,
93        disposition: Disposition,
94    ) -> Result<Disposition, Errno>;
95}
96
97/// Iterator of trap actions configured in a [trap set](TrapSet).
98///
99/// [`TrapSet::iter`] returns this type of iterator.
100#[must_use]
101pub struct Iter<'a> {
102    inner: std::collections::btree_map::Iter<'a, Condition, GrandState>,
103}
104
105impl<'a> Iterator for Iter<'a> {
106    /// Type of items yielded by this iterator
107    ///
108    /// Each item is a tuple of a condition, the currently configured trap state,
109    /// and the parent state that was active before entering a subshell.
110    type Item = (&'a Condition, &'a TrapState, Option<&'a TrapState>);
111
112    fn next(&mut self) -> Option<(&'a Condition, &'a TrapState, Option<&'a TrapState>)> {
113        self.inner
114            .next()
115            .map(|(cond, state)| (cond, state.current_state(), state.parent_state()))
116    }
117}
118
119/// Collection of event handling settings.
120///
121/// See the [module documentation](self) for details.
122#[derive(Clone, Debug, Default)]
123pub struct TrapSet {
124    traps: BTreeMap<Condition, GrandState>,
125}
126
127impl TrapSet {
128    /// Returns the current and parent states for a condition.
129    ///
130    /// This function returns a pair of optional trap states. The first is the
131    /// currently configured trap action, and the second is the action set
132    /// before [`enter_subshell`](Self::enter_subshell) was called.
133    ///
134    /// The current state (the first return value) is `None` if the state is
135    /// unknown (because no trap action or internal disposition has been set for
136    /// the signal specified by the condition). To get the current signal
137    /// disposition in this case, use [`peek_state`](Self::peek_state).
138    ///
139    /// The parent state (the second return value) is `None` if the
140    /// shell environment has not [entered a subshell](Self::enter_subshell) or
141    /// a [trap action has been modified](Self::set_action) after entering the
142    /// subshell.
143    pub fn get_state<C: Into<Condition>>(
144        &self,
145        cond: C,
146    ) -> (Option<&TrapState>, Option<&TrapState>) {
147        self.get_state_impl(cond.into())
148    }
149
150    fn get_state_impl(&self, cond: Condition) -> (Option<&TrapState>, Option<&TrapState>) {
151        match self.traps.get(&cond) {
152            None => (None, None),
153            Some(state) => (Some(state.current_state()), state.parent_state()),
154        }
155    }
156
157    /// Returns the current state for a condition.
158    ///
159    /// This function returns the trap action for the specified condition that
160    /// is to be included in the output of the `trap` built-in.
161    ///
162    /// If the current state is not yet known (because no trap action or
163    /// internal disposition has been set for the signal specified by the
164    /// condition), it calls [`SignalSystem::get_disposition`] to get the
165    /// current signal disposition and saves it as the current state.
166    ///
167    /// If the shell environment has [entered a subshell](Self::enter_subshell)
168    /// and no [trap action has been modified](Self::set_action) after entering
169    /// the subshell, the state that was active before entering the subshell is
170    /// returned.
171    ///
172    /// See also [`get_state`](Self::get_state), which returns `None` if the
173    /// current state is not yet known.
174    pub fn peek_state<S: SignalSystem, C: Into<Condition>>(
175        &mut self,
176        system: &S,
177        cond: C,
178    ) -> Result<&TrapState, Errno> {
179        self.peek_state_impl(system, cond.into())
180    }
181
182    fn peek_state_impl<S: SignalSystem>(
183        &mut self,
184        system: &S,
185        cond: Condition,
186    ) -> Result<&TrapState, Errno> {
187        let entry = self.traps.entry(cond);
188        let state = GrandState::insert_from_system_if_vacant(system, entry)?;
189        Ok(state.parent_state().unwrap_or(state.current_state()))
190    }
191
192    /// Sets a trap action for a condition.
193    ///
194    /// If the condition is a signal, this function installs a signal handler to
195    /// the specified underlying system.
196    ///
197    /// If `override_ignore` is `false`, you cannot set a trap for a signal that
198    /// has been ignored since the shell startup. An interactive shell should
199    /// set `override_ignore` to `true` to bypass this restriction.
200    ///
201    /// You can never set a trap for `SIGKILL` or `SIGSTOP`.
202    ///
203    /// `origin` should be the location of the command performing this trap
204    /// update. It is only informative: It does not affect the signal handling
205    /// behavior and can be referenced later by [`get_state`](Self::get_state).
206    ///
207    /// This function clears all parent states remembered when [entering a
208    /// subshell](Self::enter_subshell), not only for the specified condition
209    /// but also for all other conditions.
210    pub fn set_action<S: SignalSystem, C: Into<Condition>>(
211        &mut self,
212        system: &mut S,
213        cond: C,
214        action: Action,
215        origin: Location,
216        override_ignore: bool,
217    ) -> Result<(), SetActionError> {
218        self.set_action_impl(system, cond.into(), action, origin, override_ignore)
219    }
220
221    fn set_action_impl<S: SignalSystem>(
222        &mut self,
223        system: &mut S,
224        cond: Condition,
225        action: Action,
226        origin: Location,
227        override_ignore: bool,
228    ) -> Result<(), SetActionError> {
229        if let Condition::Signal(number) = cond {
230            match system.signal_name_from_number(number) {
231                signal::Name::Kill => return Err(SetActionError::SIGKILL),
232                signal::Name::Stop => return Err(SetActionError::SIGSTOP),
233                _ => {}
234            }
235        }
236
237        self.clear_parent_states();
238
239        let entry = self.traps.entry(cond);
240        GrandState::set_action(system, entry, action, origin, override_ignore)
241    }
242
243    fn clear_parent_states(&mut self) {
244        for state in self.traps.values_mut() {
245            state.clear_parent_state();
246        }
247    }
248
249    /// Returns an iterator over the signal actions configured in this trap set.
250    ///
251    /// The iterator yields tuples of the signal, the currently configured trap
252    /// action, and the action set before
253    /// [`enter_subshell`](Self::enter_subshell) was called.
254    ///
255    /// The iterator only emits conditions for which a trap action has been
256    /// [set](Self::set_action) or [peeked](Self::peek_state). In other words,
257    /// it does not include conditions for which the current state is not yet
258    /// known. To get the state for all conditions, use
259    /// [`peek_state`](Self::peek_state) for each condition.
260    #[inline]
261    pub fn iter(&self) -> Iter<'_> {
262        let inner = self.traps.iter();
263        Iter { inner }
264    }
265
266    /// Updates signal dispositions on entering a subshell.
267    ///
268    /// ## Resetting non-ignore traps
269    ///
270    /// POSIX requires that traps other than `Action::Ignore` be reset when
271    /// entering a subshell. This function achieves that effect.
272    ///
273    /// The trap set will remember the original trap states as the parent
274    /// states. You can get them from the second return value of
275    /// [`get_state`](Self::get_state) or the third item of tuples yielded by an
276    /// [iterator](Self::iter).
277    ///
278    /// Note that trap actions other than `Trap::Command` remain as before.
279    ///
280    /// ## Clearing internal dispositions
281    ///
282    /// Internal dispositions that have been installed are cleared except for
283    /// the SIGCHLD signal.
284    ///
285    /// ## Ignoring SIGINT and SIGQUIT
286    ///
287    /// If `ignore_sigint_sigquit` is true, this function sets the dispositions
288    /// for SIGINT and SIGQUIT to `Ignore`.
289    ///
290    /// ## Ignoring SIGTSTP, SIGTTIN, and SIGTTOU
291    ///
292    /// If `keep_internal_dispositions_for_stoppers` is true and the internal
293    /// dispositions have been [enabled for SIGTSTP, SIGTTIN, and
294    /// SIGTTOU](Self::enable_internal_dispositions_for_stoppers), this function
295    /// leaves the dispositions for those signals set to `Ignore`.
296    ///
297    /// ## Errors
298    ///
299    /// This function ignores any errors that may occur when setting signal
300    /// dispositions.
301    pub fn enter_subshell<S: SignalSystem>(
302        &mut self,
303        system: &mut S,
304        ignore_sigint_sigquit: bool,
305        keep_internal_dispositions_for_stoppers: bool,
306    ) {
307        self.clear_parent_states();
308
309        for (&cond, state) in &mut self.traps {
310            let option = match cond {
311                Condition::Exit => EnterSubshellOption::ClearInternalDisposition,
312                Condition::Signal(number) => {
313                    use signal::Name::*;
314                    match system.signal_name_from_number(number) {
315                        Chld => EnterSubshellOption::KeepInternalDisposition,
316                        Int | Quit if ignore_sigint_sigquit => EnterSubshellOption::Ignore,
317                        Tstp | Ttin | Ttou
318                            if keep_internal_dispositions_for_stoppers
319                                && state.internal_disposition() != Disposition::Default =>
320                        {
321                            EnterSubshellOption::Ignore
322                        }
323                        _ => EnterSubshellOption::ClearInternalDisposition,
324                    }
325                }
326            };
327            _ = state.enter_subshell(system, cond, option);
328        }
329
330        if ignore_sigint_sigquit {
331            for name in [signal::Name::Int, signal::Name::Quit] {
332                let number = system
333                    .signal_number_from_name(name)
334                    .unwrap_or_else(|| panic!("missing support for signal {name}"));
335                match self.traps.entry(Condition::Signal(number)) {
336                    Entry::Vacant(vacant) => _ = GrandState::ignore(system, vacant),
337                    // If the entry is occupied, the signal is already ignored in the loop above.
338                    Entry::Occupied(_) => {}
339                }
340            }
341        }
342    }
343
344    /// Sets the `pending` flag of the [`TrapState`] for the specified signal.
345    ///
346    /// This function does nothing if no trap action has been
347    /// [set](Self::set_action) for the signal.
348    pub fn catch_signal(&mut self, signal: signal::Number) {
349        if let Some(state) = self.traps.get_mut(&Condition::Signal(signal)) {
350            state.mark_as_caught();
351        }
352    }
353
354    /// Resets the `pending` flag of the [`TrapState`] for the specified signal.
355    ///
356    /// Returns the [`TrapState`] if the flag was set.
357    pub fn take_signal_if_caught(&mut self, signal: signal::Number) -> Option<&TrapState> {
358        self.traps
359            .get_mut(&signal.into())
360            .and_then(|state| state.handle_if_caught())
361    }
362
363    /// Returns a signal that has been [caught](Self::catch_signal).
364    ///
365    /// This function clears the `pending` flag of the [`TrapState`] for the
366    /// specified signal.
367    ///
368    /// If there is more than one caught signal, it is unspecified which one of
369    /// them is returned. If there is no caught signal, `None` is returned.
370    pub fn take_caught_signal(&mut self) -> Option<(signal::Number, &TrapState)> {
371        self.traps.iter_mut().find_map(|(&cond, state)| match cond {
372            Condition::Signal(signal) => state.handle_if_caught().map(|trap| (signal, trap)),
373            _ => None,
374        })
375    }
376
377    fn set_internal_disposition<S: SignalSystem>(
378        &mut self,
379        signal: signal::Name,
380        disposition: Disposition,
381        system: &mut S,
382    ) -> Result<(), Errno> {
383        let number = system
384            .signal_number_from_name(signal)
385            .unwrap_or_else(|| panic!("missing support for signal {signal}"));
386        let entry = self.traps.entry(Condition::Signal(number));
387        GrandState::set_internal_disposition(system, entry, disposition)
388    }
389
390    /// Installs the internal disposition for `SIGCHLD`.
391    ///
392    /// You should install the internal disposition for `SIGCHLD` by using this
393    /// function before waiting for `SIGCHLD` with [`System::wait`] and
394    /// [`SharedSystem::wait_for_signal`]. The disposition allows catching
395    /// `SIGCHLD`.
396    ///
397    /// This function remembers that the disposition has been installed, so a
398    /// second call to the function will be a no-op.
399    pub fn enable_internal_disposition_for_sigchld<S: SignalSystem>(
400        &mut self,
401        system: &mut S,
402    ) -> Result<(), Errno> {
403        self.set_internal_disposition(signal::Name::Chld, Disposition::Catch, system)
404    }
405
406    /// Installs the internal dispositions for `SIGINT`, `SIGTERM`, and `SIGQUIT`.
407    ///
408    /// An interactive shell should install the internal dispositions for these
409    /// signals by using this function. The dispositions catch `SIGINT` and
410    /// ignore `SIGTERM` and `SIGQUIT`.
411    ///
412    /// This function remembers that the dispositions have been installed, so a
413    /// second call to the function will be a no-op.
414    pub fn enable_internal_dispositions_for_terminators<S: SignalSystem>(
415        &mut self,
416        system: &mut S,
417    ) -> Result<(), Errno> {
418        self.set_internal_disposition(signal::Name::Int, Disposition::Catch, system)?;
419        self.set_internal_disposition(signal::Name::Term, Disposition::Ignore, system)?;
420        self.set_internal_disposition(signal::Name::Quit, Disposition::Ignore, system)
421    }
422
423    /// Installs the internal dispositions for `SIGTSTP`, `SIGTTIN`, and `SIGTTOU`.
424    ///
425    /// An interactive job-controlling shell should install the internal
426    /// dispositions for these signals by using this function. The dispositions
427    /// ignore the signals.
428    ///
429    /// This function remembers that the dispositions have been installed, so a
430    /// second call to the function will be a no-op.
431    pub fn enable_internal_dispositions_for_stoppers<S: SignalSystem>(
432        &mut self,
433        system: &mut S,
434    ) -> Result<(), Errno> {
435        self.set_internal_disposition(signal::Name::Tstp, Disposition::Ignore, system)?;
436        self.set_internal_disposition(signal::Name::Ttin, Disposition::Ignore, system)?;
437        self.set_internal_disposition(signal::Name::Ttou, Disposition::Ignore, system)
438    }
439
440    /// Uninstalls the internal dispositions for `SIGINT`, `SIGTERM`, and `SIGQUIT`.
441    pub fn disable_internal_dispositions_for_terminators<S: SignalSystem>(
442        &mut self,
443        system: &mut S,
444    ) -> Result<(), Errno> {
445        self.set_internal_disposition(signal::Name::Int, Disposition::Default, system)?;
446        self.set_internal_disposition(signal::Name::Term, Disposition::Default, system)?;
447        self.set_internal_disposition(signal::Name::Quit, Disposition::Default, system)
448    }
449
450    /// Uninstalls the internal dispositions for `SIGTSTP`, `SIGTTIN`, and `SIGTTOU`.
451    pub fn disable_internal_dispositions_for_stoppers<S: SignalSystem>(
452        &mut self,
453        system: &mut S,
454    ) -> Result<(), Errno> {
455        self.set_internal_disposition(signal::Name::Tstp, Disposition::Default, system)?;
456        self.set_internal_disposition(signal::Name::Ttin, Disposition::Default, system)?;
457        self.set_internal_disposition(signal::Name::Ttou, Disposition::Default, system)
458    }
459
460    /// Uninstalls all internal dispositions.
461    ///
462    /// This function removes all internal dispositions that have been
463    /// previously installed by `self`, except for the `SIGCHLD` signal.
464    /// Dispositions for any existing user-defined traps are not affected.
465    pub fn disable_internal_dispositions<S: SignalSystem>(
466        &mut self,
467        system: &mut S,
468    ) -> Result<(), Errno> {
469        self.set_internal_disposition(signal::Name::Chld, Disposition::Default, system)?;
470        self.disable_internal_dispositions_for_terminators(system)?;
471        self.disable_internal_dispositions_for_stoppers(system)
472    }
473}
474
475impl<'a> IntoIterator for &'a TrapSet {
476    /// Type of items yielded by this iterator
477    ///
478    /// Each item is a tuple of a condition, the currently configured trap state,
479    /// and the parent state that was active before entering a subshell.
480    type Item = (&'a Condition, &'a TrapState, Option<&'a TrapState>);
481    type IntoIter = Iter<'a>;
482
483    #[inline(always)]
484    fn into_iter(self) -> Iter<'a> {
485        self.iter()
486    }
487}
488
489/// Wrapper for running signal trap if caught
490///
491/// This struct declares a function type that runs a signal trap if the signal
492/// has been caught. An implementation of this function type should be provided
493/// and stored in the environment's [`any`](Env::any) storage so that it can
494/// be used by modules that need to run signal traps.
495///
496/// This function takes a mutable reference to the environment and a signal
497/// number to be checked. If the signal has been caught, the function runs the
498/// corresponding trap action and returns `Some(result)`, where `result` is the
499/// result of running the trap action. If the signal has not been caught, the
500/// function returns `None`.
501///
502/// The most standard implementation of this function type is provided in the
503/// [`yash-semantics` crate](https://crates.io/crates/yash-semantics):
504///
505/// ```
506/// # use yash_env::trap::RunSignalTrapIfCaught;
507/// let mut env = yash_env::Env::new_virtual();
508/// env.any.insert(Box::new(RunSignalTrapIfCaught(|env, signal| {
509///     Box::pin(async move { yash_semantics::trap::run_trap_if_caught(env, signal).await })
510/// })));
511/// ```
512#[derive(Clone, Copy, Debug)]
513pub struct RunSignalTrapIfCaught(
514    #[allow(clippy::type_complexity)]
515    pub  for<'a> fn(
516        env: &'a mut Env,
517        signal: signal::Number,
518    ) -> Pin<Box<dyn Future<Output = Option<crate::semantics::Result>> + 'a>>,
519);
520
521#[cfg(test)]
522mod tests {
523    use super::*;
524    use crate::job::ProcessState;
525    use crate::system::System as _;
526    use crate::system::SystemEx as _;
527    use crate::system::r#virtual::VirtualSystem;
528    use crate::system::r#virtual::{
529        SIGCHLD, SIGINT, SIGKILL, SIGQUIT, SIGSTOP, SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU, SIGUSR1,
530        SIGUSR2,
531    };
532    use crate::tests::in_virtual_system;
533    use std::collections::HashMap;
534
535    #[derive(Default)]
536    pub struct DummySystem(pub HashMap<signal::Number, Disposition>);
537
538    impl SignalSystem for DummySystem {
539        fn signal_name_from_number(&self, number: signal::Number) -> signal::Name {
540            VirtualSystem::new().signal_name_from_number(number)
541        }
542
543        fn signal_number_from_name(&self, name: signal::Name) -> Option<signal::Number> {
544            VirtualSystem::new().signal_number_from_name(name)
545        }
546
547        fn get_disposition(&self, signal: signal::Number) -> Result<Disposition, Errno> {
548            Ok(self.0.get(&signal).copied().unwrap_or_default())
549        }
550
551        fn set_disposition(
552            &mut self,
553            signal: signal::Number,
554            disposition: Disposition,
555        ) -> Result<Disposition, Errno> {
556            Ok(self.0.insert(signal, disposition).unwrap_or_default())
557        }
558    }
559
560    #[test]
561    fn default_trap() {
562        let trap_set = TrapSet::default();
563        assert_eq!(trap_set.get_state(SIGCHLD), (None, None));
564    }
565
566    #[test]
567    fn setting_trap_for_two_signals() {
568        let mut system = DummySystem::default();
569        let mut trap_set = TrapSet::default();
570        let origin_1 = Location::dummy("foo");
571        let result = trap_set.set_action(
572            &mut system,
573            SIGUSR1,
574            Action::Ignore,
575            origin_1.clone(),
576            false,
577        );
578        assert_eq!(result, Ok(()));
579
580        let command = Action::Command("echo".into());
581        let origin_2 = Location::dummy("bar");
582        let result = trap_set.set_action(
583            &mut system,
584            SIGUSR2,
585            command.clone(),
586            origin_2.clone(),
587            false,
588        );
589        assert_eq!(result, Ok(()));
590
591        assert_eq!(
592            trap_set.get_state(SIGUSR1),
593            (
594                Some(&TrapState {
595                    action: Action::Ignore,
596                    origin: Origin::User(origin_1),
597                    pending: false
598                }),
599                None
600            )
601        );
602        assert_eq!(
603            trap_set.get_state(SIGUSR2),
604            (
605                Some(&TrapState {
606                    action: command,
607                    origin: Origin::User(origin_2),
608                    pending: false
609                }),
610                None
611            )
612        );
613        assert_eq!(system.0[&SIGUSR1], Disposition::Ignore);
614        assert_eq!(system.0[&SIGUSR2], Disposition::Catch);
615    }
616
617    #[test]
618    fn setting_trap_for_sigkill() {
619        let mut system = DummySystem::default();
620        let mut trap_set = TrapSet::default();
621        let origin = Location::dummy("origin");
622        let result = trap_set.set_action(&mut system, SIGKILL, Action::Ignore, origin, false);
623        assert_eq!(result, Err(SetActionError::SIGKILL));
624        assert_eq!(trap_set.get_state(SIGKILL), (None, None));
625        assert_eq!(system.0.get(&SIGKILL), None);
626    }
627
628    #[test]
629    fn setting_trap_for_sigstop() {
630        let mut system = DummySystem::default();
631        let mut trap_set = TrapSet::default();
632        let origin = Location::dummy("origin");
633        let result = trap_set.set_action(&mut system, SIGSTOP, Action::Ignore, origin, false);
634        assert_eq!(result, Err(SetActionError::SIGSTOP));
635        assert_eq!(trap_set.get_state(SIGSTOP), (None, None));
636        assert_eq!(system.0.get(&SIGSTOP), None);
637    }
638
639    #[test]
640    fn peeking_state_with_default_inherited_disposition() {
641        let system = DummySystem::default();
642        let mut trap_set = TrapSet::default();
643        let result = trap_set.peek_state(&system, SIGCHLD);
644        assert_eq!(
645            result,
646            Ok(&TrapState {
647                action: Action::Default,
648                origin: Origin::Inherited,
649                pending: false
650            })
651        );
652    }
653
654    #[test]
655    fn peeking_state_with_inherited_disposition_of_ignore() {
656        let mut system = DummySystem::default();
657        system.0.insert(SIGCHLD, Disposition::Ignore);
658        let mut trap_set = TrapSet::default();
659        let result = trap_set.peek_state(&system, SIGCHLD);
660        assert_eq!(
661            result,
662            Ok(&TrapState {
663                action: Action::Ignore,
664                origin: Origin::Inherited,
665                pending: false
666            })
667        );
668    }
669
670    #[test]
671    fn peeking_state_with_parent_state() {
672        let mut system = DummySystem::default();
673        let mut trap_set = TrapSet::default();
674        let origin = Location::dummy("foo");
675        let command = Action::Command("echo".into());
676        trap_set
677            .set_action(&mut system, SIGUSR1, command.clone(), origin.clone(), false)
678            .unwrap();
679        trap_set.enter_subshell(&mut system, false, false);
680
681        let result = trap_set.peek_state(&system, SIGUSR1);
682        assert_eq!(
683            result,
684            Ok(&TrapState {
685                action: command,
686                origin: Origin::User(origin),
687                pending: false
688            })
689        );
690    }
691
692    #[test]
693    fn basic_iteration() {
694        let mut system = DummySystem::default();
695        let mut trap_set = TrapSet::default();
696        let origin_1 = Location::dummy("foo");
697        trap_set
698            .set_action(
699                &mut system,
700                SIGUSR1,
701                Action::Ignore,
702                origin_1.clone(),
703                false,
704            )
705            .unwrap();
706        let command = Action::Command("echo".into());
707        let origin_2 = Location::dummy("bar");
708        trap_set
709            .set_action(
710                &mut system,
711                SIGUSR2,
712                command.clone(),
713                origin_2.clone(),
714                false,
715            )
716            .unwrap();
717
718        let mut i = trap_set.iter();
719        let first = i.next().unwrap();
720        assert_eq!(first.0, &Condition::Signal(SIGUSR1));
721        assert_eq!(first.1.action, Action::Ignore);
722        assert_eq!(first.1.origin, Origin::User(origin_1));
723        assert_eq!(first.2, None);
724        let second = i.next().unwrap();
725        assert_eq!(second.0, &Condition::Signal(SIGUSR2));
726        assert_eq!(second.1.action, command);
727        assert_eq!(second.1.origin, Origin::User(origin_2));
728        assert_eq!(first.2, None);
729        assert_eq!(i.next(), None);
730    }
731
732    #[test]
733    fn iteration_after_entering_subshell() {
734        let mut system = DummySystem::default();
735        let mut trap_set = TrapSet::default();
736        let origin_1 = Location::dummy("foo");
737        trap_set
738            .set_action(
739                &mut system,
740                SIGUSR1,
741                Action::Ignore,
742                origin_1.clone(),
743                false,
744            )
745            .unwrap();
746        let command = Action::Command("echo".into());
747        let origin_2 = Location::dummy("bar");
748        trap_set
749            .set_action(
750                &mut system,
751                SIGUSR2,
752                command.clone(),
753                origin_2.clone(),
754                false,
755            )
756            .unwrap();
757        trap_set.enter_subshell(&mut system, false, false);
758
759        let mut i = trap_set.iter();
760        let first = i.next().unwrap();
761        assert_eq!(first.0, &Condition::Signal(SIGUSR1));
762        assert_eq!(first.1.action, Action::Ignore);
763        assert_eq!(first.1.origin, Origin::User(origin_1));
764        assert_eq!(first.2, None);
765        let second = i.next().unwrap();
766        assert_eq!(second.0, &Condition::Signal(SIGUSR2));
767        assert_eq!(second.1.action, Action::Default);
768        assert_eq!(second.1.origin, Origin::Subshell);
769        assert_eq!(second.2.unwrap().action, command);
770        assert_eq!(second.2.unwrap().origin, Origin::User(origin_2));
771        assert_eq!(i.next(), None);
772    }
773
774    #[test]
775    fn iteration_after_setting_trap_in_subshell() {
776        let mut system = DummySystem::default();
777        let mut trap_set = TrapSet::default();
778        let origin_1 = Location::dummy("foo");
779        let command = Action::Command("echo".into());
780        trap_set
781            .set_action(&mut system, SIGUSR1, command, origin_1, false)
782            .unwrap();
783        trap_set.enter_subshell(&mut system, false, false);
784        let origin_2 = Location::dummy("bar");
785        let command = Action::Command("ls".into());
786        trap_set
787            .set_action(
788                &mut system,
789                SIGUSR2,
790                command.clone(),
791                origin_2.clone(),
792                false,
793            )
794            .unwrap();
795
796        let mut i = trap_set.iter();
797        let first = i.next().unwrap();
798        assert_eq!(first.0, &Condition::Signal(SIGUSR1));
799        assert_eq!(first.1.action, Action::Default);
800        assert_eq!(first.1.origin, Origin::Subshell);
801        assert_eq!(first.2, None);
802        let second = i.next().unwrap();
803        assert_eq!(second.0, &Condition::Signal(SIGUSR2));
804        assert_eq!(second.1.action, command);
805        assert_eq!(second.1.origin, Origin::User(origin_2));
806        assert_eq!(second.2, None);
807        assert_eq!(i.next(), None);
808    }
809
810    #[test]
811    fn entering_subshell_resets_command_traps() {
812        let mut system = DummySystem::default();
813        let mut trap_set = TrapSet::default();
814        let action = Action::Command("".into());
815        let origin = Location::dummy("origin");
816        trap_set
817            .set_action(&mut system, SIGCHLD, action.clone(), origin.clone(), false)
818            .unwrap();
819
820        trap_set.enter_subshell(&mut system, false, false);
821        assert_eq!(
822            trap_set.get_state(SIGCHLD),
823            (
824                Some(&TrapState {
825                    action: Action::Default,
826                    origin: Origin::Subshell,
827                    pending: false
828                }),
829                Some(&TrapState {
830                    action,
831                    origin: Origin::User(origin),
832                    pending: false
833                })
834            )
835        );
836        assert_eq!(system.0[&SIGCHLD], Disposition::Default);
837    }
838
839    #[test]
840    fn entering_subshell_keeps_ignore_traps() {
841        let mut system = DummySystem::default();
842        let mut trap_set = TrapSet::default();
843        let origin = Location::dummy("origin");
844        trap_set
845            .set_action(&mut system, SIGCHLD, Action::Ignore, origin.clone(), false)
846            .unwrap();
847
848        trap_set.enter_subshell(&mut system, false, false);
849        assert_eq!(
850            trap_set.get_state(SIGCHLD),
851            (
852                Some(&TrapState {
853                    action: Action::Ignore,
854                    origin: Origin::User(origin),
855                    pending: false
856                }),
857                None
858            )
859        );
860        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
861    }
862
863    #[test]
864    fn entering_subshell_with_internal_disposition_for_sigchld() {
865        let mut system = DummySystem::default();
866        let mut trap_set = TrapSet::default();
867        let action = Action::Command("".into());
868        let origin = Location::dummy("origin");
869        trap_set
870            .set_action(&mut system, SIGCHLD, action.clone(), origin.clone(), false)
871            .unwrap();
872        trap_set
873            .enable_internal_disposition_for_sigchld(&mut system)
874            .unwrap();
875
876        trap_set.enter_subshell(&mut system, false, false);
877        assert_eq!(
878            trap_set.get_state(SIGCHLD),
879            (
880                Some(&TrapState {
881                    action: Action::Default,
882                    origin: Origin::Subshell,
883                    pending: false
884                }),
885                Some(&TrapState {
886                    action,
887                    origin: Origin::User(origin),
888                    pending: false
889                })
890            )
891        );
892        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
893    }
894
895    #[test]
896    fn entering_subshell_with_internal_disposition_for_sigint() {
897        let mut system = DummySystem::default();
898        let mut trap_set = TrapSet::default();
899        let action = Action::Command("".into());
900        let origin = Location::dummy("origin");
901        trap_set
902            .set_action(&mut system, SIGINT, action.clone(), origin.clone(), false)
903            .unwrap();
904        trap_set
905            .enable_internal_dispositions_for_terminators(&mut system)
906            .unwrap();
907
908        trap_set.enter_subshell(&mut system, false, false);
909        assert_eq!(
910            trap_set.get_state(SIGINT),
911            (
912                Some(&TrapState {
913                    action: Action::Default,
914                    origin: Origin::Subshell,
915                    pending: false
916                }),
917                Some(&TrapState {
918                    action,
919                    origin: Origin::User(origin),
920                    pending: false
921                })
922            )
923        );
924        assert_eq!(system.0[&SIGINT], Disposition::Default);
925    }
926
927    #[test]
928    fn entering_subshell_with_internal_disposition_for_sigterm() {
929        let mut system = DummySystem::default();
930        let mut trap_set = TrapSet::default();
931        let action = Action::Command("".into());
932        let origin = Location::dummy("origin");
933        trap_set
934            .set_action(&mut system, SIGTERM, action.clone(), origin.clone(), false)
935            .unwrap();
936        trap_set
937            .enable_internal_dispositions_for_terminators(&mut system)
938            .unwrap();
939
940        trap_set.enter_subshell(&mut system, false, false);
941        assert_eq!(
942            trap_set.get_state(SIGTERM),
943            (
944                Some(&TrapState {
945                    action: Action::Default,
946                    origin: Origin::Subshell,
947                    pending: false
948                }),
949                Some(&TrapState {
950                    action,
951                    origin: Origin::User(origin),
952                    pending: false
953                })
954            )
955        );
956        assert_eq!(system.0[&SIGTERM], Disposition::Default);
957    }
958
959    #[test]
960    fn entering_subshell_with_internal_disposition_for_sigquit() {
961        let mut system = DummySystem::default();
962        let mut trap_set = TrapSet::default();
963        let action = Action::Command("".into());
964        let origin = Location::dummy("origin");
965        trap_set
966            .set_action(&mut system, SIGQUIT, action.clone(), origin.clone(), false)
967            .unwrap();
968        trap_set
969            .enable_internal_dispositions_for_terminators(&mut system)
970            .unwrap();
971
972        trap_set.enter_subshell(&mut system, false, false);
973        assert_eq!(
974            trap_set.get_state(SIGQUIT),
975            (
976                Some(&TrapState {
977                    action: Action::Default,
978                    origin: Origin::Subshell,
979                    pending: false
980                }),
981                Some(&TrapState {
982                    action,
983                    origin: Origin::User(origin),
984                    pending: false
985                })
986            )
987        );
988        assert_eq!(system.0[&SIGQUIT], Disposition::Default);
989    }
990
991    #[test]
992    fn entering_subshell_with_internal_disposition_for_sigtstp() {
993        let mut system = DummySystem::default();
994        let mut trap_set = TrapSet::default();
995        let action = Action::Command("".into());
996        let origin = Location::dummy("origin");
997        trap_set
998            .set_action(&mut system, SIGTSTP, action.clone(), origin.clone(), false)
999            .unwrap();
1000        trap_set
1001            .enable_internal_dispositions_for_terminators(&mut system)
1002            .unwrap();
1003
1004        trap_set.enter_subshell(&mut system, false, false);
1005        assert_eq!(
1006            trap_set.get_state(SIGTSTP),
1007            (
1008                Some(&TrapState {
1009                    action: Action::Default,
1010                    origin: Origin::Subshell,
1011                    pending: false
1012                }),
1013                Some(&TrapState {
1014                    action,
1015                    origin: Origin::User(origin),
1016                    pending: false
1017                })
1018            )
1019        );
1020        assert_eq!(system.0[&SIGTSTP], Disposition::Default);
1021    }
1022
1023    #[test]
1024    fn entering_subshell_with_internal_disposition_for_sigttin() {
1025        let mut system = DummySystem::default();
1026        let mut trap_set = TrapSet::default();
1027        let action = Action::Command("".into());
1028        let origin = Location::dummy("origin");
1029        trap_set
1030            .set_action(&mut system, SIGTTIN, action.clone(), origin.clone(), false)
1031            .unwrap();
1032        trap_set
1033            .enable_internal_dispositions_for_terminators(&mut system)
1034            .unwrap();
1035
1036        trap_set.enter_subshell(&mut system, false, false);
1037        assert_eq!(
1038            trap_set.get_state(SIGTTIN),
1039            (
1040                Some(&TrapState {
1041                    action: Action::Default,
1042                    origin: Origin::Subshell,
1043                    pending: false
1044                }),
1045                Some(&TrapState {
1046                    action,
1047                    origin: Origin::User(origin),
1048                    pending: false
1049                })
1050            )
1051        );
1052        assert_eq!(system.0[&SIGTTIN], Disposition::Default);
1053    }
1054
1055    #[test]
1056    fn entering_subshell_with_internal_disposition_for_sigttou() {
1057        let mut system = DummySystem::default();
1058        let mut trap_set = TrapSet::default();
1059        let action = Action::Command("".into());
1060        let origin = Location::dummy("origin");
1061        trap_set
1062            .set_action(&mut system, SIGTTOU, action.clone(), origin.clone(), false)
1063            .unwrap();
1064        trap_set
1065            .enable_internal_dispositions_for_terminators(&mut system)
1066            .unwrap();
1067
1068        trap_set.enter_subshell(&mut system, false, false);
1069        assert_eq!(
1070            trap_set.get_state(SIGTTOU),
1071            (
1072                Some(&TrapState {
1073                    action: Action::Default,
1074                    origin: Origin::Subshell,
1075                    pending: false
1076                }),
1077                Some(&TrapState {
1078                    action,
1079                    origin: Origin::User(origin),
1080                    pending: false
1081                })
1082            )
1083        );
1084        assert_eq!(system.0[&SIGTTOU], Disposition::Default);
1085    }
1086
1087    #[test]
1088    fn setting_trap_after_entering_subshell_clears_parent_states() {
1089        let mut system = DummySystem::default();
1090        let mut trap_set = TrapSet::default();
1091        let origin_1 = Location::dummy("foo");
1092        let command = Action::Command("echo 1".into());
1093        trap_set
1094            .set_action(&mut system, SIGUSR1, command, origin_1, false)
1095            .unwrap();
1096        let origin_2 = Location::dummy("bar");
1097        let command = Action::Command("echo 2".into());
1098        trap_set
1099            .set_action(&mut system, SIGUSR2, command, origin_2, false)
1100            .unwrap();
1101        trap_set.enter_subshell(&mut system, false, false);
1102
1103        let command = Action::Command("echo 9".into());
1104        let origin_3 = Location::dummy("qux");
1105        trap_set
1106            .set_action(
1107                &mut system,
1108                SIGUSR1,
1109                command.clone(),
1110                origin_3.clone(),
1111                false,
1112            )
1113            .unwrap();
1114
1115        assert_eq!(
1116            trap_set.get_state(SIGUSR1),
1117            (
1118                Some(&TrapState {
1119                    action: command,
1120                    origin: Origin::User(origin_3),
1121                    pending: false
1122                }),
1123                None
1124            )
1125        );
1126        assert_eq!(
1127            trap_set.get_state(SIGUSR2),
1128            (
1129                Some(&TrapState {
1130                    action: Action::Default,
1131                    origin: Origin::Subshell,
1132                    pending: false
1133                }),
1134                None
1135            )
1136        );
1137        assert_eq!(system.0[&SIGUSR1], Disposition::Catch);
1138        assert_eq!(system.0[&SIGUSR2], Disposition::Default);
1139    }
1140
1141    #[test]
1142    fn entering_nested_subshell_clears_parent_states() {
1143        let mut system = DummySystem::default();
1144        let mut trap_set = TrapSet::default();
1145        let origin_1 = Location::dummy("foo");
1146        let command = Action::Command("echo 1".into());
1147        trap_set
1148            .set_action(&mut system, SIGUSR1, command, origin_1, false)
1149            .unwrap();
1150        let origin_2 = Location::dummy("bar");
1151        let command = Action::Command("echo 2".into());
1152        trap_set
1153            .set_action(&mut system, SIGUSR2, command, origin_2, false)
1154            .unwrap();
1155        trap_set.enter_subshell(&mut system, false, false);
1156        trap_set.enter_subshell(&mut system, false, false);
1157
1158        assert_eq!(
1159            trap_set.get_state(SIGUSR1),
1160            (
1161                Some(&TrapState {
1162                    action: Action::Default,
1163                    origin: Origin::Subshell,
1164                    pending: false
1165                }),
1166                None
1167            )
1168        );
1169        assert_eq!(
1170            trap_set.get_state(SIGUSR2),
1171            (
1172                Some(&TrapState {
1173                    action: Action::Default,
1174                    origin: Origin::Subshell,
1175                    pending: false
1176                }),
1177                None
1178            )
1179        );
1180        assert_eq!(system.0[&SIGUSR1], Disposition::Default);
1181        assert_eq!(system.0[&SIGUSR2], Disposition::Default);
1182    }
1183
1184    #[test]
1185    fn ignoring_sigint_on_entering_subshell_with_action_set() {
1186        in_virtual_system(|mut env, state| async move {
1187            env.traps
1188                .set_action(
1189                    &mut env.system,
1190                    SIGINT,
1191                    Action::Command("".into()),
1192                    Location::dummy(""),
1193                    false,
1194                )
1195                .unwrap();
1196            env.system.kill(env.main_pid, Some(SIGINT)).await.unwrap();
1197            env.traps.enter_subshell(&mut env.system, true, false);
1198
1199            let state = state.borrow();
1200            let process = &state.processes[&env.main_pid];
1201            assert_eq!(process.disposition(SIGINT), Disposition::Ignore);
1202            assert_eq!(process.state(), ProcessState::Running);
1203        })
1204    }
1205
1206    #[test]
1207    fn ignoring_sigquit_on_entering_subshell_with_action_set() {
1208        in_virtual_system(|mut env, state| async move {
1209            env.traps
1210                .set_action(
1211                    &mut env.system,
1212                    SIGQUIT,
1213                    Action::Command("".into()),
1214                    Location::dummy(""),
1215                    false,
1216                )
1217                .unwrap();
1218            env.system.kill(env.main_pid, Some(SIGQUIT)).await.unwrap();
1219            env.traps.enter_subshell(&mut env.system, true, false);
1220
1221            let state = state.borrow();
1222            let process = &state.processes[&env.main_pid];
1223            assert_eq!(process.disposition(SIGQUIT), Disposition::Ignore);
1224            assert_eq!(process.state(), ProcessState::Running);
1225        })
1226    }
1227
1228    #[test]
1229    fn ignoring_sigint_and_sigquit_on_entering_subshell_without_action_set() {
1230        let mut system = DummySystem::default();
1231        let mut trap_set = TrapSet::default();
1232        trap_set.enter_subshell(&mut system, true, false);
1233        assert_eq!(system.0[&SIGINT], Disposition::Ignore);
1234        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1235    }
1236
1237    #[test]
1238    fn keeping_stopper_internal_dispositions_ignored() {
1239        in_virtual_system(|mut env, state| async move {
1240            for signal in [SIGTSTP, SIGTTIN, SIGTTOU] {
1241                env.traps
1242                    .set_action(
1243                        &mut env.system,
1244                        signal,
1245                        Action::Command("".into()),
1246                        Location::dummy(""),
1247                        false,
1248                    )
1249                    .unwrap();
1250            }
1251            env.traps
1252                .enable_internal_dispositions_for_stoppers(&mut env.system)
1253                .unwrap();
1254            for signal in [SIGTSTP, SIGTTIN, SIGTTOU] {
1255                env.system.kill(env.main_pid, Some(signal)).await.unwrap();
1256            }
1257            env.traps.enter_subshell(&mut env.system, false, true);
1258
1259            let state = state.borrow();
1260            let process = &state.processes[&env.main_pid];
1261            assert_eq!(process.disposition(SIGTSTP), Disposition::Ignore);
1262            assert_eq!(process.disposition(SIGTTIN), Disposition::Ignore);
1263            assert_eq!(process.disposition(SIGTTOU), Disposition::Ignore);
1264            assert_eq!(process.state(), ProcessState::Running);
1265        })
1266    }
1267
1268    #[test]
1269    fn no_stopper_internal_dispositions_enabled_to_be_kept_ignored() {
1270        in_virtual_system(|mut env, state| async move {
1271            for signal in [SIGTSTP, SIGTTIN, SIGTTOU] {
1272                env.traps
1273                    .set_action(
1274                        &mut env.system,
1275                        signal,
1276                        Action::Command("".into()),
1277                        Location::dummy(""),
1278                        false,
1279                    )
1280                    .unwrap();
1281            }
1282            env.traps.enter_subshell(&mut env.system, false, true);
1283
1284            let state = state.borrow();
1285            let process = &state.processes[&env.main_pid];
1286            assert_eq!(process.disposition(SIGTSTP), Disposition::Default);
1287            assert_eq!(process.disposition(SIGTTIN), Disposition::Default);
1288            assert_eq!(process.disposition(SIGTTOU), Disposition::Default);
1289        })
1290    }
1291
1292    #[test]
1293    fn catching_signal() {
1294        let mut system = DummySystem::default();
1295        let mut trap_set = TrapSet::default();
1296        let command = Action::Command("echo INT".into());
1297        let origin = Location::dummy("origin");
1298        trap_set
1299            .set_action(&mut system, SIGINT, command, origin, false)
1300            .unwrap();
1301        let command = Action::Command("echo TERM".into());
1302        let origin = Location::dummy("origin");
1303        trap_set
1304            .set_action(&mut system, SIGTERM, command, origin, false)
1305            .unwrap();
1306
1307        trap_set.catch_signal(SIGCHLD);
1308        trap_set.catch_signal(SIGINT);
1309
1310        let trap_state = trap_set.get_state(SIGINT).0.unwrap();
1311        assert!(trap_state.pending, "trap_state = {trap_state:?}");
1312        let trap_state = trap_set.get_state(SIGTERM).0.unwrap();
1313        assert!(!trap_state.pending, "trap_state = {trap_state:?}");
1314    }
1315
1316    #[test]
1317    fn taking_signal_if_caught() {
1318        let mut system = DummySystem::default();
1319        let mut trap_set = TrapSet::default();
1320        let command = Action::Command("echo INT".into());
1321        let origin = Location::dummy("origin");
1322        trap_set
1323            .set_action(&mut system, SIGINT, command, origin, false)
1324            .unwrap();
1325
1326        let result = trap_set.take_signal_if_caught(SIGINT);
1327        assert_eq!(result, None);
1328
1329        trap_set.catch_signal(SIGINT);
1330
1331        let result = trap_set.take_signal_if_caught(SIGINT);
1332        assert!(!result.unwrap().pending);
1333
1334        let result = trap_set.take_signal_if_caught(SIGINT);
1335        assert_eq!(result, None);
1336    }
1337
1338    #[test]
1339    fn taking_caught_signal() {
1340        let mut system = DummySystem::default();
1341        let mut trap_set = TrapSet::default();
1342        assert_eq!(trap_set.take_caught_signal(), None);
1343
1344        let command = Action::Command("echo INT".into());
1345        let origin = Location::dummy("origin");
1346        trap_set
1347            .set_action(&mut system, SIGINT, command, origin, false)
1348            .unwrap();
1349        let command = Action::Command("echo TERM".into());
1350        let origin = Location::dummy("origin");
1351        trap_set
1352            .set_action(&mut system, SIGTERM, command, origin, false)
1353            .unwrap();
1354        let command = Action::Command("echo USR1".into());
1355        let origin = Location::dummy("origin");
1356        trap_set
1357            .set_action(&mut system, SIGUSR1, command, origin, false)
1358            .unwrap();
1359        assert_eq!(trap_set.take_caught_signal(), None);
1360
1361        trap_set.catch_signal(SIGINT);
1362        trap_set.catch_signal(SIGUSR1);
1363        // The order in which take_caught_signal returns the two signals is
1364        // unspecified, so we accept both the orders.
1365        let result = trap_set.take_caught_signal().unwrap();
1366        match result.0 {
1367            SIGINT => {
1368                assert_eq!(result.1.action, Action::Command("echo INT".into()));
1369                assert!(!result.1.pending);
1370
1371                let result = trap_set.take_caught_signal().unwrap();
1372                assert_eq!(result.0, SIGUSR1);
1373                assert_eq!(result.1.action, Action::Command("echo USR1".into()));
1374                assert!(!result.1.pending);
1375            }
1376            SIGUSR1 => {
1377                assert_eq!(result.1.action, Action::Command("echo USR1".into()));
1378                assert!(!result.1.pending);
1379
1380                let result = trap_set.take_caught_signal().unwrap();
1381                assert_eq!(result.0, SIGINT);
1382                assert_eq!(result.1.action, Action::Command("echo INT".into()));
1383                assert!(!result.1.pending);
1384            }
1385            _ => panic!("wrong signal: {result:?}"),
1386        }
1387
1388        assert_eq!(trap_set.take_caught_signal(), None);
1389    }
1390
1391    #[test]
1392    fn enabling_internal_disposition_for_sigchld() {
1393        let mut system = DummySystem::default();
1394        let mut trap_set = TrapSet::default();
1395        trap_set
1396            .enable_internal_disposition_for_sigchld(&mut system)
1397            .unwrap();
1398        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
1399    }
1400
1401    #[test]
1402    fn enabling_internal_dispositions_for_terminators() {
1403        let mut system = DummySystem::default();
1404        let mut trap_set = TrapSet::default();
1405        trap_set
1406            .enable_internal_dispositions_for_terminators(&mut system)
1407            .unwrap();
1408        assert_eq!(system.0[&SIGINT], Disposition::Catch);
1409        assert_eq!(system.0[&SIGTERM], Disposition::Ignore);
1410        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1411    }
1412
1413    #[test]
1414    fn enabling_internal_dispositions_for_stoppers() {
1415        let mut system = DummySystem::default();
1416        let mut trap_set = TrapSet::default();
1417        trap_set
1418            .enable_internal_dispositions_for_stoppers(&mut system)
1419            .unwrap();
1420        assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
1421        assert_eq!(system.0[&SIGTTIN], Disposition::Ignore);
1422        assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
1423    }
1424
1425    #[test]
1426    fn disabling_internal_dispositions_for_initially_defaulted_signals() {
1427        let mut system = DummySystem::default();
1428        let mut trap_set = TrapSet::default();
1429        trap_set
1430            .enable_internal_disposition_for_sigchld(&mut system)
1431            .unwrap();
1432        trap_set
1433            .enable_internal_dispositions_for_terminators(&mut system)
1434            .unwrap();
1435        trap_set
1436            .enable_internal_dispositions_for_stoppers(&mut system)
1437            .unwrap();
1438        trap_set.disable_internal_dispositions(&mut system).unwrap();
1439        assert_eq!(system.0[&SIGCHLD], Disposition::Default);
1440        assert_eq!(system.0[&SIGINT], Disposition::Default);
1441        assert_eq!(system.0[&SIGTERM], Disposition::Default);
1442        assert_eq!(system.0[&SIGQUIT], Disposition::Default);
1443        assert_eq!(system.0[&SIGTSTP], Disposition::Default);
1444        assert_eq!(system.0[&SIGTTIN], Disposition::Default);
1445        assert_eq!(system.0[&SIGTTOU], Disposition::Default);
1446    }
1447
1448    fn ignore_signals(system: &mut DummySystem) {
1449        system.0.extend(
1450            [SIGCHLD, SIGINT, SIGTERM, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU]
1451                .into_iter()
1452                .map(|signal| (signal, Disposition::Ignore)),
1453        )
1454    }
1455
1456    #[test]
1457    fn disabling_internal_dispositions_for_initially_ignored_signals() {
1458        let mut system = DummySystem::default();
1459        ignore_signals(&mut system);
1460        let mut trap_set = TrapSet::default();
1461        trap_set
1462            .enable_internal_disposition_for_sigchld(&mut system)
1463            .unwrap();
1464        trap_set
1465            .enable_internal_dispositions_for_terminators(&mut system)
1466            .unwrap();
1467        trap_set
1468            .enable_internal_dispositions_for_stoppers(&mut system)
1469            .unwrap();
1470        trap_set.disable_internal_dispositions(&mut system).unwrap();
1471        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
1472        assert_eq!(system.0[&SIGINT], Disposition::Ignore);
1473        assert_eq!(system.0[&SIGTERM], Disposition::Ignore);
1474        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1475        assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
1476        assert_eq!(system.0[&SIGTTIN], Disposition::Ignore);
1477        assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
1478    }
1479
1480    #[test]
1481    fn disabling_internal_dispositions_after_enabling_twice() {
1482        let mut system = DummySystem::default();
1483        ignore_signals(&mut system);
1484        let mut trap_set = TrapSet::default();
1485        trap_set
1486            .enable_internal_disposition_for_sigchld(&mut system)
1487            .unwrap();
1488        trap_set
1489            .enable_internal_disposition_for_sigchld(&mut system)
1490            .unwrap();
1491        trap_set
1492            .enable_internal_dispositions_for_terminators(&mut system)
1493            .unwrap();
1494        trap_set
1495            .enable_internal_dispositions_for_terminators(&mut system)
1496            .unwrap();
1497        trap_set
1498            .enable_internal_dispositions_for_stoppers(&mut system)
1499            .unwrap();
1500        trap_set
1501            .enable_internal_dispositions_for_stoppers(&mut system)
1502            .unwrap();
1503        trap_set.disable_internal_dispositions(&mut system).unwrap();
1504        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
1505        assert_eq!(system.0[&SIGINT], Disposition::Ignore);
1506        assert_eq!(system.0[&SIGTERM], Disposition::Ignore);
1507        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1508        assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
1509        assert_eq!(system.0[&SIGTTIN], Disposition::Ignore);
1510        assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
1511    }
1512
1513    #[test]
1514    fn disabling_internal_dispositions_without_enabling() {
1515        let mut system = DummySystem::default();
1516        ignore_signals(&mut system);
1517        let mut trap_set = TrapSet::default();
1518        trap_set.disable_internal_dispositions(&mut system).unwrap();
1519        assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
1520        assert_eq!(system.0[&SIGINT], Disposition::Ignore);
1521        assert_eq!(system.0[&SIGTERM], Disposition::Ignore);
1522        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1523        assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
1524        assert_eq!(system.0[&SIGTTIN], Disposition::Ignore);
1525        assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
1526    }
1527
1528    #[test]
1529    fn reenabling_internal_dispositions() {
1530        let mut system = DummySystem::default();
1531        let mut trap_set = TrapSet::default();
1532        trap_set
1533            .enable_internal_disposition_for_sigchld(&mut system)
1534            .unwrap();
1535        trap_set
1536            .enable_internal_disposition_for_sigchld(&mut system)
1537            .unwrap();
1538        trap_set
1539            .enable_internal_dispositions_for_terminators(&mut system)
1540            .unwrap();
1541        trap_set
1542            .enable_internal_dispositions_for_terminators(&mut system)
1543            .unwrap();
1544        trap_set
1545            .enable_internal_dispositions_for_stoppers(&mut system)
1546            .unwrap();
1547        trap_set
1548            .enable_internal_dispositions_for_stoppers(&mut system)
1549            .unwrap();
1550        trap_set.disable_internal_dispositions(&mut system).unwrap();
1551        trap_set
1552            .enable_internal_disposition_for_sigchld(&mut system)
1553            .unwrap();
1554        trap_set
1555            .enable_internal_dispositions_for_terminators(&mut system)
1556            .unwrap();
1557        trap_set
1558            .enable_internal_dispositions_for_stoppers(&mut system)
1559            .unwrap();
1560        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
1561        assert_eq!(system.0[&SIGINT], Disposition::Catch);
1562        assert_eq!(system.0[&SIGTERM], Disposition::Ignore);
1563        assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1564        assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
1565        assert_eq!(system.0[&SIGTTIN], Disposition::Ignore);
1566        assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
1567    }
1568
1569    #[test]
1570    fn setting_trap_to_ignore_after_enabling_internal_disposition() {
1571        let mut system = DummySystem::default();
1572        let mut trap_set = TrapSet::default();
1573        trap_set
1574            .enable_internal_disposition_for_sigchld(&mut system)
1575            .unwrap();
1576        let origin = Location::dummy("origin");
1577        let result = trap_set.set_action(&mut system, SIGCHLD, Action::Ignore, origin, false);
1578        assert_eq!(result, Ok(()));
1579        assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
1580    }
1581
1582    #[test]
1583    fn resetting_trap_from_ignore_no_override_after_enabling_internal_dispositions() {
1584        let mut system = DummySystem::default();
1585        ignore_signals(&mut system);
1586        let mut trap_set = TrapSet::default();
1587        trap_set
1588            .enable_internal_disposition_for_sigchld(&mut system)
1589            .unwrap();
1590        trap_set
1591            .enable_internal_dispositions_for_terminators(&mut system)
1592            .unwrap();
1593        trap_set
1594            .enable_internal_dispositions_for_stoppers(&mut system)
1595            .unwrap();
1596
1597        for signal in [SIGCHLD, SIGINT] {
1598            let origin = Location::dummy("origin");
1599            let result = trap_set.set_action(&mut system, signal, Action::Default, origin, false);
1600            assert_eq!(result, Err(SetActionError::InitiallyIgnored));
1601            assert_eq!(system.0[&signal], Disposition::Catch);
1602        }
1603        for signal in [SIGTERM, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU] {
1604            let origin = Location::dummy("origin");
1605            let result = trap_set.set_action(&mut system, signal, Action::Default, origin, false);
1606            assert_eq!(result, Err(SetActionError::InitiallyIgnored));
1607            assert_eq!(system.0[&signal], Disposition::Ignore);
1608        }
1609    }
1610
1611    #[test]
1612    fn resetting_trap_from_ignore_override_after_enabling_internal_dispositions() {
1613        let mut system = DummySystem::default();
1614        ignore_signals(&mut system);
1615        let mut trap_set = TrapSet::default();
1616        trap_set
1617            .enable_internal_disposition_for_sigchld(&mut system)
1618            .unwrap();
1619        trap_set
1620            .enable_internal_dispositions_for_terminators(&mut system)
1621            .unwrap();
1622        trap_set
1623            .enable_internal_dispositions_for_stoppers(&mut system)
1624            .unwrap();
1625
1626        for signal in [SIGCHLD, SIGINT] {
1627            let origin = Location::dummy("origin");
1628            let result =
1629                trap_set.set_action(&mut system, signal, Action::Ignore, origin.clone(), true);
1630            assert_eq!(result, Ok(()));
1631            assert_eq!(
1632                trap_set.get_state(signal),
1633                (
1634                    Some(&TrapState {
1635                        action: Action::Ignore,
1636                        origin: Origin::User(origin),
1637                        pending: false
1638                    }),
1639                    None
1640                )
1641            );
1642            assert_eq!(system.0[&signal], Disposition::Catch);
1643        }
1644        for signal in [SIGTERM, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU] {
1645            let origin = Location::dummy("origin");
1646            let result =
1647                trap_set.set_action(&mut system, signal, Action::Ignore, origin.clone(), true);
1648            assert_eq!(result, Ok(()));
1649            assert_eq!(
1650                trap_set.get_state(signal),
1651                (
1652                    Some(&TrapState {
1653                        action: Action::Ignore,
1654                        origin: Origin::User(origin),
1655                        pending: false
1656                    }),
1657                    None
1658                )
1659            );
1660            assert_eq!(system.0[&signal], Disposition::Ignore);
1661        }
1662    }
1663
1664    #[test]
1665    fn disabling_internal_disposition_with_ignore_trap() {
1666        let signals = [SIGCHLD, SIGINT, SIGTERM, SIGQUIT, SIGTSTP, SIGTTIN, SIGTTOU];
1667
1668        let mut system = DummySystem::default();
1669        let mut trap_set = TrapSet::default();
1670        trap_set
1671            .enable_internal_disposition_for_sigchld(&mut system)
1672            .unwrap();
1673        trap_set
1674            .enable_internal_dispositions_for_terminators(&mut system)
1675            .unwrap();
1676        let origin = Location::dummy("origin");
1677        for signal in signals {
1678            trap_set
1679                .set_action(&mut system, signal, Action::Ignore, origin.clone(), false)
1680                .unwrap();
1681        }
1682        trap_set.disable_internal_dispositions(&mut system).unwrap();
1683
1684        for signal in signals {
1685            assert_eq!(
1686                trap_set.get_state(signal),
1687                (
1688                    Some(&TrapState {
1689                        action: Action::Ignore,
1690                        origin: Origin::User(origin.clone()),
1691                        pending: false
1692                    }),
1693                    None
1694                )
1695            );
1696            assert_eq!(system.0[&signal], Disposition::Ignore);
1697        }
1698    }
1699}