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, Eq, PartialEq)]
77pub struct TrapState {
78 pub action: Action,
80 pub origin: Location,
83 pub pending: bool,
86}
87
88#[derive(Clone, Debug, Eq, PartialEq)]
90pub enum Setting {
91 InitiallyDefaulted,
95 InitiallyIgnored,
99 UserSpecified(TrapState),
101}
102
103impl Setting {
104 pub fn as_trap(&self) -> Option<&TrapState> {
105 if let Setting::UserSpecified(trap) = self {
106 Some(trap)
107 } else {
108 None
109 }
110 }
111
112 fn is_user_defined_command(&self) -> bool {
113 matches!(
114 self,
115 Setting::UserSpecified(TrapState {
116 action: Action::Command(_),
117 ..
118 })
119 )
120 }
121
122 pub fn from_initial_disposition(disposition: Disposition) -> Self {
123 match disposition {
124 Disposition::Default | Disposition::Catch => Self::InitiallyDefaulted,
125 Disposition::Ignore => Self::InitiallyIgnored,
126 }
127 }
128}
129
130impl From<&Setting> for Disposition {
131 fn from(state: &Setting) -> Self {
132 match state {
133 Setting::InitiallyDefaulted => Disposition::Default,
134 Setting::InitiallyIgnored => Disposition::Ignore,
135 Setting::UserSpecified(trap) => (&trap.action).into(),
136 }
137 }
138}
139
140#[derive(Clone, Copy, Debug, Eq, PartialEq)]
142pub enum EnterSubshellOption {
143 KeepInternalDisposition,
145 ClearInternalDisposition,
147 Ignore,
150}
151
152#[derive(Clone, Debug)]
154pub struct GrandState {
155 current_setting: Setting,
157
158 parent_setting: Option<Setting>,
160
161 internal_disposition: Disposition,
170}
171
172impl GrandState {
173 #[must_use]
182 pub fn get_state(&self) -> (Option<&TrapState>, Option<&TrapState>) {
183 let current = self.current_setting.as_trap();
184 let parent = self.parent_setting.as_ref().and_then(Setting::as_trap);
185 (current, parent)
186 }
187
188 pub fn clear_parent_setting(&mut self) {
190 self.parent_setting = None;
191 }
192
193 pub fn set_action<S: SignalSystem>(
195 system: &mut S,
196 entry: Entry<Condition, GrandState>,
197 action: Action,
198 origin: Location,
199 override_ignore: bool,
200 ) -> Result<(), SetActionError> {
201 let cond = *entry.key();
202 let setting = Setting::UserSpecified(TrapState {
203 action,
204 origin,
205 pending: false,
206 });
207 let disposition = (&setting).into();
208
209 match entry {
210 Entry::Vacant(vacant) => {
211 if let Condition::Signal(signal) = cond {
212 if !override_ignore {
213 let initial_disposition =
214 system.set_disposition(signal, Disposition::Ignore)?;
215 if initial_disposition == Disposition::Ignore {
216 vacant.insert(GrandState {
217 current_setting: Setting::InitiallyIgnored,
218 parent_setting: None,
219 internal_disposition: Disposition::Default,
220 });
221 return Err(SetActionError::InitiallyIgnored);
222 }
223 }
224
225 if override_ignore || disposition != Disposition::Ignore {
226 system.set_disposition(signal, disposition)?;
227 }
228 }
229
230 vacant.insert(GrandState {
231 current_setting: setting,
232 parent_setting: None,
233 internal_disposition: Disposition::Default,
234 });
235 }
236
237 Entry::Occupied(mut occupied) => {
238 let state = occupied.get_mut();
239 if !override_ignore && state.current_setting == Setting::InitiallyIgnored {
240 return Err(SetActionError::InitiallyIgnored);
241 }
242
243 if let Condition::Signal(signal) = cond {
244 let internal = state.internal_disposition;
245 let old_disposition = internal.max((&state.current_setting).into());
246 let new_disposition = internal.max(disposition);
247 if old_disposition != new_disposition {
248 system.set_disposition(signal, new_disposition)?;
249 }
250 }
251
252 state.current_setting = setting;
253 }
254 }
255
256 Ok(())
257 }
258
259 #[must_use]
261 pub fn internal_disposition(&self) -> Disposition {
262 self.internal_disposition
263 }
264
265 pub fn set_internal_disposition<S: SignalSystem>(
270 system: &mut S,
271 entry: Entry<Condition, GrandState>,
272 disposition: Disposition,
273 ) -> Result<(), Errno> {
274 let signal = match *entry.key() {
275 Condition::Signal(signal) => signal,
276 Condition::Exit => panic!("exit condition cannot have an internal disposition"),
277 };
278
279 match entry {
280 Entry::Vacant(_) if disposition == Disposition::Default => (),
281
282 Entry::Vacant(vacant) => {
283 let initial_disposition = system.set_disposition(signal, disposition)?;
284 vacant.insert(GrandState {
285 current_setting: Setting::from_initial_disposition(initial_disposition),
286 parent_setting: None,
287 internal_disposition: disposition,
288 });
289 }
290
291 Entry::Occupied(mut occupied) => {
292 let state = occupied.get_mut();
293 let setting = (&state.current_setting).into();
294 let old_disposition = state.internal_disposition.max(setting);
295 let new_disposition = disposition.max(setting);
296 if old_disposition != new_disposition {
297 system.set_disposition(signal, new_disposition)?;
298 }
299 state.internal_disposition = disposition;
300 }
301 }
302
303 Ok(())
304 }
305
306 pub fn enter_subshell<S: SignalSystem>(
314 &mut self,
315 system: &mut S,
316 cond: Condition,
317 option: EnterSubshellOption,
318 ) -> Result<(), Errno> {
319 let old_setting = (&self.current_setting).into();
320 let old_disposition = self.internal_disposition.max(old_setting);
321
322 if self.current_setting.is_user_defined_command() {
323 self.parent_setting = Some(std::mem::replace(
324 &mut self.current_setting,
325 Setting::InitiallyDefaulted,
326 ));
327 }
328
329 let new_setting = (&self.current_setting).into();
330 let new_disposition = match option {
331 EnterSubshellOption::KeepInternalDisposition => {
332 self.internal_disposition.max(new_setting)
333 }
334 EnterSubshellOption::ClearInternalDisposition => new_setting,
335 EnterSubshellOption::Ignore => Disposition::Ignore,
336 };
337 if old_disposition != new_disposition {
338 if let Condition::Signal(signal) = cond {
339 system.set_disposition(signal, new_disposition)?;
340 }
341 }
342 self.internal_disposition = match option {
343 EnterSubshellOption::KeepInternalDisposition => self.internal_disposition,
344 EnterSubshellOption::ClearInternalDisposition | EnterSubshellOption::Ignore => {
345 Disposition::Default
346 }
347 };
348 Ok(())
349 }
350
351 pub fn ignore<S: SignalSystem>(
361 system: &mut S,
362 vacant: VacantEntry<Condition, GrandState>,
363 ) -> Result<(), Errno> {
364 let signal = match *vacant.key() {
365 Condition::Signal(signal) => signal,
366 Condition::Exit => panic!("exit condition cannot be ignored"),
367 };
368 let initial_disposition = system.set_disposition(signal, Disposition::Ignore)?;
369 vacant.insert(GrandState {
370 current_setting: Setting::from_initial_disposition(initial_disposition),
371 parent_setting: None,
372 internal_disposition: Disposition::Default,
373 });
374 Ok(())
375 }
376
377 pub fn mark_as_caught(&mut self) {
381 if let Setting::UserSpecified(state) = &mut self.current_setting {
382 state.pending = true;
383 }
384 }
385
386 pub fn handle_if_caught(&mut self) -> Option<&TrapState> {
388 match &mut self.current_setting {
389 Setting::UserSpecified(trap) if trap.pending => {
390 trap.pending = false;
391 Some(trap)
392 }
393 _ => None,
394 }
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::super::tests::DummySystem;
401 use super::*;
402 use crate::system::r#virtual::{SIGCHLD, SIGQUIT, SIGTSTP, SIGTTOU, SIGUSR1};
403 use assert_matches::assert_matches;
404 use std::collections::BTreeMap;
405
406 #[test]
407 fn setting_trap_to_ignore_without_override_ignore() {
408 let mut system = DummySystem::default();
409 let mut map = BTreeMap::new();
410 let entry = map.entry(SIGCHLD.into());
411 let origin = Location::dummy("origin");
412
413 let result =
414 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
415 assert_eq!(result, Ok(()));
416 assert_eq!(
417 map[&SIGCHLD.into()].get_state(),
418 (
419 Some(&TrapState {
420 action: Action::Ignore,
421 origin,
422 pending: false
423 }),
424 None
425 )
426 );
427 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
428 }
429
430 #[test]
431 fn setting_trap_to_ignore_with_override_ignore() {
432 let mut system = DummySystem::default();
433 let mut map = BTreeMap::new();
434 let entry = map.entry(SIGCHLD.into());
435 let origin = Location::dummy("origin");
436
437 let result =
438 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
439 assert_eq!(result, Ok(()));
440 assert_eq!(
441 map[&SIGCHLD.into()].get_state(),
442 (
443 Some(&TrapState {
444 action: Action::Ignore,
445 origin,
446 pending: false
447 }),
448 None
449 )
450 );
451 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
452 }
453
454 #[test]
455 fn setting_trap_to_command() {
456 let mut system = DummySystem::default();
457 let mut map = BTreeMap::new();
458 let entry = map.entry(SIGCHLD.into());
459 let action = Action::Command("echo".into());
460 let origin = Location::dummy("origin");
461
462 let result =
463 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
464 assert_eq!(result, Ok(()));
465 assert_eq!(
466 map[&SIGCHLD.into()].get_state(),
467 (
468 Some(&TrapState {
469 action,
470 origin,
471 pending: false
472 }),
473 None
474 )
475 );
476 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
477 }
478
479 #[test]
480 fn setting_trap_to_default() {
481 let mut system = DummySystem::default();
482 let mut map = BTreeMap::new();
483 let entry = map.entry(SIGCHLD.into());
484 let origin = Location::dummy("foo");
485 GrandState::set_action(&mut system, entry, Action::Ignore, origin, false).unwrap();
486
487 let entry = map.entry(SIGCHLD.into());
488 let origin = Location::dummy("bar");
489 let result =
490 GrandState::set_action(&mut system, entry, Action::Default, origin.clone(), false);
491 assert_eq!(result, Ok(()));
492 assert_eq!(
493 map[&SIGCHLD.into()].get_state(),
494 (
495 Some(&TrapState {
496 action: Action::Default,
497 origin,
498 pending: false
499 }),
500 None
501 )
502 );
503 assert_eq!(system.0[&SIGCHLD], Disposition::Default);
504 }
505
506 #[test]
507 fn resetting_trap_from_ignore_no_override() {
508 let mut system = DummySystem::default();
509 system.0.insert(SIGCHLD, Disposition::Ignore);
510 let mut map = BTreeMap::new();
511 let entry = map.entry(SIGCHLD.into());
512 let origin = Location::dummy("foo");
513 let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
514 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
515
516 let entry = map.entry(SIGCHLD.into());
518 let origin = Location::dummy("bar");
519 let result = GrandState::set_action(&mut system, entry, Action::Ignore, origin, false);
520 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
521
522 assert_eq!(map[&SIGCHLD.into()].get_state(), (None, None));
523 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
524 }
525
526 #[test]
527 fn resetting_trap_from_ignore_override() {
528 let mut system = DummySystem::default();
529 system.0.insert(SIGCHLD, Disposition::Ignore);
530 let mut map = BTreeMap::new();
531 let entry = map.entry(SIGCHLD.into());
532 let origin = Location::dummy("origin");
533 let result =
534 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), true);
535 assert_eq!(result, Ok(()));
536 assert_eq!(
537 map[&SIGCHLD.into()].get_state(),
538 (
539 Some(&TrapState {
540 action: Action::Ignore,
541 origin,
542 pending: false
543 }),
544 None
545 )
546 );
547 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
548 }
549
550 #[test]
551 fn internal_disposition_ignore() {
552 let mut system = DummySystem::default();
553 let mut map = BTreeMap::new();
554 let entry = map.entry(SIGCHLD.into());
555
556 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
557 assert_eq!(result, Ok(()));
558 assert_eq!(
559 map[&SIGCHLD.into()].internal_disposition(),
560 Disposition::Ignore
561 );
562 assert_eq!(map[&SIGCHLD.into()].get_state(), (None, None));
563 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
564 }
565
566 #[test]
567 fn internal_disposition_catch() {
568 let mut system = DummySystem::default();
569 let mut map = BTreeMap::new();
570 let entry = map.entry(SIGCHLD.into());
571
572 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
573 assert_eq!(result, Ok(()));
574 assert_eq!(
575 map[&SIGCHLD.into()].internal_disposition(),
576 Disposition::Catch
577 );
578 assert_eq!(map[&SIGCHLD.into()].get_state(), (None, None));
579 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
580 }
581
582 #[test]
583 fn action_ignore_and_internal_disposition_catch() {
584 let mut system = DummySystem::default();
585 let mut map = BTreeMap::new();
586 let entry = map.entry(SIGCHLD.into());
587 let origin = Location::dummy("origin");
588 let _ = GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false);
589 let entry = map.entry(SIGCHLD.into());
590
591 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch);
592 assert_eq!(result, Ok(()));
593 assert_eq!(
594 map[&SIGCHLD.into()].internal_disposition(),
595 Disposition::Catch
596 );
597 assert_matches!(map[&SIGCHLD.into()].get_state(), (Some(state), None) => {
598 assert_eq!(state.action, Action::Ignore);
599 assert_eq!(state.origin, origin);
600 });
601 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
602 }
603
604 #[test]
605 fn action_catch_and_internal_disposition_ignore() {
606 let mut system = DummySystem::default();
607 let mut map = BTreeMap::new();
608 let entry = map.entry(SIGCHLD.into());
609 let origin = Location::dummy("origin");
610 let action = Action::Command("echo".into());
611 let _ = GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
612 let entry = map.entry(SIGCHLD.into());
613
614 let result = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
615 assert_eq!(result, Ok(()));
616 assert_eq!(
617 map[&SIGCHLD.into()].internal_disposition(),
618 Disposition::Ignore
619 );
620 assert_matches!(map[&SIGCHLD.into()].get_state(), (Some(state), None) => {
621 assert_eq!(state.action, action);
622 assert_eq!(state.origin, origin);
623 });
624 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
625 }
626
627 #[test]
628 fn set_internal_disposition_for_initially_defaulted_signal_then_allow_override() {
629 let mut system = DummySystem::default();
630 let mut map = BTreeMap::new();
631 let entry = map.entry(SIGTTOU.into());
632 let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
633 let entry = map.entry(SIGTTOU.into());
634 let origin = Location::dummy("origin");
635 let action = Action::Command("echo".into());
636
637 let result =
638 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false);
639 assert_eq!(result, Ok(()));
640 assert_eq!(
641 map[&SIGTTOU.into()].internal_disposition(),
642 Disposition::Ignore
643 );
644 assert_eq!(
645 map[&SIGTTOU.into()].get_state(),
646 (
647 Some(&TrapState {
648 action,
649 origin,
650 pending: false
651 }),
652 None
653 )
654 );
655 assert_eq!(system.0[&SIGTTOU], Disposition::Catch);
656 }
657
658 #[test]
659 fn set_internal_disposition_for_initially_ignored_signal_then_reject_override() {
660 let mut system = DummySystem::default();
661 system.0.insert(SIGTTOU, Disposition::Ignore);
662 let mut map = BTreeMap::new();
663 let cond = SIGTTOU.into();
664 let entry = map.entry(cond);
665 let _ = GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore);
666 let entry = map.entry(cond);
667 let origin = Location::dummy("origin");
668 let action = Action::Command("echo".into());
669
670 let result = GrandState::set_action(&mut system, entry, action, origin, false);
671 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
672 assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
673 assert_eq!(map[&cond].get_state(), (None, None));
674 assert_eq!(system.0[&SIGTTOU], Disposition::Ignore);
675 }
676
677 #[test]
678 fn enter_subshell_with_internal_disposition_keeping_internal_disposition() {
679 let mut system = DummySystem::default();
680 let mut map = BTreeMap::new();
681 let cond = SIGCHLD.into();
682 GrandState::set_internal_disposition(&mut system, map.entry(cond), Disposition::Catch)
683 .unwrap();
684
685 let result = map.get_mut(&cond).unwrap().enter_subshell(
686 &mut system,
687 cond,
688 EnterSubshellOption::KeepInternalDisposition,
689 );
690 assert_eq!(result, Ok(()));
691 assert_eq!(map[&cond].internal_disposition(), Disposition::Catch);
692 assert_eq!(map[&cond].get_state(), (None, None));
693 assert_eq!(system.0[&SIGCHLD], Disposition::Catch);
694 }
695
696 #[test]
697 fn enter_subshell_with_internal_disposition_clearing_internal_disposition() {
698 let mut system = DummySystem::default();
699 let mut map = BTreeMap::new();
700 let cond = SIGCHLD.into();
701 let entry = map.entry(cond);
702 GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
703
704 let result = map.get_mut(&cond).unwrap().enter_subshell(
705 &mut system,
706 cond,
707 EnterSubshellOption::ClearInternalDisposition,
708 );
709 assert_eq!(result, Ok(()));
710 assert_eq!(
711 map[&SIGCHLD.into()].internal_disposition(),
712 Disposition::Default
713 );
714 assert_eq!(map[&cond].get_state(), (None, None));
715 assert_eq!(system.0[&SIGCHLD], Disposition::Default);
716 }
717
718 #[test]
719 fn enter_subshell_with_ignore_and_no_internal_disposition() {
720 let mut system = DummySystem::default();
721 let mut map = BTreeMap::new();
722 let cond = SIGCHLD.into();
723 let entry = map.entry(cond);
724 let origin = Location::dummy("foo");
725 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
726
727 let result = map.get_mut(&cond).unwrap().enter_subshell(
728 &mut system,
729 cond,
730 EnterSubshellOption::KeepInternalDisposition,
731 );
732 assert_eq!(result, Ok(()));
733 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
734 assert_eq!(
735 map[&cond].get_state(),
736 (
737 Some(&TrapState {
738 action: Action::Ignore,
739 origin,
740 pending: false
741 }),
742 None
743 )
744 );
745 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
746 }
747
748 #[test]
749 fn enter_subshell_with_ignore_clearing_internal_disposition() {
750 let mut system = DummySystem::default();
751 let mut map = BTreeMap::new();
752 let cond = SIGCHLD.into();
753 let entry = map.entry(cond);
754 let origin = Location::dummy("foo");
755 GrandState::set_action(&mut system, entry, Action::Ignore, origin.clone(), false).unwrap();
756 let entry = map.entry(cond);
757 GrandState::set_internal_disposition(&mut system, entry, Disposition::Catch).unwrap();
758
759 let result = map.get_mut(&cond).unwrap().enter_subshell(
760 &mut system,
761 cond,
762 EnterSubshellOption::ClearInternalDisposition,
763 );
764 assert_eq!(result, Ok(()));
765 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
766 assert_eq!(
767 map[&cond].get_state(),
768 (
769 Some(&TrapState {
770 action: Action::Ignore,
771 origin,
772 pending: false
773 }),
774 None
775 )
776 );
777 assert_eq!(system.0[&SIGCHLD], Disposition::Ignore);
778 }
779
780 #[test]
781 fn enter_subshell_with_command_and_no_internal_disposition() {
782 let mut system = DummySystem::default();
783 let mut map = BTreeMap::new();
784 let cond = SIGCHLD.into();
785 let entry = map.entry(cond);
786 let origin = Location::dummy("foo");
787 let action = Action::Command("echo".into());
788 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
789
790 let result = map.get_mut(&cond).unwrap().enter_subshell(
791 &mut system,
792 cond,
793 EnterSubshellOption::ClearInternalDisposition,
794 );
795 assert_eq!(result, Ok(()));
796 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
797 assert_eq!(
798 map[&cond].get_state(),
799 (
800 None,
801 Some(&TrapState {
802 action,
803 origin,
804 pending: false
805 }),
806 )
807 );
808 assert_eq!(system.0[&SIGCHLD], Disposition::Default);
809 }
810
811 #[test]
812 fn enter_subshell_with_command_keeping_internal_disposition() {
813 let mut system = DummySystem::default();
814 let mut map = BTreeMap::new();
815 let cond = SIGTSTP.into();
816 let entry = map.entry(cond);
817 let origin = Location::dummy("foo");
818 let action = Action::Command("echo".into());
819 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
820 let entry = map.entry(cond);
821 GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
822
823 let result = map.get_mut(&cond).unwrap().enter_subshell(
824 &mut system,
825 cond,
826 EnterSubshellOption::KeepInternalDisposition,
827 );
828 assert_eq!(result, Ok(()));
829 assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
830 assert_eq!(
831 map[&cond].get_state(),
832 (
833 None,
834 Some(&TrapState {
835 action,
836 origin,
837 pending: false
838 }),
839 )
840 );
841 assert_eq!(system.0[&SIGTSTP], Disposition::Ignore);
842 }
843
844 #[test]
845 fn enter_subshell_with_command_clearing_internal_disposition() {
846 let mut system = DummySystem::default();
847 let mut map = BTreeMap::new();
848 let cond = SIGTSTP.into();
849 let entry = map.entry(cond);
850 let origin = Location::dummy("foo");
851 let action = Action::Command("echo".into());
852 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
853 let entry = map.entry(cond);
854 GrandState::set_internal_disposition(&mut system, entry, Disposition::Ignore).unwrap();
855
856 let result = map.get_mut(&cond).unwrap().enter_subshell(
857 &mut system,
858 cond,
859 EnterSubshellOption::ClearInternalDisposition,
860 );
861 assert_eq!(result, Ok(()));
862 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
863 assert_eq!(
864 map[&cond].get_state(),
865 (
866 None,
867 Some(&TrapState {
868 action,
869 origin,
870 pending: false
871 }),
872 )
873 );
874 assert_eq!(system.0[&SIGTSTP], Disposition::Default);
875 }
876
877 #[test]
878 fn enter_subshell_with_command_ignoring() {
879 let mut system = DummySystem::default();
880 let mut map = BTreeMap::new();
881 let cond = SIGQUIT.into();
882 let entry = map.entry(cond);
883 let origin = Location::dummy("foo");
884 let action = Action::Command("echo".into());
885 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
886
887 let result = map.get_mut(&cond).unwrap().enter_subshell(
888 &mut system,
889 cond,
890 EnterSubshellOption::Ignore,
891 );
892 assert_eq!(result, Ok(()));
893 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
894 assert_eq!(
895 map[&cond].get_state(),
896 (
897 None,
898 Some(&TrapState {
899 action,
900 origin,
901 pending: false
902 }),
903 )
904 );
905 assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
906 }
907
908 #[test]
909 fn ignoring_initially_defaulted_signal() {
910 let mut system = DummySystem::default();
911 let mut map = BTreeMap::new();
912 let cond = SIGQUIT.into();
913 let entry = map.entry(cond);
914 let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
915
916 let result = GrandState::ignore(&mut system, vacant);
917 assert_eq!(result, Ok(()));
918 assert_eq!(map[&cond].get_state(), (None, None));
919 assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
920
921 let entry = map.entry(cond);
922 let origin = Location::dummy("foo");
923 let action = Action::Command("echo".into());
924 let result = GrandState::set_action(&mut system, entry, action, origin, false);
925 assert_eq!(result, Ok(()));
926 }
927
928 #[test]
929 fn ignoring_initially_ignored_signal() {
930 let mut system = DummySystem::default();
931 system.0.insert(SIGQUIT, Disposition::Ignore);
932 let mut map = BTreeMap::new();
933 let cond = SIGQUIT.into();
934 let entry = map.entry(cond);
935 let vacant = assert_matches!(entry, Entry::Vacant(vacant) => vacant);
936
937 let result = GrandState::ignore(&mut system, vacant);
938 assert_eq!(result, Ok(()));
939 assert_eq!(map[&cond].get_state(), (None, None));
940 assert_eq!(system.0[&SIGQUIT], Disposition::Ignore);
941
942 let entry = map.entry(cond);
943 let origin = Location::dummy("foo");
944 let action = Action::Command("echo".into());
945 let result = GrandState::set_action(&mut system, entry, action, origin, false);
946 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
947 }
948
949 #[test]
950 fn clearing_parent_setting() {
951 let mut system = DummySystem::default();
952 let mut map = BTreeMap::new();
953 let cond = SIGCHLD.into();
954 let entry = map.entry(cond);
955 let origin = Location::dummy("foo");
956 let action = Action::Command("echo".into());
957 GrandState::set_action(&mut system, entry, action, origin, false).unwrap();
958 let state = map.get_mut(&cond).unwrap();
959 state
960 .enter_subshell(
961 &mut system,
962 cond,
963 EnterSubshellOption::ClearInternalDisposition,
964 )
965 .unwrap();
966
967 state.clear_parent_setting();
968 assert_eq!(state.get_state(), (None, None));
969 }
970
971 #[test]
972 fn marking_as_caught_and_handling() {
973 let mut system = DummySystem::default();
974 let mut map = BTreeMap::new();
975 let cond = SIGUSR1.into();
976 let entry = map.entry(cond);
977 let origin = Location::dummy("foo");
978 let action = Action::Command("echo".into());
979 GrandState::set_action(&mut system, entry, action.clone(), origin.clone(), false).unwrap();
980
981 let state = &mut map.get_mut(&cond).unwrap();
982 state.mark_as_caught();
983 let expected_trap = TrapState {
984 action,
985 origin,
986 pending: true,
987 };
988 assert_eq!(state.get_state(), (Some(&expected_trap), None));
989
990 let trap = state.handle_if_caught();
991 let expected_trap = TrapState {
992 pending: false,
993 ..expected_trap
994 };
995 assert_eq!(trap, Some(&expected_trap));
996
997 let trap = state.handle_if_caught();
998 assert_eq!(trap, None);
999 }
1000}