Skip to main content

ic_host_process/signal/
mod.rs

1//! Explicit process-global Unix signal actions, independent of cancellation policy.
2
3use std::{fmt, io};
4
5#[cfg(test)]
6mod tests;
7
8/// Failed installation retains its signal, native cause and rollback failure.
9#[derive(Debug)]
10pub struct SignalActionError {
11    /// Signal whose admission or installation failed.
12    pub signal: libc::c_int,
13    /// Original input or native installation failure.
14    pub source: io::Error,
15    /// First failure restoring earlier actions after partial installation.
16    pub rollback_error: Option<io::Error>,
17}
18
19impl fmt::Display for SignalActionError {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        write!(
22            formatter,
23            "signal {} action failed: {}",
24            self.signal, self.source
25        )
26    }
27}
28
29impl std::error::Error for SignalActionError {
30    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
31        Some(&self.source)
32    }
33}
34
35/// Saved dispositions for an explicitly selected set of Unix signals.
36///
37/// Actions are process-global. The caller must exclusively coordinate changes to
38/// these signals for the guard's whole lifetime, restore overlapping guards in
39/// reverse installation order, and supply an async-signal-safe static handler.
40/// This guard chooses no signals, flags, cancellation, threads or shutdown policy.
41/// Drop attempts restoration; call [`Self::restore`] to observe native failures.
42pub struct SignalActions {
43    previous: Vec<(libc::c_int, libc::sigaction)>,
44}
45
46impl SignalActions {
47    /// Install the same caller-supplied handler for each selected signal.
48    ///
49    /// The handler uses an empty additional mask and no optional action flags.
50    /// Empty selection has no effects. Duplicate signals refuse before native
51    /// effects; failed installation attempts rollback of earlier changes.
52    ///
53    /// # Errors
54    /// Returns the original installation failure separately from a rollback error.
55    ///
56    /// # Safety
57    /// The caller must supply an async-signal-safe handler and exclusively
58    /// coordinate process-global changes to the selected dispositions through
59    /// restoration/Drop, including reverse-order restoration of overlapping guards.
60    #[allow(unsafe_code)]
61    pub unsafe fn install(
62        signals: &[libc::c_int],
63        handler: extern "C" fn(libc::c_int),
64    ) -> Result<Self, SignalActionError> {
65        for (index, &signal) in signals.iter().enumerate() {
66            if signals[..index].contains(&signal) {
67                return Err(SignalActionError {
68                    signal,
69                    source: io::Error::new(io::ErrorKind::InvalidInput, "duplicate signal"),
70                    rollback_error: None,
71                });
72            }
73        }
74        let mut guard = Self {
75            previous: Vec::new(),
76        };
77        for &signal in signals {
78            // SAFETY: zero initializes these C structures, and both pointers
79            // remain writable for the native call. The static callback has C ABI.
80            let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
81            action.sa_sigaction = handler as *const () as libc::sighandler_t;
82            let mut previous = unsafe { std::mem::zeroed() };
83            if unsafe { libc::sigemptyset(&raw mut action.sa_mask) } != 0
84                || unsafe { libc::sigaction(signal, &raw const action, &raw mut previous) } != 0
85            {
86                let source = io::Error::last_os_error();
87                return Err(SignalActionError {
88                    signal,
89                    source,
90                    rollback_error: guard.restore().err(),
91                });
92            }
93            guard.previous.push((signal, previous));
94        }
95        Ok(guard)
96    }
97
98    /// Restore all saved dispositions in reverse order, retaining failures for retry.
99    ///
100    /// Successful restoration is idempotent. Every saved action is attempted even
101    /// if another fails; Drop makes one further best-effort attempt for failures.
102    ///
103    /// # Errors
104    /// Returns the first native restoration failure.
105    #[allow(unsafe_code)]
106    pub fn restore(&mut self) -> io::Result<()> {
107        let mut first_error = None;
108        let previous = std::mem::take(&mut self.previous);
109        for (signal, action) in previous.into_iter().rev() {
110            // SAFETY: this is the disposition returned by installation; the
111            // caller retains exclusive process-global registration custody.
112            if unsafe { libc::sigaction(signal, &raw const action, std::ptr::null_mut()) } != 0 {
113                first_error.get_or_insert_with(io::Error::last_os_error);
114                self.previous.push((signal, action));
115            }
116        }
117        self.previous.reverse();
118        first_error.map_or(Ok(()), Err)
119    }
120}
121
122impl Drop for SignalActions {
123    fn drop(&mut self) {
124        let _ = self.restore();
125    }
126}