From 886c76cfbbb2b22ac8b30919bff50fe5975488a9 Mon Sep 17 00:00:00 2001 From: Julio Merino Date: Mon, 24 Sep 2018 18:46:03 +0200 Subject: [PATCH] Add wrappers for futimens(2) and utimesat(2) --- CHANGELOG.md | 2 ++ src/sys/stat.rs | 78 +++++++++++++++++++++++++++++++++++++++++++---- test/test_stat.rs | 53 ++++++++++++++++++++++++++++++-- 3 files changed, 124 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b63ea5b387..326f5e3172 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ This project adheres to [Semantic Versioning](http://semver.org/). ([#916](https://github.com/nix-rust/nix/pull/916)) - Added `kmod` module that allows loading and unloading kernel modules on Linux. ([#930](https://github.com/nix-rust/nix/pull/930)) +- Added `futimens` and `utimesat` wrappers. + ([#944](https://github.com/nix-rust/nix/pull/944)) ### Changed - Increased required Rust version to 1.22.1/ diff --git a/src/sys/stat.rs b/src/sys/stat.rs index 7bcf1fce3b..6ac4444e71 100644 --- a/src/sys/stat.rs +++ b/src/sys/stat.rs @@ -6,7 +6,9 @@ use errno::Errno; use fcntl::AtFlags; use libc::{self, mode_t}; use std::mem; +use std::os::raw; use std::os::unix::io::RawFd; +use sys::time::TimeSpec; libc_bitflags!( pub struct SFlag: mode_t { @@ -133,6 +135,15 @@ pub fn fchmod(fd: RawFd, mode: Mode) -> Result<()> { Errno::result(res).map(|_| ()) } +/// Computes the raw fd consumed by a function of the form `*at`. +#[inline] +fn actual_atfd(fd: Option) -> raw::c_int { + match fd { + None => libc::AT_FDCWD, + Some(fd) => fd, + } +} + /// Flags for `fchmodat` function. #[derive(Clone, Copy, Debug)] pub enum FchmodatFlags { @@ -162,11 +173,6 @@ pub fn fchmodat( mode: Mode, flag: FchmodatFlags, ) -> Result<()> { - let actual_dirfd = - match dirfd { - None => libc::AT_FDCWD, - Some(fd) => fd, - }; let atflag = match flag { FchmodatFlags::FollowSymlink => AtFlags::empty(), @@ -174,7 +180,7 @@ pub fn fchmodat( }; let res = path.with_nix_path(|cstr| unsafe { libc::fchmodat( - actual_dirfd, + actual_atfd(dirfd), cstr.as_ptr(), mode.bits() as mode_t, atflag.bits() as libc::c_int, @@ -183,3 +189,63 @@ pub fn fchmodat( Errno::result(res).map(|_| ()) } + +/// Change the access and modification times of the file specified by a file descriptor. +/// +/// # References +/// +/// [futimens(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/futimens.html). +#[inline] +pub fn futimens(fd: RawFd, atime: &TimeSpec, mtime: &TimeSpec) -> Result<()> { + let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = unsafe { libc::futimens(fd, ×[0]) }; + + Errno::result(res).map(|_| ()) +} + +/// Flags for `utimensat` function. +#[derive(Clone, Copy, Debug)] +pub enum UtimensatFlags { + FollowSymlink, + NoFollowSymlink, +} + +/// Change the access and modification times of a file. +/// +/// The file to be changed is determined relative to the directory associated +/// with the file descriptor `dirfd` or the current working directory +/// if `dirfd` is `None`. +/// +/// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link, +/// then the mode of the symbolic link is changed. +/// +/// `utimensat(None, path, times, UtimensatFlags::FollowSymlink)` is identical to +/// `libc::utimes(path, times)`. That's why `utimes` is unimplemented in the `nix` crate. +/// +/// # References +/// +/// [utimensat(2)](http://pubs.opengroup.org/onlinepubs/9699919799/functions/utimens.html). +pub fn utimensat( + dirfd: Option, + path: &P, + atime: &TimeSpec, + mtime: &TimeSpec, + flag: UtimensatFlags +) -> Result<()> { + let atflag = + match flag { + UtimensatFlags::FollowSymlink => AtFlags::empty(), + UtimensatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW, + }; + let times: [libc::timespec; 2] = [*atime.as_ref(), *mtime.as_ref()]; + let res = path.with_nix_path(|cstr| unsafe { + libc::utimensat( + actual_atfd(dirfd), + cstr.as_ptr(), + ×[0], + atflag.bits() as libc::c_int, + ) + })?; + + Errno::result(res).map(|_| ()) +} diff --git a/test/test_stat.rs b/test/test_stat.rs index 4135052efe..49cc49fded 100644 --- a/test/test_stat.rs +++ b/test/test_stat.rs @@ -1,12 +1,14 @@ -use std::fs::File; +use std::fs::{self, File}; use std::os::unix::fs::symlink; use std::os::unix::prelude::AsRawFd; +use std::time::{Duration, UNIX_EPOCH}; use libc::{S_IFMT, S_IFLNK}; use nix::fcntl; -use nix::sys::stat::{self, fchmod, fchmodat, fstat, lstat, stat}; -use nix::sys::stat::{FileStat, Mode, FchmodatFlags}; +use nix::sys::stat::{self, fchmod, fchmodat, fstat, futimens, lstat, stat, utimensat}; +use nix::sys::stat::{FileStat, Mode, FchmodatFlags, UtimensatFlags}; +use nix::sys::time::{TimeSpec, TimeValLike}; use nix::unistd::chdir; use nix::Result; use tempfile; @@ -152,3 +154,48 @@ fn test_fchmodat() { let file_stat2 = stat(&fullpath).unwrap(); assert_eq!(file_stat2.st_mode & 0o7777, mode2.bits()); } + +/// Asserts that the atime and mtime in a file's metadata match expected values. +/// +/// The atime and mtime are expressed with a resolution of seconds because some file systems +/// (like macOS's HFS+) do not have higher granularity. +fn assert_times_eq(exp_atime_sec: u64, exp_mtime_sec: u64, attr: &fs::Metadata) { + assert_eq!( + Duration::new(exp_atime_sec, 0), + attr.accessed().unwrap().duration_since(UNIX_EPOCH).unwrap()); + assert_eq!( + Duration::new(exp_mtime_sec, 0), + attr.modified().unwrap().duration_since(UNIX_EPOCH).unwrap()); +} + +#[test] +fn test_futimens() { + let tempdir = tempfile::tempdir().unwrap(); + let fullpath = tempdir.path().join("file"); + drop(File::create(&fullpath).unwrap()); + + let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); + + futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap(); + assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap()); +} + +#[test] +fn test_utimensat() { + let tempdir = tempfile::tempdir().unwrap(); + let filename = "foo.txt"; + let fullpath = tempdir.path().join(filename); + drop(File::create(&fullpath).unwrap()); + + let dirfd = fcntl::open(tempdir.path(), fcntl::OFlag::empty(), stat::Mode::empty()).unwrap(); + + utimensat(Some(dirfd), filename, &TimeSpec::seconds(12345), &TimeSpec::seconds(678), + UtimensatFlags::FollowSymlink).unwrap(); + assert_times_eq(12345, 678, &fs::metadata(&fullpath).unwrap()); + + chdir(tempdir.path()).unwrap(); + + utimensat(None, filename, &TimeSpec::seconds(500), &TimeSpec::seconds(800), + UtimensatFlags::FollowSymlink).unwrap(); + assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap()); +}