• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 //! ```
31 use crate::sys::time::TimeSpec;
32 use crate::unistd::read;
33 use crate::{errno::Errno, Result};
34 use bitflags::bitflags;
35 use libc::c_int;
36 use 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)]
41 pub struct TimerFd {
42     fd: RawFd,
43 }
44 
45 impl AsRawFd for TimerFd {
as_raw_fd(&self) -> RawFd46     fn as_raw_fd(&self) -> RawFd {
47         self.fd
48     }
49 }
50 
51 impl FromRawFd for TimerFd {
from_raw_fd(fd: RawFd) -> Self52     unsafe fn from_raw_fd(fd: RawFd) -> Self {
53         TimerFd { fd }
54     }
55 }
56 
57 libc_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 
71 libc_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 
80 bitflags! {
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)]
88 struct TimerSpec(libc::itimerspec);
89 
90 impl TimerSpec {
none() -> Self91     pub const fn none() -> Self {
92         Self(libc::itimerspec {
93             it_interval: libc::timespec {
94                 tv_sec: 0,
95                 tv_nsec: 0,
96             },
97             it_value: libc::timespec {
98                 tv_sec: 0,
99                 tv_nsec: 0,
100             },
101         })
102     }
103 }
104 
105 impl AsRef<libc::itimerspec> for TimerSpec {
as_ref(&self) -> &libc::itimerspec106     fn as_ref(&self) -> &libc::itimerspec {
107         &self.0
108     }
109 }
110 
111 impl From<Expiration> for TimerSpec {
from(expiration: Expiration) -> TimerSpec112     fn from(expiration: Expiration) -> TimerSpec {
113         match expiration {
114             Expiration::OneShot(t) => TimerSpec(libc::itimerspec {
115                 it_interval: libc::timespec {
116                     tv_sec: 0,
117                     tv_nsec: 0,
118                 },
119                 it_value: *t.as_ref(),
120             }),
121             Expiration::IntervalDelayed(start, interval) => TimerSpec(libc::itimerspec {
122                 it_interval: *interval.as_ref(),
123                 it_value: *start.as_ref(),
124             }),
125             Expiration::Interval(t) => TimerSpec(libc::itimerspec {
126                 it_interval: *t.as_ref(),
127                 it_value: *t.as_ref(),
128             }),
129         }
130     }
131 }
132 
133 impl From<TimerSpec> for Expiration {
from(timerspec: TimerSpec) -> Expiration134     fn from(timerspec: TimerSpec) -> Expiration {
135         match timerspec {
136             TimerSpec(libc::itimerspec {
137                 it_interval:
138                     libc::timespec {
139                         tv_sec: 0,
140                         tv_nsec: 0,
141                     },
142                 it_value: ts,
143             }) => Expiration::OneShot(ts.into()),
144             TimerSpec(libc::itimerspec {
145                 it_interval: int_ts,
146                 it_value: val_ts,
147             }) => {
148                 if (int_ts.tv_sec == val_ts.tv_sec) && (int_ts.tv_nsec == val_ts.tv_nsec) {
149                     Expiration::Interval(int_ts.into())
150                 } else {
151                     Expiration::IntervalDelayed(val_ts.into(), int_ts.into())
152                 }
153             }
154         }
155     }
156 }
157 
158 /// An enumeration allowing the definition of the expiration time of an alarm,
159 /// recurring or not.
160 #[derive(Debug, Clone, Copy, PartialEq)]
161 pub enum Expiration {
162     OneShot(TimeSpec),
163     IntervalDelayed(TimeSpec, TimeSpec),
164     Interval(TimeSpec),
165 }
166 
167 impl TimerFd {
168     /// Creates a new timer based on the clock defined by `clockid`. The
169     /// underlying fd can be assigned specific flags with `flags` (CLOEXEC,
170     /// NONBLOCK). The underlying fd will be closed on drop.
new(clockid: ClockId, flags: TimerFlags) -> Result<Self>171     pub fn new(clockid: ClockId, flags: TimerFlags) -> Result<Self> {
172         Errno::result(unsafe { libc::timerfd_create(clockid as i32, flags.bits()) })
173             .map(|fd| Self { fd })
174     }
175 
176     /// Sets a new alarm on the timer.
177     ///
178     /// # Types of alarm
179     ///
180     /// There are 3 types of alarms you can set:
181     ///
182     ///   - one shot: the alarm will trigger once after the specified amount of
183     /// time.
184     ///     Example: I want an alarm to go off in 60s and then disables itself.
185     ///
186     ///   - interval: the alarm will trigger every specified interval of time.
187     ///     Example: I want an alarm to go off every 60s. The alarm will first
188     ///     go off 60s after I set it and every 60s after that. The alarm will
189     ///     not disable itself.
190     ///
191     ///   - interval delayed: the alarm will trigger after a certain amount of
192     ///     time and then trigger at a specified interval.
193     ///     Example: I want an alarm to go off every 60s but only start in 1h.
194     ///     The alarm will first trigger 1h after I set it and then every 60s
195     ///     after that. The alarm will not disable itself.
196     ///
197     /// # Relative vs absolute alarm
198     ///
199     /// If you do not set any `TimerSetTimeFlags`, then the `TimeSpec` you pass
200     /// to the `Expiration` you want is relative. If however you want an alarm
201     /// to go off at a certain point in time, you can set `TFD_TIMER_ABSTIME`.
202     /// Then the one shot TimeSpec and the delay TimeSpec of the delayed
203     /// interval are going to be interpreted as absolute.
204     ///
205     /// # Disabling alarms
206     ///
207     /// Note: Only one alarm can be set for any given timer. Setting a new alarm
208     /// actually removes the previous one.
209     ///
210     /// Note: Setting a one shot alarm with a 0s TimeSpec disables the alarm
211     /// altogether.
set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()>212     pub fn set(&self, expiration: Expiration, flags: TimerSetTimeFlags) -> Result<()> {
213         let timerspec: TimerSpec = expiration.into();
214         Errno::result(unsafe {
215             libc::timerfd_settime(
216                 self.fd,
217                 flags.bits(),
218                 timerspec.as_ref(),
219                 std::ptr::null_mut(),
220             )
221         })
222         .map(drop)
223     }
224 
225     /// Get the parameters for the alarm currently set, if any.
get(&self) -> Result<Option<Expiration>>226     pub fn get(&self) -> Result<Option<Expiration>> {
227         let mut timerspec = TimerSpec::none();
228         let timerspec_ptr: *mut libc::itimerspec = &mut timerspec.0;
229 
230         Errno::result(unsafe { libc::timerfd_gettime(self.fd, timerspec_ptr) }).map(|_| {
231             if timerspec.0.it_interval.tv_sec == 0
232                 && timerspec.0.it_interval.tv_nsec == 0
233                 && timerspec.0.it_value.tv_sec == 0
234                 && timerspec.0.it_value.tv_nsec == 0
235             {
236                 None
237             } else {
238                 Some(timerspec.into())
239             }
240         })
241     }
242 
243     /// Remove the alarm if any is set.
unset(&self) -> Result<()>244     pub fn unset(&self) -> Result<()> {
245         Errno::result(unsafe {
246             libc::timerfd_settime(
247                 self.fd,
248                 TimerSetTimeFlags::empty().bits(),
249                 TimerSpec::none().as_ref(),
250                 std::ptr::null_mut(),
251             )
252         })
253         .map(drop)
254     }
255 
256     /// Wait for the configured alarm to expire.
257     ///
258     /// Note: If the alarm is unset, then you will wait forever.
wait(&self) -> Result<()>259     pub fn wait(&self) -> Result<()> {
260         while let Err(e) = read(self.fd, &mut [0u8; 8]) {
261             if e != Errno::EINTR {
262                 return Err(e)
263             }
264         }
265 
266         Ok(())
267     }
268 }
269 
270 impl Drop for TimerFd {
drop(&mut self)271     fn drop(&mut self) {
272         if !std::thread::panicking() {
273             let result = Errno::result(unsafe {
274                 libc::close(self.fd)
275             });
276             if let Err(Errno::EBADF) = result {
277                 panic!("close of TimerFd encountered EBADF");
278             }
279         }
280     }
281 }
282