#[cfg(unix)]
pub fn notify(state: &str) {
use std::os::unix::net::UnixDatagram;
let path = match std::env::var("NOTIFY_SOCKET") {
Ok(p) if !p.is_empty() => p,
_ => return,
};
let Ok(sock) = UnixDatagram::unbound() else { return };
if let Some(name) = path.strip_prefix('@') {
#[cfg(target_os = "linux")]
{
use std::os::linux::net::SocketAddrExt;
if let Ok(addr) = std::os::unix::net::SocketAddr::from_abstract_name(name.as_bytes()) {
let _ = sock.send_to_addr(state.as_bytes(), &addr);
}
}
#[cfg(not(target_os = "linux"))]
let _ = name;
} else {
let _ = sock.send_to(state.as_bytes(), &path);
}
}
#[cfg(not(unix))]
pub fn notify(_state: &str) {}
pub fn ready() {
notify("READY=1");
}
pub fn watchdog() {
notify("WATCHDOG=1");
}
pub fn status(s: &str) {
notify(&format!("STATUS={s}"));
}
#[cfg(all(test, unix))]
mod tests {
use super::*;
use std::os::unix::net::UnixDatagram;
#[test]
fn notify_protocol_and_noop() {
unsafe { std::env::remove_var("NOTIFY_SOCKET") };
ready();
watchdog();
status("x");
let path = std::env::temp_dir().join(format!("fil-sdnotify-{}.sock", std::process::id()));
let _ = std::fs::remove_file(&path);
let listener = UnixDatagram::bind(&path).expect("bind notify listener");
listener
.set_read_timeout(Some(std::time::Duration::from_secs(2)))
.unwrap();
unsafe { std::env::set_var("NOTIFY_SOCKET", &path) };
ready();
watchdog();
status("up, serving");
let mut got = Vec::new();
let mut buf = [0u8; 256];
for _ in 0..3 {
let n = listener.recv(&mut buf).expect("recv datagram");
got.push(String::from_utf8_lossy(&buf[..n]).to_string());
}
unsafe { std::env::remove_var("NOTIFY_SOCKET") };
let _ = std::fs::remove_file(&path);
assert!(got.contains(&"READY=1".to_string()), "got: {got:?}");
assert!(got.contains(&"WATCHDOG=1".to_string()), "got: {got:?}");
assert!(
got.iter().any(|s| s == "STATUS=up, serving"),
"got: {got:?}"
);
}
}