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