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