Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add wrappers for futimens(2) and utimesat(2) #944

Merged
merged 1 commit into from Sep 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -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.
jmmv marked this conversation as resolved.
Show resolved Hide resolved
([#944](https://github.com/nix-rust/nix/pull/944))

### Changed
- Increased required Rust version to 1.22.1/
Expand Down
78 changes: 72 additions & 6 deletions src/sys/stat.rs
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand All @@ -183,3 +189,63 @@ 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
/// `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
@@ -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;
Expand Down Expand Up @@ -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());
}