diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c01a4117..ac4c8379d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] - ReleaseDate ### Added +- Added `clock_gettime`, `clock_settime`, `clock_getres`, + `clock_getcpuclockid` functions and `ClockId` struct. + (#[1281](https://github.com/nix-rust/nix/pull/1281)) ### Changed ### Fixed ### Removed diff --git a/Cargo.toml b/Cargo.toml index 8456ad7c48..35af931388 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.74", 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/time.rs b/src/time.rs new file mode 100644 index 0000000000..7db9e98402 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,239 @@ +use crate::sys::time::TimeSpec; +use crate::unistd::Pid; +use crate::{Errno, Error, 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(target_os = "linux")] + 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 time on the clock id + pub fn time(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://www.man7.org/linux/man-pages/man2/clock_getres.2.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://www.man7.org/linux/man-pages/man2/clock_gettime.2.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://www.man7.org/linux/man-pages/man2/clock_settime.2.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://www.man7.org/linux/man-pages/man3/clock_getcpuclockid.3.html)). +#[cfg(target_os = "linux")] +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..5398744011 --- /dev/null +++ b/test/test_time.rs @@ -0,0 +1,38 @@ +#[cfg(target_os = "linux")] +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(target_os = "linux")] +#[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_time() { + assert!(ClockId::CLOCK_REALTIME.time().is_ok()); +} + +#[cfg(target_os = "linux")] +#[test] +pub fn test_clock_id_pid_cpu_clock_id() { + assert!(ClockId::pid_cpu_clock_id(nix::unistd::Pid::this()) + .map(ClockId::time) + .is_ok()); +}