1#[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#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)]
30pub enum Action {
31 #[default]
36 Default,
37
38 Ignore,
40
41 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#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
57pub enum SetActionError {
58 #[error("the signal has been ignored since startup")]
60 InitiallyIgnored,
61
62 #[error("cannot set a trap for SIGKILL")]
64 SIGKILL,
65
66 #[error("cannot set a trap for SIGSTOP")]
68 SIGSTOP,
69
70 #[error(transparent)]
72 SystemError(#[from] Errno),
73}
74
75#[derive(Clone, Debug, Default, Eq, PartialEq)]
79pub enum Origin {
80 #[default]
85 Inherited,
86
87 Subshell,
89
90 User(Location),
95}
96
97#[derive(Clone, Debug, Default, Eq, PartialEq)]
99pub struct TrapState {
100 pub action: Action,
102
103 pub origin: Origin,
105
106 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 Disposition::Catch => Action::Default,
121 };
122 TrapState {
123 action,
124 origin: Origin::Inherited,
125 pending: false,
126 }
127 }
128}
129
130#[derive(Clone, Copy, Debug, Eq, PartialEq)]
132pub enum EnterSubshellOption {
133 KeepInternalDisposition,
135 ClearInternalDisposition,
137 Ignore,
140}
141
142#[derive(Clone, Debug)]
144pub struct GrandState {
145 current_state: TrapState,
147
148 parent_state: Option<TrapState>,
150
151 internal_disposition: Disposition,
160}
161
162impl GrandState {
163 #[inline]
165 #[must_use]
166 pub fn current_state(&self) -> &TrapState {
167 &self.current_state
168 }
169
170 #[inline]
177 #[must_use]
178 pub fn parent_state(&self) -> Option<&TrapState> {
179 self.parent_state.as_ref()
180 }
181
182 pub fn clear_parent_state(&mut self) {
184 self.parent_state = None;
185 }
186
187 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 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 #[must_use]
286 pub fn internal_disposition(&self) -> Disposition {
287 self.internal_disposition
288 }
289
290 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 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 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 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 pub fn mark_as_caught(&mut self) {
428 self.current_state.pending = true;
429 }
430
431 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 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 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}