22
33#[ cfg( all( racy_asserts, not( windows) ) ) ]
44use crate :: fs:: append_dir_suffix;
5+ #[ cfg( any( target_os = "macos" , target_os = "linux" , target_os = "redox" ) ) ]
6+ use crate :: fs:: rename_excl_impl;
57use crate :: fs:: rename_impl;
68use std:: path:: Path ;
79use std:: { fs, io} ;
1416 std:: path:: PathBuf ,
1517} ;
1618
19+ #[ cfg( all(
20+ racy_asserts,
21+ any( target_os = "macos" , target_os = "linux" , target_os = "redox" )
22+ ) ) ]
23+ use crate :: fs:: rename_excl_unchecked;
24+
1725/// Perform a `renameat`-like operation, ensuring that the resolution of both
1826/// the old and new paths never escape the directory tree rooted at their
1927/// respective starts.
@@ -51,6 +59,54 @@ pub fn rename(
5159 & result,
5260 & old_metadata_after,
5361 & new_metadata_after,
62+ rename_unchecked,
63+ ) ;
64+
65+ result
66+ }
67+
68+ /// Perform a `renameat`-like operation, ensuring that the resolution of both
69+ /// the old and new paths never escape the directory tree rooted at their
70+ /// respective starts.
71+ ///
72+ /// Unlike [`rename`], the rename fails if the target exists. The check is atomic on supported
73+ /// platform which mitigates potential races (TOCTOU).
74+ #[ cfg_attr( not( racy_asserts) , allow( clippy:: let_and_return) ) ]
75+ #[ cfg( any( target_os = "macos" , target_os = "linux" , target_os = "redox" ) ) ]
76+ #[ inline]
77+ pub fn rename_exclusive (
78+ old_start : & fs:: File ,
79+ old_path : & Path ,
80+ new_start : & fs:: File ,
81+ new_path : & Path ,
82+ ) -> io:: Result < ( ) > {
83+ #[ cfg( racy_asserts) ]
84+ let ( old_metadata_before, new_metadata_before) = (
85+ stat_unchecked ( old_start, old_path, FollowSymlinks :: No ) ,
86+ stat_unchecked ( new_start, new_path, FollowSymlinks :: No ) ,
87+ ) ;
88+
89+ // Call the underlying implementation.
90+ let result = rename_excl_impl ( old_start, old_path, new_start, new_path) ;
91+
92+ #[ cfg( racy_asserts) ]
93+ let ( old_metadata_after, new_metadata_after) = (
94+ stat_unchecked ( old_start, old_path, FollowSymlinks :: No ) ,
95+ stat_unchecked ( new_start, new_path, FollowSymlinks :: No ) ,
96+ ) ;
97+
98+ #[ cfg( racy_asserts) ]
99+ check_rename (
100+ old_start,
101+ old_path,
102+ new_start,
103+ new_path,
104+ & old_metadata_before,
105+ & new_metadata_before,
106+ & result,
107+ & old_metadata_after,
108+ & new_metadata_after,
109+ rename_excl_unchecked,
54110 ) ;
55111
56112 result
@@ -69,6 +125,7 @@ fn check_rename(
69125 result : & io:: Result < ( ) > ,
70126 old_metadata_after : & io:: Result < Metadata > ,
71127 new_metadata_after : & io:: Result < Metadata > ,
128+ rename_impl : impl Fn ( & fs:: File , & Path , & fs:: File , & Path ) -> io:: Result < ( ) > ,
72129) {
73130 use io:: ErrorKind :: * ;
74131
@@ -97,20 +154,20 @@ fn check_rename(
97154 map_result ( & canonicalize_for_rename ( old_start, old_path) ) ,
98155 map_result ( & canonicalize_for_rename ( new_start, new_path) ) ,
99156 ) {
100- ( Ok ( old_canon) , Ok ( new_canon) ) => match map_result ( & rename_unchecked (
101- old_start, & old_canon, new_start, & new_canon,
102- ) ) {
103- Err ( ( _unchecked_kind, _unchecked_message) ) => {
104- /* TODO: Check error messages.
105- assert_eq!(kind, unchecked_kind);
106- assert_eq!(message, unchecked_message);
107- */
157+ ( Ok ( old_canon) , Ok ( new_canon) ) => {
158+ match map_result ( & rename_impl ( old_start, & old_canon, new_start, & new_canon) ) {
159+ Err ( ( _unchecked_kind, _unchecked_message) ) => {
160+ /* TODO: Check error messages.
161+ assert_eq!(kind, unchecked_kind);
162+ assert_eq!(message, unchecked_message);
163+ */
164+ }
165+ other => panic ! (
166+ "unsandboxed rename success:\n {:#?}\n {:?} {:?}" ,
167+ other, kind, message
168+ ) ,
108169 }
109- other => panic ! (
110- "unsandboxed rename success:\n {:#?}\n {:?} {:?}" ,
111- other, kind, message
112- ) ,
113- } ,
170+ }
114171 ( Err ( ( _old_canon_kind, _old_canon_message) ) , _) => {
115172 /* TODO: Check error messages.
116173 assert_eq!(kind, old_canon_kind);
0 commit comments