sd_notify/
lib.rs

1#![doc(html_root_url = "https://docs.rs/sd-notify/0.1.0")]
2#![deny(missing_docs)]
3
4//! Lightweight crate for interacting with `systemd`.
5//!
6//! This crate can be used to send service readiness or state change notifications
7//! to `systemd` or compatible service managers. It doesn't offer the full
8//! functionality of `libsystemd`, but is pure-Rust and has no dependencies.
9//!
10//! For bindings to the native `libsystemd` library, see the [`systemd`][systemd]
11//! crate. For a more complete Rust reimplementation of `libsystemd` API, see the
12//! [`libsystemd`][libsystemd] crate.
13//!
14//! [systemd]: https://crates.io/crates/systemd
15//! [libsystemd]: https://crates.io/crates/libsystemd
16//!
17//! # Example
18//!
19//! ```no_run
20//! # use sd_notify::NotifyState;
21//! #
22//! let _ = sd_notify::notify(true, &[NotifyState::Ready]);
23//! ```
24
25use 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/// Daemon notification for the service manager.
41#[derive(Clone, Debug)]
42pub enum NotifyState<'a> {
43    /// Service startup is finished.
44    Ready,
45    /// Service is reloading its configuration.
46    ///
47    /// On systemd v253 and newer, this message MUST be followed by a
48    /// [`NotifyState::MonotonicUsec`] notification, or the reload will fail
49    /// and the service will be terminated.
50    Reloading,
51    /// Service is stopping.
52    Stopping,
53    /// Free-form status message for the service manager.
54    Status(&'a str),
55    /// Service has failed with an `errno`-style error code, e.g. `2` for `ENOENT`.
56    Errno(u32),
57    /// Service has failed with a D-Bus-style error code, e.g. `org.freedesktop.DBus.Error.TimedOut`.
58    BusError(&'a str),
59    /// Main process ID (PID) of the service, in case it wasn't started directly by the service manager.
60    MainPid(u32),
61    /// Tells the service manager to update the watchdog timestamp.
62    Watchdog,
63    /// Tells the service manager to trigger a watchdog failure.
64    WatchdogTrigger,
65    /// Resets the configured watchdog value.
66    WatchdogUsec(u32),
67    /// Tells the service manager to extend the service timeout.
68    ExtendTimeoutUsec(u32),
69    /// Tells the service manager to store attached file descriptors.
70    #[cfg(feature = "fdstore")]
71    FdStore,
72    /// Tells the service manager to remove stored file descriptors.
73    #[cfg(feature = "fdstore")]
74    FdStoreRemove,
75    /// Tells the service manager to use this name for the attached file descriptor.
76    #[cfg(feature = "fdstore")]
77    FdName(&'a str),
78    /// Notify systemd of the current monotonic time in microseconds.
79    /// You can construct this value by calling [`NotifyState::monotonic_usec_now()`].
80    MonotonicUsec(i128),
81    /// Custom state.
82    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    /// Create a new [`NotifyState::MonotonicUsec`] using the current system monotonic time.
113    ///
114    /// # Example
115    ///
116    /// ```
117    /// # use sd_notify::NotifyState;
118    /// #
119    /// let _ = NotifyState::monotonic_usec_now().expect("Failed to read monotonic time");
120    /// ```
121    pub fn monotonic_usec_now() -> io::Result<Self> {
122        monotonic_time_usec().map(NotifyState::MonotonicUsec)
123    }
124}
125
126/// Checks whether the system has been booted by `systemd`.
127///
128/// This is implemented by verifying that a `/run/systemd/system` directory exists.
129///
130/// # Example
131///
132/// ```no_run
133/// let _ = sd_notify::booted();
134/// ```
135pub fn booted() -> io::Result<bool> {
136    let m = fs::symlink_metadata("/run/systemd/system")?;
137    Ok(m.is_dir())
138}
139
140/// Sends the service manager a list of state changes.
141///
142/// If the `unset_env` parameter is set, the `NOTIFY_SOCKET` environment variable
143/// will be unset before returning. Further calls to `sd_notify` will fail, but
144/// child processes will no longer inherit the variable.
145///
146/// The notification mechanism involves sending a datagram to a Unix domain socket.
147/// See [`sd_notify(3)`][sd_notify] for details.
148///
149/// [sd_notify]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
150///
151/// # Limitations
152///
153/// The implementation of this function is somewhat naive: it doesn't support
154/// sending notifications on behalf of other processes, doesn't send credentials,
155/// and does not increase the send buffer size. It's still useful, though, in
156/// usual situations.
157///
158/// If you wish to send file descriptors, use the `notify_with_fds` function.
159///
160/// # Example
161///
162/// ```no_run
163/// # use sd_notify::NotifyState;
164/// #
165/// let _ = sd_notify::notify(true, &[NotifyState::Ready]);
166/// ```
167pub 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/// Sends the service manager a list of state changes with file descriptors.
184///
185/// If the `unset_env` parameter is set, the `NOTIFY_SOCKET` environment variable
186/// will be unset before returning. Further calls to `sd_notify` will fail, but
187/// child processes will no longer inherit the variable.
188///
189/// The notification mechanism involves sending a datagram to a Unix domain socket.
190/// See [`sd_pid_notify_with_fds(3)`][sd_pid_notify_with_fds] for details.
191///
192/// [sd_pid_notify_with_fds]: https://www.freedesktop.org/software/systemd/man/sd_notify.html
193///
194/// # Limitations
195///
196/// The implementation of this function is somewhat naive: it doesn't support
197/// sending notifications on behalf of other processes, doesn't send credentials,
198/// and does not increase the send buffer size. It's still useful, though, in
199/// usual situations.
200///
201/// # Example
202///
203/// ```no_run
204/// # use sd_notify::NotifyState;
205/// # use std::os::fd::BorrowedFd;
206/// #
207/// # let fd = unsafe { BorrowedFd::borrow_raw(0) };
208/// #
209/// let _ = sd_notify::notify_with_fds(false, &[NotifyState::FdStore], &[fd]);
210/// ```
211#[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    // SAFETY: BorrowedFd is #[repr(transparent)] over RawFd (memory safety)
239    // and implements AsRawFd (lifetime safety).
240    // Required only because sendfd does not have i/o safety traits.
241    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
258/// Checks for file descriptors passed by the service manager for socket
259/// activation.
260///
261/// The function returns an iterator over file descriptors, starting from
262/// `SD_LISTEN_FDS_START`. The number of descriptors is obtained from the
263/// `LISTEN_FDS` environment variable.
264///
265/// Before returning, the file descriptors are set as `O_CLOEXEC`.
266///
267/// See [`sd_listen_fds(3)`][sd_listen_fds] for details.
268///
269/// [sd_listen_fds]: https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
270///
271/// # Example
272///
273/// ```no_run
274/// let socket = sd_notify::listen_fds().map(|mut fds| fds.next().expect("missing fd"));
275/// ```
276pub 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
326/// Checks for file descriptors passed by the service manager for socket
327/// activation.
328///
329/// The function returns an iterator over file descriptors, starting from
330/// `SD_LISTEN_FDS_START`. The number of descriptors is obtained from the
331/// `LISTEN_FDS` environment variable.
332///
333/// If the `unset_env` parameter is set, the `LISTEN_PID`, `LISTEN_FDS` and
334/// `LISTEN_FDNAMES` environment variable will be unset before returning.
335/// Child processes will not see the fdnames passed to this process. This is
336/// usually not necessary, as a process should only use the `LISTEN_FDS`
337/// variable if it is the PID given in `LISTEN_PID`.
338///
339/// Before returning, the file descriptors are set as `O_CLOEXEC`.
340///
341/// See [`sd_listen_fds_with_names(3)`][sd_listen_fds_with_names] for details.
342///
343/// [sd_listen_fds_with_names]: https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html
344///
345/// # Example
346///
347/// ```no_run
348/// let socket = sd_notify::listen_fds().map(|mut fds| fds.next().expect("missing fd"));
349/// ```
350pub 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
361/// Internal helper that is independent of listen_fds function, for testing purposes.
362fn 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        // systemd shouldn't provide an empty fdname element. However if it does, the
368        // sd_listen_fds_with_names function will return an empty string for that fd,
369        // as in the following C example:
370        //
371        // void main() {
372        //  char **names;
373        //  setenv("LISTEN_FDNAMES", "x::z", 1);
374        //  int n = sd_listen_fds_with_names(0, &names);
375        //  assert(*names[1] == 0);
376        // }
377        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
423/// Asks the service manager for enabled watchdog.
424///
425/// If the `unset_env` parameter is set, the `WATCHDOG_USEC` and `WATCHDOG_PID` environment variables
426/// will be unset before returning. Further calls to `watchdog_enabled` will fail, but
427/// child processes will no longer inherit the variable.
428///
429/// See [`sd_watchdog_enabled(3)`][sd_watchdog_enabled] for details.
430///
431/// [sd_watchdog_enabled]: https://www.freedesktop.org/software/systemd/man/sd_watchdog_enabled.html
432///
433///
434/// # Example
435///
436/// ```no_run
437/// # use sd_notify;
438/// #
439/// let mut usec = 0;
440/// let enabled = sd_notify::watchdog_enabled(true, &mut usec);
441/// ```
442pub 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    // nanoseconds / 1_000 -> microseconds.
483    let lower_msec = (timespec.tv_nsec / 1_000) as i128;
484    // seconds * 1_000_000 -> microseconds
485    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        // We are not testing the success case because `fd_cloexec` would fail.
551
552        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        // LISTEN_FDNAMES is cleared
590        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        // LISTEN_FDNAMES is cleared, every fd should have the name "unknown"
598        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        // Raise an error if LISTEN_FDNAMES has a different number of entries as fds
609        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        // test original logic: https://github.com/systemd/systemd/blob/f3376ee8fa28aab3f7edfad1ddfbcceca5bc841c/src/libsystemd/sd-daemon/sd-daemon.c#L632
619
620        // invalid pid and unset env
621        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        // invalid usec and no unset env
632        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        // no usec, no pip no unset env
643        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        // valid pip
651        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}