Skip to content

Commit b376266

Browse files
authored
Merge pull request #30 from lucab/ups/update-timestamps
OpenatDirExt: add helper to update atime and mtime
2 parents 21b1edf + 93843ac commit b376266

File tree

1 file changed

+69
-0
lines changed

1 file changed

+69
-0
lines changed

src/lib.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,26 @@ use std::os::unix::prelude::FileExt as UnixFileExt;
3232
use std::path::Path;
3333
use std::{fs, io};
3434

35+
/// Private helper to retry interruptible syscalls.
36+
macro_rules! retry_eintr {
37+
($inner:expr) => {
38+
loop {
39+
let err = match $inner {
40+
Err(e) => e,
41+
val => break val,
42+
};
43+
44+
if let Some(errno) = err.raw_os_error() {
45+
if errno == libc::EINTR {
46+
continue;
47+
}
48+
}
49+
50+
break Err(err);
51+
}
52+
};
53+
}
54+
3555
/// Helper functions for openat::Dir
3656
pub trait OpenatDirExt {
3757
/// Checking for nonexistent files (`ENOENT`) is by far the most common case of inspecting error
@@ -89,6 +109,12 @@ pub trait OpenatDirExt {
89109
newpath: R,
90110
) -> io::Result<bool>;
91111

112+
/// Update timestamps (both access and modification) to the current time.
113+
///
114+
/// If the entry at `path` is a symlink, its direct timestamps are updated without
115+
/// following the link.
116+
fn update_timestamps<P: openat::AsPath>(&self, path: P) -> io::Result<()>;
117+
92118
/// Copy a regular file. The semantics here are intended to match `std::fs::copy()`.
93119
/// If the target exists, it will be overwritten. The mode bits (permissions) will match, but
94120
/// owner/group will be derived from the current process. Extended attributes are not
@@ -332,6 +358,27 @@ impl OpenatDirExt for openat::Dir {
332358
}
333359
}
334360

361+
fn update_timestamps<P: openat::AsPath>(&self, p: P) -> io::Result<()> {
362+
use nix::sys::stat::{utimensat, UtimensatFlags};
363+
use nix::sys::time::TimeSpec;
364+
365+
let path = p
366+
.to_path()
367+
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "null byte in path"))?;
368+
let now = TimeSpec::from(libc::timespec {
369+
tv_nsec: libc::UTIME_NOW,
370+
tv_sec: 0,
371+
});
372+
retry_eintr!(utimensat(
373+
Some(self.as_raw_fd()),
374+
path.as_ref(),
375+
&now,
376+
&now,
377+
UtimensatFlags::NoFollowSymlink,
378+
)
379+
.map_err(map_nix_error))
380+
}
381+
335382
fn new_file_writer<'a>(&'a self, mode: libc::mode_t) -> io::Result<FileWriter> {
336383
let (tmpf, name) = if let Some(tmpf) = self.new_unnamed_file(mode).ok() {
337384
(tmpf, None)
@@ -812,6 +859,28 @@ mod tests {
812859
}
813860
}
814861

862+
#[test]
863+
fn test_update_timestamps() {
864+
let td = tempfile::tempdir().unwrap();
865+
let d = openat::Dir::open(td.path()).unwrap();
866+
d.ensure_dir("foo", 0o755).unwrap();
867+
d.syncfs().unwrap();
868+
let before = d.metadata("foo").unwrap();
869+
870+
d.update_timestamps("foo").unwrap();
871+
d.syncfs().unwrap();
872+
let after = d.metadata("foo").unwrap();
873+
874+
assert!(
875+
before.stat().st_atime != after.stat().st_atime
876+
|| before.stat().st_atime_nsec != after.stat().st_atime_nsec
877+
);
878+
assert!(
879+
before.stat().st_mtime != after.stat().st_mtime
880+
|| before.stat().st_mtime_nsec != after.stat().st_mtime_nsec
881+
);
882+
}
883+
815884
fn find_test_file(tempdir: &Path) -> Result<PathBuf> {
816885
for p in ["/proc/self/exe", "/usr/bin/bash"].iter() {
817886
let p = Path::new(p);

0 commit comments

Comments
 (0)