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