Skip to main content

coreshift_core/
signal.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/
4
5//! Signal and shutdown helpers.
6//!
7//! This module provides small process-global signal utilities intended for
8//! low-level daemons and worker processes that want explicit signal handling
9//! without a heavier runtime.
10
11use crate::CoreError;
12use crate::error::syscall_ret;
13use crate::reactor::Fd;
14use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
15
16pub type SignalSet = libc::sigset_t;
17pub type ThreadId = libc::pthread_t;
18
19pub const SIGINT: i32 = libc::SIGINT;
20pub const SIGTERM: i32 = libc::SIGTERM;
21pub const SIGPIPE: i32 = libc::SIGPIPE;
22pub const SIGKILL: i32 = libc::SIGKILL;
23pub const SIGUSR1: i32 = libc::SIGUSR1;
24pub const SIGUSR2: i32 = libc::SIGUSR2;
25
26static SHUTDOWN_FLAG_PTR: AtomicPtr<AtomicBool> = AtomicPtr::new(std::ptr::null_mut());
27
28extern "C" fn shutdown_signal_handler(_sig: libc::c_int) {
29    let flag = SHUTDOWN_FLAG_PTR.load(Ordering::Relaxed);
30    if !flag.is_null() {
31        unsafe {
32            (*flag).store(true, Ordering::Release);
33        }
34    }
35}
36
37/// Install SIGINT and SIGTERM handlers that flip a shared shutdown flag.
38///
39/// This is intended for simple daemon shutdown loops that want a reusable
40/// signal hook without direct `sigaction(2)` setup. The handlers are
41/// process-global and remain installed until replaced by another install.
42/// Use [`install_shutdown_flag_guard`] when the previous process-global
43/// handlers must be restored automatically.
44///
45/// ### Reactor Compatibility
46/// This function uses standard Unix `signal()`/`sigaction()` handlers and is
47/// **not** directly compatible with the `Reactor`. For event-loop based
48/// applications, prefer using [`SignalRuntime::signalfd_new`].
49///
50/// ### Fork Safety
51/// Signal handlers are inherited by the child. The shutdown flag pointer is
52/// also inherited. If the child process receives SIGINT/SIGTERM, it will
53/// attempt to flip the flag in its own address space at the same virtual
54/// address.
55///
56/// ### Errors
57/// - `EINVAL`: Invalid signal number.
58pub fn install_shutdown_flag(flag: &'static AtomicBool) -> Result<(), CoreError> {
59    install_shutdown_flag_inner(flag).map(|_| ())
60}
61
62/// Guard that restores previous SIGINT/SIGTERM handlers and shutdown flag on drop.
63///
64/// ### Fork Safety
65/// The guard is owned by the process that created it. If the process forks,
66/// the child will also have a copy of the guard, but dropping it in the child
67/// will restore handlers in the child's context only.
68pub struct ShutdownFlagGuard {
69    old_sigint: libc::sigaction,
70    old_sigterm: libc::sigaction,
71    old_flag: *mut AtomicBool,
72}
73
74impl Drop for ShutdownFlagGuard {
75    fn drop(&mut self) {
76        SHUTDOWN_FLAG_PTR.store(self.old_flag, Ordering::Release);
77        let _ = restore_signal_handler(SIGTERM, &self.old_sigterm);
78        let _ = restore_signal_handler(SIGINT, &self.old_sigint);
79    }
80}
81
82/// Install SIGINT and SIGTERM handlers and return a restore guard.
83///
84/// Dropping the guard restores the previous handlers and previous shutdown
85/// flag pointer. This is the scoped form for tests and callers that do not
86/// want the global convenience behavior of [`install_shutdown_flag`].
87pub fn install_shutdown_flag_guard(
88    flag: &'static AtomicBool,
89) -> Result<ShutdownFlagGuard, CoreError> {
90    let (old_sigint, old_sigterm, old_flag) = install_shutdown_flag_inner(flag)?;
91    Ok(ShutdownFlagGuard {
92        old_sigint,
93        old_sigterm,
94        old_flag,
95    })
96}
97
98fn install_shutdown_flag_inner(
99    flag: &'static AtomicBool,
100) -> Result<(libc::sigaction, libc::sigaction, *mut AtomicBool), CoreError> {
101    let old_flag = SHUTDOWN_FLAG_PTR.load(Ordering::Acquire);
102    let old_sigint = install_signal_handler(SIGINT)?;
103    match install_signal_handler(SIGTERM) {
104        Ok(old_sigterm) => {
105            SHUTDOWN_FLAG_PTR.store(
106                flag as *const AtomicBool as *mut AtomicBool,
107                Ordering::Release,
108            );
109            Ok((old_sigint, old_sigterm, old_flag))
110        }
111        Err(err) => {
112            restore_signal_handler(SIGINT, &old_sigint)?;
113            Err(err)
114        }
115    }
116}
117
118/// Return whether a shutdown flag was flipped by the installed handler.
119#[inline]
120pub fn shutdown_requested(flag: &AtomicBool) -> bool {
121    flag.load(Ordering::Acquire)
122}
123
124fn install_signal_handler(sig: libc::c_int) -> Result<libc::sigaction, CoreError> {
125    let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
126    let mut old_action: libc::sigaction = unsafe { std::mem::zeroed() };
127    action.sa_sigaction = shutdown_signal_handler as *const () as usize;
128    action.sa_flags = 0;
129    unsafe { libc::sigemptyset(&mut action.sa_mask) };
130
131    let ret = unsafe { libc::sigaction(sig, &action, &mut old_action) };
132    if ret == -1 {
133        Err(last_sigaction_error(sig))
134    } else {
135        Ok(old_action)
136    }
137}
138
139fn restore_signal_handler(sig: libc::c_int, old_action: &libc::sigaction) -> Result<(), CoreError> {
140    let ret = unsafe { libc::sigaction(sig, old_action, std::ptr::null_mut()) };
141    if ret == -1 {
142        Err(last_sigaction_error(sig))
143    } else {
144        Ok(())
145    }
146}
147
148fn last_sigaction_error(sig: libc::c_int) -> CoreError {
149    let op = match sig {
150        SIGINT => "sigaction(SIGINT)",
151        SIGTERM => "sigaction(SIGTERM)",
152        _ => "sigaction",
153    };
154    let code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
155    CoreError::sys(code, op)
156}
157
158/// Utilities for process signal management.
159pub struct SignalRuntime;
160
161impl SignalRuntime {
162    /// Create an empty signal set.
163    pub fn empty_set() -> SignalSet {
164        let mut set: SignalSet = unsafe { std::mem::zeroed() };
165        unsafe { libc::sigemptyset(&mut set) };
166        set
167    }
168
169    /// Create a signal set containing the specified signals.
170    ///
171    /// ### Errors
172    /// - `EINVAL`: One of the signal numbers is invalid.
173    pub fn set_with(signals: &[i32]) -> Result<SignalSet, CoreError> {
174        let mut set: SignalSet = unsafe { std::mem::zeroed() };
175        unsafe { libc::sigemptyset(&mut set) };
176        for &sig in signals {
177            let ret = unsafe { libc::sigaddset(&mut set, sig) };
178            if ret == -1 {
179                return Err(CoreError::sys(libc::EINVAL, "sigaddset"));
180            }
181        }
182        Ok(set)
183    }
184
185    /// Block the specified signals for the current thread and return the previous mask.
186    ///
187    /// ### Errors
188    /// - `EINVAL`: `how` or `signals` is invalid.
189    pub fn block_current_thread(signals: &SignalSet) -> Result<SignalSet, CoreError> {
190        let mut previous = Self::empty_set();
191        let result = unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, signals, &mut previous) };
192        if result == 0 {
193            Ok(previous)
194        } else {
195            Err(CoreError::sys(result, "pthread_sigmask(SIG_BLOCK)"))
196        }
197    }
198
199    /// Restore the current thread signal mask.
200    ///
201    /// ### Errors
202    /// - `EINVAL`: `mask` is invalid.
203    pub fn restore_current_thread(mask: &SignalSet) -> Result<(), CoreError> {
204        let result =
205            unsafe { libc::pthread_sigmask(libc::SIG_SETMASK, mask, std::ptr::null_mut()) };
206        if result == 0 {
207            Ok(())
208        } else {
209            Err(CoreError::sys(result, "pthread_sigmask(SIG_SETMASK)"))
210        }
211    }
212
213    /// Wait synchronously for one of the supplied signals.
214    ///
215    /// ### Errors
216    /// - `EINVAL`: `signals` contains invalid signal numbers.
217    pub fn wait(signals: &SignalSet) -> Result<i32, CoreError> {
218        let mut received_signal = 0;
219        let result = unsafe { libc::sigwait(signals, &mut received_signal) };
220        if result == 0 {
221            Ok(received_signal)
222        } else {
223            Err(CoreError::sys(result, "sigwait"))
224        }
225    }
226
227    /// Deliver a signal to a specific thread.
228    ///
229    /// ### Errors
230    /// - `EINVAL`: Invalid signal number.
231    /// - `ESRCH`: The thread ID is invalid or the thread has terminated.
232    pub fn interrupt_thread(thread: ThreadId, signal: i32) -> Result<(), CoreError> {
233        let result = unsafe { libc::pthread_kill(thread, signal) };
234        if result == 0 {
235            Ok(())
236        } else {
237            Err(CoreError::sys(result, "pthread_kill"))
238        }
239    }
240
241    /// Block or unblock signals for the current thread and return the previous mask.
242    pub fn set_current_thread_mask(
243        how: i32,
244        signals: &SignalSet,
245    ) -> Result<SignalSet, CoreError> {
246        let mut previous = Self::empty_set();
247        let result = unsafe { libc::pthread_sigmask(how, signals, &mut previous) };
248        if result == 0 {
249            Ok(previous)
250        } else {
251            let op = match how {
252                libc::SIG_BLOCK => "pthread_sigmask(SIG_BLOCK)",
253                libc::SIG_UNBLOCK => "pthread_sigmask(SIG_UNBLOCK)",
254                libc::SIG_SETMASK => "pthread_sigmask(SIG_SETMASK)",
255                _ => "pthread_sigmask",
256            };
257            Err(CoreError::sys(result, op))
258        }
259    }
260
261    /// Unblock all signals for the current thread.
262    pub fn unblock_all() -> Result<(), CoreError> {
263        let empty_mask = Self::empty_set();
264        let r = unsafe { libc::sigprocmask(libc::SIG_SETMASK, &empty_mask, std::ptr::null_mut()) };
265        syscall_ret(r, "sigprocmask")
266    }
267
268    /// Create a new `signalfd` for the specified signal set.
269    ///
270    /// The descriptor is created with `SFD_CLOEXEC` and `SFD_NONBLOCK` set.
271    /// Callers are responsible for blocking the signals in the set before
272    /// reading from the `signalfd`.
273    ///
274    /// ### Fork Safety
275    /// The descriptor is `O_CLOEXEC` and will be closed in the child after `exec`.
276    ///
277    /// ### Errors
278    /// - `EINVAL`: `signals` is invalid.
279    /// - `EMFILE`: Process limit on open file descriptors hit.
280    /// - `ENFILE`: System-wide limit on open files hit.
281    pub fn signalfd_new(signals: &SignalSet) -> Result<Fd, CoreError> {
282        let fd = unsafe { libc::signalfd(-1, signals, libc::SFD_NONBLOCK | libc::SFD_CLOEXEC) };
283        syscall_ret(fd, "signalfd")?;
284        Fd::new(fd, "signalfd")
285    }
286
287    /// Register a process-wide handler for a single signal.
288    ///
289    /// This is a low-level wrapper around `sigaction(2)`.
290    ///
291    /// ### Fork Safety
292    /// Signal handlers are inherited across `fork`.
293    ///
294    /// ### Errors
295    /// - `EINVAL`: Invalid signal number.
296    ///
297    /// # Example
298    /// ```no_run
299    /// # use coreshift_core::signal::{SignalRuntime, SIGUSR1};
300    /// extern "C" fn handler(_: i32) {}
301    /// SignalRuntime::register_handler(SIGUSR1, handler).unwrap();
302    /// ```
303    pub fn register_handler(
304        sig: i32,
305        handler: extern "C" fn(i32),
306    ) -> Result<libc::sigaction, CoreError> {
307        let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
308        let mut old_action: libc::sigaction = unsafe { std::mem::zeroed() };
309        action.sa_sigaction = handler as *const () as usize;
310        action.sa_flags = 0;
311        unsafe { libc::sigemptyset(&mut action.sa_mask) };
312
313        let ret = unsafe { libc::sigaction(sig, &action, &mut old_action) };
314        if ret == -1 {
315            let code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
316            Err(CoreError::sys(code, "sigaction"))
317        } else {
318            Ok(old_action)
319        }
320    }
321
322    /// Reset a signal to its default kernel handler.
323    ///
324    /// ### Errors
325    /// - `EINVAL`: Invalid signal number.
326    pub fn reset_default(sig: i32) -> Result<(), CoreError> {
327        let prev = unsafe { libc::signal(sig, libc::SIG_DFL) };
328        if prev == libc::SIG_ERR {
329            Err(CoreError::sys(
330                std::io::Error::last_os_error().raw_os_error().unwrap_or(0),
331                "signal(SIG_DFL)",
332            ))
333        } else {
334            Ok(())
335        }
336    }
337}