Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add wrappers for futimens(2) and utimesat(2)
Browse files Browse the repository at this point in the history
jmmv committed Sep 28, 2018

Verified

This commit was signed with the committer’s verified signature.
trxcllnt Paul Taylor
1 parent d302e8d commit 313f857
Showing 2 changed files with 123 additions and 9 deletions.
79 changes: 73 additions & 6 deletions src/sys/stat.rs
Original file line number Diff line number Diff line change
@@ -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<RawFd>) -> raw::c_int {
match fd {
None => libc::AT_FDCWD,
Some(fd) => fd,
}
}

/// Flags for `fchmodat` function.
#[derive(Clone, Copy, Debug)]
pub enum FchmodatFlags {
@@ -162,19 +173,14 @@ pub fn fchmodat<P: ?Sized + NixPath>(
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(),
FchmodatFlags::NoFollowSymlink => AtFlags::AT_SYMLINK_NOFOLLOW,
};
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,64 @@ pub fn fchmodat<P: ?Sized + NixPath>(

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, &times[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
/// a call `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<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
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(),
&times[0],
atflag.bits() as libc::c_int,
)
})?;

Errno::result(res).map(|_| ())
}
53 changes: 50 additions & 3 deletions test/test_stat.rs
Original file line number Diff line number Diff line change
@@ -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());
}

0 comments on commit 313f857

Please sign in to comment.