@@ -167,9 +167,9 @@ pub trait DirExtUtf8 {
167
167
168
168
/// Removes a file or symlink from a filesystem.
169
169
///
170
- /// Removal of symlinks has different behavior under Windows - if a symlink
171
- /// points to a directory, it cannot be removed with the remove_file
172
- /// operation. This method will remove files and all symlinks .
170
+ /// This is similar to [`std::fs::remove_file`], except that it also works
171
+ /// on symlinks to directories on Windows, similar to how `unlink` works
172
+ /// on symlinks to directories on Posix-ish platforms .
173
173
fn remove_file_or_symlink < P : AsRef < str > > ( & self , path : P ) -> io:: Result < ( ) > ;
174
174
}
175
175
@@ -262,37 +262,39 @@ impl DirExt for cap_std::fs::Dir {
262
262
#[ cfg( windows) ]
263
263
#[ inline]
264
264
fn remove_file_or_symlink < P : AsRef < Path > > ( & self , path : P ) -> io:: Result < ( ) > {
265
- // This operation may race, because it checks the metadata before deleting
266
- // the symlink. We tried to do this atomically by ReOpenFile with DELETE_ON_CLOSE but could
267
- // not get it to work.
268
- fn delete_symlink_to_dir ( dir : & cap_std:: fs:: Dir , path : & Path ) -> io:: Result < ( ) > {
269
- use crate :: { FollowSymlinks , OpenOptionsFollowExt } ;
270
- use cap_std:: fs:: OpenOptions ;
271
- use std:: os:: windows:: fs:: OpenOptionsExt ;
272
- use winapi:: um:: winbase:: { FILE_FLAG_BACKUP_SEMANTICS , FILE_FLAG_OPEN_REPARSE_POINT } ;
273
-
274
- let mut opts = OpenOptions :: new ( ) ;
275
- opts. read ( true ) ;
276
- opts. custom_flags ( FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS ) ;
277
- opts. follow ( FollowSymlinks :: No ) ;
278
- let file = dir. open_with ( path, & opts) ?;
279
-
280
- let meta = file. metadata ( ) ?;
281
- if meta. file_type ( ) . is_symlink ( ) {
282
- drop ( file) ;
283
- // Symlinks that point to directories use the remove_dir, not remove_file,
284
- // operation on windows:
285
- dir. remove_dir ( path) ?;
286
- Ok ( ( ) )
287
- } else {
288
- Err ( io:: Error :: from_raw_os_error (
289
- winapi:: shared:: winerror:: ERROR_DIRECTORY as i32 ,
290
- ) )
291
- }
265
+ use crate :: { FollowSymlinks , OpenOptionsFollowExt } ;
266
+ use cap_primitives:: fs:: _WindowsByHandle;
267
+ use cap_std:: fs:: OpenOptions ;
268
+ use std:: os:: windows:: fs:: OpenOptionsExt ;
269
+ use winapi:: um:: {
270
+ winbase:: { FILE_FLAG_BACKUP_SEMANTICS , FILE_FLAG_OPEN_REPARSE_POINT } ,
271
+ winnt:: { DELETE , FILE_ATTRIBUTE_DIRECTORY } ,
272
+ } ;
273
+ let path = path. as_ref ( ) ;
274
+
275
+ let mut opts = OpenOptions :: new ( ) ;
276
+ opts. access_mode ( DELETE ) ;
277
+ opts. custom_flags ( FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS ) ;
278
+ opts. follow ( FollowSymlinks :: No ) ;
279
+ let file = self . open_with ( path, & opts) ?;
280
+
281
+ let meta = file. metadata ( ) ?;
282
+ if meta. file_type ( ) . is_symlink ( )
283
+ && unsafe { meta. file_attributes ( ) } & FILE_ATTRIBUTE_DIRECTORY
284
+ == FILE_ATTRIBUTE_DIRECTORY
285
+ {
286
+ self . remove_dir ( path) ?;
287
+ } else {
288
+ self . remove_file ( path) ?;
292
289
}
293
290
294
- self . remove_file ( path. as_ref ( ) )
295
- . or_else ( |e| delete_symlink_to_dir ( & self , path. as_ref ( ) ) . map_err ( |_| e) )
291
+ // Drop the file after calling `remove_file` or `remove_dir`, since
292
+ // Windows doesn't actually remove the file until after the last open
293
+ // handle is closed, and this protects us from race conditions where
294
+ // other processes replace the file out from underneath us.
295
+ drop ( file) ;
296
+
297
+ Ok ( ( ) )
296
298
}
297
299
}
298
300
0 commit comments