1//! An abstraction over exfiltrating information out of signal handlers.
2//!
3//! The [`Exfiltrator`] trait provides a way to abstract the information extracted from a signal
4//! handler and the way it is extracted out of it.
5//!
6//! The implementations can be used to parametrize the
7//! [`SignalsInfo`][crate::iterator::SignalsInfo] to specify what results are returned.
8//!
9//! # Sealed
10//!
11//! Currently, the trait is sealed and all methods hidden. This is likely temporary, until some
12//! experience with them is gained.
13
14#[cfg(feature = "extended-siginfo")]
15#[cfg_attr(docsrs, doc(cfg(feature = "extended-siginfo")))]
16pub mod origin;
17pub mod raw;
18
19#[cfg(feature = "extended-siginfo")]
20pub use origin::WithOrigin;
21pub use raw::WithRawSiginfo;
22
23use std::sync::atomic::{AtomicBool, Ordering};
24
25use libc::{c_int, siginfo_t};
26
27mod sealed {
28 use std::fmt::Debug;
29
30 use libc::{c_int, siginfo_t};
31
32 /// The actual implementation of the [`Exfiltrator`][super::Exfiltrator].
33 ///
34 /// For now, this is hidden from the public API, but the intention is to move it to a public
35 /// place so users can implement it eventually, once we verify that it works well.
36 ///
37 /// # Safety
38 ///
39 /// The trait is unsafe as the [`Exfiltrator::store`] is called inside the signal handler and
40 /// must be async-signal-safe. Implementing this correctly may be difficult, therefore care
41 /// needs to be taken. One method known to work is encoding the data into an atomic variable.
42 /// Other, less limiting approaches, will be eventually explored.
43 pub unsafe trait Exfiltrator: Debug + Send + Sync + 'static {
44 /// One slot for storing the data.
45 ///
46 /// Each signal will get its one slot of this type, independent of other signals. It can
47 /// store the information in there inside the signal handler and will be loaded from it in
48 /// load.
49 ///
50 /// Each slot is initialized to the [`Default`] value. It is expected this value represents
51 /// „no signal delivered“ state.
52 type Storage: Debug + Default + Send + Sync + 'static;
53
54 /// The type returned to the user.
55 type Output;
56
57 /// If the given signal is supported by this specific exfiltrator.
58 ///
59 /// Not all information is available to all signals, therefore not all exfiltrators must
60 /// support all signals. If `false` is returned, the user is prevented for registering such
61 /// signal number with the given exfiltrator.
62 fn supports_signal(&self, sig: c_int) -> bool;
63
64 /// Puts the signal information inside the slot.
65 ///
66 /// It needs to somehow store the relevant information and the fact that a signal happened.
67 ///
68 /// # Warning
69 ///
70 /// This will be called inside the signal handler. It needs to be async-signal-safe. In
71 /// particular, very small amount of operations are allowed in there. This namely does
72 /// *not* include any locking nor allocation.
73 ///
74 /// It is also possible that multiple store methods are called concurrently; it is up to
75 /// the implementor to deal with that.
76 fn store(&self, slot: &Self::Storage, signal: c_int, info: &siginfo_t);
77
78 /// Loads the signal information from the given slot.
79 ///
80 /// The method shall check if the signal happened (it may be possible to be called without
81 /// the signal previously being delivered; it is up to the implementer to recognize it). It
82 /// is assumed the [`Default`] value is recognized as no signal delivered.
83 ///
84 /// If it was delivered, the method shall extract the relevant information *and reset the
85 /// slot* to the no signal delivered state.
86 ///
87 /// It shall return `Some(value)` if the signal was successfully received and `None` in
88 /// case no signal was delivered.
89 ///
90 /// No blocking shall happen inside this method. It may be called concurrently with
91 /// [`store`][Exfiltrator::store] (due to how signals work, concurrently even inside the
92 /// same thread ‒ a `store` may „interrupt“ a call to `load`). It is up to the implementer
93 /// to deal with that.
94 fn load(&self, slot: &Self::Storage, signal: c_int) -> Option<Self::Output>;
95
96 /// Initialize the given slot for the given signal before the first use.
97 ///
98 /// This is called before the first use of the given slot (and it is annotated with the
99 /// corresponding signal). The default does nothing, this is just an opportunity to
100 /// allocate data lazily (this is called outside of the signal handler, so it doesn't have
101 /// to be async-signal-safe). It will be called at most once for each slot.
102 ///
103 /// Note that you can rely on this being called for correctness, but not for safety (this
104 /// crate calls it before the first use, but a user abusing the trait might not and in such
105 /// case it is OK to eg. lose signals, but not segfault).
106 fn init(&self, slot: &Self::Storage, signal: c_int) {
107 // Suppress unused variable warning without putting the underscores into public
108 // signature.
109 let _ = slot;
110 let _ = signal;
111 }
112 }
113}
114
115/// A trait describing what and how is extracted from signal handlers.
116///
117/// By choosing a specific implementor as the type parameter for
118/// [`SignalsInfo`][crate::iterator::SignalsInfo], one can pick how much and what information is
119/// returned from the iterator.
120pub trait Exfiltrator: sealed::Exfiltrator {}
121
122impl<E: sealed::Exfiltrator> Exfiltrator for E {}
123
124/// An [`Exfiltrator`] providing just the signal numbers.
125///
126/// This is the basic exfiltrator for most needs. For that reason, there's the
127/// [`crate::iterator::Signals`] type alias, to simplify the type names for usual needs.
128#[derive(Clone, Copy, Debug, Default)]
129pub struct SignalOnly;
130
131unsafe impl sealed::Exfiltrator for SignalOnly {
132 type Storage = AtomicBool;
133 fn supports_signal(&self, _: c_int) -> bool {
134 true
135 }
136 type Output = c_int;
137
138 fn store(&self, slot: &Self::Storage, _: c_int, _: &siginfo_t) {
139 slot.store(val:true, order:Ordering::SeqCst);
140 }
141
142 fn load(&self, slot: &Self::Storage, signal: c_int) -> Option<Self::Output> {
143 if slotResult
144 .compare_exchange(current:true, new:false, success:Ordering::SeqCst, failure:Ordering::Relaxed)
145 .is_ok()
146 {
147 Some(signal)
148 } else {
149 None
150 }
151 }
152}
153