Skip to content

Commit

Permalink
feat(nix-rust#1384): change timestamps args of utimensat and futimens…
Browse files Browse the repository at this point in the history
… to be optional
  • Loading branch information
SteveLauC committed Nov 21, 2022
1 parent 33b5f92 commit 16fd0b7
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -17,7 +17,7 @@ usage.

As an example of what Nix provides, examine the differences between what is
exposed by libc and nix for the
[gethostname](https://man7.org/linux/man-pages/man2/gethostname.2.html) system
[gethostname](https://pubs.opengroup.org/onlinepubs/9699919799/functions/gethostname.html) system
call:

```rust,ignore
Expand Down
54 changes: 48 additions & 6 deletions src/sys/stat.rs
Expand Up @@ -7,6 +7,7 @@ pub use libc::c_uint;
))]
pub use libc::c_ulong;
pub use libc::stat as FileStat;
pub use libc::UTIME_NOW;
pub use libc::{dev_t, mode_t};

#[cfg(not(target_os = "redox"))]
Expand Down Expand Up @@ -402,14 +403,51 @@ pub fn lutimes<P: ?Sized + NixPath>(
Errno::result(res).map(drop)
}

/// Change the access and modification times of the file specified by a file descriptor.
/// A helper function to convert `atime: Option<&TimeSpec>, mtime: Option<&TimeSpec>`
/// to `[libc::timespec; 2], used in the implementation of [`utimnsat(3)`]
/// and [`futimens(3)`]
fn time_convert(
atime: Option<&TimeSpec>,
mtime: Option<&TimeSpec>,
) -> [libc::timespec; 2] {
let mut times = [
libc::timespec {
tv_sec: 0,
tv_nsec: libc::UTIME_OMIT,
},
libc::timespec {
tv_sec: 0,
tv_nsec: libc::UTIME_OMIT,
},
];
if let Some(atime) = atime {
times[0].tv_sec = atime.tv_sec();
times[0].tv_nsec = atime.tv_nsec();
}
if let Some(mtime) = mtime {
times[1].tv_sec = mtime.tv_sec();
times[1].tv_nsec = mtime.tv_nsec();
}
times
}

/// Change the access and modification times of the file specified by a file
/// descriptor.
///
/// When a timestamp argument is set to `None`, it remains unchanged. If you
/// would like to change a timestamp to the special value `Now`, set the `st_nsec`
/// field of that timestamp to [`UTIME_NOW`].
///
/// # References
///
/// [futimens(2)](https://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()];
pub fn futimens(
fd: RawFd,
atime: Option<&TimeSpec>,
mtime: Option<&TimeSpec>,
) -> Result<()> {
let times = time_convert(atime, mtime);
let res = unsafe { libc::futimens(fd, &times[0]) };

Errno::result(res).map(drop)
Expand All @@ -429,6 +467,10 @@ pub enum UtimensatFlags {
/// with the file descriptor `dirfd` or the current working directory
/// if `dirfd` is `None`.
///
/// When a timestamp argument is set to `None`, it remains unchanged. If you
/// would like to change a timestamp to the special value `Now`, set the `st_nsec`
/// field of that timestamp to [`UTIME_NOW`].
///
/// If `flag` is `UtimensatFlags::NoFollowSymlink` and `path` names a symbolic link,
/// then the mode of the symbolic link is changed.
///
Expand All @@ -444,15 +486,15 @@ pub enum UtimensatFlags {
pub fn utimensat<P: ?Sized + NixPath>(
dirfd: Option<RawFd>,
path: &P,
atime: &TimeSpec,
mtime: &TimeSpec,
atime: Option<&TimeSpec>,
mtime: Option<&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 times = time_convert(atime, mtime);
let res = path.with_nix_path(|cstr| unsafe {
libc::utimensat(
at_rawfd(dirfd),
Expand Down
87 changes: 82 additions & 5 deletions test/test_stat.rs
Expand Up @@ -275,10 +275,47 @@ fn test_futimens() {
let fd = fcntl::open(&fullpath, fcntl::OFlag::empty(), stat::Mode::empty())
.unwrap();

futimens(fd, &TimeSpec::seconds(10), &TimeSpec::seconds(20)).unwrap();
futimens(
fd,
Some(&TimeSpec::seconds(10)),
Some(&TimeSpec::seconds(20)),
)
.unwrap();
assert_times_eq(10, 20, &fs::metadata(&fullpath).unwrap());
}

#[test]
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
fn test_futimens_unchanged() {
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();

let old_atime = fs::metadata(fullpath.as_path())
.unwrap()
.accessed()
.unwrap();
let old_mtime = fs::metadata(fullpath.as_path())
.unwrap()
.modified()
.unwrap();

futimens(fd, None, None).unwrap();

let new_atime = fs::metadata(fullpath.as_path())
.unwrap()
.accessed()
.unwrap();
let new_mtime = fs::metadata(fullpath.as_path())
.unwrap()
.modified()
.unwrap();
assert_eq!(old_atime, new_atime);
assert_eq!(old_mtime, new_mtime);
}

#[test]
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
fn test_utimensat() {
Expand All @@ -295,8 +332,8 @@ fn test_utimensat() {
utimensat(
Some(dirfd),
filename,
&TimeSpec::seconds(12345),
&TimeSpec::seconds(678),
Some(&TimeSpec::seconds(12345)),
Some(&TimeSpec::seconds(678)),
UtimensatFlags::FollowSymlink,
)
.unwrap();
Expand All @@ -307,14 +344,54 @@ fn test_utimensat() {
utimensat(
None,
filename,
&TimeSpec::seconds(500),
&TimeSpec::seconds(800),
Some(&TimeSpec::seconds(500)),
Some(&TimeSpec::seconds(800)),
UtimensatFlags::FollowSymlink,
)
.unwrap();
assert_times_eq(500, 800, &fs::metadata(&fullpath).unwrap());
}

#[test]
#[cfg(not(any(target_os = "redox", target_os = "haiku")))]
fn test_utimensat_unchanged() {
let _dr = crate::DirRestore::new();
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();

let old_atime = fs::metadata(fullpath.as_path())
.unwrap()
.accessed()
.unwrap();
let old_mtime = fs::metadata(fullpath.as_path())
.unwrap()
.modified()
.unwrap();
utimensat(
Some(dirfd),
filename,
None,
None,
UtimensatFlags::NoFollowSymlink,
)
.unwrap();
let new_atime = fs::metadata(fullpath.as_path())
.unwrap()
.accessed()
.unwrap();
let new_mtime = fs::metadata(fullpath.as_path())
.unwrap()
.modified()
.unwrap();
assert_eq!(old_atime, new_atime);
assert_eq!(old_mtime, new_mtime);
}

#[test]
#[cfg(not(target_os = "redox"))]
fn test_mkdirat_success_path() {
Expand Down

0 comments on commit 16fd0b7

Please sign in to comment.