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