@@ -32,6 +32,26 @@ use std::os::unix::prelude::FileExt as UnixFileExt;
32
32
use std:: path:: Path ;
33
33
use std:: { fs, io} ;
34
34
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
+
35
55
/// Helper functions for openat::Dir
36
56
pub trait OpenatDirExt {
37
57
/// Checking for nonexistent files (`ENOENT`) is by far the most common case of inspecting error
@@ -89,6 +109,12 @@ pub trait OpenatDirExt {
89
109
newpath : R ,
90
110
) -> io:: Result < bool > ;
91
111
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
+
92
118
/// Copy a regular file. The semantics here are intended to match `std::fs::copy()`.
93
119
/// If the target exists, it will be overwritten. The mode bits (permissions) will match, but
94
120
/// owner/group will be derived from the current process. Extended attributes are not
@@ -332,6 +358,27 @@ impl OpenatDirExt for openat::Dir {
332
358
}
333
359
}
334
360
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
+
335
382
fn new_file_writer < ' a > ( & ' a self , mode : libc:: mode_t ) -> io:: Result < FileWriter > {
336
383
let ( tmpf, name) = if let Some ( tmpf) = self . new_unnamed_file ( mode) . ok ( ) {
337
384
( tmpf, None )
@@ -812,6 +859,28 @@ mod tests {
812
859
}
813
860
}
814
861
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
+
815
884
fn find_test_file ( tempdir : & Path ) -> Result < PathBuf > {
816
885
for p in [ "/proc/self/exe" , "/usr/bin/bash" ] . iter ( ) {
817
886
let p = Path :: new ( p) ;
0 commit comments