use crate::journald_native::{IDENTIFIER, Priority, raw_address, send_without_waiting};
use crate::service_timing::{LOG_PREFIX, sink};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU64, Ordering};
pub const HANDLED: [libc::c_int; 3] = [libc::SIGTERM, libc::SIGINT, libc::SIGHUP];
const BEFORE_SIGNAL: &str = "exiting reason=signal-";
const BEFORE_UPTIME: &str = " uptime_ms=";
const BEFORE_TIME: &str = " at_ms=";
const LINE_CAPACITY: usize = 256;
static STARTED_NANOS: AtomicU64 = AtomicU64::new(0);
static NATIVE_DESCRIPTOR: AtomicI32 = AtomicI32::new(-1);
static NATIVE_ADDRESS: OnceLock<(libc::sockaddr_un, libc::socklen_t)> = OnceLock::new();
static ON_STREAM: AtomicBool = AtomicBool::new(false);
const NATIVE_HEAD: &str = "SYSLOG_IDENTIFIER=";
const NATIVE_TIME_FIELD: &str = "\nCLI_GIT_AT_MS=";
fn wall_millis_signal_safe() -> u64 {
let mut now: libc::timespec = libc::timespec { tv_sec: 0, tv_nsec: 0 };
let status: libc::c_int = unsafe { libc::clock_gettime(libc::CLOCK_REALTIME, &raw mut now) };
if status != 0 {
return 0;
}
return u64::try_from(now.tv_sec).unwrap_or(0).saturating_mul(1_000).saturating_add(u64::try_from(now.tv_nsec).unwrap_or(0) / 1_000_000);
}
fn monotonic_nanos() -> u64 {
let mut now: libc::timespec = libc::timespec { tv_sec: 0, tv_nsec: 0 };
let status: libc::c_int = unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &raw mut now) };
if status != 0 {
return 0;
}
return u64::try_from(now.tv_sec).unwrap_or(0).saturating_mul(1_000_000_000).saturating_add(u64::try_from(now.tv_nsec).unwrap_or(0));
}
fn push_number(line: &mut [u8; LINE_CAPACITY], used: usize, value: u64) -> usize {
let mut digits: [u8; 20] = [0; 20];
let mut count: usize = 0;
let mut rest: u64 = value;
loop {
digits[count] = b'0' + u8::try_from(rest % 10).unwrap_or(0);
count += 1;
rest /= 10;
if rest == 0 {
break;
}
}
let mut end: usize = used;
for index in (0..count).rev() {
line[end] = digits[index];
end += 1;
}
return end;
}
fn push_text(line: &mut [u8; LINE_CAPACITY], used: usize, text: &str) -> usize {
let bytes: &[u8] = text.as_bytes();
line[used..used + bytes.len()].copy_from_slice(bytes);
return used + bytes.len();
}
pub fn signal_line(signal: libc::c_int, uptime_nanos: u64, at_ms: u64, on_stream: bool) -> ([u8; LINE_CAPACITY], usize) {
let mut line: [u8; LINE_CAPACITY] = [0; LINE_CAPACITY];
let mut used: usize = push_text(&mut line, 0, if on_stream { "<6>" } else { "" });
used = push_text(&mut line, used, LOG_PREFIX);
used = push_text(&mut line, used, BEFORE_SIGNAL);
used = push_number(&mut line, used, u64::try_from(signal).unwrap_or(0));
used = push_text(&mut line, used, BEFORE_UPTIME);
used = push_number(&mut line, used, uptime_nanos / 1_000_000);
used = push_text(&mut line, used, BEFORE_TIME);
used = push_number(&mut line, used, at_ms);
line[used] = b'\n';
return (line, used + 1);
}
pub fn signal_datagram(signal: libc::c_int, uptime_nanos: u64, at_ms: u64) -> ([u8; LINE_CAPACITY], usize) {
let mut line: [u8; LINE_CAPACITY] = [0; LINE_CAPACITY];
let mut used: usize = push_text(&mut line, 0, "PRIORITY=");
used = push_number(&mut line, used, u64::from(Priority::Info.number()));
used = push_text(&mut line, used, "\n");
used = push_text(&mut line, used, NATIVE_HEAD);
used = push_text(&mut line, used, IDENTIFIER);
used = push_text(&mut line, used, "\nMESSAGE=");
used = push_text(&mut line, used, BEFORE_SIGNAL);
used = push_number(&mut line, used, u64::try_from(signal).unwrap_or(0));
used = push_text(&mut line, used, BEFORE_UPTIME);
used = push_number(&mut line, used, uptime_nanos / 1_000_000);
used = push_text(&mut line, used, BEFORE_TIME);
used = push_number(&mut line, used, at_ms);
used = push_text(&mut line, used, NATIVE_TIME_FIELD);
used = push_number(&mut line, used, at_ms);
line[used] = b'\n';
return (line, used + 1);
}
extern "C" fn on_signal(signal: libc::c_int) {
let started: u64 = STARTED_NANOS.load(Ordering::Relaxed);
let uptime: u64 = monotonic_nanos().saturating_sub(started);
let at_ms: u64 = wall_millis_signal_safe();
let on_stream: bool = ON_STREAM.load(Ordering::Relaxed);
let (line, length) = signal_line(signal, uptime, at_ms, on_stream);
let (entry, entry_length) = signal_datagram(signal, uptime, at_ms);
let descriptor: libc::c_int = NATIVE_DESCRIPTOR.load(Ordering::Relaxed);
unsafe {
let sent: bool = match NATIVE_ADDRESS.get() {
Some((address, address_length)) if descriptor >= 0 => {
libc::sendto(descriptor, entry.as_ptr().cast(), entry_length, libc::MSG_DONTWAIT, std::ptr::from_ref(address).cast(), *address_length) >= 0
}
_ => false,
};
if !sent {
let _written = send_without_waiting(libc::STDERR_FILENO, &line[..length]);
}
libc::signal(signal, libc::SIG_DFL);
libc::raise(signal);
}
}
pub fn install() {
STARTED_NANOS.store(monotonic_nanos(), Ordering::Relaxed);
ON_STREAM.store(sink().on_stream(), Ordering::Relaxed);
if let Some((descriptor, address)) = sink().signal_target()
&& let Some(raw) = raw_address(address)
&& NATIVE_ADDRESS.set(raw).is_ok()
{
NATIVE_DESCRIPTOR.store(descriptor, Ordering::Relaxed);
}
for signal in HANDLED {
unsafe {
libc::signal(signal, on_signal as *const () as libc::sighandler_t);
}
}
}
#[cfg(test)]
#[path = "service_signal_tests.rs"]
mod tests;