Skip to content

Commit d7388d1

Browse files
authored
Rollup merge of #96412 - ChrisDenton:remove-dir-all, r=thomcc
Windows: Iterative `remove_dir_all` This will allow better strategies for use of memory and File handles. However, fully taking advantage of that is left to future work. Note to reviewer: It's probably best to view the `remove_dir_all_recursive` as a new function. The diff is not very helpful (imho).
2 parents 1aabd8a + d579665 commit d7388d1

File tree

1 file changed

+81
-75
lines changed
  • library/std/src/sys/windows

1 file changed

+81
-75
lines changed

library/std/src/sys/windows/fs.rs

+81-75
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::sys::handle::Handle;
1313
use crate::sys::time::SystemTime;
1414
use crate::sys::{c, cvt};
1515
use crate::sys_common::{AsInner, FromInner, IntoInner};
16+
use crate::thread;
1617

1718
use super::path::maybe_verbatim;
1819
use super::to_u16s;
@@ -679,7 +680,7 @@ impl<'a> DirBuffIter<'a> {
679680
}
680681
}
681682
impl<'a> Iterator for DirBuffIter<'a> {
682-
type Item = &'a [u16];
683+
type Item = (&'a [u16], bool);
683684
fn next(&mut self) -> Option<Self::Item> {
684685
use crate::mem::size_of;
685686
let buffer = &self.buffer?[self.cursor..];
@@ -688,14 +689,16 @@ impl<'a> Iterator for DirBuffIter<'a> {
688689
// SAFETY: The buffer contains a `FILE_ID_BOTH_DIR_INFO` struct but the
689690
// last field (the file name) is unsized. So an offset has to be
690691
// used to get the file name slice.
691-
let (name, next_entry) = unsafe {
692+
let (name, is_directory, next_entry) = unsafe {
692693
let info = buffer.as_ptr().cast::<c::FILE_ID_BOTH_DIR_INFO>();
693694
let next_entry = (*info).NextEntryOffset as usize;
694695
let name = crate::slice::from_raw_parts(
695696
(*info).FileName.as_ptr().cast::<u16>(),
696697
(*info).FileNameLength as usize / size_of::<u16>(),
697698
);
698-
(name, next_entry)
699+
let is_directory = ((*info).FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) != 0;
700+
701+
(name, is_directory, next_entry)
699702
};
700703

701704
if next_entry == 0 {
@@ -708,7 +711,7 @@ impl<'a> Iterator for DirBuffIter<'a> {
708711
const DOT: u16 = b'.' as u16;
709712
match name {
710713
[DOT] | [DOT, DOT] => self.next(),
711-
_ => Some(name),
714+
_ => Some((name, is_directory)),
712715
}
713716
}
714717
}
@@ -993,89 +996,92 @@ pub fn remove_dir_all(path: &Path) -> io::Result<()> {
993996
if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {
994997
return Err(io::Error::from_raw_os_error(c::ERROR_DIRECTORY as _));
995998
}
996-
let mut delete: fn(&File) -> io::Result<()> = File::posix_delete;
997-
let result = match delete(&file) {
998-
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {
999-
match remove_dir_all_recursive(&file, delete) {
1000-
// Return unexpected errors.
1001-
Err(e) if e.kind() != io::ErrorKind::DirectoryNotEmpty => return Err(e),
1002-
result => result,
1003-
}
1004-
}
1005-
// If POSIX delete is not supported for this filesystem then fallback to win32 delete.
1006-
Err(e)
1007-
if e.raw_os_error() == Some(c::ERROR_NOT_SUPPORTED as i32)
1008-
|| e.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as i32) =>
1009-
{
1010-
delete = File::win32_delete;
1011-
Err(e)
1012-
}
1013-
result => result,
1014-
};
1015-
if result.is_ok() {
1016-
Ok(())
1017-
} else {
1018-
// This is a fallback to make sure the directory is actually deleted.
1019-
// Otherwise this function is prone to failing with `DirectoryNotEmpty`
1020-
// due to possible delays between marking a file for deletion and the
1021-
// file actually being deleted from the filesystem.
1022-
//
1023-
// So we retry a few times before giving up.
1024-
for _ in 0..5 {
1025-
match remove_dir_all_recursive(&file, delete) {
1026-
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {}
1027-
result => return result,
999+
1000+
match remove_dir_all_iterative(&file, File::posix_delete) {
1001+
Err(e) => {
1002+
if let Some(code) = e.raw_os_error() {
1003+
match code as u32 {
1004+
// If POSIX delete is not supported for this filesystem then fallback to win32 delete.
1005+
c::ERROR_NOT_SUPPORTED
1006+
| c::ERROR_INVALID_FUNCTION
1007+
| c::ERROR_INVALID_PARAMETER => {
1008+
remove_dir_all_iterative(&file, File::win32_delete)
1009+
}
1010+
_ => Err(e),
1011+
}
1012+
} else {
1013+
Err(e)
10281014
}
10291015
}
1030-
// Try one last time.
1031-
delete(&file)
1016+
ok => ok,
10321017
}
10331018
}
10341019

1035-
fn remove_dir_all_recursive(f: &File, delete: fn(&File) -> io::Result<()>) -> io::Result<()> {
1020+
fn remove_dir_all_iterative(f: &File, delete: fn(&File) -> io::Result<()>) -> io::Result<()> {
1021+
// When deleting files we may loop this many times when certain error conditions occur.
1022+
// This allows remove_dir_all to succeed when the error is temporary.
1023+
const MAX_RETRIES: u32 = 10;
1024+
10361025
let mut buffer = DirBuff::new();
1037-
let mut restart = true;
1038-
// Fill the buffer and iterate the entries.
1039-
while f.fill_dir_buff(&mut buffer, restart)? {
1040-
for name in buffer.iter() {
1041-
// Open the file without following symlinks and try deleting it.
1042-
// We try opening will all needed permissions and if that is denied
1043-
// fallback to opening without `FILE_LIST_DIRECTORY` permission.
1044-
// Note `SYNCHRONIZE` permission is needed for synchronous access.
1045-
let mut result =
1046-
open_link_no_reparse(&f, name, c::SYNCHRONIZE | c::DELETE | c::FILE_LIST_DIRECTORY);
1047-
if matches!(&result, Err(e) if e.kind() == io::ErrorKind::PermissionDenied) {
1048-
result = open_link_no_reparse(&f, name, c::SYNCHRONIZE | c::DELETE);
1026+
let mut dirlist = vec![f.duplicate()?];
1027+
1028+
// FIXME: This is a hack so we can push to the dirlist vec after borrowing from it.
1029+
fn copy_handle(f: &File) -> mem::ManuallyDrop<File> {
1030+
unsafe { mem::ManuallyDrop::new(File::from_raw_handle(f.as_raw_handle())) }
1031+
}
1032+
1033+
while let Some(dir) = dirlist.last() {
1034+
let dir = copy_handle(dir);
1035+
1036+
// Fill the buffer and iterate the entries.
1037+
let more_data = dir.fill_dir_buff(&mut buffer, false)?;
1038+
for (name, is_directory) in buffer.iter() {
1039+
if is_directory {
1040+
let child_dir = open_link_no_reparse(
1041+
&dir,
1042+
name,
1043+
c::SYNCHRONIZE | c::DELETE | c::FILE_LIST_DIRECTORY,
1044+
)?;
1045+
dirlist.push(child_dir);
1046+
} else {
1047+
for i in 1..=MAX_RETRIES {
1048+
let result = open_link_no_reparse(&dir, name, c::SYNCHRONIZE | c::DELETE);
1049+
match result {
1050+
Ok(f) => delete(&f)?,
1051+
// Already deleted, so skip.
1052+
Err(e) if e.kind() == io::ErrorKind::NotFound => break,
1053+
// Retry a few times if the file is locked or a delete is already in progress.
1054+
Err(e)
1055+
if i < MAX_RETRIES
1056+
&& (e.raw_os_error() == Some(c::ERROR_DELETE_PENDING as _)
1057+
|| e.raw_os_error()
1058+
== Some(c::ERROR_SHARING_VIOLATION as _)) => {}
1059+
// Otherwise return the error.
1060+
Err(e) => return Err(e),
1061+
}
1062+
thread::yield_now();
1063+
}
10491064
}
1050-
match result {
1051-
Ok(file) => match delete(&file) {
1052-
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {
1053-
// Iterate the directory's files.
1054-
// Ignore `DirectoryNotEmpty` errors here. They will be
1055-
// caught when `remove_dir_all` tries to delete the top
1056-
// level directory. It can then decide if to retry or not.
1057-
match remove_dir_all_recursive(&file, delete) {
1058-
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {}
1059-
result => result?,
1065+
}
1066+
// If there were no more files then delete the directory.
1067+
if !more_data {
1068+
if let Some(dir) = dirlist.pop() {
1069+
// Retry deleting a few times in case we need to wait for a file to be deleted.
1070+
for i in 1..=MAX_RETRIES {
1071+
let result = delete(&dir);
1072+
if let Err(e) = result {
1073+
if i == MAX_RETRIES || e.kind() != io::ErrorKind::DirectoryNotEmpty {
1074+
return Err(e);
10601075
}
1076+
thread::yield_now();
1077+
} else {
1078+
break;
10611079
}
1062-
result => result?,
1063-
},
1064-
// Ignore error if a delete is already in progress or the file
1065-
// has already been deleted. It also ignores sharing violations
1066-
// (where a file is locked by another process) as these are
1067-
// usually temporary.
1068-
Err(e)
1069-
if e.raw_os_error() == Some(c::ERROR_DELETE_PENDING as _)
1070-
|| e.kind() == io::ErrorKind::NotFound
1071-
|| e.raw_os_error() == Some(c::ERROR_SHARING_VIOLATION as _) => {}
1072-
Err(e) => return Err(e),
1080+
}
10731081
}
10741082
}
1075-
// Continue reading directory entries without restarting from the beginning,
1076-
restart = false;
10771083
}
1078-
delete(&f)
1084+
Ok(())
10791085
}
10801086

10811087
pub fn readlink(path: &Path) -> io::Result<PathBuf> {

0 commit comments

Comments
 (0)