summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--src/sys/stat.rs78
-rw-r--r--test/test_stat.rs53
3 files changed, 124 insertions, 9 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b63ea5b3..326f5e31 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 7bcf1fce..6ac4444e 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<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,11 +173,6 @@ 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(),
@@ -174,7 +180,7 @@ pub fn fchmodat<P: ?Sized + NixPath>(
};
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<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(|_| ())
+}
diff --git a/test/test_stat.rs b/test/test_stat.rs
index 4135052e..49cc49fd 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());
+}