nix/sys/
timerfd.rs

1//! Timer API via file descriptors.
2//!
3//! Timer FD is a Linux-only API to create timers and get expiration
4//! notifications through file descriptors.
5//!
6//! For more documentation, please read [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html).
7//!
8//! # Examples
9//!
10//! Create a new one-shot timer that expires after 1 second.
11//! ```
12//! # use std::os::unix::io::AsRawFd;
13//! # use nix::sys::timerfd::{TimerFd, ClockId, TimerFlags, TimerSetTimeFlags,
14//! #    Expiration};
15//! # use nix::sys::time::{TimeSpec, TimeValLike};
16//! # use nix::unistd::read;
17//! #
18//! // We create a new monotonic timer.
19//! let timer = TimerFd::new(ClockId::CLOCK_MONOTONIC, TimerFlags::empty())
20//!     .unwrap();
21//!
22//! // We set a new one-shot timer in 1 seconds.
23//! timer.set(
24//!     Expiration::OneShot(TimeSpec::seconds(1)),
25//!     TimerSetTimeFlags::empty()
26//! ).unwrap();
27//!
28//! // We wait for the timer to expire.
29//! timer.wait().unwrap();
30//! ```
31use crate::sys::time::{TIMESPEC_ZERO, TimeSpec};
32use crate::unistd::read;
33use crate::{errno::Errno, Result};
34use bitflags::bitflags;
35use libc::c_int;
36use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
37
38/// A timerfd instance. This is also a file descriptor, you can feed it to
39/// other interfaces consuming file descriptors, epoll for example.
40#[derive(Debug)]
41pub struct TimerFd {
42    fd: RawFd,
43}
44
45impl AsRawFd for TimerFd {
46    fn as_raw_fd(&self) -> RawFd {
47        self.fd
48    }
49}
50
51impl FromRawFd for TimerFd {
52    unsafe fn from_raw_fd(fd: RawFd) -> Self {
53        TimerFd { fd }
54    }
55}
56
57libc_enum! {
58    /// The type of the clock used to mark the progress of the timer. For more
59    /// details on each kind of clock, please refer to [timerfd_create(2)](https://man7.org/linux/man-pages/man2/timerfd_create.2.html).
60    #[repr(i32)]
61    #[non_exhaustive]
62    pub enum ClockId {
63        CLOCK_REALTIME,
64        CLOCK_MONOTONIC,
65        CLOCK_BOOTTIME,
66        CLOCK_REALTIME_ALARM,
67        CLOCK_BOOTTIME_ALARM,
68    }
69}
70
71libc_bitflags! {
72    /// Additional flags to change the behaviour of the file descriptor at the
73    /// time of creation.
74    pub struct TimerFlags: c_int {
75        TFD_NONBLOCK;
76        TFD_CLOEXEC;
77    }
78}
79
80bitflags! {
81    /// Flags that are used for arming the timer.
82    pub struct TimerSetTimeFlags: libc::c_int {
83        const TFD_TIMER_ABSTIME = libc::TFD_TIMER_ABSTIME;
84    }
85}
86
87#[derive(Debug, Clone, Copy)]
88struct TimerSpec(libc::itimerspec);
89
90impl TimerSpec {
91    pub const fn none() -> Self {
92        Self(libc::itimerspec {
93            it_interval: TIMESPEC_ZERO,
94            it_value: TIMESPEC_ZERO,
95        })
96    }
97}
98
99impl AsRef<libc::itimerspec> for TimerSpec {
100    fn as_ref(&self) -> &libc::itimerspec {
101        &self.0
102    }
103}
104
105impl From<Expiration> for TimerSpec {
106    fn from(expiration: Expiration) -> TimerSpec {
107        match expiration {
108            Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
109                it_interval: TIMESPEC_ZERO,
110                it_value: *t.as_ref(),
111            }),
112            Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
113                it_interval: *interval.as_ref(),
114                it_value: *start.as_ref(),
115            }),
116            Expiration::Interval(t) => TimerSpec(libc::itimerspec {
117                it_interval: *t.as_ref(),
118                it_value: *t.as_ref(),
119            }),
120        }
121    }
122}
123
124impl From<TimerSpec> for Expiration {
125    fn from(timerspec: TimerSpec) -> Expiration {
126        match timerspec {
127            TimerSpec(libc::itimerspec {
128                it_interval:
129                    libc::timespec {
130                        tv_sec: 0,
131                        tv_nsec: 0,
132                        ..
133                    },
134                it_value: ts,
135            }) => Expiration::OneShot(ts.into()),
136            TimerSpec(libc::itimerspec {
137                it_interval: int_ts,
138                it_value: val_ts,
139            }) => {
140                if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) {
141                    Expiration::Interval(int_ts.into())
142                } else {
143                    Expiration::IntervalDelayed(val_ts.into(), int_ts.into())
144                }
145            }
146        }
147    }
148}
149
150/// An enumeration allowing the definition of the expiration time of an alarm,
151/// recurring or not.
152#[derive(Debug, Clone, Copy, Eq, PartialEq)]
153pub enum Expiration {
154    OneShot(TimeSpec),
155    IntervalDelayed(TimeSpec, TimeSpec),
156    Interval(TimeSpec),
157}
158
159impl TimerFd {
160    /// Creates a new timer based on the clock defined by `clockid`. The
161    /// underlying fd can be assigned specific flags with `flags` (CLOEXEC,
162    /// NONBLOCK). The underlying fd will be closed on drop.
163    pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> {
164        Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) })
165            .map(|fd| Self { fd })
166    }
167
168    /// Sets a new alarm on the timer.
169    ///
170    /// # Types of alarm
171    ///
172    /// There are 3 types of alarms you can set:
173    ///
174    ///   - one shot: the alarm will trigger once after the specified amount of
175    /// time.
176    ///     Example: I want an alarm to go off in 60s and then disables itself.
177    ///
178    ///   - interval: the alarm will trigger every specified interval of time.
179    ///     Example: I want an alarm to go off every 60s. The alarm will first
180    ///     go off 60s after I set it and every 60s after that. The alarm will
181    ///     not disable itself.
182    ///
183    ///   - interval delayed: the alarm will trigger after a certain amount of
184    ///     time and then trigger at a specified interval.
185    ///     Example: I want an alarm to go off every 60s but only start in 1h.
186    ///     The alarm will first trigger 1h after I set it and then every 60s
187    ///     after that. The alarm will not disable itself.
188    ///
189    /// # Relative vs absolute alarm
190    ///
191    /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass
192    /// to the `Expiration` you want is relative. If however you want an alarm
193    /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`.
194    /// Then the one shot TimeSpec and the delay TimeSpec of the delayed
195    /// interval are going to be interpreted as absolute.
196    ///
197    /// # Disabling alarms
198    ///
199    /// Note: Only one alarm can be set for any given timer. Setting a new alarm
200    /// actually removes the previous one.
201    ///
202    /// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm
203    /// altogether.
204    pub fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
205        let timerspec: TimerSpec = expiration.into();
206        Errno::result(unsafe {
207            libc::timerfd_settime(
208                self.fd,
209                flags.bits(),
210                timerspec.as_ref(),
211                std::ptr::null_mut(),
212            )
213        })
214        .map(drop)
215    }
216
217    /// Get the parameters for the alarm currently set, if any.
218    pub fn get(&self) -> Result<Option<Expiration>> {
219        let mut timerspec = TimerSpec::none();
220        let timerspec_ptr: *mut libc::itimerspec = &mut timerspec.0;
221
222        Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec_ptr) }).map(|_| {
223            if timerspec.0.it_interval.tv_sec == 0
224                && timerspec.0.it_interval.tv_nsec == 0
225                && timerspec.0.it_value.tv_sec == 0
226                && timerspec.0.it_value.tv_nsec == 0
227            {
228                None
229            } else {
230                Some(timerspec.into())
231            }
232        })
233    }
234
235    /// Remove the alarm if any is set.
236    pub fn unset(&self) -> Result<()> {
237        Errno::result(unsafe {
238            libc::timerfd_settime(
239                self.fd,
240                TimerSetTimeFlags::empty().bits(),
241                TimerSpec::none().as_ref(),
242                std::ptr::null_mut(),
243            )
244        })
245        .map(drop)
246    }
247
248    /// Wait for the configured alarm to expire.
249    ///
250    /// Note: If the alarm is unset, then you will wait forever.
251    pub fn wait(&self) -> Result<()> {
252        while let Err(e) = read(self.fd, &mut [0u8; 8]) {
253            if e != Errno::EINTR {
254                return Err(e)
255            }
256        }
257
258        Ok(())
259    }
260}
261
262impl Drop for TimerFd {
263    fn drop(&mut self) {
264        if !std::thread::panicking() {
265            let result = Errno::result(unsafe {
266                libc::close(self.fd)
267            });
268            if let Err(Errno::EBADF) = result {
269                panic!("close of TimerFd encountered EBADF");
270            }
271        }
272    }
273}