2023-12-08 16:21:00 +01:00
//! Interface for the `NOTIFY_SOCKET` readiness protocol
//!
//! The entry point of this module is the [`Notifier`] struct.
//!
//! # Example
//!
2023-12-08 17:01:42 +01:00
//! ```no_run
2023-12-08 16:21:00 +01:00
//! # main() -> Result<(), Box<dyn std::error::Error>> {
//! // Do whatever you need for your service to be ready.
//! if let Some(notifier) = Notifier::new(true)? {
//! notifier.notify(&[NotifyState::Ready])?
//! }
//! # Ok(())
//! # }
//! ```
2023-12-08 14:46:54 +01:00
use core ::fmt ::Display ;
use std ::env ;
use std ::ffi ::OsString ;
use std ::io ::IoSlice ;
use std ::os ::unix ::net ::UnixDatagram ;
2024-01-05 21:27:09 +01:00
use rustix ::{
fd ::{ AsFd , BorrowedFd } ,
pipe ::PipeFlags ,
} ;
2023-12-08 14:46:54 +01:00
2024-01-05 22:28:57 +01:00
use self ::error ::{ BarrierError , NewNotifierError , NotifyError } ;
2023-12-08 14:46:54 +01:00
pub mod error ;
2024-01-04 13:48:45 +01:00
pub mod types ;
2023-12-08 14:46:54 +01:00
2023-12-08 16:21:00 +01:00
/// A wrapper around the socket specified by `$NOTIFY_SOCKET`
#[ derive(Debug) ]
pub struct Notifier {
sock_addr : rustix ::net ::SocketAddrUnix ,
socket : UnixDatagram ,
}
impl Notifier {
/// Create a new [`Notifier`].
///
/// If `unset_env` is set to `true` this will unset `NOTIFY_SOCKET` resulting in further call to this function to return `Ok(None)`.
///
/// Returns `Ok(None)` if notification isn't supported (`NOTIFY_SOCKET` isn't defined).
///
/// # Errors
///
/// This function error out if the socket couldn't be opened.
pub fn new ( unset_env : bool ) -> Result < Option < Self > , NewNotifierError > {
2023-12-18 15:41:16 +01:00
let span = tracing ::info_span! ( " new " , f_unset_env = ? unset_env ) ;
let _enter = span . enter ( ) ;
tracing ::info! ( " Opening NOTIFY_SOCKET if available. " ) ;
2023-12-08 16:21:00 +01:00
let env_sock = match env ::var_os ( " NOTIFY_SOCKET " ) {
None = > return Ok ( None ) ,
Some ( v ) = > v ,
} ;
2023-12-18 15:41:16 +01:00
tracing ::debug! ( " NOTIFY_SOCKET = {env_sock:?} " ) ;
2023-12-08 14:46:54 +01:00
2023-12-08 16:21:00 +01:00
if unset_env {
2023-12-18 15:41:16 +01:00
tracing ::trace! ( " Removing NOTIFY_SOCKET from environment. " ) ;
2023-12-08 16:21:00 +01:00
env ::remove_var ( " NOTIFY_SOCKET " ) ;
}
2023-12-08 14:46:54 +01:00
2023-12-18 15:41:52 +01:00
// False positive
#[ allow(clippy::single_match_else) ]
2023-12-08 16:21:00 +01:00
// If the first character of `$NOTIFY_SOCKET` is '@', the string
// is understood as Linux abstract namespace socket.
let socket_addr = match env_sock . as_encoded_bytes ( ) . strip_prefix ( b " @ " ) . map ( | v | {
// SAFETY:
// - Only strip ASCII '@' which is a non-empty UTF-8 substring
unsafe { OsString ::from_encoded_bytes_unchecked ( v . to_vec ( ) ) }
} ) {
Some ( stripped_addr ) = > {
2023-12-18 15:41:16 +01:00
tracing ::trace! ( " Opening abstract socket {stripped_addr:?}. " ) ;
2023-12-08 16:21:00 +01:00
rustix ::net ::SocketAddrUnix ::new_abstract_name ( stripped_addr . as_encoded_bytes ( ) )
. map_err ( NewNotifierError ::InvalidAbstractSocket ) ?
}
2023-12-18 15:41:16 +01:00
None = > {
tracing ::trace! ( " Opening named socket {env_sock:?}. " ) ;
rustix ::net ::SocketAddrUnix ::new ( env_sock )
. map_err ( NewNotifierError ::InvalidSocketPath ) ?
}
2023-12-08 16:21:00 +01:00
} ;
let socket = UnixDatagram ::unbound ( ) . map_err ( NewNotifierError ::CouldntOpenSocket ) ? ;
let ret = Self {
sock_addr : socket_addr ,
socket ,
} ;
Ok ( Some ( ret ) )
2023-12-08 14:46:54 +01:00
}
2023-12-08 16:21:00 +01:00
/// Notify service manager about status change and send file descriptors.
///
2023-12-08 17:01:42 +01:00
/// Use this together with [`NotifyState::FdStore`]. Otherwise works like [`Notifier::notify()`].
2023-12-08 16:21:00 +01:00
///
/// # Errors
///
/// This function will error out if the passed [`NotifyState`] do not follow the rules set by systemd or if they couldn't be fully sent.
pub fn notify_with_fds (
& self ,
state : & [ NotifyState < '_ > ] ,
fds : & [ BorrowedFd ] ,
) -> Result < ( ) , NotifyError > {
2023-12-18 15:41:16 +01:00
let span =
tracing ::info_span! ( " notify_with_fds " , f_self = ? self , f_state = ? state , f_fds = ? fds ) ;
let _enter = span . enter ( ) ;
2023-12-08 16:21:00 +01:00
let msg = state
. iter ( )
. fold ( String ::new ( ) , | acc , state | format! ( " {acc} {state} \n " ) )
. into_bytes ( ) ;
let msg_len = msg . len ( ) ;
let msg_iov = IoSlice ::new ( & msg ) ;
2023-12-08 17:26:04 +01:00
let mut ancillary = if fds . is_empty ( ) {
2023-12-18 15:41:16 +01:00
tracing ::trace! ( " No file descriptors provided, not sending ancillary messages. " ) ;
2023-12-08 17:26:04 +01:00
rustix ::net ::SendAncillaryBuffer ::default ( )
} else {
2023-12-18 15:41:16 +01:00
tracing ::trace! (
" {} file descriptors provided, sending through ancillary messages " ,
fds . len ( )
) ;
2023-12-08 16:21:00 +01:00
let mut ancillary = rustix ::net ::SendAncillaryBuffer ::default ( ) ;
let tmp = rustix ::net ::SendAncillaryMessage ::ScmRights ( fds ) ;
if ! ancillary . push ( tmp ) {
return Err ( NotifyError ::PushAncillaryMessage ) ;
}
ancillary
} ;
2023-12-18 15:41:16 +01:00
span . record ( " expected_length " , msg_len ) ;
tracing ::debug! ( " Sending notification messages. " ) ;
2023-12-08 16:21:00 +01:00
let sent_len = rustix ::net ::sendmsg_unix (
self . socket . as_fd ( ) ,
& self . sock_addr ,
& [ msg_iov ] ,
& mut ancillary ,
rustix ::net ::SendFlags ::empty ( ) ,
)
. map_err ( NotifyError ::SendMsg ) ? ;
2023-12-18 15:41:16 +01:00
span . record ( " sent_length " , sent_len ) ;
tracing ::debug! ( " Notification message sent. {sent_len} bytes sent. " ) ;
2023-12-08 16:21:00 +01:00
if sent_len ! = msg_len {
2023-12-18 15:41:16 +01:00
tracing ::error! ( " The notification message couldn't be completely sent! " ) ;
2023-12-08 16:21:00 +01:00
return Err ( NotifyError ::PartialSend ) ;
2023-12-08 14:46:54 +01:00
}
2023-12-08 16:21:00 +01:00
Ok ( ( ) )
2023-12-08 14:46:54 +01:00
}
2023-12-08 16:21:00 +01:00
/// Notify service manager about status changes.
///
2023-12-08 17:01:42 +01:00
/// Send a notification to the manager about service status changes. Also see [`Notifier::notify_with_fds()`] to send file descriptors.
2023-12-08 16:21:00 +01:00
///
/// # Errors
///
2024-01-05 22:28:57 +01:00
/// This function will error out if the passed [`NotifyState`] couldn't be fully sent.
2023-12-08 16:21:00 +01:00
pub fn notify ( & self , state : & [ NotifyState < '_ > ] ) -> Result < ( ) , NotifyError > {
self . notify_with_fds ( state , & [ ] )
}
2024-01-05 21:27:09 +01:00
2024-01-05 22:28:57 +01:00
/// Ensure that all previous notifications have been treated by the service manager.
///
/// **This is a blocking call. If you are using it in an async function you might want to use an equivalent of [`tokio::task::spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html).**
///
/// # Errors
///
/// This function will error out if the synchronisation mechanism couldn't be created, the synchronising notification failed or the synchronisation timed out.
pub fn barrier ( & self , timeout : types ::BarrierTimeout ) -> Result < ( ) , BarrierError > {
2024-01-05 21:27:09 +01:00
let ( to_poll , sent ) = rustix ::pipe ::pipe_with ( PipeFlags ::CLOEXEC )
. map_err ( BarrierError ::FailedPipeCreation ) ? ;
self . notify_with_fds (
& [ NotifyState ::Other ( types ::OtherState ::barrier ( ) ) ] ,
& [ sent . as_fd ( ) ] ,
) ? ;
core ::mem ::drop ( sent ) ;
let to_poll = rustix ::event ::PollFd ::new ( & to_poll , rustix ::event ::PollFlags ::HUP ) ;
2024-01-05 22:28:57 +01:00
rustix ::event ::poll ( & mut [ to_poll ] , timeout . to_raw ( ) )
2024-01-05 21:27:09 +01:00
. map_err ( BarrierError ::FailedPolling )
. and_then ( | events | {
if events = = 0_ usize {
2024-01-05 22:51:28 +01:00
return Err ( BarrierError ::TimedOut ) ;
2024-01-05 21:27:09 +01:00
}
Ok ( ( ) )
} )
}
2024-01-05 22:28:57 +01:00
/// Create a synchronizing RAII guard.
///
/// This create an RAII guard that automatically call [`barrier()`](Self::barrier) with the provided timeout when dropped.
///
/// Do note that this guard's [`Drop`] implementation will block for the provided timeout and ignore all errors returned by [`barrier()`](Self::barrier).
pub fn guard ( & self , timeout : types ::BarrierTimeout ) -> NotifyBarrierGuard < '_ > {
2024-01-05 21:27:09 +01:00
NotifyBarrierGuard {
notifier : self ,
timeout ,
}
}
2024-01-05 22:28:57 +01:00
/// Create a scope at the end of which all notifications sent inside should have been treated by the service manager.
pub fn with_guard < F , T > ( & self , timeout : types ::BarrierTimeout , f : F ) -> T
2024-01-05 21:27:09 +01:00
where
F : FnOnce ( NotifyBarrierGuard ) -> T ,
{
f ( self . guard ( timeout ) )
}
}
2024-01-05 22:28:57 +01:00
/// RAII guard automatically synchronizing the notifications with the service manager.
///
/// This is created by [`Notifier::guard`].
///
/// Do note that this guard's [`Drop`] implementation will block for the provided timeout and ignore all errors returned by [`barrier()`](Self::barrier).
2024-01-05 21:27:09 +01:00
pub struct NotifyBarrierGuard < ' a > {
notifier : & ' a Notifier ,
2024-01-05 22:28:57 +01:00
timeout : types ::BarrierTimeout ,
2024-01-05 21:27:09 +01:00
}
impl < ' a > NotifyBarrierGuard < ' a > {
/// Notify service manager about status changes.
///
/// Send a notification to the manager about service status changes. Also see [`notify_with_fds()`](Self::notify_with_fds) to send file descriptors.
///
/// # Errors
///
/// This function will error out if the passed [`NotifyState`] do not follow the rules set by systemd or if they couldn't be fully sent.
2024-01-05 22:28:57 +01:00
#[ inline ]
2024-01-05 21:27:09 +01:00
pub fn notify ( & self , state : & [ NotifyState < '_ > ] ) -> Result < ( ) , NotifyError > {
self . notifier . notify ( state )
}
/// Notify service manager about status change and send file descriptors.
///
/// Use this together with [`NotifyState::FdStore`]. Otherwise works like [`notify()`](Self::notify).
///
/// # Errors
///
/// This function will error out if the passed [`NotifyState`] do not follow the rules set by systemd or if they couldn't be fully sent.
2024-01-05 22:28:57 +01:00
#[ inline ]
2024-01-05 21:27:09 +01:00
pub fn notify_with_fds (
& self ,
state : & [ NotifyState < '_ > ] ,
fds : & [ BorrowedFd ] ,
) -> Result < ( ) , NotifyError > {
self . notifier . notify_with_fds ( state , fds )
}
2024-01-05 22:28:57 +01:00
/// Ensure that all previous notifications have been treated by the service manager.
///
/// **This is a blocking call. If you are using it in an async function you might want to use an equivalent of [`tokio::task::spawn_blocking`](https://docs.rs/tokio/latest/tokio/task/fn.spawn_blocking.html).**
///
/// # Errors
///
/// This function will error out if the synchronisation mechanism couldn't be created, the synchronising notification failed or the synchronisation timed out.
#[ inline ]
pub fn barrier ( & self , timeout : types ::BarrierTimeout ) -> Result < ( ) , BarrierError > {
2024-01-05 21:27:09 +01:00
self . notifier . barrier ( timeout )
}
2024-01-05 22:28:57 +01:00
/// Create a scope at the end of which all notifications sent inside should have been treated by the service manager.
#[ inline ]
pub fn with_guard < F , T > ( & self , timeout : types ::BarrierTimeout , f : F ) -> T
2024-01-05 21:27:09 +01:00
where
F : FnOnce ( Self ) -> T ,
{
f ( self . notifier . guard ( timeout ) )
}
}
impl Drop for NotifyBarrierGuard < '_ > {
fn drop ( & mut self ) {
self . barrier ( self . timeout ) . unwrap_or_default ( ) ;
}
2023-12-08 14:46:54 +01:00
}
2024-01-04 13:47:14 +01:00
/// Check for watchdog support at runtime
///
/// If `unset_env` is true, the environment variables related to watchdog support will be cleared.
///
/// # Return
///
/// * [`None`] if watchdog support is not enabled.
/// * The timeout before which the watchdog expects a response from the process otherwise.
pub fn is_watchdog_enabled ( unset_env : bool ) -> Option < std ::time ::Duration > {
let timeout = std ::env ::var ( " WATCHDOG_USEC " ) . ok ( ) ;
let watchdog_pid = std ::env ::var ( " WATCHDOG_PID " ) . ok ( ) ;
if unset_env {
std ::env ::remove_var ( " WATCHDOG_USEC " ) ;
std ::env ::remove_var ( " WATCHDOG_PID " ) ;
}
let timeout = timeout
. and_then ( | timeout | timeout . parse ::< u64 > ( ) . ok ( ) )
. map ( std ::time ::Duration ::from_micros ) ? ;
let watchdog_pid = if let Some ( pid ) = watchdog_pid {
pid . parse ::< u32 > ( ) . ok ( ) ?
} else {
return Some ( timeout ) ;
} ;
if watchdog_pid = = std ::process ::id ( ) {
Some ( timeout )
} else {
None
}
}
2023-12-08 17:26:04 +01:00
/// Status changes, see `sd_notify(3)`.
#[ allow(clippy::module_name_repetitions) ]
2023-12-08 14:46:54 +01:00
#[ derive(Debug, Clone, Copy, PartialEq, Eq, Hash) ]
pub enum NotifyState < ' a > {
/// D-Bus error-style error code.
2024-01-04 13:48:45 +01:00
BusError ( types ::BusError < ' a > ) ,
2023-12-08 14:46:54 +01:00
/// errno-style error code.
Errno ( u8 ) ,
/// A name for the submitted file descriptors.
2024-01-04 13:48:45 +01:00
FdName ( types ::FdName < ' a > ) ,
2023-12-08 17:01:42 +01:00
/// Stores additional file descriptors in the service manager. Use [`Notifier::notify_with_fds()`] with this.
2023-12-08 16:21:00 +01:00
FdStore ,
/// Remove stored file descriptors. Must be used together with [`NotifyState::FdName`].
FdStoreRemove ,
2023-12-08 14:46:54 +01:00
/// Tell the service manager to not poll the filedescriptors for errors. This causes
/// systemd to hold on to broken file descriptors which must be removed manually.
2023-12-08 16:21:00 +01:00
/// Must be used together with [`NotifyState::FdStore`].
2023-12-08 14:46:54 +01:00
FdpollDisable ,
/// The main process ID of the service, in case of forking applications.
Mainpid ( libc ::pid_t ) ,
/// Custom state change, as a `KEY=VALUE` string.
2024-01-04 14:31:02 +01:00
Other ( types ::OtherState < ' a > ) ,
2023-12-08 14:46:54 +01:00
/// Service startup is finished.
Ready ,
/// Service is reloading.
Reloading ,
/// Custom status change.
2024-01-04 13:48:45 +01:00
Status ( types ::StatusLine < ' a > ) ,
2023-12-08 14:46:54 +01:00
/// Service is beginning to shutdown.
Stopping ,
/// Tell the service manager to update the watchdog timestamp.
Watchdog ,
/// Tell the service manager to execute the configured watchdog option.
WatchdogTrigger ,
/// Reset watchdog timeout value during runtime.
2024-01-06 14:45:36 +01:00
/// Minimal precision is microseconds, not nanoseconds.
2024-01-05 22:33:53 +01:00
WatchdogUsec ( types ::Microseconds ) ,
2023-12-08 14:46:54 +01:00
/// Tells the service manager to extend the startup, runtime or shutdown service timeout corresponding the current state.
2024-01-06 14:45:36 +01:00
/// Minimal precision is microseconds, not nanoseconds.
2024-01-05 22:33:53 +01:00
ExtendTimeoutUsec ( types ::Microseconds ) ,
2023-12-08 14:46:54 +01:00
}
impl < ' a > Display for NotifyState < ' a > {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match * self {
2024-01-05 21:27:09 +01:00
NotifyState ::BusError ( s ) = > write! ( f , " BUSERROR={s} " ) ,
2023-12-08 14:46:54 +01:00
NotifyState ::Errno ( e ) = > write! ( f , " ERRNO={e} " ) ,
2024-01-05 21:27:09 +01:00
NotifyState ::FdName ( name ) = > write! ( f , " FDNAME={name} " ) ,
2023-12-08 16:21:00 +01:00
NotifyState ::FdStore = > f . write_str ( " FDSTORE=1 " ) ,
NotifyState ::FdStoreRemove = > f . write_str ( " FDSTOREREMOVE=1 " ) ,
2023-12-08 14:46:54 +01:00
NotifyState ::FdpollDisable = > f . write_str ( " FDPOLL=0 " ) ,
NotifyState ::Mainpid ( pid ) = > write! ( f , " MAINPID={pid} " ) ,
2024-01-05 21:27:09 +01:00
NotifyState ::Other ( message ) = > f . write_str ( message . as_ref ( ) ) ,
2023-12-08 14:46:54 +01:00
NotifyState ::Ready = > f . write_str ( " READY=1 " ) ,
2023-12-08 17:26:04 +01:00
NotifyState ::Reloading = > f . write_str ( " RELOADING=1 " ) ,
2024-01-05 21:27:09 +01:00
NotifyState ::Status ( status ) = > write! ( f , " STATUS={status} " ) ,
2023-12-08 14:46:54 +01:00
NotifyState ::Stopping = > f . write_str ( " STOPPING=1 " ) ,
NotifyState ::Watchdog = > f . write_str ( " WATCHDOG=1 " ) ,
NotifyState ::WatchdogTrigger = > f . write_str ( " WATCHDOG=trigger " ) ,
2024-01-06 14:45:36 +01:00
NotifyState ::WatchdogUsec ( duration ) = > {
write! ( f , " WATCHDOG_USEC={duration} " )
2023-12-08 14:46:54 +01:00
}
2024-01-06 14:45:36 +01:00
NotifyState ::ExtendTimeoutUsec ( duration ) = > {
write! ( f , " EXTEND_TIMEOUT_USEC={duration} " )
2023-12-08 14:46:54 +01:00
}
}
}
}