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 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
//! 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.
#[cfg(feature = "extended-siginfo")]
#[cfg_attr(docsrs, doc(cfg(feature = "extended-siginfo")))]
pub mod origin;
pub mod raw;
#[cfg(feature = "extended-siginfo")]
pub use origin::WithOrigin;
pub use raw::WithRawSiginfo;
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.
///
/// # Safety
///
/// 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>;
/// Initialize the given slot for the given signal before the first use.
///
/// This is called before the first use of the given slot (and it is annotated with the
/// corresponding signal). The default does nothing, this is just an opportunity to
/// allocate data lazily (this is called outside of the signal handler, so it doesn't have
/// to be async-signal-safe). It will be called at most once for each slot.
///
/// Note that you can rely on this being called for correctness, but not for safety (this
/// crate calls it before the first use, but a user abusing the trait might not and in such
/// case it is OK to eg. lose signals, but not segfault).
fn init(&self, slot: &Self::Storage, signal: c_int) {
// Suppress unused variable warning without putting the underscores into public
// signature.
let _ = slot;
let _ = signal;
}
}
}
/// 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
}
}
}