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
        }
    }
}