extern crate libc;
use std::os::unix::prelude::*;
use std::time::Duration;
use std::io::Result as IoResult;
use std::io::ErrorKind;
use std::fmt;
extern "C" {
fn timerfd_create(clockid: libc::c_int, flags: libc::c_int) -> RawFd;
fn timerfd_settime(fd: RawFd, flags: libc::c_int,
new_value: *const itimerspec, old_value: *mut itimerspec) -> libc::c_int;
fn timerfd_gettime(fd: RawFd, curr_value: *mut itimerspec) -> libc::c_int;
}
#[derive(Clone, PartialEq, Eq)]
pub enum ClockId {
Realtime = libc::CLOCK_REALTIME as isize,
RealtimeAlarm = libc::CLOCK_REALTIME_ALARM as isize,
Monotonic = libc::CLOCK_MONOTONIC as isize,
Boottime = libc::CLOCK_BOOTTIME as isize,
BoottimeAlarm = libc::CLOCK_BOOTTIME_ALARM as isize,
}
fn clock_name (clock: &ClockId) -> &'static str {
match *clock {
ClockId::Realtime => "CLOCK_REALTIME",
ClockId::RealtimeAlarm => "CLOCK_REALTIME_ALARM",
ClockId::Monotonic => "CLOCK_MONOTONIC",
ClockId::Boottime => "CLOCK_BOOTTIME",
ClockId::BoottimeAlarm => "CLOCK_BOOTTIME_ALARM",
}
}
impl fmt::Display for ClockId {
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", clock_name(self))
}
}
impl fmt::Debug for ClockId {
fn fmt (&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ({})", self.clone() as libc::c_int, clock_name(self))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SetTimeFlags {
Default,
Abstime,
TimerCancelOnSet,
}
use libc::{TFD_CLOEXEC, TFD_NONBLOCK, TFD_TIMER_ABSTIME};
static TFD_TIMER_CANCEL_ON_SET: libc::c_int = 0o0000002;
mod structs;
use structs::itimerspec;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TimerState {
Disarmed,
Oneshot(Duration),
Periodic {
current: Duration,
interval: Duration,
}
}
pub struct TimerFd(RawFd);
fn neg_is_err(i: libc::c_int) -> IoResult<libc::c_int> {
if i >= 0 {
Ok(i)
} else {
Err(std::io::Error::last_os_error())
}
}
impl TimerFd {
pub fn new_custom(clock: ClockId, nonblocking: bool, cloexec: bool) -> IoResult<TimerFd> {
let mut flags = 0;
if nonblocking {
flags |= TFD_NONBLOCK;
}
if cloexec {
flags |= TFD_CLOEXEC;
}
let fd = neg_is_err(unsafe { timerfd_create(clock as libc::c_int, flags) })?;
Ok(TimerFd(fd))
}
pub fn new() -> IoResult<TimerFd> {
TimerFd::new_custom(ClockId::Monotonic, false, false)
}
pub fn set_state(&mut self, state: TimerState, sflags: SetTimeFlags) -> TimerState {
let flags = match sflags {
SetTimeFlags::Default => 0,
SetTimeFlags::Abstime => TFD_TIMER_ABSTIME,
SetTimeFlags::TimerCancelOnSet => TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET,
};
let mut old = itimerspec::null();
let new: itimerspec = state.into();
neg_is_err(unsafe { timerfd_settime(self.0, flags, &new, &mut old) })
.expect("Looks like timerfd_settime failed in some undocumented way");
old.into()
}
pub fn get_state(&self) -> TimerState {
let mut state = itimerspec::null();
neg_is_err(unsafe { timerfd_gettime(self.0, &mut state) })
.expect("Looks like timerfd_gettime failed in some undocumented way");
state.into()
}
pub fn read(&mut self) -> u64 {
const BUFSIZE: usize = 8;
let mut buffer: u64 = 0;
let bufptr: *mut _ = &mut buffer;
loop {
let res = unsafe { libc::read(self.0, bufptr as *mut libc::c_void, BUFSIZE) };
match res {
8 => {
assert!(buffer != 0);
return buffer;
}
-1 => {
let err = std::io::Error::last_os_error();
match err.kind() {
ErrorKind::WouldBlock => return 0,
ErrorKind::Interrupted => (),
_ => panic!("Unexpected read error: {}", err),
}
}
_ => unreachable!(),
}
}
}
}
impl AsRawFd for TimerFd {
fn as_raw_fd(&self) -> RawFd {
self.0
}
}
impl FromRawFd for TimerFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self {
TimerFd(fd)
}
}
impl Drop for TimerFd {
fn drop(&mut self) {
unsafe {
libc::close(self.0);
}
}
}
#[cfg(test)]
mod tests {
extern crate libc;
use super::{Duration,ClockId,TimerFd,TimerState,SetTimeFlags};
#[test]
fn clockid_new_custom () {
fn __test_clockid (clockid: ClockId) {
let tfd = TimerFd::new_custom(clockid, true, false).unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
}
__test_clockid(ClockId::Realtime);
__test_clockid(ClockId::Monotonic);
__test_clockid(ClockId::Boottime);
}
const TEST_TIMER_OFFSET: u64 = 100;
#[test]
fn timerfd_settime_flags_default () {
let mut tfd = TimerFd::new().unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
tfd.set_state(TimerState::Oneshot(Duration::new(TEST_TIMER_OFFSET, 0)),
SetTimeFlags::Default);
assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
}
#[test]
fn timerfd_settime_flags_abstime () {
let mut tfd = TimerFd::new_custom(ClockId::Realtime, true, true).unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
let mut now = libc::timespec { tv_sec: 0, tv_nsec: 0 };
assert_eq!(unsafe { libc::clock_gettime(ClockId::Realtime as libc::c_int, &mut now) }, 0);
tfd.set_state(TimerState::Oneshot(Duration::new(now.tv_sec as u64 + TEST_TIMER_OFFSET, 0)),
SetTimeFlags::Abstime);
assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
}
#[test]
fn timerfd_settime_flags_abstime_cancel () {
let mut tfd = TimerFd::new_custom(ClockId::Realtime, true, true).unwrap();
assert_eq!(tfd.get_state(), TimerState::Disarmed);
let mut now = libc::timespec { tv_sec: 0, tv_nsec: 0 };
assert_eq!(unsafe { libc::clock_gettime(ClockId::Realtime as libc::c_int, &mut now) }, 0);
tfd.set_state(TimerState::Oneshot(Duration::new(now.tv_sec as u64 + TEST_TIMER_OFFSET, 0)),
SetTimeFlags::TimerCancelOnSet);
assert!(match tfd.get_state() { TimerState::Oneshot(_) => true, _ => false });
}
}