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    #[expect(
61        unsafe_code,
62        reason = "install caller-owned Unix signal dispositions with reviewed sigaction arguments"
63    )]
64    pub unsafe fn install(
65        signals: &[libc::c_int],
66        handler: extern "C" fn(libc::c_int),
67    ) -> Result<Self, SignalActionError> {
68        for (index, &signal) in signals.iter().enumerate() {
69            if signals[..index].contains(&signal) {
70                return Err(SignalActionError {
71                    signal,
72                    source: io::Error::new(io::ErrorKind::InvalidInput, "duplicate signal"),
73                    rollback_error: None,
74                });
75            }
76        }
77        let mut guard = Self {
78            previous: Vec::new(),
79        };
80        for &signal in signals {
81            // SAFETY: zero initializes these C structures, and both pointers
82            // remain writable for the native call. The static callback has C ABI.
83            let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
84            action.sa_sigaction = handler as *const () as libc::sighandler_t;
85            let mut previous = unsafe { std::mem::zeroed() };
86            if unsafe { libc::sigemptyset(&raw mut action.sa_mask) } != 0
87                || unsafe { libc::sigaction(signal, &raw const action, &raw mut previous) } != 0
88            {
89                let source = io::Error::last_os_error();
90                return Err(SignalActionError {
91                    signal,
92                    source,
93                    rollback_error: guard.restore().err(),
94                });
95            }
96            guard.previous.push((signal, previous));
97        }
98        Ok(guard)
99    }
100
101    /// Restore all saved dispositions in reverse order, retaining failures for retry.
102    ///
103    /// Successful restoration is idempotent. Every saved action is attempted even
104    /// if another fails; Drop makes one further best-effort attempt for failures.
105    ///
106    /// # Errors
107    /// Returns the first native restoration failure.
108    #[expect(
109        unsafe_code,
110        reason = "restore the original caller-owned Unix signal dispositions"
111    )]
112    pub fn restore(&mut self) -> io::Result<()> {
113        let mut first_error = None;
114        let previous = std::mem::take(&mut self.previous);
115        for (signal, action) in previous.into_iter().rev() {
116            // SAFETY: this is the disposition returned by installation; the
117            // caller retains exclusive process-global registration custody.
118            if unsafe { libc::sigaction(signal, &raw const action, std::ptr::null_mut()) } != 0 {
119                first_error.get_or_insert_with(io::Error::last_os_error);
120                self.previous.push((signal, action));
121            }
122        }
123        self.previous.reverse();
124        first_error.map_or(Ok(()), Err)
125    }
126}
127
128impl Drop for SignalActions {
129    fn drop(&mut self) {
130        let _ = self.restore();
131    }
132}