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}