use nix::sys::signal::Signal;
use crate::core::log::{log_level_decrement, log_level_increment};
use crate::core::types::Status;
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum SignalAction {
Noop,
LogLevelUp,
LogLevelDown,
ReopenLog,
Shutdown,
StackTrace,
Ignore,
}
#[derive(Debug, Clone, Copy)]
pub struct SignalEntry {
pub signal: Signal,
pub name: &'static str,
pub action: SignalAction,
}
pub fn default_actions() -> &'static [SignalEntry] {
&SIGNAL_TABLE
}
const SIGNAL_TABLE: [SignalEntry; 8] = [
SignalEntry {
signal: Signal::SIGUSR1,
name: "SIGUSR1",
action: SignalAction::Noop,
},
SignalEntry {
signal: Signal::SIGUSR2,
name: "SIGUSR2",
action: SignalAction::Noop,
},
SignalEntry {
signal: Signal::SIGTTIN,
name: "SIGTTIN",
action: SignalAction::LogLevelUp,
},
SignalEntry {
signal: Signal::SIGTTOU,
name: "SIGTTOU",
action: SignalAction::LogLevelDown,
},
SignalEntry {
signal: Signal::SIGHUP,
name: "SIGHUP",
action: SignalAction::ReopenLog,
},
SignalEntry {
signal: Signal::SIGINT,
name: "SIGINT",
action: SignalAction::Shutdown,
},
SignalEntry {
signal: Signal::SIGSEGV,
name: "SIGSEGV",
action: SignalAction::StackTrace,
},
SignalEntry {
signal: Signal::SIGPIPE,
name: "SIGPIPE",
action: SignalAction::Ignore,
},
];
pub fn action_for(signal: Signal) -> Option<SignalAction> {
SIGNAL_TABLE
.iter()
.find(|entry| entry.signal == signal)
.map(|entry| entry.action)
}
pub fn dispatch(signal: Signal) -> Result<bool, crate::core::types::DynError> {
let Some(action) = action_for(signal) else {
return Ok(false);
};
match action {
SignalAction::Noop | SignalAction::Ignore => Ok(false),
SignalAction::LogLevelUp => {
log_level_increment();
Ok(false)
}
SignalAction::LogLevelDown => {
log_level_decrement();
Ok(false)
}
SignalAction::ReopenLog => {
crate::core::log::reopen_on_sighup()?;
Ok(false)
}
SignalAction::Shutdown => Ok(true),
SignalAction::StackTrace => {
tracing::error!(signal = %signal, "fatal signal received, terminating");
Ok(true)
}
}
}
pub fn handle(signal: Signal) -> Status {
dispatch(signal).map(|_| ())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn table_covers_every_c_entry() {
for sig in [
Signal::SIGUSR1,
Signal::SIGUSR2,
Signal::SIGTTIN,
Signal::SIGTTOU,
Signal::SIGHUP,
Signal::SIGINT,
Signal::SIGSEGV,
Signal::SIGPIPE,
] {
assert!(action_for(sig).is_some(), "missing entry for {sig:?}");
}
}
#[test]
fn unknown_signals_return_none() {
assert!(action_for(Signal::SIGCHLD).is_none());
}
#[test]
fn shutdown_is_signalled_for_sigint() {
assert!(!dispatch(Signal::SIGUSR1).unwrap());
assert!(!dispatch(Signal::SIGPIPE).unwrap());
assert!(dispatch(Signal::SIGINT).unwrap());
}
}