@@ -22,21 +22,27 @@ where
2222/// A wrapper around [`std::fs::remove_dir_all`] that can also be used on *non-directory entries*,
2323/// including files and symbolic links.
2424///
25- /// - This will produce an error if the target path is not found.
25+ /// - This will not produce an error if the target path is not found.
2626/// - Like [`std::fs::remove_dir_all`], this helper does not traverse symbolic links, will remove
2727/// symbolic link itself.
2828/// - This helper is **not** robust against races on the underlying filesystem, behavior is
2929/// unspecified if this helper is called concurrently.
3030/// - This helper is not robust against TOCTOU problems.
3131///
32- /// FIXME: this implementation is insufficiently robust to replace bootstrap's clean `rm_rf`
33- /// implementation:
34- ///
35- /// - This implementation currently does not perform retries.
32+ /// FIXME: Audit whether this implementation is robust enough to replace bootstrap's clean `rm_rf`.
3633#[ track_caller]
3734pub fn recursive_remove < P : AsRef < Path > > ( path : P ) -> io:: Result < ( ) > {
3835 let path = path. as_ref ( ) ;
39- let metadata = fs:: symlink_metadata ( path) ?;
36+
37+ // If the path doesn't exist, we treat it as a successful no-op.
38+ // From the caller's perspective, the goal is simply "ensure this file/dir is gone" —
39+ // if it's already not there, that's a success, not an error.
40+ let metadata = match fs:: symlink_metadata ( path) {
41+ Ok ( m) => m,
42+ Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => return Ok ( ( ) ) ,
43+ Err ( e) => return Err ( e) ,
44+ } ;
45+
4046 #[ cfg( windows) ]
4147 let is_dir_like = |meta : & fs:: Metadata | {
4248 use std:: os:: windows:: fs:: FileTypeExt ;
@@ -45,11 +51,35 @@ pub fn recursive_remove<P: AsRef<Path>>(path: P) -> io::Result<()> {
4551 #[ cfg( not( windows) ) ]
4652 let is_dir_like = fs:: Metadata :: is_dir;
4753
48- if is_dir_like ( & metadata) {
49- fs:: remove_dir_all ( path)
50- } else {
51- try_remove_op_set_perms ( fs:: remove_file, path, metadata)
54+ const MAX_RETRIES : usize = 5 ;
55+ const RETRY_DELAY_MS : u64 = 100 ;
56+
57+ let try_remove = || {
58+ if is_dir_like ( & metadata) {
59+ fs:: remove_dir_all ( path)
60+ } else {
61+ try_remove_op_set_perms ( fs:: remove_file, path, metadata. clone ( ) )
62+ }
63+ } ;
64+
65+ // Retry deletion a few times to handle transient filesystem errors.
66+ // This is unusual for local file operations, but it's a mitigation
67+ // against unlikely events where malware scanners may be holding a
68+ // file beyond our control, to give the malware scanners some opportunity
69+ // to release their hold.
70+ for attempt in 0 ..MAX_RETRIES {
71+ match try_remove ( ) {
72+ Ok ( ( ) ) => return Ok ( ( ) ) ,
73+ Err ( e) if e. kind ( ) == io:: ErrorKind :: NotFound => return Ok ( ( ) ) ,
74+ Err ( _) if attempt < MAX_RETRIES - 1 => {
75+ std:: thread:: sleep ( std:: time:: Duration :: from_millis ( RETRY_DELAY_MS ) ) ;
76+ continue ;
77+ }
78+ Err ( e) => return Err ( e) ,
79+ }
5280 }
81+
82+ Ok ( ( ) )
5383}
5484
5585fn try_remove_op_set_perms < ' p , Op > ( mut op : Op , path : & ' p Path , metadata : Metadata ) -> io:: Result < ( ) >
0 commit comments