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}