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")))]
16 pub mod origin;
17 pub mod raw;
18 
19 #[cfg(feature = "extended-siginfo")]
20 pub use origin::WithOrigin;
21 pub use raw::WithRawSiginfo;
22 
23 use std::sync::atomic::{AtomicBool, Ordering};
24 
25 use libc::{c_int, siginfo_t};
26 
27 mod 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.
supports_signal(&self, sig: c_int) -> bool62         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.
store(&self, slot: &Self::Storage, signal: c_int, info: &siginfo_t)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.
load(&self, slot: &Self::Storage, signal: c_int) -> Option<Self::Output>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).
init(&self, slot: &Self::Storage, signal: c_int)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.
120 pub trait Exfiltrator: sealed::Exfiltrator {}
121 
122 impl<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)]
129 pub struct SignalOnly;
130 
131 unsafe impl sealed::Exfiltrator for SignalOnly {
132     type Storage = AtomicBool;
supports_signal(&self, _: c_int) -> bool133     fn supports_signal(&self, _: c_int) -> bool {
134         true
135     }
136     type Output = c_int;
137 
store(&self, slot: &Self::Storage, _: c_int, _: &siginfo_t)138     fn store(&self, slot: &Self::Storage, _: c_int, _: &siginfo_t) {
139         slot.store(true, Ordering::SeqCst);
140     }
141 
load(&self, slot: &Self::Storage, signal: c_int) -> Option<Self::Output>142     fn load(&self, slot: &Self::Storage, signal: c_int) -> Option<Self::Output> {
143         if slot
144             .compare_exchange(true, false, Ordering::SeqCst, Ordering::Relaxed)
145             .is_ok()
146         {
147             Some(signal)
148         } else {
149             None
150         }
151     }
152 }
153