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 std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
14
15pub type SignalSet = libc::sigset_t;
16pub type ThreadId = libc::pthread_t;
17
18pub const SIGINT: i32 = libc::SIGINT;
19pub const SIGTERM: i32 = libc::SIGTERM;
20pub const SIGPIPE: i32 = libc::SIGPIPE;
21pub const SIGKILL: i32 = libc::SIGKILL;
22
23static SHUTDOWN_FLAG_PTR: AtomicPtr<AtomicBool> = AtomicPtr::new(std::ptr::null_mut());
24
25extern "C" fn shutdown_signal_handler(_sig: libc::c_int) {
26    let flag = SHUTDOWN_FLAG_PTR.load(Ordering::Relaxed);
27    if !flag.is_null() {
28        unsafe {
29            (*flag).store(true, Ordering::Release);
30        }
31    }
32}
33
34/// Install SIGINT and SIGTERM handlers that flip a shared shutdown flag.
35///
36/// This is intended for simple daemon shutdown loops that want a reusable
37/// signal hook without direct `sigaction(2)` setup. The handlers are
38/// process-global and remain installed until replaced by another install.
39/// Use [`install_shutdown_flag_guard`] when the previous process-global
40/// handlers must be restored automatically.
41pub fn install_shutdown_flag(flag: &'static AtomicBool) -> Result<(), CoreError> {
42    install_shutdown_flag_inner(flag).map(|_| ())
43}
44
45/// Guard that restores previous SIGINT/SIGTERM handlers and shutdown flag on drop.
46pub struct ShutdownFlagGuard {
47    old_sigint: libc::sigaction,
48    old_sigterm: libc::sigaction,
49    old_flag: *mut AtomicBool,
50}
51
52impl Drop for ShutdownFlagGuard {
53    fn drop(&mut self) {
54        SHUTDOWN_FLAG_PTR.store(self.old_flag, Ordering::Release);
55        let _ = restore_signal_handler(SIGTERM, &self.old_sigterm);
56        let _ = restore_signal_handler(SIGINT, &self.old_sigint);
57    }
58}
59
60/// Install SIGINT and SIGTERM handlers and return a restore guard.
61///
62/// Dropping the guard restores the previous handlers and previous shutdown
63/// flag pointer. This is the scoped form for tests and callers that do not
64/// want the global convenience behavior of [`install_shutdown_flag`].
65pub fn install_shutdown_flag_guard(
66    flag: &'static AtomicBool,
67) -> Result<ShutdownFlagGuard, CoreError> {
68    let (old_sigint, old_sigterm, old_flag) = install_shutdown_flag_inner(flag)?;
69    Ok(ShutdownFlagGuard {
70        old_sigint,
71        old_sigterm,
72        old_flag,
73    })
74}
75
76fn install_shutdown_flag_inner(
77    flag: &'static AtomicBool,
78) -> Result<(libc::sigaction, libc::sigaction, *mut AtomicBool), CoreError> {
79    let old_flag = SHUTDOWN_FLAG_PTR.load(Ordering::Acquire);
80    let old_sigint = install_signal_handler(SIGINT)?;
81    match install_signal_handler(SIGTERM) {
82        Ok(old_sigterm) => {
83            SHUTDOWN_FLAG_PTR.store(
84                flag as *const AtomicBool as *mut AtomicBool,
85                Ordering::Release,
86            );
87            Ok((old_sigint, old_sigterm, old_flag))
88        }
89        Err(err) => {
90            restore_signal_handler(SIGINT, &old_sigint)?;
91            Err(err)
92        }
93    }
94}
95
96/// Return whether a shutdown flag was flipped by the installed handler.
97#[inline]
98pub fn shutdown_requested(flag: &AtomicBool) -> bool {
99    flag.load(Ordering::Acquire)
100}
101
102fn install_signal_handler(sig: libc::c_int) -> Result<libc::sigaction, CoreError> {
103    let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
104    let mut old_action: libc::sigaction = unsafe { std::mem::zeroed() };
105    action.sa_sigaction = shutdown_signal_handler as *const () as usize;
106    action.sa_flags = 0;
107    unsafe { libc::sigemptyset(&mut action.sa_mask) };
108
109    let ret = unsafe { libc::sigaction(sig, &action, &mut old_action) };
110    if ret == -1 {
111        Err(last_sigaction_error(sig))
112    } else {
113        Ok(old_action)
114    }
115}
116
117fn restore_signal_handler(sig: libc::c_int, old_action: &libc::sigaction) -> Result<(), CoreError> {
118    let ret = unsafe { libc::sigaction(sig, old_action, std::ptr::null_mut()) };
119    if ret == -1 {
120        Err(last_sigaction_error(sig))
121    } else {
122        Ok(())
123    }
124}
125
126fn last_sigaction_error(sig: libc::c_int) -> CoreError {
127    let op = match sig {
128        SIGINT => "sigaction(SIGINT)",
129        SIGTERM => "sigaction(SIGTERM)",
130        _ => "sigaction",
131    };
132    let code = std::io::Error::last_os_error().raw_os_error().unwrap_or(0);
133    CoreError::sys(code, op)
134}
135
136/// Utilities for process signal management.
137pub struct SignalRuntime;
138
139impl SignalRuntime {
140    /// Create an empty signal set.
141    pub fn empty_set() -> SignalSet {
142        let mut set: SignalSet = unsafe { std::mem::zeroed() };
143        unsafe { libc::sigemptyset(&mut set) };
144        set
145    }
146
147    /// Create a signal set containing the specified signals.
148    pub fn set_with(signals: &[i32]) -> Result<SignalSet, CoreError> {
149        let mut set: SignalSet = unsafe { std::mem::zeroed() };
150        unsafe { libc::sigemptyset(&mut set) };
151        for &sig in signals {
152            let ret = unsafe { libc::sigaddset(&mut set, sig) };
153            if ret == -1 {
154                return Err(CoreError::sys(libc::EINVAL, "sigaddset"));
155            }
156        }
157        Ok(set)
158    }
159
160    /// Block the specified signals for the current thread and return the previous mask.
161    pub fn block_current_thread(signals: &SignalSet) -> Result<SignalSet, CoreError> {
162        let mut previous = Self::empty_set();
163        let result = unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, signals, &mut previous) };
164        if result == 0 {
165            Ok(previous)
166        } else {
167            Err(CoreError::sys(result, "pthread_sigmask(SIG_BLOCK)"))
168        }
169    }
170
171    /// Restore the current thread signal mask.
172    pub fn restore_current_thread(mask: &SignalSet) -> Result<(), CoreError> {
173        let result =
174            unsafe { libc::pthread_sigmask(libc::SIG_SETMASK, mask, std::ptr::null_mut()) };
175        if result == 0 {
176            Ok(())
177        } else {
178            Err(CoreError::sys(result, "pthread_sigmask(SIG_SETMASK)"))
179        }
180    }
181
182    /// Wait synchronously for one of the supplied signals.
183    pub fn wait(signals: &SignalSet) -> Result<i32, CoreError> {
184        let mut received_signal = 0;
185        let result = unsafe { libc::sigwait(signals, &mut received_signal) };
186        if result == 0 {
187            Ok(received_signal)
188        } else {
189            Err(CoreError::sys(result, "sigwait"))
190        }
191    }
192
193    /// Deliver a signal to a specific thread.
194    pub fn interrupt_thread(thread: ThreadId, signal: i32) -> Result<(), CoreError> {
195        let result = unsafe { libc::pthread_kill(thread, signal) };
196        if result == 0 {
197            Ok(())
198        } else {
199            Err(CoreError::sys(result, "pthread_kill"))
200        }
201    }
202
203    /// Unblock all signals for the current thread.
204    pub fn unblock_all() -> Result<(), CoreError> {
205        let empty_mask = Self::empty_set();
206        let r = unsafe { libc::sigprocmask(libc::SIG_SETMASK, &empty_mask, std::ptr::null_mut()) };
207        syscall_ret(r, "sigprocmask")
208    }
209
210    /// Reset a signal to its default kernel handler.
211    pub fn reset_default(sig: i32) -> Result<(), CoreError> {
212        let prev = unsafe { libc::signal(sig, libc::SIG_DFL) };
213        if prev == libc::SIG_ERR {
214            Err(CoreError::sys(
215                std::io::Error::last_os_error().raw_os_error().unwrap_or(0),
216                "signal(SIG_DFL)",
217            ))
218        } else {
219            Ok(())
220        }
221    }
222}