@@ -80,6 +80,15 @@ pub trait OpenatDirExt {
80
80
/// Synchronize to disk the filesystem containing this directory.
81
81
fn syncfs ( & self ) -> io:: Result < ( ) > ;
82
82
83
+ /// If `oldpath` exists, rename it to `newpath`. Otherwise do nothing.
84
+ ///
85
+ /// This returns `true` if the old path has been succesfully renamed, `false` otherwise.
86
+ fn local_rename_optional < P : AsRef < Path > , R : AsRef < Path > > (
87
+ & self ,
88
+ oldpath : P ,
89
+ newpath : R ,
90
+ ) -> io:: Result < bool > ;
91
+
83
92
/// Copy a regular file. The semantics here are intended to match `std::fs::copy()`.
84
93
/// If the target exists, it will be overwritten. The mode bits (permissions) will match, but
85
94
/// owner/group will be derived from the current process. Extended attributes are not
@@ -308,6 +317,21 @@ impl OpenatDirExt for openat::Dir {
308
317
}
309
318
}
310
319
320
+ // NOTE(lucab): this isn't strictly an atomic operation, because
321
+ // unfortunately `renameat` overloads `ENOENT` for multiple error cases.
322
+ fn local_rename_optional < P : AsRef < Path > , R : AsRef < Path > > (
323
+ & self ,
324
+ oldpath : P ,
325
+ newpath : R ,
326
+ ) -> io:: Result < bool > {
327
+ if self . exists ( oldpath. as_ref ( ) ) ? {
328
+ self . local_rename ( oldpath. as_ref ( ) , newpath. as_ref ( ) )
329
+ . and ( Ok ( true ) )
330
+ } else {
331
+ Ok ( false )
332
+ }
333
+ }
334
+
311
335
fn new_file_writer < ' a > ( & ' a self , mode : libc:: mode_t ) -> io:: Result < FileWriter > {
312
336
let ( tmpf, name) = if let Some ( tmpf) = self . new_unnamed_file ( mode) . ok ( ) {
313
337
( tmpf, None )
@@ -757,6 +781,37 @@ mod tests {
757
781
Ok ( ( ) )
758
782
}
759
783
784
+ #[ test]
785
+ fn test_local_rename ( ) {
786
+ let td = tempfile:: tempdir ( ) . unwrap ( ) ;
787
+ let d = openat:: Dir :: open ( td. path ( ) ) . unwrap ( ) ;
788
+
789
+ {
790
+ d. ensure_dir_all ( "src/foo" , 0o755 ) . unwrap ( ) ;
791
+ let renamed = d. local_rename_optional ( "src" , "dst" ) . unwrap ( ) ;
792
+ assert_eq ! ( renamed, true ) ;
793
+ assert_eq ! ( d. exists( "src" ) . unwrap( ) , false ) ;
794
+ assert_eq ! ( d. exists( "dst/foo" ) . unwrap( ) , true ) ;
795
+ let noent = d. local_rename_optional ( "src" , "dst" ) . unwrap ( ) ;
796
+ assert_eq ! ( noent, false ) ;
797
+ assert_eq ! ( d. remove_all( "dst" ) . unwrap( ) , true ) ;
798
+ }
799
+ {
800
+ let noent = d. local_rename_optional ( "missing" , "dst" ) . unwrap ( ) ;
801
+ assert_eq ! ( noent, false ) ;
802
+ }
803
+ {
804
+ d. ensure_dir_all ( "src/foo" , 0o755 ) . unwrap ( ) ;
805
+ let renamed = d. local_rename_optional ( "src" , "dst" ) . unwrap ( ) ;
806
+ assert_eq ! ( renamed, true ) ;
807
+ assert_eq ! ( d. exists( "dst/foo" ) . unwrap( ) , true ) ;
808
+ d. ensure_dir_all ( "src" , 0o755 ) . unwrap ( ) ;
809
+ let _ = d. local_rename_optional ( "src" , "dst" ) . unwrap_err ( ) ;
810
+ assert_eq ! ( d. exists( "dst/foo" ) . unwrap( ) , true ) ;
811
+ assert_eq ! ( d. remove_all( "dst" ) . unwrap( ) , true ) ;
812
+ }
813
+ }
814
+
760
815
fn find_test_file ( tempdir : & Path ) -> Result < PathBuf > {
761
816
for p in [ "/proc/self/exe" , "/usr/bin/bash" ] . iter ( ) {
762
817
let p = Path :: new ( p) ;
0 commit comments