timerfd/
lib.rs

1//! A rust interface to the Linux kernel's timerfd API.
2//!
3//! # Example
4//!
5//! ```
6//! use timerfd::{TimerFd, TimerState, SetTimeFlags};
7//! use std::time::Duration;
8//!
9//! // Create a new timerfd
10//! // (unwrap is actually fine here for most usecases)
11//! let mut tfd = TimerFd::new().unwrap();
12//!
13//! // The timer is initially disarmed
14//! assert_eq!(tfd.get_state(), TimerState::Disarmed);
15//!
16//! // Set the timer
17//! tfd.set_state(TimerState::Oneshot(Duration::new(1, 0)), SetTimeFlags::Default);
18//!
19//! // Observe that the timer is now set
20//! match tfd.get_state() {
21//!     TimerState::Oneshot(d) => println!("Remaining: {:?}", d),
22//!     _ => unreachable!(),
23//! }
24//!
25//! // Wait for the remaining time
26//! tfd.read();
27//!
28//! // It was a oneshot timer, so it's now disarmed
29//! assert_eq!(tfd.get_state(), TimerState::Disarmed);
30//! ```
31//!
32//! # Usage
33//!
34//! Unfortunately, this example can't show why you would use
35//! timerfd in the first place: Because it creates a file descriptor
36//! that you can monitor with `select(2)`, `poll(2)` and `epoll(2)`.
37//!
38//! In other words, the primary advantage this offers over any other
39//! timer implementation is that it implements the `AsFd`/`AsRawFd` traits.
40//!
41//! The file descriptor becomes ready/readable whenever the timer expires.
42#![warn(missing_debug_implementations)]
43
44extern crate rustix;
45
46use std::os::unix::prelude::*;
47use std::time::Duration;
48use std::io::Result as IoResult;
49use std::fmt;
50use rustix::fd::{AsFd, BorrowedFd, OwnedFd};
51use rustix::time::{Itimerspec, TimerfdClockId};
52
53#[derive(Clone, PartialEq, Eq)]
54pub enum ClockId {
55    /// Available clocks:
56    ///
57    /// A settable system-wide real-time clock.
58    Realtime       = TimerfdClockId::Realtime   as isize,
59
60    /// This clock is like CLOCK_REALTIME, but will wake the system if it is suspended. The
61    /// caller must have the CAP_WAKE_ALARM capability in order to set a timer against this
62    /// clock.
63    RealtimeAlarm  = TimerfdClockId::RealtimeAlarm as isize,
64
65    /// A nonsettable monotonically increasing clock that measures time from some unspecified
66    /// point in the past that does not change after system startup.
67    Monotonic      = TimerfdClockId::Monotonic  as isize,
68
69    /// Like CLOCK_MONOTONIC, this is a monotonically increasing clock. However, whereas the
70    /// CLOCK_MONOTONIC clock does not measure the time while a system is suspended, the
71    /// CLOCK_BOOTTIME clock does include the time during which the system is suspended. This
72    /// is useful for applications that need to be suspend-aware. CLOCK_REALTIME is not
73    /// suitable for such applications, since that clock is affected by discon‐ tinuous
74    /// changes to the system clock.
75    Boottime       = TimerfdClockId::Boottime   as isize,
76
77    /// This clock is like CLOCK_BOOTTIME, but will wake the system if it is suspended. The
78    /// caller must have the CAP_WAKE_ALARM capability in order to set a timer against this
79    /// clock.
80    BoottimeAlarm  = TimerfdClockId::BoottimeAlarm as isize,
81}
82
83fn clock_name (clock: &ClockId) -> &'static str {
84    match *clock {
85        ClockId::Realtime       => "CLOCK_REALTIME",
86        ClockId::RealtimeAlarm  => "CLOCK_REALTIME_ALARM",
87        ClockId::Monotonic      => "CLOCK_MONOTONIC",
88        ClockId::Boottime       => "CLOCK_BOOTTIME",
89        ClockId::BoottimeAlarm  => "CLOCK_BOOTTIME_ALARM",
90    }
91}
92
93impl fmt::Display for ClockId {
94    fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
95        write!(f, "{}", clock_name(self))
96    }
97}
98
99impl fmt::Debug for ClockId {
100    fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
101        write!(f, "{} ({})", self.clone() as isize, clock_name(self))
102    }
103}
104
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum SetTimeFlags {
107    /// Flags to `timerfd_settime(2)`.
108    ///
109    /// The default is zero, i. e. all bits unset.
110    Default,
111
112    /// Interpret new_value.it_value as an absolute value on the timer's clock. The timer will
113    /// expire when the value of the timer's clock reaches the value specified in
114    /// new_value.it_value.
115    Abstime,
116
117    /// If this flag is specified along with TFD_TIMER_ABSTIME and the clock for this timer is
118    /// CLOCK_REALTIME or CLOCK_REALTIME_ALARM, then mark this timer as cancelable if the
119    /// real-time clock undergoes a discontinuous change (settimeofday(2), clock_settime(2),
120    /// or similar). When such changes occur, a current or future read(2) from the file
121    /// descriptor will fail with the error ECANCELED.
122    ///
123    /// `TFD_TIMER_CANCEL_ON_SET` is useless without `TFD_TIMER_ABSTIME` set, cf. `fs/timerfd.c`.
124    /// Thus `TimerCancelOnSet`` implies `Abstime`.
125    TimerCancelOnSet,
126}
127
128use rustix::time::{TimerfdFlags, TimerfdTimerFlags};
129
130mod structs;
131
132/// Holds the state of a `TimerFd`.
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub enum TimerState {
135    /// The timer is disarmed and will not fire.
136    Disarmed,
137
138    /// The timer will fire once after the specified duration
139    /// and then disarm.
140    Oneshot(Duration),
141
142    /// The timer will fire once after `current` and then
143    /// automatically rearm with `interval` as its duration.
144    Periodic {
145        current: Duration,
146        interval: Duration,
147    }
148}
149
150/// Represents a timerfd.
151///
152/// See also [`timerfd_create(2)`].
153///
154/// [`timerfd_create(2)`]: http://man7.org/linux/man-pages/man2/timerfd_create.2.html
155#[derive(Debug)]
156pub struct TimerFd(OwnedFd);
157
158impl TimerFd {
159    /// Creates a new `TimerFd`.
160    ///
161    /// By default, it uses the monotonic clock, is blocking and does not close on exec.
162    /// The parameters allow you to change that.
163    ///
164    /// # Errors
165    ///
166    /// On Linux 2.6.26 and earlier, nonblocking and cloexec are not supported and setting them
167    /// will return an error of kind `ErrorKind::InvalidInput`.
168    ///
169    /// This can also fail in various cases of resource exhaustion. Please check
170    /// `timerfd_create(2)` for details.
171    pub fn new_custom(clock: ClockId, nonblocking: bool, cloexec: bool) -> IoResult<TimerFd> {
172        let mut flags = TimerfdFlags::empty();
173        if nonblocking {
174            flags |= TimerfdFlags::NONBLOCK;
175        }
176        if cloexec {
177            flags |= TimerfdFlags::CLOEXEC;
178        }
179
180        let clock = match clock {
181            ClockId::Realtime => TimerfdClockId::Realtime,
182            ClockId::RealtimeAlarm => TimerfdClockId::RealtimeAlarm,
183            ClockId::Monotonic => TimerfdClockId::Monotonic,
184            ClockId::Boottime => TimerfdClockId::Boottime,
185            ClockId::BoottimeAlarm => TimerfdClockId::BoottimeAlarm,
186        };
187        let fd = rustix::time::timerfd_create(clock, flags)?;
188        Ok(TimerFd(fd))
189    }
190
191    /// Creates a new `TimerFd` with default settings.
192    ///
193    /// Use `new_custom` to specify custom settings.
194    pub fn new() -> IoResult<TimerFd> {
195        TimerFd::new_custom(ClockId::Monotonic, false, false)
196    }
197
198    /// Sets this timerfd to a given `TimerState` and returns the old state.
199    pub fn set_state(&mut self, state: TimerState, sflags: SetTimeFlags) -> TimerState {
200        let flags = match sflags {
201            SetTimeFlags::Default => TimerfdTimerFlags::empty(),
202            SetTimeFlags::Abstime => TimerfdTimerFlags::ABSTIME,
203            SetTimeFlags::TimerCancelOnSet => {
204                TimerfdTimerFlags::ABSTIME | TimerfdTimerFlags::CANCEL_ON_SET
205            }
206        };
207        let new: Itimerspec = state.into();
208        let old = rustix::time::timerfd_settime(&self.0, flags, &new)
209            .expect("Looks like timerfd_settime failed in some undocumented way");
210        old.into()
211    }
212
213    /// Returns the current `TimerState`.
214    pub fn get_state(&self) -> TimerState {
215        let state = rustix::time::timerfd_gettime(&self.0)
216            .expect("Looks like timerfd_gettime failed in some undocumented way");
217        state.into()
218    }
219
220    /// Read from this timerfd.
221    ///
222    /// Returns the number of timer expirations since the last read.
223    /// If this timerfd is operating in blocking mode (the default), it will
224    /// not return zero but instead block until the timer has expired at least once.
225    pub fn read(&self) -> u64 {
226        let mut buffer = [0_u8; 8];
227        loop {
228            match rustix::io::read(&self.0, &mut buffer) {
229                Ok(8) => {
230                    let value = u64::from_ne_bytes(buffer);
231                    assert_ne!(value, 0);
232                    return value;
233                }
234                Err(rustix::io::Errno::WOULDBLOCK) => return 0,
235                Err(rustix::io::Errno::INTR) => (),
236                Err(e) => panic!("Unexpected read error: {}", e),
237                _ => unreachable!(),
238            }
239        }
240    }
241}
242
243impl AsRawFd for TimerFd {
244    fn as_raw_fd(&self) -> RawFd {
245        self.0.as_raw_fd()
246    }
247}
248
249impl FromRawFd for TimerFd {
250    unsafe fn from_raw_fd(fd: RawFd) -> Self {
251        TimerFd(FromRawFd::from_raw_fd(fd))
252    }
253}
254
255impl AsFd for TimerFd {
256    fn as_fd(&self) -> BorrowedFd<'_> {
257        self.0.as_fd()
258    }
259}
260
261impl From<TimerFd> for OwnedFd {
262    fn from(fd: TimerFd) -> OwnedFd {
263        fd.0
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    extern crate rustix;
270    use super::{ClockId, Duration, SetTimeFlags, TimerFd, TimerState};
271
272    #[test]
273    fn clockid_new_custom () {
274
275        fn __test_clockid (clockid: ClockId) {
276            let tfd = TimerFd::new_custom(clockid, true, false).unwrap();
277            assert_eq!(tfd.get_state(), TimerState::Disarmed);
278        }
279
280        __test_clockid(ClockId::Realtime);
281        __test_clockid(ClockId::Monotonic);
282        __test_clockid(ClockId::Boottime);
283        //__test_clockid(ClockId::RealtimeAlarm); // requires CAP_WAKE_ALARM
284        //__test_clockid(ClockId::BoottimeAlarm); // requires CAP_WAKE_ALARM
285    }
286
287    const TEST_TIMER_OFFSET: u64 = 100; // seconds from now
288
289    /// trivial monotonic timer some seconds into the future
290    #[test]
291    fn timerfd_settime_flags_default () {
292        let mut tfd = TimerFd::new().unwrap();
293        assert_eq!(tfd.get_state(), TimerState::Disarmed);
294
295        tfd.set_state(TimerState::Oneshot(Duration::new(TEST_TIMER_OFFSET, 0)),
296                      SetTimeFlags::Default);
297        assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
298    }
299
300
301    /// timer set from realtime clock
302    #[test]
303    fn timerfd_settime_flags_abstime () {
304        let mut tfd = TimerFd::new_custom(ClockId::Realtime, true, true).unwrap();
305        assert_eq!(tfd.get_state(), TimerState::Disarmed);
306
307        let now = rustix::time::clock_gettime(rustix::time::ClockId::Realtime);
308        tfd.set_state(TimerState::Oneshot(Duration::new(now.tv_sec as u64 + TEST_TIMER_OFFSET, 0)),
309                      SetTimeFlags::Abstime);
310        assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
311    }
312
313    /// same as abstime, with `TimerCancelOnSet`
314    #[test]
315    fn timerfd_settime_flags_abstime_cancel () {
316        let mut tfd = TimerFd::new_custom(ClockId::Realtime, true, true).unwrap();
317        assert_eq!(tfd.get_state(), TimerState::Disarmed);
318
319        let now = rustix::time::clock_gettime(rustix::time::ClockId::Realtime);
320        tfd.set_state(TimerState::Oneshot(Duration::new(now.tv_sec as u64 + TEST_TIMER_OFFSET, 0)),
321                      SetTimeFlags::TimerCancelOnSet);
322        assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
323    }
324}
325