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 async fn set_action<S: SignalSystem>(
215 system: &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).await?;
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).await?;
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).await?;
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 async fn set_internal_disposition<S: SignalSystem>(
295 system: &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).await?;
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).await?;
323 }
324 state.internal_disposition = disposition;
325 }
326 }
327
328 Ok(())
329 }
330
331 pub async fn enter_subshell<S: SignalSystem>(
339 &mut self,
340 system: &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 && let Condition::Signal(signal) = cond
371 {
372 system.set_disposition(signal, new_disposition).await?;
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 async fn ignore<S: SignalSystem>(
396 system: &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).await?;
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 futures_util::FutureExt as _;
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 &self,
536 signal: crate::signal::Number,
537 disposition: Disposition,
538 ) -> impl Future<Output = Result<Disposition, Errno>> + use<> {
539 unreachable!("set_disposition({signal}, {disposition:?})") as std::future::Ready<_>
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 system = DummySystem::default();
564 system.0.borrow_mut().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 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 GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
588 .now_or_never()
589 .unwrap()
590 .unwrap();
591
592 let entry = map.entry(SIGCHLD.into());
595 let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
596 assert_eq!(
597 state.current_state(),
598 &TrapState {
599 action,
600 origin: Origin::User(origin),
601 pending: false,
602 }
603 );
604 assert_eq!(state.parent_state(), None);
605 assert_eq!(state.internal_disposition(), Disposition::Default);
606 }
607
608 #[test]
609 fn insertion_with_non_signal_condition() {
610 let mut map = BTreeMap::new();
611 let entry = map.entry(Condition::Exit);
612 let state = GrandState::insert_from_system_if_vacant(&UnusedSystem, entry).unwrap();
613 assert_eq!(
614 state.current_state(),
615 &TrapState {
616 action: Action::Default,
617 origin: Origin::Inherited,
618 pending: false,
619 }
620 );
621 assert_eq!(state.parent_state(), None);
622 assert_eq!(state.internal_disposition(), Disposition::Default);
623 }
624
625 #[test]
626 fn setting_trap_to_ignore_without_override_ignore() {
627 let system = DummySystem::default();
628 let mut map = BTreeMap::new();
629 let entry = map.entry(SIGCHLD.into());
630 let origin = Location::dummy("origin");
631
632 let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
633 .now_or_never()
634 .unwrap();
635 assert_eq!(result, Ok(()));
636 assert_eq!(
637 map[&SIGCHLD.into()].current_state(),
638 &TrapState {
639 action: Action::Ignore,
640 origin: Origin::User(origin),
641 pending: false
642 }
643 );
644 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
645 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
646 }
647
648 #[test]
649 fn setting_trap_to_ignore_with_override_ignore() {
650 let system = DummySystem::default();
651 let mut map = BTreeMap::new();
652 let entry = map.entry(SIGCHLD.into());
653 let origin = Location::dummy("origin");
654
655 let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), true)
656 .now_or_never()
657 .unwrap();
658 assert_eq!(result, Ok(()));
659 assert_eq!(
660 map[&SIGCHLD.into()].current_state(),
661 &TrapState {
662 action: Action::Ignore,
663 origin: Origin::User(origin),
664 pending: false
665 }
666 );
667 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
668 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
669 }
670
671 #[test]
672 fn setting_trap_to_command() {
673 let system = DummySystem::default();
674 let mut map = BTreeMap::new();
675 let entry = map.entry(SIGCHLD.into());
676 let action = Action::Command("echo".into());
677 let origin = Location::dummy("origin");
678
679 let result = GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
680 .now_or_never()
681 .unwrap();
682 assert_eq!(result, Ok(()));
683 assert_eq!(
684 map[&SIGCHLD.into()].current_state(),
685 &TrapState {
686 action,
687 origin: Origin::User(origin),
688 pending: false
689 }
690 );
691 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
692 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
693 }
694
695 #[test]
696 fn setting_trap_to_default() {
697 let system = DummySystem::default();
698 let mut map = BTreeMap::new();
699 let entry = map.entry(SIGCHLD.into());
700 let origin = Location::dummy("foo");
701 GrandState::set_action(&system, entry, Action::Ignore, origin, false)
702 .now_or_never()
703 .unwrap()
704 .unwrap();
705
706 let entry = map.entry(SIGCHLD.into());
707 let origin = Location::dummy("bar");
708 let result = GrandState::set_action(&system, entry, Action::Default, origin.clone(), false)
709 .now_or_never()
710 .unwrap();
711 assert_eq!(result, Ok(()));
712 assert_eq!(
713 map[&SIGCHLD.into()].current_state(),
714 &TrapState {
715 action: Action::Default,
716 origin: Origin::User(origin),
717 pending: false
718 }
719 );
720 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
721 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
722 }
723
724 #[test]
725 fn resetting_trap_from_ignore_no_override() {
726 let system = DummySystem::default();
727 system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
728 let mut map = BTreeMap::new();
729 let entry = map.entry(SIGCHLD.into());
730 let origin = Location::dummy("foo");
731 let result = GrandState::set_action(&system, entry, Action::Ignore, origin, false)
732 .now_or_never()
733 .unwrap();
734 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
735
736 let entry = map.entry(SIGCHLD.into());
738 let origin = Location::dummy("bar");
739 let result = GrandState::set_action(&system, entry, Action::Ignore, origin, false)
740 .now_or_never()
741 .unwrap();
742 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
743
744 assert_eq!(
745 map[&SIGCHLD.into()].current_state(),
746 &TrapState {
747 action: Action::Ignore,
748 origin: Origin::Inherited,
749 pending: false
750 }
751 );
752 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
753 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
754 }
755
756 #[test]
757 fn resetting_trap_from_ignore_override() {
758 let system = DummySystem::default();
759 system.0.borrow_mut().insert(SIGCHLD, Disposition::Ignore);
760 let mut map = BTreeMap::new();
761 let entry = map.entry(SIGCHLD.into());
762 let origin = Location::dummy("origin");
763 let result = GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), true)
764 .now_or_never()
765 .unwrap();
766 assert_eq!(result, Ok(()));
767 assert_eq!(
768 map[&SIGCHLD.into()].current_state(),
769 &TrapState {
770 action: Action::Ignore,
771 origin: Origin::User(origin),
772 pending: false
773 }
774 );
775 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
776 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
777 }
778
779 #[test]
780 fn internal_disposition_ignore() {
781 let system = DummySystem::default();
782 let mut map = BTreeMap::new();
783 let entry = map.entry(SIGCHLD.into());
784
785 let result = GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
786 .now_or_never()
787 .unwrap();
788 assert_eq!(result, Ok(()));
789 assert_eq!(
790 map[&SIGCHLD.into()].internal_disposition(),
791 Disposition::Ignore
792 );
793 assert_eq!(
794 map[&SIGCHLD.into()].current_state(),
795 &TrapState {
796 action: Action::Default,
797 origin: Origin::Inherited,
798 pending: false
799 }
800 );
801 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
802 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
803 }
804
805 #[test]
806 fn internal_disposition_catch() {
807 let system = DummySystem::default();
808 let mut map = BTreeMap::new();
809 let entry = map.entry(SIGCHLD.into());
810
811 let result = GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
812 .now_or_never()
813 .unwrap();
814 assert_eq!(result, Ok(()));
815 assert_eq!(
816 map[&SIGCHLD.into()].internal_disposition(),
817 Disposition::Catch
818 );
819 assert_eq!(
820 map[&SIGCHLD.into()].current_state(),
821 &TrapState {
822 action: Action::Default,
823 origin: Origin::Inherited,
824 pending: false
825 }
826 );
827 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
828 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
829 }
830
831 #[test]
832 fn action_ignore_and_internal_disposition_catch() {
833 let system = DummySystem::default();
834 let mut map = BTreeMap::new();
835 let entry = map.entry(SIGCHLD.into());
836 let origin = Location::dummy("origin");
837 GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
838 .now_or_never()
839 .unwrap()
840 .unwrap();
841 let entry = map.entry(SIGCHLD.into());
842
843 let result = GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
844 .now_or_never()
845 .unwrap();
846 assert_eq!(result, Ok(()));
847 assert_eq!(
848 map[&SIGCHLD.into()].internal_disposition(),
849 Disposition::Catch
850 );
851 assert_eq!(
852 map[&SIGCHLD.into()].current_state(),
853 &TrapState {
854 action: Action::Ignore,
855 origin: Origin::User(origin),
856 pending: false
857 }
858 );
859 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
860 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
861 }
862
863 #[test]
864 fn action_catch_and_internal_disposition_ignore() {
865 let system = DummySystem::default();
866 let mut map = BTreeMap::new();
867 let entry = map.entry(SIGCHLD.into());
868 let origin = Location::dummy("origin");
869 let action = Action::Command("echo".into());
870 GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
871 .now_or_never()
872 .unwrap()
873 .unwrap();
874 let entry = map.entry(SIGCHLD.into());
875
876 let result = GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
877 .now_or_never()
878 .unwrap();
879 assert_eq!(result, Ok(()));
880 assert_eq!(
881 map[&SIGCHLD.into()].internal_disposition(),
882 Disposition::Ignore
883 );
884 assert_eq!(
885 map[&SIGCHLD.into()].current_state(),
886 &TrapState {
887 action,
888 origin: Origin::User(origin),
889 pending: false
890 }
891 );
892 assert_eq!(map[&SIGCHLD.into()].parent_state(), None);
893 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
894 }
895
896 #[test]
897 fn set_internal_disposition_for_initially_defaulted_signal_then_allow_override() {
898 let system = DummySystem::default();
899 let mut map = BTreeMap::new();
900 let entry = map.entry(SIGTTOU.into());
901 GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
902 .now_or_never()
903 .unwrap()
904 .unwrap();
905 let entry = map.entry(SIGTTOU.into());
906 let origin = Location::dummy("origin");
907 let action = Action::Command("echo".into());
908
909 let result = GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
910 .now_or_never()
911 .unwrap();
912 assert_eq!(result, Ok(()));
913 assert_eq!(
914 map[&SIGTTOU.into()].internal_disposition(),
915 Disposition::Ignore
916 );
917 assert_eq!(
918 map[&SIGTTOU.into()].current_state(),
919 &TrapState {
920 action,
921 origin: Origin::User(origin),
922 pending: false
923 }
924 );
925 assert_eq!(map[&SIGTTOU.into()].parent_state(), None);
926 assert_eq!(system.0.borrow()[&SIGTTOU], Disposition::Catch);
927 }
928
929 #[test]
930 fn set_internal_disposition_for_initially_ignored_signal_then_reject_override() {
931 let system = DummySystem::default();
932 system.0.borrow_mut().insert(SIGTTOU, Disposition::Ignore);
933 let mut map = BTreeMap::new();
934 let cond = SIGTTOU.into();
935 let entry = map.entry(cond);
936 GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
937 .now_or_never()
938 .unwrap()
939 .unwrap();
940 let entry = map.entry(cond);
941 let origin = Location::dummy("origin");
942 let action = Action::Command("echo".into());
943
944 let result = GrandState::set_action(&system, entry, action, origin, false)
945 .now_or_never()
946 .unwrap();
947 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
948 assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
949 assert_eq!(
950 map[&cond].current_state(),
951 &TrapState {
952 action: Action::Ignore,
953 origin: Origin::Inherited,
954 pending: false
955 }
956 );
957 assert_eq!(map[&cond].parent_state(), None);
958 assert_eq!(system.0.borrow()[&SIGTTOU], Disposition::Ignore);
959 }
960
961 #[test]
962 fn enter_subshell_with_internal_disposition_keeping_internal_disposition() {
963 let system = DummySystem::default();
964 let mut map = BTreeMap::new();
965 let cond = SIGCHLD.into();
966 GrandState::set_internal_disposition(&system, map.entry(cond), Disposition::Catch)
967 .now_or_never()
968 .unwrap()
969 .unwrap();
970
971 let result = map
972 .get_mut(&cond)
973 .unwrap()
974 .enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
975 .now_or_never()
976 .unwrap();
977 assert_eq!(result, Ok(()));
978 assert_eq!(map[&cond].internal_disposition(), Disposition::Catch);
979 assert_eq!(
980 map[&cond].current_state(),
981 &TrapState {
982 action: Action::Default,
983 origin: Origin::Inherited,
984 pending: false
985 }
986 );
987 assert_eq!(map[&cond].parent_state(), None);
988 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Catch);
989 }
990
991 #[test]
992 fn enter_subshell_with_internal_disposition_clearing_internal_disposition() {
993 let system = DummySystem::default();
994 let mut map = BTreeMap::new();
995 let cond = SIGCHLD.into();
996 let entry = map.entry(cond);
997 GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
998 .now_or_never()
999 .unwrap()
1000 .unwrap();
1001
1002 let result = map
1003 .get_mut(&cond)
1004 .unwrap()
1005 .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1006 .now_or_never()
1007 .unwrap();
1008 assert_eq!(result, Ok(()));
1009 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1010 assert_eq!(
1011 map[&cond].current_state(),
1012 &TrapState {
1013 action: Action::Default,
1014 origin: Origin::Inherited,
1015 pending: false
1016 }
1017 );
1018 assert_eq!(map[&cond].parent_state(), None);
1019 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
1020 }
1021
1022 #[test]
1023 fn enter_subshell_with_ignore_and_no_internal_disposition() {
1024 let system = DummySystem::default();
1025 let mut map = BTreeMap::new();
1026 let cond = SIGCHLD.into();
1027 let entry = map.entry(cond);
1028 let origin = Location::dummy("foo");
1029 GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
1030 .now_or_never()
1031 .unwrap()
1032 .unwrap();
1033
1034 let result = map
1035 .get_mut(&cond)
1036 .unwrap()
1037 .enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
1038 .now_or_never()
1039 .unwrap();
1040 assert_eq!(result, Ok(()));
1041 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1042 assert_eq!(
1043 map[&cond].current_state(),
1044 &TrapState {
1045 action: Action::Ignore,
1046 origin: Origin::User(origin),
1047 pending: false
1048 }
1049 );
1050 assert_eq!(map[&cond].parent_state(), None);
1051 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
1052 }
1053
1054 #[test]
1055 fn enter_subshell_with_ignore_clearing_internal_disposition() {
1056 let system = DummySystem::default();
1057 let mut map = BTreeMap::new();
1058 let cond = SIGCHLD.into();
1059 let entry = map.entry(cond);
1060 let origin = Location::dummy("foo");
1061 GrandState::set_action(&system, entry, Action::Ignore, origin.clone(), false)
1062 .now_or_never()
1063 .unwrap()
1064 .unwrap();
1065 let entry = map.entry(cond);
1066 GrandState::set_internal_disposition(&system, entry, Disposition::Catch)
1067 .now_or_never()
1068 .unwrap()
1069 .unwrap();
1070
1071 let result = map
1072 .get_mut(&cond)
1073 .unwrap()
1074 .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1075 .now_or_never()
1076 .unwrap();
1077 assert_eq!(result, Ok(()));
1078 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1079 assert_eq!(
1080 map[&cond].current_state(),
1081 &TrapState {
1082 action: Action::Ignore,
1083 origin: Origin::User(origin),
1084 pending: false
1085 }
1086 );
1087 assert_eq!(map[&cond].parent_state(), None);
1088 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Ignore);
1089 }
1090
1091 #[test]
1092 fn enter_subshell_with_command_and_no_internal_disposition() {
1093 let system = DummySystem::default();
1094 let mut map = BTreeMap::new();
1095 let cond = SIGCHLD.into();
1096 let entry = map.entry(cond);
1097 let origin = Location::dummy("foo");
1098 let action = Action::Command("echo".into());
1099 GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1100 .now_or_never()
1101 .unwrap()
1102 .unwrap();
1103
1104 let result = map
1105 .get_mut(&cond)
1106 .unwrap()
1107 .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1108 .now_or_never()
1109 .unwrap();
1110 assert_eq!(result, Ok(()));
1111 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1112 assert_eq!(
1113 map[&cond].current_state(),
1114 &TrapState {
1115 action: Action::Default,
1116 origin: Origin::Subshell,
1117 pending: false
1118 }
1119 );
1120 assert_eq!(
1121 map[&cond].parent_state(),
1122 Some(&TrapState {
1123 action,
1124 origin: Origin::User(origin),
1125 pending: false
1126 })
1127 );
1128 assert_eq!(system.0.borrow()[&SIGCHLD], Disposition::Default);
1129 }
1130
1131 #[test]
1132 fn enter_subshell_with_command_keeping_internal_disposition() {
1133 let system = DummySystem::default();
1134 let mut map = BTreeMap::new();
1135 let cond = SIGTSTP.into();
1136 let entry = map.entry(cond);
1137 let origin = Location::dummy("foo");
1138 let action = Action::Command("echo".into());
1139 GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1140 .now_or_never()
1141 .unwrap()
1142 .unwrap();
1143 let entry = map.entry(cond);
1144 GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
1145 .now_or_never()
1146 .unwrap()
1147 .unwrap();
1148
1149 let result = map
1150 .get_mut(&cond)
1151 .unwrap()
1152 .enter_subshell(&system, cond, EnterSubshellOption::KeepInternalDisposition)
1153 .now_or_never()
1154 .unwrap();
1155 assert_eq!(result, Ok(()));
1156 assert_eq!(map[&cond].internal_disposition(), Disposition::Ignore);
1157 assert_eq!(
1158 map[&cond].current_state(),
1159 &TrapState {
1160 action: Action::Default,
1161 origin: Origin::Subshell,
1162 pending: false
1163 }
1164 );
1165 assert_eq!(
1166 map[&cond].parent_state(),
1167 Some(&TrapState {
1168 action,
1169 origin: Origin::User(origin),
1170 pending: false
1171 })
1172 );
1173 assert_eq!(system.0.borrow()[&SIGTSTP], Disposition::Ignore);
1174 }
1175
1176 #[test]
1177 fn enter_subshell_with_command_clearing_internal_disposition() {
1178 let system = DummySystem::default();
1179 let mut map = BTreeMap::new();
1180 let cond = SIGTSTP.into();
1181 let entry = map.entry(cond);
1182 let origin = Location::dummy("foo");
1183 let action = Action::Command("echo".into());
1184 GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1185 .now_or_never()
1186 .unwrap()
1187 .unwrap();
1188 let entry = map.entry(cond);
1189 GrandState::set_internal_disposition(&system, entry, Disposition::Ignore)
1190 .now_or_never()
1191 .unwrap()
1192 .unwrap();
1193
1194 let result = map
1195 .get_mut(&cond)
1196 .unwrap()
1197 .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1198 .now_or_never()
1199 .unwrap();
1200 assert_eq!(result, Ok(()));
1201 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1202 assert_eq!(
1203 map[&cond].current_state(),
1204 &TrapState {
1205 action: Action::Default,
1206 origin: Origin::Subshell,
1207 pending: false
1208 }
1209 );
1210 assert_eq!(
1211 map[&cond].parent_state(),
1212 Some(&TrapState {
1213 action,
1214 origin: Origin::User(origin),
1215 pending: false
1216 })
1217 );
1218 assert_eq!(system.0.borrow()[&SIGTSTP], Disposition::Default);
1219 }
1220
1221 #[test]
1222 fn enter_subshell_with_command_ignoring() {
1223 let system = DummySystem::default();
1224 let mut map = BTreeMap::new();
1225 let cond = SIGQUIT.into();
1226 let entry = map.entry(cond);
1227 let origin = Location::dummy("foo");
1228 let action = Action::Command("echo".into());
1229 GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1230 .now_or_never()
1231 .unwrap()
1232 .unwrap();
1233
1234 let result = map
1235 .get_mut(&cond)
1236 .unwrap()
1237 .enter_subshell(&system, cond, EnterSubshellOption::Ignore)
1238 .now_or_never()
1239 .unwrap();
1240 assert_eq!(result, Ok(()));
1241 assert_eq!(map[&cond].internal_disposition(), Disposition::Default);
1242 assert_eq!(
1243 map[&cond].current_state(),
1244 &TrapState {
1245 action: Action::Ignore,
1246 origin: Origin::Subshell,
1247 pending: false
1248 }
1249 );
1250 assert_eq!(
1251 map[&cond].parent_state(),
1252 Some(&TrapState {
1253 action,
1254 origin: Origin::User(origin),
1255 pending: false
1256 })
1257 );
1258 assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
1259 }
1260
1261 #[test]
1262 fn ignoring_initially_defaulted_signal() {
1263 let system = DummySystem::default();
1264 let mut map = BTreeMap::new();
1265 let cond = SIGQUIT.into();
1266 let entry = map.entry(cond);
1267 let Entry::Vacant(vacant) = entry else {
1268 panic!("expected vacant entry: {entry:?}");
1269 };
1270
1271 let result = GrandState::ignore(&system, vacant).now_or_never().unwrap();
1272 assert_eq!(result, Ok(()));
1273 assert_eq!(
1274 map[&cond].current_state(),
1275 &TrapState {
1276 action: Action::Ignore,
1277 origin: Origin::Subshell,
1278 pending: false
1279 }
1280 );
1281 assert_eq!(map[&cond].parent_state(), None);
1282 assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
1283
1284 let entry = map.entry(cond);
1285 let origin = Location::dummy("foo");
1286 let action = Action::Command("echo".into());
1287 let result = GrandState::set_action(&system, entry, action, origin, false)
1288 .now_or_never()
1289 .unwrap();
1290 assert_eq!(result, Ok(()));
1291 }
1292
1293 #[test]
1294 fn ignoring_initially_ignored_signal() {
1295 let system = DummySystem::default();
1296 system.0.borrow_mut().insert(SIGQUIT, Disposition::Ignore);
1297 let mut map = BTreeMap::new();
1298 let cond = SIGQUIT.into();
1299 let entry = map.entry(cond);
1300 let Entry::Vacant(vacant) = entry else {
1301 panic!("expected vacant entry: {entry:?}");
1302 };
1303
1304 let result = GrandState::ignore(&system, vacant).now_or_never().unwrap();
1305 assert_eq!(result, Ok(()));
1306 assert_eq!(
1307 map[&cond].current_state(),
1308 &TrapState {
1309 action: Action::Ignore,
1310 origin: Origin::Inherited,
1311 pending: false
1312 }
1313 );
1314 assert_eq!(map[&cond].parent_state(), None);
1315 assert_eq!(system.0.borrow()[&SIGQUIT], Disposition::Ignore);
1316
1317 let entry = map.entry(cond);
1318 let origin = Location::dummy("foo");
1319 let action = Action::Command("echo".into());
1320 let result = GrandState::set_action(&system, entry, action, origin, false)
1321 .now_or_never()
1322 .unwrap();
1323 assert_eq!(result, Err(SetActionError::InitiallyIgnored));
1324 }
1325
1326 #[test]
1327 fn clearing_parent_setting() {
1328 let system = DummySystem::default();
1329 let mut map = BTreeMap::new();
1330 let cond = SIGCHLD.into();
1331 let entry = map.entry(cond);
1332 let origin = Location::dummy("foo");
1333 let action = Action::Command("echo".into());
1334 GrandState::set_action(&system, entry, action, origin, false)
1335 .now_or_never()
1336 .unwrap()
1337 .unwrap();
1338 let state = map.get_mut(&cond).unwrap();
1339 state
1340 .enter_subshell(&system, cond, EnterSubshellOption::ClearInternalDisposition)
1341 .now_or_never()
1342 .unwrap()
1343 .unwrap();
1344
1345 state.clear_parent_state();
1346 assert_eq!(
1347 state.current_state(),
1348 &TrapState {
1349 action: Action::Default,
1350 origin: Origin::Subshell,
1351 pending: false
1352 }
1353 );
1354 assert_eq!(state.parent_state(), None);
1355 }
1356
1357 #[test]
1358 fn marking_as_caught_and_handling() {
1359 let system = DummySystem::default();
1360 let mut map = BTreeMap::new();
1361 let cond = SIGUSR1.into();
1362 let entry = map.entry(cond);
1363 let origin = Location::dummy("foo");
1364 let action = Action::Command("echo".into());
1365 GrandState::set_action(&system, entry, action.clone(), origin.clone(), false)
1366 .now_or_never()
1367 .unwrap()
1368 .unwrap();
1369
1370 let state = &mut map.get_mut(&cond).unwrap();
1371 state.mark_as_caught();
1372 let expected_trap = TrapState {
1373 action,
1374 origin: Origin::User(origin),
1375 pending: true,
1376 };
1377 assert_eq!(state.current_state(), &expected_trap);
1378 assert_eq!(state.parent_state(), None);
1379
1380 let trap = state.handle_if_caught();
1381 let expected_trap = TrapState {
1382 pending: false,
1383 ..expected_trap
1384 };
1385 assert_eq!(trap, Some(&expected_trap));
1386
1387 let trap = state.handle_if_caught();
1388 assert_eq!(trap, None);
1389 }
1390}