pub fn notify_ready() -> std::io::Result<()> {
#[cfg(target_os = "linux")]
{
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])?;
}
Ok(())
}
pub fn notify_status(message: &str) -> std::io::Result<()> {
#[cfg(target_os = "linux")]
{
sd_notify::notify(false, &[sd_notify::NotifyState::Status(message)])?;
}
#[cfg(not(target_os = "linux"))]
let _ = message;
Ok(())
}
pub fn notify_stopping() -> std::io::Result<()> {
#[cfg(target_os = "linux")]
{
sd_notify::notify(false, &[sd_notify::NotifyState::Stopping])?;
}
Ok(())
}
#[must_use]
pub fn is_under_systemd() -> bool {
#[cfg(target_os = "linux")]
{
std::env::var_os("NOTIFY_SOCKET").is_some()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn notify_ready_is_ok_without_notify_socket() {
let _guard = NotifySocketGuard::unset();
assert!(
notify_ready().is_ok(),
"notify_ready must not error when NOTIFY_SOCKET is absent"
);
}
#[test]
fn notify_status_is_ok_without_notify_socket() {
let _guard = NotifySocketGuard::unset();
assert!(
notify_status("test status message").is_ok(),
"notify_status must not error when NOTIFY_SOCKET is absent"
);
}
#[test]
fn notify_stopping_is_ok_without_notify_socket() {
let _guard = NotifySocketGuard::unset();
assert!(
notify_stopping().is_ok(),
"notify_stopping must not error when NOTIFY_SOCKET is absent"
);
}
#[test]
fn is_under_systemd_returns_false_without_notify_socket() {
let _guard = NotifySocketGuard::unset();
assert!(
!is_under_systemd(),
"is_under_systemd must return false when NOTIFY_SOCKET is absent"
);
}
#[test]
#[cfg(not(target_os = "linux"))]
fn is_under_systemd_is_always_false_on_non_linux() {
let _guard = NotifySocketGuard::set("/run/systemd/notify");
assert!(
!is_under_systemd(),
"is_under_systemd must always be false on non-Linux platforms \
even when NOTIFY_SOCKET is set"
);
}
#[test]
#[cfg(target_os = "linux")]
fn is_under_systemd_returns_true_when_notify_socket_set() {
let _guard = NotifySocketGuard::set("/run/systemd/notify");
assert!(
is_under_systemd(),
"is_under_systemd must return true when NOTIFY_SOCKET is set"
);
}
#[test]
#[cfg(target_os = "linux")]
fn notify_ready_errors_on_stale_notify_socket() {
let dir = tempfile::tempdir().expect("tempdir allocation failed");
let nonexistent = dir.path().join("notify.sock");
debug_assert!(!nonexistent.exists(), "notify.sock must not pre-exist");
let socket_path = nonexistent
.to_str()
.expect("tempdir path must be valid UTF-8");
let _guard = NotifySocketGuard::set(socket_path);
let result = notify_ready();
assert!(
result.is_err(),
"notify_ready must return Err when NOTIFY_SOCKET points to a non-existent socket"
);
}
static NOTIFY_SOCKET_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
struct NotifySocketGuard {
previous: Option<std::ffi::OsString>,
_lock: std::sync::MutexGuard<'static, ()>,
}
impl NotifySocketGuard {
fn unset() -> Self {
let _lock = NOTIFY_SOCKET_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let previous = std::env::var_os("NOTIFY_SOCKET");
unsafe {
std::env::remove_var("NOTIFY_SOCKET");
}
Self { previous, _lock }
}
fn set(value: &str) -> Self {
let _lock = NOTIFY_SOCKET_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let previous = std::env::var_os("NOTIFY_SOCKET");
unsafe {
std::env::set_var("NOTIFY_SOCKET", value);
}
Self { previous, _lock }
}
}
impl Drop for NotifySocketGuard {
fn drop(&mut self) {
match &self.previous {
Some(v) => unsafe {
std::env::set_var("NOTIFY_SOCKET", v);
},
None => unsafe {
std::env::remove_var("NOTIFY_SOCKET");
},
}
}
}
}