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