Skip to content

Commit 93843ac

Browse files
committed
OpenatDirExt: add helper to update atime and mtime
This adds one helper method to update timestamps (atime and mtime) to current time, taking care of retrying in case of interrupted syscall. This occurs in a couple of places in rpm-ostree, although in a non-uniform way (missing retries or missing AT_SYMLINK_NOFOLLOW).
1 parent 21b1edf commit 93843ac

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)