1#![doc(html_root_url = "https://docs.rs/sd-notify/0.1.0")]
2#![deny(missing_docs)]
3
4use std::convert::TryFrom;
26use std::env;
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;
37
38use libc::CLOCK_MONOTONIC;
39
40#[derive(Clone, Debug)]
42pub enum NotifyState<'a> {
43 Ready,
45 Reloading,
51 Stopping,
53 Status(&'a str),
55 Errno(u32),
57 BusError(&'a str),
59 MainPid(u32),
61 Watchdog,
63 WatchdogTrigger,
65 WatchdogUsec(u32),
67 ExtendTimeoutUsec(u32),
69 #[cfg(feature = "fdstore")]
71 FdStore,
72 #[cfg(feature = "fdstore")]
74 FdStoreRemove,
75 #[cfg(feature = "fdstore")]
77 FdName(&'a str),
78 MonotonicUsec(i128),
81 Custom(&'a str),
83}
84
85impl Display for NotifyState<'_> {
86 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
87 match self {
88 NotifyState::Ready => write!(f, "READY=1"),
89 NotifyState::Reloading => write!(f, "RELOADING=1"),
90 NotifyState::Stopping => write!(f, "STOPPING=1"),
91 NotifyState::Status(msg) => write!(f, "STATUS={}", msg),
92 NotifyState::Errno(err) => write!(f, "ERRNO={}", err),
93 NotifyState::BusError(addr) => write!(f, "BUSERROR={}", addr),
94 NotifyState::MainPid(pid) => write!(f, "MAINPID={}", pid),
95 NotifyState::Watchdog => write!(f, "WATCHDOG=1"),
96 NotifyState::WatchdogTrigger => write!(f, "WATCHDOG=trigger"),
97 NotifyState::WatchdogUsec(usec) => write!(f, "WATCHDOG_USEC={}", usec),
98 NotifyState::ExtendTimeoutUsec(usec) => write!(f, "EXTEND_TIMEOUT_USEC={}", usec),
99 #[cfg(feature = "fdstore")]
100 NotifyState::FdStore => write!(f, "FDSTORE=1"),
101 #[cfg(feature = "fdstore")]
102 NotifyState::FdStoreRemove => write!(f, "FDSTOREREMOVE=1"),
103 #[cfg(feature = "fdstore")]
104 NotifyState::FdName(name) => write!(f, "FDNAME={}", name),
105 NotifyState::MonotonicUsec(usec) => write!(f, "MONOTONIC_USEC={}", usec),
106 NotifyState::Custom(state) => write!(f, "{}", state),
107 }
108 }
109}
110
111impl NotifyState<'_> {
112 pub fn monotonic_usec_now() -> io::Result<Self> {
122 monotonic_time_usec().map(NotifyState::MonotonicUsec)
123 }
124}
125
126pub fn booted() -> io::Result<bool> {
136 let m = fs::symlink_metadata("/run/systemd/system")?;
137 Ok(m.is_dir())
138}
139
140pub fn notify(unset_env: bool, state: &[NotifyState]) -> io::Result<()> {
168 let mut msg = String::new();
169 let Some(sock) = connect_notify_socket(unset_env)? else {
170 return Ok(());
171 };
172 for s in state {
173 let _ = writeln!(msg, "{}", s);
174 }
175 let len = sock.send(msg.as_bytes())?;
176 if len != msg.len() {
177 Err(io::Error::new(ErrorKind::WriteZero, "incomplete write"))
178 } else {
179 Ok(())
180 }
181}
182
183#[cfg(feature = "fdstore")]
212pub fn notify_with_fds(
213 unset_env: bool,
214 state: &[NotifyState],
215 fds: &[BorrowedFd<'_>],
216) -> io::Result<()> {
217 use sendfd::SendWithFd;
218
219 let mut msg = String::new();
220 let Some(sock) = connect_notify_socket(unset_env)? else {
221 return Ok(());
222 };
223 for s in state {
224 let _ = writeln!(msg, "{}", s);
225 }
226 let len = sock.send_with_fd(msg.as_bytes(), borrowed_fd_slice(fds))?;
227 if len != msg.len() {
228 Err(io::Error::new(ErrorKind::WriteZero, "incomplete write"))
229 } else {
230 Ok(())
231 }
232}
233
234#[cfg(feature = "fdstore")]
235fn borrowed_fd_slice<'a>(s: &'a [BorrowedFd<'_>]) -> &'a [RawFd] {
236 use std::slice;
237
238 unsafe { slice::from_raw_parts(s.as_ptr() as *const RawFd, s.len()) }
242}
243
244fn connect_notify_socket(unset_env: bool) -> io::Result<Option<UnixDatagram>> {
245 let Some(socket_path) = env::var_os("NOTIFY_SOCKET") else {
246 return Ok(None);
247 };
248
249 if unset_env {
250 env::remove_var("NOTIFY_SOCKET");
251 }
252
253 let sock = UnixDatagram::unbound()?;
254 sock.connect(socket_path)?;
255 Ok(Some(sock))
256}
257
258pub fn listen_fds() -> io::Result<impl Iterator<Item = RawFd>> {
277 listen_fds_internal(true)
278}
279
280fn listen_fds_internal(unset_env: bool) -> io::Result<impl ExactSizeIterator<Item = RawFd>> {
281 let _guard1 = UnsetEnvGuard {
282 name: "LISTEN_PID",
283 unset_env,
284 };
285 let _guard2 = UnsetEnvGuard {
286 name: "LISTEN_FDS",
287 unset_env,
288 };
289
290 let listen_pid = if let Ok(pid) = env::var("LISTEN_PID") {
291 pid
292 } else {
293 return Ok(0..0);
294 }
295 .parse::<u32>()
296 .map_err(|_| io::Error::new(ErrorKind::InvalidInput, "invalid LISTEN_PID"))?;
297
298 if listen_pid != process::id() {
299 return Ok(0..0);
300 }
301
302 let listen_fds = if let Ok(fds) = env::var("LISTEN_FDS") {
303 fds
304 } else {
305 return Ok(0..0);
306 }
307 .parse::<u32>()
308 .map_err(|_| io::Error::new(ErrorKind::InvalidInput, "invalid LISTEN_FDS"))?;
309
310 let overflow = || io::Error::new(ErrorKind::InvalidInput, "fd count overflowed");
311
312 const SD_LISTEN_FDS_START: u32 = 3;
313 let last = SD_LISTEN_FDS_START
314 .checked_add(listen_fds)
315 .ok_or_else(overflow)?;
316
317 for fd in SD_LISTEN_FDS_START..last {
318 fd_cloexec(fd)?
319 }
320
321 let last = RawFd::try_from(last).map_err(|_| overflow())?;
322 let listen_fds = SD_LISTEN_FDS_START as RawFd..last;
323 Ok(listen_fds)
324}
325
326pub fn listen_fds_with_names(
351 unset_env: bool,
352) -> io::Result<impl ExactSizeIterator<Item = (RawFd, String)>> {
353 let listen_fds = listen_fds_internal(unset_env)?;
354 let _guard = UnsetEnvGuard {
355 name: "LISTEN_FDNAMES",
356 unset_env,
357 };
358 zip_fds_with_names(listen_fds, env::var("LISTEN_FDNAMES").ok())
359}
360
361fn zip_fds_with_names(
363 listen_fds: impl ExactSizeIterator<Item = RawFd>,
364 listen_fdnames: Option<String>,
365) -> io::Result<impl ExactSizeIterator<Item = (RawFd, String)>> {
366 let listen_fdnames = if let Some(names) = listen_fdnames {
367 names.split(':').map(|x| x.to_owned()).collect::<Vec<_>>()
378 } else {
379 let mut names = vec![];
380 names.resize(listen_fds.len(), "unknown".to_string());
381 names
382 };
383
384 if listen_fdnames.len() == listen_fds.len() {
385 Ok(listen_fds.zip(listen_fdnames))
386 } else {
387 Err(io::Error::new(
388 ErrorKind::InvalidInput,
389 "invalid LISTEN_FDNAMES",
390 ))
391 }
392}
393
394struct UnsetEnvGuard {
395 name: &'static str,
396 unset_env: bool,
397}
398
399impl Drop for UnsetEnvGuard {
400 fn drop(&mut self) {
401 if self.unset_env {
402 env::remove_var(self.name);
403 }
404 }
405}
406
407fn fd_cloexec(fd: u32) -> io::Result<()> {
408 let fd = RawFd::try_from(fd).map_err(|_| io::Error::from_raw_os_error(libc::EBADF))?;
409 let flags = unsafe { libc::fcntl(fd, libc::F_GETFD, 0) };
410 if flags < 0 {
411 return Err(io::Error::last_os_error());
412 }
413 let new_flags = flags | libc::FD_CLOEXEC;
414 if new_flags != flags {
415 let r = unsafe { libc::fcntl(fd, libc::F_SETFD, new_flags) };
416 if r < 0 {
417 return Err(io::Error::last_os_error());
418 }
419 }
420 Ok(())
421}
422
423pub fn watchdog_enabled(unset_env: bool, usec: &mut u64) -> bool {
443 struct Guard {
444 unset_env: bool,
445 }
446
447 impl Drop for Guard {
448 fn drop(&mut self) {
449 if self.unset_env {
450 env::remove_var("WATCHDOG_USEC");
451 env::remove_var("WATCHDOG_PID");
452 }
453 }
454 }
455
456 let _guard = Guard { unset_env };
457
458 let s = env::var("WATCHDOG_USEC")
459 .ok()
460 .and_then(|s| u64::from_str(&s).ok());
461 let p = env::var("WATCHDOG_PID")
462 .ok()
463 .and_then(|s| u32::from_str(&s).ok());
464
465 match (s, p) {
466 (Some(usec_val), Some(pid)) if pid == process::id() => {
467 *usec = usec_val;
468 true
469 }
470 _ => false,
471 }
472}
473
474fn monotonic_time_usec() -> io::Result<i128> {
475 let mut timespec = MaybeUninit::uninit();
476 let rv = unsafe { libc::clock_gettime(CLOCK_MONOTONIC, timespec.as_mut_ptr()) };
477 if rv != 0 {
478 return Err(io::Error::last_os_error());
479 }
480 let timespec = unsafe { timespec.assume_init() };
481
482 let lower_msec = (timespec.tv_nsec / 1_000) as i128;
484 let upper_msec = (timespec.tv_sec * 1_000_000) as i128;
486 Ok(upper_msec + lower_msec)
487}
488
489#[cfg(test)]
490mod tests {
491 use super::NotifyState;
492 use std::env;
493 use std::fs;
494 use std::os::fd::RawFd;
495 use std::os::unix::net::UnixDatagram;
496 use std::path::PathBuf;
497 use std::process;
498
499 struct SocketHelper(PathBuf, UnixDatagram);
500
501 impl SocketHelper {
502 pub fn recv_string(&self) -> String {
503 let mut buf = [0; 1024];
504 let len = self.1.recv(&mut buf).unwrap();
505 String::from_utf8(Vec::from(&buf[0..len])).unwrap()
506 }
507 }
508
509 impl Drop for SocketHelper {
510 fn drop(&mut self) {
511 let _ = fs::remove_file(&self.0);
512 }
513 }
514
515 fn bind_socket() -> SocketHelper {
516 let path = env::temp_dir().join("sd-notify-test-sock");
517 let _ = fs::remove_file(&path);
518
519 env::set_var("NOTIFY_SOCKET", &path);
520 let sock = UnixDatagram::bind(&path).unwrap();
521 SocketHelper(path, sock)
522 }
523
524 #[test]
525 fn notify() {
526 let s = bind_socket();
527
528 super::notify(false, &[NotifyState::Ready]).unwrap();
529 assert_eq!(s.recv_string(), "READY=1\n");
530 assert!(env::var_os("NOTIFY_SOCKET").is_some());
531
532 super::notify(
533 true,
534 &[
535 NotifyState::Status("Reticulating splines"),
536 NotifyState::Watchdog,
537 NotifyState::Custom("X_WORKS=1"),
538 ],
539 )
540 .unwrap();
541 assert_eq!(
542 s.recv_string(),
543 "STATUS=Reticulating splines\nWATCHDOG=1\nX_WORKS=1\n"
544 );
545 assert!(env::var_os("NOTIFY_SOCKET").is_none());
546 }
547
548 #[test]
549 fn listen_fds() {
550 assert!(super::listen_fds().unwrap().next().is_none());
553
554 env::set_var("LISTEN_PID", "1");
555 env::set_var("LISTEN_FDS", "1");
556 assert!(super::listen_fds().unwrap().next().is_none());
557 assert!(env::var_os("LISTEN_PID").is_none());
558 assert!(env::var_os("LISTEN_FDS").is_none());
559
560 env::set_var("LISTEN_PID", "no way");
561 env::set_var("LISTEN_FDS", "1");
562 assert!(super::listen_fds().is_err());
563 assert!(env::var_os("LISTEN_PID").is_none());
564 assert!(env::var_os("LISTEN_FDS").is_none());
565 }
566
567 #[test]
568 fn listen_fds_with_names() {
569 assert_eq!(
570 super::zip_fds_with_names(3 as RawFd..4 as RawFd, Some("omelette".to_string()))
571 .unwrap()
572 .collect::<Vec<_>>(),
573 vec![(3 as RawFd, "omelette".to_string())]
574 );
575
576 assert_eq!(
577 super::zip_fds_with_names(
578 3 as RawFd..5 as RawFd,
579 Some("omelette:baguette".to_string())
580 )
581 .unwrap()
582 .collect::<Vec<_>>(),
583 vec![
584 (3 as RawFd, "omelette".to_string()),
585 (4 as RawFd, "baguette".to_string())
586 ]
587 );
588
589 assert_eq!(
591 super::zip_fds_with_names(3 as RawFd..4 as RawFd, None)
592 .unwrap()
593 .next(),
594 Some((3 as RawFd, "unknown".to_string()))
595 );
596
597 assert_eq!(
599 super::zip_fds_with_names(3 as RawFd..5 as RawFd, None)
600 .unwrap()
601 .collect::<Vec<_>>(),
602 vec![
603 (3 as RawFd, "unknown".to_string()),
604 (4 as RawFd, "unknown".to_string())
605 ],
606 );
607
608 assert!(super::zip_fds_with_names(
610 3 as RawFd..6 as RawFd,
611 Some("omelette:baguette".to_string())
612 )
613 .is_err());
614 }
615
616 #[test]
617 fn watchdog_enabled() {
618 env::set_var("WATCHDOG_USEC", "5");
622 env::set_var("WATCHDOG_PID", "1");
623
624 let mut usec = 0;
625 assert!(!super::watchdog_enabled(true, &mut usec));
626 assert_eq!(usec, 0);
627
628 assert!(env::var_os("WATCHDOG_USEC").is_none());
629 assert!(env::var_os("WATCHDOG_PID").is_none());
630
631 env::set_var("WATCHDOG_USEC", "invalid-usec");
633 env::set_var("WATCHDOG_PID", process::id().to_string());
634
635 let mut usec = 0;
636 assert!(!super::watchdog_enabled(true, &mut usec));
637 assert_eq!(usec, 0);
638
639 assert!(env::var_os("WATCHDOG_USEC").is_none());
640 assert!(env::var_os("WATCHDOG_PID").is_none());
641
642 let mut usec = 0;
644 assert!(!super::watchdog_enabled(false, &mut usec));
645 assert_eq!(usec, 0);
646
647 assert!(env::var_os("WATCHDOG_USEC").is_none());
648 assert!(env::var_os("WATCHDOG_PID").is_none());
649
650 env::set_var("WATCHDOG_USEC", "5");
652 env::set_var("WATCHDOG_PID", process::id().to_string());
653
654 let mut usec = 0;
655 assert!(super::watchdog_enabled(false, &mut usec));
656 assert_eq!(usec, 5);
657 assert!(env::var_os("WATCHDOG_USEC").is_some());
658 assert!(env::var_os("WATCHDOG_PID").is_some());
659 }
660}