sd_notify/lib.rs
1#![deny(missing_docs)]
2
3//! Lightweight crate for interacting with `systemd`.
4//!
5//! This crate can be used to send service readiness or state change notifications
6//! to `systemd` or compatible service managers. It doesn't offer the full
7//! functionality of `libsystemd`, but is pure-Rust and has no dependencies.
8//!
9//! For bindings to the native `libsystemd` library, see the [`systemd`][systemd]
10//! crate. For a more complete Rust reimplementation of `libsystemd` API, see the
11//! [`libsystemd`][libsystemd] crate.
12//!
13//! [systemd]: https://crates.io/crates/systemd
14//! [libsystemd]: https://crates.io/crates/libsystemd
15//!
16//! # Example
17//!
18//! ```no_run
19//! # use sd_notify::NotifyState;
20//! #
21//! let _ = sd_notify::notify(&[NotifyState::Ready]);
22//! ```
23
24use std::convert::TryFrom;
25use std::env;
26use std::ffi::OsStr;
27use std::fmt::{self, Display, Formatter, Write};
28use std::fs;
29use std::io::{self, ErrorKind};
30use std::mem::MaybeUninit;
31#[cfg(feature = "fdstore")]
32use std::os::fd::BorrowedFd;
33use std::os::unix::io::RawFd;
34use std::os::unix::net::UnixDatagram;
35use std::process;
36use std::str::FromStr;
37use std::time::Duration;
38
39use libc::CLOCK_MONOTONIC;
40
41/// Daemon notification for the service manager.
42#[derive(Clone, Debug)]
43pub enum NotifyState<'a> {
44 /// Service startup is finished.
45 Ready,
46 /// Service is reloading its configuration.
47 ///
48 /// On systemd v253 and newer, this message MUST be followed by a
49 /// [`NotifyState::MonotonicUsec`] notification, or the reload will fail
50 /// and the service will be terminated.
51 Reloading,
52 /// Service is stopping.
53 Stopping,
54 /// Free-form status message for the service manager.
55 Status(&'a str),
56 /// Service has failed with an `errno`-style error code, e.g. `2` for `ENOENT`.
57 Errno(u32),
58 /// Service has failed with a D-Bus-style error code, e.g. `org.freedesktop.DBus.Error.TimedOut`.
59 BusError(&'a str),
60 /// Main process ID (PID) of the service, in case it wasn't started directly by the service manager.
61 MainPid(u32),
62 /// Tells the service manager to update the watchdog timestamp.
63 Watchdog,
64 /// Tells the service manager to trigger a watchdog failure.
65 WatchdogTrigger,
66 /// Resets the configured watchdog value.
67 WatchdogUsec(u32),
68 /// Tells the service manager to extend the service timeout.
69 ExtendTimeoutUsec(u32),
70 /// Tells the service manager to store attached file descriptors.
71 #[cfg(feature = "fdstore")]
72 FdStore,
73 /// Tells the service manager to remove stored file descriptors.
74 #[cfg(feature = "fdstore")]
75 FdStoreRemove,
76 /// Tells the service manager to use this name for the attached file descriptor.
77 #[cfg(feature = "fdstore")]
78 FdName(&'a str),
79 /// Notify systemd of the current monotonic time in microseconds.
80 /// You can construct this value by calling [`NotifyState::monotonic_usec_now()`].
81 MonotonicUsec(i128),
82 /// Custom state.
83 Custom(&'a str),
84}
85
86impl Display for NotifyState<'_> {
87 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
88 match self {
89 NotifyState::Ready => write!(f, "READY=1"),
90 NotifyState::Reloading => write!(f, "RELOADING=1"),
91 NotifyState::Stopping => write!(f, "STOPPING=1"),
92 NotifyState::Status(msg) => write!(f, "STATUS={}", msg),
93 NotifyState::Errno(err) => write!(f, "ERRNO={}", err),
94 NotifyState::BusError(addr) => write!(f, "BUSERROR={}", addr),
95 NotifyState::MainPid(pid) => write!(f, "MAINPID={}", pid),
96 NotifyState::Watchdog => write!(f, "WATCHDOG=1"),
97 NotifyState::WatchdogTrigger => write!(f, "WATCHDOG=trigger"),
98 NotifyState::WatchdogUsec(usec) => write!(f, "WATCHDOG_USEC={}", usec),
99 NotifyState::ExtendTimeoutUsec(usec) => write!(f, "EXTEND_TIMEOUT_USEC={}", usec),
100 #[cfg(feature = "fdstore")]
101 NotifyState::FdStore => write!(f, "FDSTORE=1"),
102 #[cfg(feature = "fdstore")]
103 NotifyState::FdStoreRemove => write!(f, "FDSTOREREMOVE=1"),
104 #[cfg(feature = "fdstore")]
105 NotifyState::FdName(name) => write!(f, "FDNAME={}", name),
106 NotifyState::MonotonicUsec(usec) => write!(f, "MONOTONIC_USEC={}", usec),
107 NotifyState::Custom(state) => write!(f, "{}", state),
108 }
109 }
110}
111
112impl NotifyState<'_> {
113 /// Create a new [`NotifyState::MonotonicUsec`] using the current system monotonic time.
114 ///
115 /// # Example
116 ///
117 /// ```
118 /// # use sd_notify::NotifyState;
119 /// #
120 /// let _ = NotifyState::monotonic_usec_now().expect("Failed to read monotonic time");
121 /// ```
122 pub fn monotonic_usec_now() -> io::Result<Self> {
123 monotonic_time_usec().map(NotifyState::MonotonicUsec)
124 }
125}
126
127/// Checks whether the system has been booted by `systemd`.
128///
129/// This is implemented by verifying that a `/run/systemd/system` directory exists.
130///
131/// # Example
132///
133/// ```no_run
134/// match sd_notify::booted() {
135/// Ok(true) => { println!("System is booted under systemd") },
136/// Ok(false) => { println!("System is not booted under systemd") },
137/// Err(e) => { println!("Error: {}", e) },
138/// }
139/// ```
140pub fn booted() -> io::Result<bool> {
141 match fs::symlink_metadata("/run/systemd/system") {
142 Ok(m) => Ok(m.is_dir()),
143 Err(e) if matches!(e.kind(), ErrorKind::NotFound) => Ok(false),
144 Err(e) => Err(e),
145 }
146}
147
148// Constants for env variable names so that we don't typo them.
149const LISTEN_FDNAMES: &str = "LISTEN_FDNAMES";
150const LISTEN_FDS: &str = "LISTEN_FDS";
151const LISTEN_PID: &str = "LISTEN_PID";
152const NOTIFY_SOCKET: &str = "NOTIFY_SOCKET";
153const WATCHDOG_PID: &str = "WATCHDOG_PID";
154const WATCHDOG_USEC: &str = "WATCHDOG_USEC";
155
156/// Sends the service manager a list of state changes.
157///
158/// See [`sd_notify(3)`][sd_notify] for details.
159///
160/// [sd_notify]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
161///
162/// # Limitations
163///
164/// The implementation of this function is somewhat naive: it doesn't support
165/// sending notifications on behalf of other processes, doesn't send credentials,
166/// and does not increase the send buffer size. It's still useful, though, in
167/// usual situations.
168///
169/// If you wish to send file descriptors, use the `notify_with_fds` function.
170///
171/// # Example
172///
173/// ```no_run
174/// # use sd_notify::NotifyState;
175/// #
176/// let _ = sd_notify::notify(&[NotifyState::Ready]);
177/// ```
178pub fn notify(state: &[NotifyState]) -> io::Result<()> {
179 let Some(socket_path) = env::var_os(NOTIFY_SOCKET) else {
180 return Ok(());
181 };
182 let mut msg = String::new();
183 for s in state {
184 writeln!(msg, "{}", s).unwrap();
185 }
186 let sock = connected_unix_datagram(&socket_path)?;
187 let len = sock.send(msg.as_bytes())?;
188 if len != msg.len() {
189 Err(io::Error::new(ErrorKind::WriteZero, "incomplete write"))
190 } else {
191 Ok(())
192 }
193}
194
195/// Sends the service manager a list of state changes.
196///
197/// This does the same as the [`notify`] and unsets the `NOTIFY_SOCKET`
198/// environment variable afterwards, so that child processes no longer inherit
199/// the variable.
200///
201/// # Safety
202///
203/// Since this function calls [`std::env::remove_var`], it has the same safety
204/// preconditions. See its safety documentation for more details. It can only
205/// be safely called before threads are spawned, in particular before any
206/// `tokio` runtime initialization or `#[tokio::main]`.
207pub unsafe fn notify_and_unset_env(state: &[NotifyState]) -> io::Result<()> {
208 let result = notify(state);
209 unsafe {
210 env::remove_var(NOTIFY_SOCKET);
211 }
212 result
213}
214
215/// Sends the service manager a list of state changes with file descriptors.
216///
217/// The notification mechanism involves sending a datagram to a Unix domain socket.
218/// See [`sd_pid_notify_with_fds(3)`][sd_pid_notify_with_fds] for details.
219///
220/// [sd_pid_notify_with_fds]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
221///
222/// # Limitations
223///
224/// The implementation of this function is somewhat naive: it doesn't support
225/// sending notifications on behalf of other processes, doesn't send credentials,
226/// and does not increase the send buffer size. It's still useful, though, in
227/// usual situations.
228///
229/// # Example
230///
231/// ```no_run
232/// # use sd_notify::NotifyState;
233/// # use std::os::fd::BorrowedFd;
234/// #
235/// # let fd = unsafe { BorrowedFd::borrow_raw(0) };
236/// #
237/// let _ = sd_notify::notify_with_fds(&[NotifyState::FdStore], &[fd]);
238/// ```
239#[cfg(feature = "fdstore")]
240pub fn notify_with_fds(state: &[NotifyState], fds: &[BorrowedFd<'_>]) -> io::Result<()> {
241 use sendfd::SendWithFd;
242
243 let Some(socket_path) = env::var_os(NOTIFY_SOCKET) else {
244 return Ok(());
245 };
246 let mut msg = String::new();
247 for s in state {
248 writeln!(msg, "{}", s).unwrap();
249 }
250 let sock = connected_unix_datagram(&socket_path)?;
251 let len = sock.send_with_fd(msg.as_bytes(), borrowed_fd_slice(fds))?;
252 if len != msg.len() {
253 Err(io::Error::new(ErrorKind::WriteZero, "incomplete write"))
254 } else {
255 Ok(())
256 }
257}
258
259/// Sends the service manager a list of state changes with file descriptors.
260///
261/// This does the same as the [`notify_with_fds`] and unsets the
262/// `NOTIFY_SOCKET` environment variable afterwards, so that child processes no
263/// longer inherit the variable.
264///
265/// # Safety
266///
267/// Since this function calls [`std::env::remove_var`], it has the same safety
268/// preconditions. See its safety documentation for more details. It can only
269/// be safely called before threads are spawned, in particular before any
270/// `tokio` runtime initialization or `#[tokio::main]`.
271#[cfg(feature = "fdstore")]
272pub unsafe fn notify_with_fds_and_unset_env(
273 state: &[NotifyState],
274 fds: &[BorrowedFd<'_>],
275) -> io::Result<()> {
276 let result = notify_with_fds(state, fds);
277 unsafe {
278 env::remove_var(NOTIFY_SOCKET);
279 }
280 result
281}
282
283#[cfg(feature = "fdstore")]
284fn borrowed_fd_slice<'a>(s: &'a [BorrowedFd<'_>]) -> &'a [RawFd] {
285 use std::slice;
286
287 // SAFETY: BorrowedFd is #[repr(transparent)] over RawFd (memory safety)
288 // and implements AsRawFd (lifetime safety).
289 // Required only because sendfd does not have i/o safety traits.
290 unsafe { slice::from_raw_parts(s.as_ptr() as *const RawFd, s.len()) }
291}
292
293fn connected_unix_datagram(path: &OsStr) -> io::Result<UnixDatagram> {
294 let result = UnixDatagram::unbound()?;
295 result.connect(path)?;
296 Ok(result)
297}
298
299/// Checks for file descriptors passed by the service manager for socket
300/// activation.
301///
302/// The function returns an iterator over file descriptors, starting from
303/// `SD_LISTEN_FDS_START`. The number of descriptors is obtained from the
304/// `LISTEN_FDS` environment variable.
305///
306/// Before returning, the file descriptors are set as `O_CLOEXEC`.
307///
308/// See [`sd_listen_fds(3)`][sd_listen_fds] for details.
309///
310/// [sd_listen_fds]: https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
311///
312/// # Example
313///
314/// ```no_run
315/// let socket = sd_notify::listen_fds().map(|mut fds| fds.next().expect("missing fd"));
316/// ```
317pub fn listen_fds() -> io::Result<impl ExactSizeIterator<Item = RawFd>> {
318 let listen_pid = if let Ok(pid) = env::var(LISTEN_PID) {
319 pid
320 } else {
321 return Ok(0..0);
322 }
323 .parse::<u32>()
324 .map_err(|_| io::Error::new(ErrorKind::InvalidInput, "invalid LISTEN_PID"))?;
325
326 if listen_pid != process::id() {
327 return Ok(0..0);
328 }
329
330 let listen_fds = if let Ok(fds) = env::var(LISTEN_FDS) {
331 fds
332 } else {
333 return Ok(0..0);
334 }
335 .parse::<u32>()
336 .map_err(|_| io::Error::new(ErrorKind::InvalidInput, "invalid LISTEN_FDS"))?;
337
338 let overflow = || io::Error::new(ErrorKind::InvalidInput, "fd count overflowed");
339
340 const SD_LISTEN_FDS_START: u32 = 3;
341 let last = SD_LISTEN_FDS_START
342 .checked_add(listen_fds)
343 .ok_or_else(overflow)?;
344
345 for fd in SD_LISTEN_FDS_START..last {
346 fd_cloexec(fd)?
347 }
348
349 let last = RawFd::try_from(last).map_err(|_| overflow())?;
350 let listen_fds = SD_LISTEN_FDS_START as RawFd..last;
351 Ok(listen_fds)
352}
353
354/// Checks for file descriptors passed by the service manager for socket
355/// activation.
356///
357/// This does the same as the [`listen_fds`] and unsets the `LISTEN_FDS` and
358/// `LISTEN_PID` environment variables afterwards, so that child processes no
359/// longer inherit the variable.
360///
361/// # Safety
362///
363/// Since this function calls [`std::env::remove_var`], it has the same safety
364/// preconditions. See its safety documentation for more details. It can only
365/// be safely called before threads are spawned, in particular before any
366/// `tokio` runtime initialization or `#[tokio::main]`.
367pub unsafe fn listen_fds_and_unset_env() -> io::Result<impl ExactSizeIterator<Item = RawFd>> {
368 let result = listen_fds();
369 unsafe {
370 env::remove_var(LISTEN_PID);
371 env::remove_var(LISTEN_FDS);
372 }
373 result
374}
375
376/// Checks for file descriptors passed by the service manager for socket
377/// activation.
378///
379/// The function returns an iterator over file descriptors, starting from
380/// `SD_LISTEN_FDS_START`. The number of descriptors is obtained from the
381/// `LISTEN_FDS` environment variable.
382///
383/// Before returning, the file descriptors are set as `O_CLOEXEC`.
384///
385/// See [`sd_listen_fds_with_names(3)`][sd_listen_fds_with_names] for details.
386///
387/// [sd_listen_fds_with_names]: https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
388///
389/// # Example
390///
391/// ```no_run
392/// let socket = sd_notify::listen_fds().map(|mut fds| fds.next().expect("missing fd"));
393/// ```
394pub fn listen_fds_with_names() -> io::Result<impl ExactSizeIterator<Item = (RawFd, String)>> {
395 let listen_fds = listen_fds()?;
396 zip_fds_with_names(listen_fds, env::var(LISTEN_FDNAMES).ok())
397}
398
399/// Checks for file descriptors passed by the service manager for socket
400/// activation.
401///
402/// This does the same as the [`listen_fds_with_names`] and unsets the
403/// `LISTEN_FDNAMES`, `LISTEN_FDS` and `LISTEN_PID` environment variables
404/// afterwards, so that child processes no longer inherit the variable.
405///
406/// # Safety
407///
408/// Since this function calls [`std::env::remove_var`], it has the same safety
409/// preconditions. See its safety documentation for more details. It can only
410/// be safely called before threads are spawned, in particular before any
411/// `tokio` runtime initialization or `#[tokio::main]`.
412pub unsafe fn listen_fds_with_names_and_unset_env(
413) -> io::Result<impl ExactSizeIterator<Item = (RawFd, String)>> {
414 let result = listen_fds_with_names();
415 unsafe {
416 env::remove_var(LISTEN_PID);
417 env::remove_var(LISTEN_FDS);
418 env::remove_var(LISTEN_FDNAMES);
419 }
420 result
421}
422
423/// Internal helper that is independent of listen_fds function, for testing purposes.
424fn zip_fds_with_names(
425 listen_fds: impl ExactSizeIterator<Item = RawFd>,
426 listen_fdnames: Option<String>,
427) -> io::Result<impl ExactSizeIterator<Item = (RawFd, String)>> {
428 let listen_fdnames = if let Some(names) = listen_fdnames {
429 // systemd shouldn't provide an empty fdname element. However if it does, the
430 // sd_listen_fds_with_names function will return an empty string for that fd,
431 // as in the following C example:
432 //
433 // void main() {
434 // char **names;
435 // setenv("LISTEN_FDNAMES", "x::z", 1);
436 // int n = sd_listen_fds_with_names(0, &names);
437 // assert(*names[1] == 0);
438 // }
439 names.split(':').map(|x| x.to_owned()).collect::<Vec<_>>()
440 } else {
441 let mut names = vec![];
442 names.resize(listen_fds.len(), "unknown".to_string());
443 names
444 };
445
446 if listen_fdnames.len() == listen_fds.len() {
447 Ok(listen_fds.zip(listen_fdnames))
448 } else {
449 Err(io::Error::new(
450 ErrorKind::InvalidInput,
451 "invalid LISTEN_FDNAMES",
452 ))
453 }
454}
455
456fn fd_cloexec(fd: u32) -> io::Result<()> {
457 let fd = RawFd::try_from(fd).map_err(|_| io::Error::from_raw_os_error(libc::EBADF))?;
458 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
459 if flags < 0 {
460 return Err(io::Error::last_os_error());
461 }
462 let new_flags = flags | libc::FD_CLOEXEC;
463 if new_flags != flags {
464 let r = unsafe { libc::fcntl(fd, libc::F_SETFD, new_flags) };
465 if r < 0 {
466 return Err(io::Error::last_os_error());
467 }
468 }
469 Ok(())
470}
471
472/// Asks the service manager for enabled watchdog.
473///
474/// See [`sd_watchdog_enabled(3)`][sd_watchdog_enabled] for details.
475///
476/// [sd_watchdog_enabled]: https://www.freedesktop.org/software/systemd/man/sd_watchdog_enabled.html
477///
478/// # Example
479///
480/// ```no_run
481/// # use sd_notify;
482/// #
483/// if let Some(duration) = sd_notify::watchdog_enabled() {
484/// // watchdog enabled with `duration`
485/// } else {
486/// // watchdog disabled
487/// }
488/// ```
489pub fn watchdog_enabled() -> Option<Duration> {
490 let s = env::var(WATCHDOG_USEC)
491 .ok()
492 .and_then(|s| u64::from_str(&s).ok());
493 let p = env::var(WATCHDOG_PID)
494 .ok()
495 .and_then(|s| u32::from_str(&s).ok());
496 s.filter(|_| p.is_none_or(|pid| pid == process::id()))
497 .map(Duration::from_micros)
498}
499
500/// Asks the service manager for enabled watchdog.
501///
502/// This does the same as the [`watchdog_enabled`] and unsets the
503/// `WATCHDOG_PID` and `WATCHDOG_USEC` environment variables afterwards, so
504/// that child processes no longer inherit the variable.
505///
506/// # Safety
507///
508/// Since this function calls [`std::env::remove_var`], it has the same safety
509/// preconditions. See its safety documentation for more details. It can only
510/// be safely called before threads are spawned, in particular before any
511/// `tokio` runtime initialization or `#[tokio::main]`.
512pub unsafe fn watchdog_enabled_and_unset_env() -> Option<Duration> {
513 let result = watchdog_enabled();
514 unsafe {
515 env::remove_var(WATCHDOG_USEC);
516 env::remove_var(WATCHDOG_PID);
517 }
518 result
519}
520
521fn monotonic_time_usec() -> io::Result<i128> {
522 let mut timespec = MaybeUninit::uninit();
523 let rv = unsafe { libc::clock_gettime(CLOCK_MONOTONIC, timespec.as_mut_ptr()) };
524 if rv != 0 {
525 return Err(io::Error::last_os_error());
526 }
527 let timespec = unsafe { timespec.assume_init() };
528
529 // nanoseconds / 1_000 -> microseconds.
530 let lower_msec = (timespec.tv_nsec / 1_000) as i128;
531 // seconds * 1_000_000 -> microseconds
532 let upper_msec = (timespec.tv_sec * 1_000_000) as i128;
533 Ok(upper_msec + lower_msec)
534}
535
536#[cfg(test)]
537mod tests {
538 use std::os::fd::RawFd;
539
540 #[test]
541 fn listen_fds_with_names() {
542 assert_eq!(
543 super::zip_fds_with_names(3 as RawFd..4 as RawFd, Some("omelette".to_string()))
544 .unwrap()
545 .collect::<Vec<_>>(),
546 vec![(3 as RawFd, "omelette".to_string())]
547 );
548
549 assert_eq!(
550 super::zip_fds_with_names(
551 3 as RawFd..5 as RawFd,
552 Some("omelette:baguette".to_string())
553 )
554 .unwrap()
555 .collect::<Vec<_>>(),
556 vec![
557 (3 as RawFd, "omelette".to_string()),
558 (4 as RawFd, "baguette".to_string())
559 ]
560 );
561
562 // LISTEN_FDNAMES is cleared
563 assert_eq!(
564 super::zip_fds_with_names(3 as RawFd..4 as RawFd, None)
565 .unwrap()
566 .next(),
567 Some((3 as RawFd, "unknown".to_string()))
568 );
569
570 // LISTEN_FDNAMES is cleared, every fd should have the name "unknown"
571 assert_eq!(
572 super::zip_fds_with_names(3 as RawFd..5 as RawFd, None)
573 .unwrap()
574 .collect::<Vec<_>>(),
575 vec![
576 (3 as RawFd, "unknown".to_string()),
577 (4 as RawFd, "unknown".to_string())
578 ],
579 );
580
581 // Raise an error if LISTEN_FDNAMES has a different number of entries as fds
582 assert!(super::zip_fds_with_names(
583 3 as RawFd..6 as RawFd,
584 Some("omelette:baguette".to_string())
585 )
586 .is_err());
587 }
588}