1#[cfg(doc)]
20use super::TrapSet;
21use super::{Condition, SignalSystem};
22use crate::system::{Disposition, Errno};
23use std::collections::btree_map::{Entry, VacantEntry};
24use std::rc::Rc;
25use thiserror::Error;
26use yash_syntax::source::Location;
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::r#virtual::{SIGCHLD, SIGQUIT, SIGTSTP, SIGTTOU, SIGUSR1};
447 use assert_matches::assert_matches;
448 use std::collections::BTreeMap;
449
450 struct UnusedSystem;
451
452 impl SignalSystem for UnusedSystem {
453 fn signal_name_from_number(&self, number: crate::signal::Number) -> crate::signal::Name {
454 unreachable!("signal_name_from_number({number})")
455 }
456 fn signal_number_from_name(
457 &self,
458 name: crate::signal::Name,
459 ) -> Option<crate::signal::Number> {
460 unreachable!("signal_number_from_name({name})")
461 }
462 fn get_disposition(&self, signal: crate::signal::Number) -> Result<Disposition, Errno> {
463 unreachable!("get_disposition({signal})")
464 }
465 fn set_disposition(
466 &mut self,
467 signal: crate::signal::Number,
468 disposition: Disposition,
469 ) -> Result<Disposition, Errno> {
470 unreachable!("set_disposition({signal}, {disposition:?})")
471 }
472 }
473
474 #[test]
475 fn insertion_with_default_inherited_disposition() {
476 let system = DummySystem::default();
477 let mut map = BTreeMap::new();
478 let entry = map.entry(SIGCHLD.into());
479 let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
480 assert_eq!(
481 state.current_state(),
482 &TrapState {
483 action: Action::Default,
484 origin: Origin::Inherited,
485 pending: false,
486 }
487 );
488 assert_eq!(state.parent_state(), None);
489 assert_eq!(state.internal_disposition(), Disposition::Default);
490 }
491
492 #[test]
493 fn insertion_with_inherited_disposition_of_ignore() {
494 let mut system = DummySystem::default();
495 system.0.insert(SIGCHLD, Disposition::Ignore);
496 let mut map = BTreeMap::new();
497 let entry = map.entry(SIGCHLD.into());
498 let state = GrandState::insert_from_system_if_vacant(&system, entry).unwrap();
499 assert_eq!(
500 state.current_state(),
501 &TrapState {
502 action: Action::Ignore,
503 origin: Origin::Inherited,
504 pending: false,
505 }
506 );
507 assert_eq!(state.parent_state(), None);
508 assert_eq!(state.internal_disposition(), Disposition::Default);
509 }
510
511 #[test]
512 fn insertion_with_occupied_entry() {
513 let mut system = DummySystem::default();
514 let mut map = BTreeMap::new();
515 let entry = map.entry(SIGCHLD.into());
516 let origin = Location::dummy("origin");
517 let action = Action::Command("echo".into());
518 let _ = GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
519
520 let entry = map.entry(SIGCHLD.into());
523 let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
524 assert_eq!(
525 state.current_state(),
526 &TrapState {
527 action,
528 origin: Origin::User(origin),
529 pending: false,
530 }
531 );
532 assert_eq!(state.parent_state(), None);
533 assert_eq!(state.internal_disposition(), Disposition::Default);
534 }
535
536 #[test]
537 fn insertion_with_non_signal_condition() {
538 let mut map = BTreeMap::new();
539 let entry = map.entry(Condition::Exit);
540 let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
541 assert_eq!(
542 state.current_state(),
543 &TrapState {
544 action: Action::Default,
545 origin: Origin::Inherited,
546 pending: false,
547 }
548 );
549 assert_eq!(state.parent_state(), None);
550 assert_eq!(state.internal_disposition(), Disposition::Default);
551 }
552
553 #[test]
554 fn setting_trap_to_ignore_without_override_ignore() {
555 let mut system = DummySystem::default();
556 let mut map = BTreeMap::new();
557 let entry = map.entry(SIGCHLD.into());
558 let origin = Location::dummy("origin");
559
560 let result =
561 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
562 assert_eq!(result, Ok(()));
563 assert_eq!(
564 map[&SIGCHLD.into()].current_state(),
565 &TrapState {
566 action: Action::Ignore,
567 origin: Origin::User(origin),
568 pending: false
569 }
570 );
571 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
572 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
573 }
574
575 #[test]
576 fn setting_trap_to_ignore_with_override_ignore() {
577 let mut system = DummySystem::default();
578 let mut map = BTreeMap::new();
579 let entry = map.entry(SIGCHLD.into());
580 let origin = Location::dummy("origin");
581
582 let result =
583 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
584 assert_eq!(result, Ok(()));
585 assert_eq!(
586 map[&SIGCHLD.into()].current_state(),
587 &TrapState {
588 action: Action::Ignore,
589 origin: Origin::User(origin),
590 pending: false
591 }
592 );
593 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
594 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
595 }
596
597 #[test]
598 fn setting_trap_to_command() {
599 let mut system = DummySystem::default();
600 let mut map = BTreeMap::new();
601 let entry = map.entry(SIGCHLD.into());
602 let action = Action::Command("echo".into());
603 let origin = Location::dummy("origin");
604
605 let result =
606 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
607 assert_eq!(result, Ok(()));
608 assert_eq!(
609 map[&SIGCHLD.into()].current_state(),
610 &TrapState {
611 action,
612 origin: Origin::User(origin),
613 pending: false
614 }
615 );
616 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
617 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
618 }
619
620 #[test]
621 fn setting_trap_to_default() {
622 let mut system = DummySystem::default();
623 let mut map = BTreeMap::new();
624 let entry = map.entry(SIGCHLD.into());
625 let origin = Location::dummy("foo");
626 GrandState::set_action(&mut system, entry, Action::Ignore, origin, false).unwrap();
627
628 let entry = map.entry(SIGCHLD.into());
629 let origin = Location::dummy("bar");
630 let result =
631 GrandState::set_action(&mut system, entry, Action::Default, origin.clone(), false);
632 assert_eq!(result, Ok(()));
633 assert_eq!(
634 map[&SIGCHLD.into()].current_state(),
635 &TrapState {
636 action: Action::Default,
637 origin: Origin::User(origin),
638 pending: false
639 }
640 );
641 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
642 assert_eq!(system.0[&SIGCHLD], Disposition::Default);
643 }
644
645 #[test]
646 fn resetting_trap_from_ignore_no_override() {
647 let mut system = DummySystem::default();
648 system.0.insert(SIGCHLD, Disposition::Ignore);
649 let mut map = BTreeMap::new();
650 let entry = map.entry(SIGCHLD.into());
651 let origin = Location::dummy("foo");
652 let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
653 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
654
655 let entry = map.entry(SIGCHLD.into());
657 let origin = Location::dummy("bar");
658 let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
659 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
660
661 assert_eq!(
662 map[&SIGCHLD.into()].current_state(),
663 &TrapState {
664 action: Action::Ignore,
665 origin: Origin::Inherited,
666 pending: false
667 }
668 );
669 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
670 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
671 }
672
673 #[test]
674 fn resetting_trap_from_ignore_override() {
675 let mut system = DummySystem::default();
676 system.0.insert(SIGCHLD, Disposition::Ignore);
677 let mut map = BTreeMap::new();
678 let entry = map.entry(SIGCHLD.into());
679 let origin = Location::dummy("origin");
680 let result =
681 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
682 assert_eq!(result, Ok(()));
683 assert_eq!(
684 map[&SIGCHLD.into()].current_state(),
685 &TrapState {
686 action: Action::Ignore,
687 origin: Origin::User(origin),
688 pending: false
689 }
690 );
691 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
692 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
693 }
694
695 #[test]
696 fn internal_disposition_ignore() {
697 let mut system = DummySystem::default();
698 let mut map = BTreeMap::new();
699 let entry = map.entry(SIGCHLD.into());
700
701 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
702 assert_eq!(result, Ok(()));
703 assert_eq!(
704 map[&SIGCHLD.into()].internal_disposition(),
705 Disposition::Ignore
706 );
707 assert_eq!(
708 map[&SIGCHLD.into()].current_state(),
709 &TrapState {
710 action: Action::Default,
711 origin: Origin::Inherited,
712 pending: false
713 }
714 );
715 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
716 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
717 }
718
719 #[test]
720 fn internal_disposition_catch() {
721 let mut system = DummySystem::default();
722 let mut map = BTreeMap::new();
723 let entry = map.entry(SIGCHLD.into());
724
725 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
726 assert_eq!(result, Ok(()));
727 assert_eq!(
728 map[&SIGCHLD.into()].internal_disposition(),
729 Disposition::Catch
730 );
731 assert_eq!(
732 map[&SIGCHLD.into()].current_state(),
733 &TrapState {
734 action: Action::Default,
735 origin: Origin::Inherited,
736 pending: false
737 }
738 );
739 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
740 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
741 }
742
743 #[test]
744 fn action_ignore_and_internal_disposition_catch() {
745 let mut system = DummySystem::default();
746 let mut map = BTreeMap::new();
747 let entry = map.entry(SIGCHLD.into());
748 let origin = Location::dummy("origin");
749 let _ = GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
750 let entry = map.entry(SIGCHLD.into());
751
752 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
753 assert_eq!(result, Ok(()));
754 assert_eq!(
755 map[&SIGCHLD.into()].internal_disposition(),
756 Disposition::Catch
757 );
758 assert_eq!(
759 map[&SIGCHLD.into()].current_state(),
760 &TrapState {
761 action: Action::Ignore,
762 origin: Origin::User(origin),
763 pending: false
764 }
765 );
766 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
767 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
768 }
769
770 #[test]
771 fn action_catch_and_internal_disposition_ignore() {
772 let mut system = DummySystem::default();
773 let mut map = BTreeMap::new();
774 let entry = map.entry(SIGCHLD.into());
775 let origin = Location::dummy("origin");
776 let action = Action::Command("echo".into());
777 let _ = GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
778 let entry = map.entry(SIGCHLD.into());
779
780 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
781 assert_eq!(result, Ok(()));
782 assert_eq!(
783 map[&SIGCHLD.into()].internal_disposition(),
784 Disposition::Ignore
785 );
786 assert_eq!(
787 map[&SIGCHLD.into()].current_state(),
788 &TrapState {
789 action,
790 origin: Origin::User(origin),
791 pending: false
792 }
793 );
794 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
795 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
796 }
797
798 #[test]
799 fn set_internal_disposition_for_initially_defaulted_signal_then_allow_override() {
800 let mut system = DummySystem::default();
801 let mut map = BTreeMap::new();
802 let entry = map.entry(SIGTTOU.into());
803 let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
804 let entry = map.entry(SIGTTOU.into());
805 let origin = Location::dummy("origin");
806 let action = Action::Command("echo".into());
807
808 let result =
809 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
810 assert_eq!(result, Ok(()));
811 assert_eq!(
812 map[&SIGTTOU.into()].internal_disposition(),
813 Disposition::Ignore
814 );
815 assert_eq!(
816 map[&SIGTTOU.into()].current_state(),
817 &TrapState {
818 action,
819 origin: Origin::User(origin),
820 pending: false
821 }
822 );
823 assert_eq!(map[&SIGTTOU.into()].parent_state(), None);
824 assert_eq!(system.0[&SIGTTOU], Disposition::Catch);
825 }
826
827 #[test]
828 fn set_internal_disposition_for_initially_ignored_signal_then_reject_override() {
829 let mut system = DummySystem::default();
830 system.0.insert(SIGTTOU, Disposition::Ignore);
831 let mut map = BTreeMap::new();
832 let cond = SIGTTOU.into();
833 let entry = map.entry(cond);
834 let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
835 let entry = map.entry(cond);
836 let origin = Location::dummy("origin");
837 let action = Action::Command("echo".into());
838
839 let result = GrandState::set_action(&mut system, entry, action, origin, false);
840 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
841 assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
842 assert_eq!(
843 map[&cond].current_state(),
844 &TrapState {
845 action: Action::Ignore,
846 origin: Origin::Inherited,
847 pending: false
848 }
849 );
850 assert_eq!(map[&cond].parent_state(), None);
851 assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
852 }
853
854 #[test]
855 fn enter_subshell_with_internal_disposition_keeping_internal_disposition() {
856 let mut system = DummySystem::default();
857 let mut map = BTreeMap::new();
858 let cond = SIGCHLD.into();
859 GrandState::set_internal_disposition(&mut system, map.entry(cond), Disposition::Catch)
860 .unwrap();
861
862 let result = map.get_mut(&cond).unwrap().enter_subshell(
863 &mut system,
864 cond,
865 EnterSubshellOption::KeepInternalDisposition,
866 );
867 assert_eq!(result, Ok(()));
868 assert_eq!(map[&cond].internal_disposition(), Disposition::Catch);
869 assert_eq!(
870 map[&cond].current_state(),
871 &TrapState {
872 action: Action::Default,
873 origin: Origin::Inherited,
874 pending: false
875 }
876 );
877 assert_eq!(map[&cond].parent_state(), None);
878 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
879 }
880
881 #[test]
882 fn enter_subshell_with_internal_disposition_clearing_internal_disposition() {
883 let mut system = DummySystem::default();
884 let mut map = BTreeMap::new();
885 let cond = SIGCHLD.into();
886 let entry = map.entry(cond);
887 GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
888
889 let result = map.get_mut(&cond).unwrap().enter_subshell(
890 &mut system,
891 cond,
892 EnterSubshellOption::ClearInternalDisposition,
893 );
894 assert_eq!(result, Ok(()));
895 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
896 assert_eq!(
897 map[&cond].current_state(),
898 &TrapState {
899 action: Action::Default,
900 origin: Origin::Inherited,
901 pending: false
902 }
903 );
904 assert_eq!(map[&cond].parent_state(), None);
905 assert_eq!(system.0[&SIGCHLD], Disposition::Default);
906 }
907
908 #[test]
909 fn enter_subshell_with_ignore_and_no_internal_disposition() {
910 let mut system = DummySystem::default();
911 let mut map = BTreeMap::new();
912 let cond = SIGCHLD.into();
913 let entry = map.entry(cond);
914 let origin = Location::dummy("foo");
915 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
916
917 let result = map.get_mut(&cond).unwrap().enter_subshell(
918 &mut system,
919 cond,
920 EnterSubshellOption::KeepInternalDisposition,
921 );
922 assert_eq!(result, Ok(()));
923 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
924 assert_eq!(
925 map[&cond].current_state(),
926 &TrapState {
927 action: Action::Ignore,
928 origin: Origin::User(origin),
929 pending: false
930 }
931 );
932 assert_eq!(map[&cond].parent_state(), None);
933 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
934 }
935
936 #[test]
937 fn enter_subshell_with_ignore_clearing_internal_disposition() {
938 let mut system = DummySystem::default();
939 let mut map = BTreeMap::new();
940 let cond = SIGCHLD.into();
941 let entry = map.entry(cond);
942 let origin = Location::dummy("foo");
943 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
944 let entry = map.entry(cond);
945 GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
946
947 let result = map.get_mut(&cond).unwrap().enter_subshell(
948 &mut system,
949 cond,
950 EnterSubshellOption::ClearInternalDisposition,
951 );
952 assert_eq!(result, Ok(()));
953 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
954 assert_eq!(
955 map[&cond].current_state(),
956 &TrapState {
957 action: Action::Ignore,
958 origin: Origin::User(origin),
959 pending: false
960 }
961 );
962 assert_eq!(map[&cond].parent_state(), None);
963 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
964 }
965
966 #[test]
967 fn enter_subshell_with_command_and_no_internal_disposition() {
968 let mut system = DummySystem::default();
969 let mut map = BTreeMap::new();
970 let cond = SIGCHLD.into();
971 let entry = map.entry(cond);
972 let origin = Location::dummy("foo");
973 let action = Action::Command("echo".into());
974 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
975
976 let result = map.get_mut(&cond).unwrap().enter_subshell(
977 &mut system,
978 cond,
979 EnterSubshellOption::ClearInternalDisposition,
980 );
981 assert_eq!(result, Ok(()));
982 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
983 assert_eq!(
984 map[&cond].current_state(),
985 &TrapState {
986 action: Action::Default,
987 origin: Origin::Subshell,
988 pending: false
989 }
990 );
991 assert_eq!(
992 map[&cond].parent_state(),
993 Some(&TrapState {
994 action,
995 origin: Origin::User(origin),
996 pending: false
997 })
998 );
999 assert_eq!(system.0[&SIGCHLD], Disposition::Default);
1000 }
1001
1002 #[test]
1003 fn enter_subshell_with_command_keeping_internal_disposition() {
1004 let mut system = DummySystem::default();
1005 let mut map = BTreeMap::new();
1006 let cond = SIGTSTP.into();
1007 let entry = map.entry(cond);
1008 let origin = Location::dummy("foo");
1009 let action = Action::Command("echo".into());
1010 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1011 let entry = map.entry(cond);
1012 GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
1013
1014 let result = map.get_mut(&cond).unwrap().enter_subshell(
1015 &mut system,
1016 cond,
1017 EnterSubshellOption::KeepInternalDisposition,
1018 );
1019 assert_eq!(result, Ok(()));
1020 assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
1021 assert_eq!(
1022 map[&cond].current_state(),
1023 &TrapState {
1024 action: Action::Default,
1025 origin: Origin::Subshell,
1026 pending: false
1027 }
1028 );
1029 assert_eq!(
1030 map[&cond].parent_state(),
1031 Some(&TrapState {
1032 action,
1033 origin: Origin::User(origin),
1034 pending: false
1035 })
1036 );
1037 assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
1038 }
1039
1040 #[test]
1041 fn enter_subshell_with_command_clearing_internal_disposition() {
1042 let mut system = DummySystem::default();
1043 let mut map = BTreeMap::new();
1044 let cond = SIGTSTP.into();
1045 let entry = map.entry(cond);
1046 let origin = Location::dummy("foo");
1047 let action = Action::Command("echo".into());
1048 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1049 let entry = map.entry(cond);
1050 GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
1051
1052 let result = map.get_mut(&cond).unwrap().enter_subshell(
1053 &mut system,
1054 cond,
1055 EnterSubshellOption::ClearInternalDisposition,
1056 );
1057 assert_eq!(result, Ok(()));
1058 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1059 assert_eq!(
1060 map[&cond].current_state(),
1061 &TrapState {
1062 action: Action::Default,
1063 origin: Origin::Subshell,
1064 pending: false
1065 }
1066 );
1067 assert_eq!(
1068 map[&cond].parent_state(),
1069 Some(&TrapState {
1070 action,
1071 origin: Origin::User(origin),
1072 pending: false
1073 })
1074 );
1075 assert_eq!(system.0[&SIGTSTP], Disposition::Default);
1076 }
1077
1078 #[test]
1079 fn enter_subshell_with_command_ignoring() {
1080 let mut system = DummySystem::default();
1081 let mut map = BTreeMap::new();
1082 let cond = SIGQUIT.into();
1083 let entry = map.entry(cond);
1084 let origin = Location::dummy("foo");
1085 let action = Action::Command("echo".into());
1086 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1087
1088 let result = map.get_mut(&cond).unwrap().enter_subshell(
1089 &mut system,
1090 cond,
1091 EnterSubshellOption::Ignore,
1092 );
1093 assert_eq!(result, Ok(()));
1094 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1095 assert_eq!(
1096 map[&cond].current_state(),
1097 &TrapState {
1098 action: Action::Ignore,
1099 origin: Origin::Subshell,
1100 pending: false
1101 }
1102 );
1103 assert_eq!(
1104 map[&cond].parent_state(),
1105 Some(&TrapState {
1106 action,
1107 origin: Origin::User(origin),
1108 pending: false
1109 })
1110 );
1111 assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1112 }
1113
1114 #[test]
1115 fn ignoring_initially_defaulted_signal() {
1116 let mut system = DummySystem::default();
1117 let mut map = BTreeMap::new();
1118 let cond = SIGQUIT.into();
1119 let entry = map.entry(cond);
1120 let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
1121
1122 let result = GrandState::ignore(&mut system, vacant);
1123 assert_eq!(result, Ok(()));
1124 assert_eq!(
1125 map[&cond].current_state(),
1126 &TrapState {
1127 action: Action::Ignore,
1128 origin: Origin::Subshell,
1129 pending: false
1130 }
1131 );
1132 assert_eq!(map[&cond].parent_state(), None);
1133 assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1134
1135 let entry = map.entry(cond);
1136 let origin = Location::dummy("foo");
1137 let action = Action::Command("echo".into());
1138 let result = GrandState::set_action(&mut system, entry, action, origin, false);
1139 assert_eq!(result, Ok(()));
1140 }
1141
1142 #[test]
1143 fn ignoring_initially_ignored_signal() {
1144 let mut system = DummySystem::default();
1145 system.0.insert(SIGQUIT, Disposition::Ignore);
1146 let mut map = BTreeMap::new();
1147 let cond = SIGQUIT.into();
1148 let entry = map.entry(cond);
1149 let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
1150
1151 let result = GrandState::ignore(&mut system, vacant);
1152 assert_eq!(result, Ok(()));
1153 assert_eq!(
1154 map[&cond].current_state(),
1155 &TrapState {
1156 action: Action::Ignore,
1157 origin: Origin::Inherited,
1158 pending: false
1159 }
1160 );
1161 assert_eq!(map[&cond].parent_state(), None);
1162 assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
1163
1164 let entry = map.entry(cond);
1165 let origin = Location::dummy("foo");
1166 let action = Action::Command("echo".into());
1167 let result = GrandState::set_action(&mut system, entry, action, origin, false);
1168 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
1169 }
1170
1171 #[test]
1172 fn clearing_parent_setting() {
1173 let mut system = DummySystem::default();
1174 let mut map = BTreeMap::new();
1175 let cond = SIGCHLD.into();
1176 let entry = map.entry(cond);
1177 let origin = Location::dummy("foo");
1178 let action = Action::Command("echo".into());
1179 GrandState::set_action(&mut system, entry, action, origin, false).unwrap();
1180 let state = map.get_mut(&cond).unwrap();
1181 state
1182 .enter_subshell(
1183 &mut system,
1184 cond,
1185 EnterSubshellOption::ClearInternalDisposition,
1186 )
1187 .unwrap();
1188
1189 state.clear_parent_state();
1190 assert_eq!(
1191 state.current_state(),
1192 &TrapState {
1193 action: Action::Default,
1194 origin: Origin::Subshell,
1195 pending: false
1196 }
1197 );
1198 assert_eq!(state.parent_state(), None);
1199 }
1200
1201 #[test]
1202 fn marking_as_caught_and_handling() {
1203 let mut system = DummySystem::default();
1204 let mut map = BTreeMap::new();
1205 let cond = SIGUSR1.into();
1206 let entry = map.entry(cond);
1207 let origin = Location::dummy("foo");
1208 let action = Action::Command("echo".into());
1209 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
1210
1211 let state = &mut map.get_mut(&cond).unwrap();
1212 state.mark_as_caught();
1213 let expected_trap = TrapState {
1214 action,
1215 origin: Origin::User(origin),
1216 pending: true,
1217 };
1218 assert_eq!(state.current_state(), &expected_trap);
1219 assert_eq!(state.parent_state(), None);
1220
1221 let trap = state.handle_if_caught();
1222 let expected_trap = TrapState {
1223 pending: false,
1224 ..expected_trap
1225 };
1226 assert_eq!(trap, Some(&expected_trap));
1227
1228 let trap = state.handle_if_caught();
1229 assert_eq!(trap, None);
1230 }
1231}