From 91effece134079bebd07869089ec36b1efbdc06a Mon Sep 17 00:00:00 2001 From: Daniil Bondarev Date: Sat, 1 Aug 2020 11:28:24 -0700 Subject: [PATCH] Added clock_gettime, clock_getres, clock_settime, clock_getcpuclockid --- CHANGELOG.md | 3 + Cargo.toml | 2 +- src/lib.rs | 1 + src/sys/time.rs | 26 +++++ src/time.rs | 258 ++++++++++++++++++++++++++++++++++++++++++++++ test/test.rs | 1 + test/test_time.rs | 56 ++++++++++ 7 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 src/time.rs create mode 100644 test/test_time.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 061d1b9fda..5c208d7435 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Added - Added Netlink protocol families to the `SockProtocol` enum (#[1289](https://github.com/nix-rust/nix/pull/1289)) +- Added `clock_gettime`, `clock_settime`, `clock_getres`, + `clock_getcpuclockid` functions and `ClockId` struct. + (#[1281](https://github.com/nix-rust/nix/pull/1281)) ### Changed - Expose `SeekData` and `SeekHole` on all Linux targets (#[1284](https://github.com/nix-rust/nix/pull/1284)) diff --git a/Cargo.toml b/Cargo.toml index 8456ad7c48..5dde591eae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ exclude = [ ] [dependencies] -libc = { version = "0.2.73", features = [ "extra_traits" ] } +libc = { version = "0.2.77", features = [ "extra_traits" ] } bitflags = "1.1" cfg-if = "0.1.10" diff --git a/src/lib.rs b/src/lib.rs index bb2db87342..da517b59f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ pub mod poll; pub mod pty; pub mod sched; pub mod sys; +pub mod time; // This can be implemented for other platforms as soon as libc // provides bindings for them. #[cfg(all(target_os = "linux", diff --git a/src/sys/time.rs b/src/sys/time.rs index 973a3526c7..269b425316 100644 --- a/src/sys/time.rs +++ b/src/sys/time.rs @@ -1,4 +1,5 @@ use std::{cmp, fmt, ops}; +use std::time::Duration; use std::convert::From; use libc::{c_long, timespec, timeval}; pub use libc::{time_t, suseconds_t}; @@ -66,6 +67,21 @@ impl From for TimeSpec { } } +impl From for TimeSpec { + fn from(duration: Duration) -> Self { + TimeSpec(timespec { + tv_sec: duration.as_secs() as time_t, + tv_nsec: duration.subsec_nanos() as c_long + }) + } +} + +impl From for Duration { + fn from(timespec: TimeSpec) -> Self { + Duration::new(timespec.0.tv_sec as u64, timespec.0.tv_nsec as u32) + } +} + impl AsRef for TimeSpec { fn as_ref(&self) -> ×pec { &self.0 @@ -484,6 +500,7 @@ fn div_rem_64(this: i64, other: i64) -> (i64, i64) { #[cfg(test)] mod test { use super::{TimeSpec, TimeVal, TimeValLike}; + use std::time::Duration; #[test] pub fn test_timespec() { @@ -494,6 +511,15 @@ mod test { TimeSpec::seconds(182)); } + #[test] + pub fn test_timespec_from() { + let duration = Duration::new(123, 123_456_789); + let timespec = TimeSpec::nanoseconds(123_123_456_789); + + assert_eq!(TimeSpec::from(duration), timespec); + assert_eq!(Duration::from(timespec), duration); + } + #[test] pub fn test_timespec_neg() { let a = TimeSpec::seconds(1) + TimeSpec::nanoseconds(123); diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000000..54989c2e28 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,258 @@ +use crate::sys::time::TimeSpec; +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +use crate::{unistd::Pid, Error}; +use crate::{Errno, Result}; +use libc::{self, clockid_t}; +use std::mem::MaybeUninit; + +/// Clock identifier +/// +/// Newtype pattern around `clockid_t` (which is just alias). It pervents bugs caused by +/// accidentally passing wrong value. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct ClockId(clockid_t); + +impl ClockId { + /// Creates `ClockId` from raw `clockid_t` + pub fn from_raw(clk_id: clockid_t) -> Self { + ClockId(clk_id) + } + + /// Returns `ClockId` of a `pid` CPU-time clock + #[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", + ))] + pub fn pid_cpu_clock_id(pid: Pid) -> Result { + clock_getcpuclockid(pid) + } + + /// Returns resolution of the clock id + pub fn res(self) -> Result { + clock_getres(self) + } + + /// Returns the current time on the clock id + pub fn now(self) -> Result { + clock_gettime(self) + } + + /// Sets time to `timespec` on the clock id + #[cfg(not(any( + target_os = "macos", + target_os = "ios", + all( + not(any(target_env = "uclibc", target_env = "newlibc")), + any(target_os = "redox", target_os = "hermit",), + ), + )))] + pub fn set_time(self, timespec: TimeSpec) -> Result<()> { + clock_settime(self, timespec) + } + + /// Gets the raw `clockid_t` wrapped by `self` + pub fn as_raw(self) -> clockid_t { + self.0 + } + + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any(target_os = "linux", target_os = "android", target_os = "emscripten"), + ) + ))] + pub const CLOCK_BOOTTIME: ClockId = ClockId(libc::CLOCK_BOOTTIME); + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any(target_os = "linux", target_os = "android", target_os = "emscripten") + ) + ))] + pub const CLOCK_BOOTTIME_ALARM: ClockId = ClockId(libc::CLOCK_BOOTTIME_ALARM); + pub const CLOCK_MONOTONIC: ClockId = ClockId(libc::CLOCK_MONOTONIC); + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any(target_os = "linux", target_os = "android", target_os = "emscripten") + ) + ))] + pub const CLOCK_MONOTONIC_COARSE: ClockId = ClockId(libc::CLOCK_MONOTONIC_COARSE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_MONOTONIC_FAST: ClockId = ClockId(libc::CLOCK_MONOTONIC_FAST); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_MONOTONIC_PRECISE: ClockId = ClockId(libc::CLOCK_MONOTONIC_PRECISE); + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any(target_os = "linux", target_os = "android", target_os = "emscripten") + ) + ))] + pub const CLOCK_MONOTONIC_RAW: ClockId = ClockId(libc::CLOCK_MONOTONIC_RAW); + #[cfg(any( + target_os = "fuchsia", + target_env = "uclibc", + target_os = "macos", + target_os = "ios", + target_os = "freebsd", + target_os = "dragonfly", + all( + not(target_env = "newlib"), + any(target_os = "linux", target_os = "android", target_os = "emscripten") + ) + ))] + pub const CLOCK_PROCESS_CPUTIME_ID: ClockId = ClockId(libc::CLOCK_PROCESS_CPUTIME_ID); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_PROF: ClockId = ClockId(libc::CLOCK_PROF); + pub const CLOCK_REALTIME: ClockId = ClockId(libc::CLOCK_REALTIME); + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any(target_os = "linux", target_os = "android", target_os = "emscripten") + ) + ))] + pub const CLOCK_REALTIME_ALARM: ClockId = ClockId(libc::CLOCK_REALTIME_ALARM); + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any(target_os = "linux", target_os = "android", target_os = "emscripten") + ) + ))] + pub const CLOCK_REALTIME_COARSE: ClockId = ClockId(libc::CLOCK_REALTIME_COARSE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_REALTIME_FAST: ClockId = ClockId(libc::CLOCK_REALTIME_FAST); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_REALTIME_PRECISE: ClockId = ClockId(libc::CLOCK_REALTIME_PRECISE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_SECOND: ClockId = ClockId(libc::CLOCK_SECOND); + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any( + target_os = "emscripten", + all(target_os = "linux", target_env = "musl") + ) + ) + ))] + pub const CLOCK_SGI_CYCLE: ClockId = ClockId(libc::CLOCK_SGI_CYCLE); + #[cfg(any( + target_os = "fuchsia", + all( + not(any(target_env = "uclibc", target_env = "newlib")), + any( + target_os = "emscripten", + all(target_os = "linux", target_env = "musl") + ) + ) + ))] + pub const CLOCK_TAI: ClockId = ClockId(libc::CLOCK_TAI); + #[cfg(any( + target_env = "uclibc", + target_os = "fuchsia", + target_os = "ios", + target_os = "macos", + target_os = "freebsd", + target_os = "dragonfly", + all( + not(target_env = "newlib"), + any(target_os = "linux", target_os = "android", target_os = "emscripten",), + ), + ))] + pub const CLOCK_THREAD_CPUTIME_ID: ClockId = ClockId(libc::CLOCK_THREAD_CPUTIME_ID); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_UPTIME: ClockId = ClockId(libc::CLOCK_UPTIME); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_UPTIME_FAST: ClockId = ClockId(libc::CLOCK_UPTIME_FAST); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_UPTIME_PRECISE: ClockId = ClockId(libc::CLOCK_UPTIME_PRECISE); + #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] + pub const CLOCK_VIRTUAL: ClockId = ClockId(libc::CLOCK_VIRTUAL); +} + +impl Into for ClockId { + fn into(self) -> clockid_t { + self.as_raw() + } +} + +impl From for ClockId { + fn from(clk_id: clockid_t) -> Self { + ClockId::from_raw(clk_id) + } +} + +impl std::fmt::Display for ClockId { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } +} + +/// Get the resolution of the specified clock, (see +/// [clock_getres(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_getres.html)). +pub fn clock_getres(clock_id: ClockId) -> Result { + let mut c_time: MaybeUninit = MaybeUninit::uninit(); + let ret = unsafe { libc::clock_getres(clock_id.as_raw(), c_time.as_mut_ptr()) }; + Errno::result(ret)?; + let res = unsafe { c_time.assume_init() }; + Ok(TimeSpec::from(res)) +} + +/// Get the time of the specified clock, (see +/// [clock_gettime(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_gettime.html)). +pub fn clock_gettime(clock_id: ClockId) -> Result { + let mut c_time: MaybeUninit = MaybeUninit::uninit(); + let ret = unsafe { libc::clock_gettime(clock_id.as_raw(), c_time.as_mut_ptr()) }; + Errno::result(ret)?; + let res = unsafe { c_time.assume_init() }; + Ok(TimeSpec::from(res)) +} + +/// Set the time of the specified clock, (see +/// [clock_settime(2)](https://pubs.opengroup.org/onlinepubs/7908799/xsh/clock_settime.html)). +#[cfg(not(any( + target_os = "macos", + target_os = "ios", + all( + not(any(target_env = "uclibc", target_env = "newlibc")), + any(target_os = "redox", target_os = "hermit",), + ), +)))] +pub fn clock_settime(clock_id: ClockId, timespec: TimeSpec) -> Result<()> { + let ret = unsafe { libc::clock_settime(clock_id.as_raw(), timespec.as_ref()) }; + Errno::result(ret).map(drop) +} + +/// Get the clock id of the specified process id, (see +/// [clock_getcpuclockid(3)](https://pubs.opengroup.org/onlinepubs/009695399/functions/clock_getcpuclockid.html)). +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +pub fn clock_getcpuclockid(pid: Pid) -> Result { + let mut clk_id: MaybeUninit = MaybeUninit::uninit(); + let ret = unsafe { libc::clock_getcpuclockid(pid.into(), clk_id.as_mut_ptr()) }; + if ret == 0 { + let res = unsafe { clk_id.assume_init() }; + Ok(ClockId::from(res)) + } else { + Err(Error::Sys(Errno::from_i32(ret))) + } +} diff --git a/test/test.rs b/test/test.rs index 2d43196c87..8ad09b6ab8 100644 --- a/test/test.rs +++ b/test/test.rs @@ -139,6 +139,7 @@ mod test_sched; target_os = "macos"))] mod test_sendfile; mod test_stat; +mod test_time; mod test_unistd; use std::os::unix::io::RawFd; diff --git a/test/test_time.rs b/test/test_time.rs new file mode 100644 index 0000000000..c321352d79 --- /dev/null +++ b/test/test_time.rs @@ -0,0 +1,56 @@ +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +use nix::time::clock_getcpuclockid; +use nix::time::{clock_getres, clock_gettime, ClockId}; + +#[test] +pub fn test_clock_getres() { + assert!(clock_getres(ClockId::CLOCK_REALTIME).is_ok()); +} + +#[test] +pub fn test_clock_gettime() { + assert!(clock_gettime(ClockId::CLOCK_REALTIME).is_ok()); +} + +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +#[test] +pub fn test_clock_getcpuclockid() { + let clock_id = clock_getcpuclockid(nix::unistd::Pid::this()).unwrap(); + assert!(clock_gettime(clock_id).is_ok()); +} + +#[test] +pub fn test_clock_id_res() { + assert!(ClockId::CLOCK_REALTIME.res().is_ok()); +} + +#[test] +pub fn test_clock_id_now() { + assert!(ClockId::CLOCK_REALTIME.now().is_ok()); +} + +#[cfg(any( + target_os = "freebsd", + target_os = "dragonfly", + target_os = "linux", + target_os = "android", + target_os = "emscripten", +))] +#[test] +pub fn test_clock_id_pid_cpu_clock_id() { + assert!(ClockId::pid_cpu_clock_id(nix::unistd::Pid::this()) + .map(ClockId::now) + .is_ok()); +}