// Copyright 2019 Intel Corporation. All Rights Reserved.
//
// Copyright 2018 The Chromium OS Authors. All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause

//! Structure and functions for working with
//! [`timerfd`](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).

use std::fs::File;
use std::mem;
use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
use std::ptr;
use std::time::Duration;

use libc::{self, timerfd_create, timerfd_gettime, timerfd_settime, CLOCK_MONOTONIC, TFD_CLOEXEC};

use crate::errno::{errno_result, Result};

/// A safe wrapper around a Linux
/// [`timerfd`](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).
#[derive(Debug)]
pub struct TimerFd(File);

impl TimerFd {
    /// Create a new [`TimerFd`](struct.TimerFd.html).
    ///
    /// This creates a nonsettable monotonically increasing clock that does not
    /// change after system startup. The timer is initally disarmed and must be
    /// armed by calling [`reset`](fn.reset.html).
    pub fn new() -> Result<TimerFd> {
        // SAFETY: Safe because this doesn't modify any memory and we check the return value.
        let ret = unsafe { timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC) };
        if ret < 0 {
            return errno_result();
        }

        // SAFETY: Safe because we uniquely own the file descriptor.
        Ok(TimerFd(unsafe { File::from_raw_fd(ret) }))
    }

    /// Arm the [`TimerFd`](struct.TimerFd.html).
    ///
    /// Set the timer to expire after `dur`.
    ///
    /// # Arguments
    ///
    /// * `dur`: Specify the initial expiration of the timer.
    /// * `interval`: Specify the period for repeated expirations, depending on the
    /// value passed. If `interval` is not `None`, it represents the period after
    /// the initial expiration. Otherwise the timer will expire just once. Cancels
    /// any existing duration and repeating interval.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate vmm_sys_util;
    /// # use std::time::Duration;
    /// use vmm_sys_util::timerfd::TimerFd;
    ///
    /// let mut timer = TimerFd::new().unwrap();
    /// let dur = Duration::from_millis(100);
    /// let interval = Duration::from_millis(100);
    ///
    /// timer.reset(dur, Some(interval)).unwrap();
    /// ```
    pub fn reset(&mut self, dur: Duration, interval: Option<Duration>) -> Result<()> {
        // SAFETY: Safe because we are zero-initializing a struct with only primitive member fields.
        let mut spec: libc::itimerspec = unsafe { mem::zeroed() };
        // https://github.com/rust-lang/libc/issues/1848
        #[cfg_attr(target_env = "musl", allow(deprecated))]
        {
            spec.it_value.tv_sec = dur.as_secs() as libc::time_t;
        }
        // nsec always fits in i32 because subsec_nanos is defined to be less than one billion.
        let nsec = dur.subsec_nanos() as i32;
        spec.it_value.tv_nsec = libc::c_long::from(nsec);

        if let Some(int) = interval {
            // https://github.com/rust-lang/libc/issues/1848
            #[cfg_attr(target_env = "musl", allow(deprecated))]
            {
                spec.it_interval.tv_sec = int.as_secs() as libc::time_t;
            }
            // nsec always fits in i32 because subsec_nanos is defined to be less than one billion.
            let nsec = int.subsec_nanos() as i32;
            spec.it_interval.tv_nsec = libc::c_long::from(nsec);
        }

        // SAFETY: Safe because this doesn't modify any memory and we check the return value.
        let ret = unsafe { timerfd_settime(self.as_raw_fd(), 0, &spec, ptr::null_mut()) };
        if ret < 0 {
            return errno_result();
        }

        Ok(())
    }

    /// Wait until the timer expires.
    ///
    /// The return value represents the number of times the timer has expired since
    /// the last time `wait` was called. If the timer has not yet expired once,
    /// this call will block until it does.
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate vmm_sys_util;
    /// # use std::time::Duration;
    /// # use std::thread::sleep;
    /// use vmm_sys_util::timerfd::TimerFd;
    ///
    /// let mut timer = TimerFd::new().unwrap();
    /// let dur = Duration::from_millis(100);
    /// let interval = Duration::from_millis(100);
    /// timer.reset(dur, Some(interval)).unwrap();
    ///
    /// sleep(dur * 3);
    /// let count = timer.wait().unwrap();
    /// assert!(count >= 3);
    /// ```
    pub fn wait(&mut self) -> Result<u64> {
        let mut count = 0u64;

        // SAFETY: Safe because this will only modify |buf| and we check the return value.
        let ret = unsafe {
            libc::read(
                self.as_raw_fd(),
                &mut count as *mut _ as *mut libc::c_void,
                mem::size_of_val(&count),
            )
        };
        if ret < 0 {
            return errno_result();
        }

        // The bytes in the buffer are guaranteed to be in native byte-order so we don't need to
        // use from_le or from_be.
        Ok(count)
    }

    /// Tell if the timer is armed.
    ///
    /// Returns `Ok(true)` if the timer is currently armed, otherwise the errno set by
    /// [`timerfd_gettime`](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate vmm_sys_util;
    /// # use std::time::Duration;
    /// use vmm_sys_util::timerfd::TimerFd;
    ///
    /// let mut timer = TimerFd::new().unwrap();
    /// let dur = Duration::from_millis(100);
    ///
    /// timer.reset(dur, None).unwrap();
    /// assert!(timer.is_armed().unwrap());
    /// ```
    pub fn is_armed(&self) -> Result<bool> {
        // SAFETY: Safe because we are zero-initializing a struct with only primitive member fields.
        let mut spec: libc::itimerspec = unsafe { mem::zeroed() };

        // SAFETY: Safe because timerfd_gettime is trusted to only modify `spec`.
        let ret = unsafe { timerfd_gettime(self.as_raw_fd(), &mut spec) };
        if ret < 0 {
            return errno_result();
        }

        Ok(spec.it_value.tv_sec != 0 || spec.it_value.tv_nsec != 0)
    }

    /// Disarm the timer.
    ///
    /// Set zero to disarm the timer, referring to
    /// [`timerfd_settime`](http://man7.org/linux/man-pages/man2/timerfd_create.2.html).
    ///
    /// # Examples
    ///
    /// ```
    /// extern crate vmm_sys_util;
    /// # use std::time::Duration;
    /// use vmm_sys_util::timerfd::TimerFd;
    ///
    /// let mut timer = TimerFd::new().unwrap();
    /// let dur = Duration::from_millis(100);
    ///
    /// timer.reset(dur, None).unwrap();
    /// timer.clear().unwrap();
    /// ```
    pub fn clear(&mut self) -> Result<()> {
        // SAFETY: Safe because we are zero-initializing a struct with only primitive member fields.
        let spec: libc::itimerspec = unsafe { mem::zeroed() };

        // SAFETY: Safe because this doesn't modify any memory and we check the return value.
        let ret = unsafe { timerfd_settime(self.as_raw_fd(), 0, &spec, ptr::null_mut()) };
        if ret < 0 {
            return errno_result();
        }

        Ok(())
    }
}

impl AsRawFd for TimerFd {
    fn as_raw_fd(&self) -> RawFd {
        self.0.as_raw_fd()
    }
}

impl FromRawFd for TimerFd {
    /// This function is unsafe as the primitives currently returned
    /// have the contract that they are the sole owner of the file
    /// descriptor they are wrapping. Usage of this function could
    /// accidentally allow violating this contract which can cause memory
    /// unsafety in code that relies on it being true.
    unsafe fn from_raw_fd(fd: RawFd) -> Self {
        TimerFd(File::from_raw_fd(fd))
    }
}

impl IntoRawFd for TimerFd {
    fn into_raw_fd(self) -> RawFd {
        self.0.into_raw_fd()
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::undocumented_unsafe_blocks)]
    use super::*;
    use std::thread::sleep;
    use std::time::{Duration, Instant};

    #[test]
    fn test_from_raw_fd() {
        let ret = unsafe { timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC) };
        let tfd = unsafe { TimerFd::from_raw_fd(ret) };
        assert!(!tfd.is_armed().unwrap());
    }

    #[test]
    fn test_into_raw_fd() {
        let tfd = TimerFd::new().expect("failed to create timerfd");
        let fd = tfd.into_raw_fd();
        assert!(fd > 0);
    }
    #[test]
    fn test_one_shot() {
        let mut tfd = TimerFd::new().expect("failed to create timerfd");
        assert!(!tfd.is_armed().unwrap());

        let dur = Duration::from_millis(200);
        let now = Instant::now();
        tfd.reset(dur, None).expect("failed to arm timer");

        assert!(tfd.is_armed().unwrap());

        let count = tfd.wait().expect("unable to wait for timer");

        assert_eq!(count, 1);
        assert!(now.elapsed() >= dur);
        tfd.clear().expect("unable to clear the timer");
        assert!(!tfd.is_armed().unwrap());
    }

    #[test]
    fn test_repeating() {
        let mut tfd = TimerFd::new().expect("failed to create timerfd");

        let dur = Duration::from_millis(200);
        let interval = Duration::from_millis(100);
        tfd.reset(dur, Some(interval)).expect("failed to arm timer");

        sleep(dur * 3);

        let count = tfd.wait().expect("unable to wait for timer");
        assert!(count >= 5, "count = {}", count);
        tfd.clear().expect("unable to clear the timer");
        assert!(!tfd.is_armed().unwrap());
    }
}
