Skip to content

Commit 8b1f85c

Browse files
committed
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.
1 parent 7417110 commit 8b1f85c

File tree

1 file changed

+67
-77
lines changed
  • library/std/src/sys/windows

1 file changed

+67
-77
lines changed

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

+67-77
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ impl<'a> DirBuffIter<'a> {
680680
}
681681
}
682682
impl<'a> Iterator for DirBuffIter<'a> {
683-
type Item = &'a [u16];
683+
type Item = (&'a [u16], bool);
684684
fn next(&mut self) -> Option<Self::Item> {
685685
use crate::mem::size_of;
686686
let buffer = &self.buffer?[self.cursor..];
@@ -689,14 +689,16 @@ impl<'a> Iterator for DirBuffIter<'a> {
689689
// SAFETY: The buffer contains a `FILE_ID_BOTH_DIR_INFO` struct but the
690690
// last field (the file name) is unsized. So an offset has to be
691691
// used to get the file name slice.
692-
let (name, next_entry) = unsafe {
692+
let (name, is_directory, next_entry) = unsafe {
693693
let info = buffer.as_ptr().cast::<c::FILE_ID_BOTH_DIR_INFO>();
694694
let next_entry = (*info).NextEntryOffset as usize;
695695
let name = crate::slice::from_raw_parts(
696696
(*info).FileName.as_ptr().cast::<u16>(),
697697
(*info).FileNameLength as usize / size_of::<u16>(),
698698
);
699-
(name, next_entry)
699+
let is_directory = ((*info).FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) != 0;
700+
701+
(name, is_directory, next_entry)
700702
};
701703

702704
if next_entry == 0 {
@@ -709,7 +711,7 @@ impl<'a> Iterator for DirBuffIter<'a> {
709711
const DOT: u16 = b'.' as u16;
710712
match name {
711713
[DOT] | [DOT, DOT] => self.next(),
712-
_ => Some(name),
714+
_ => Some((name, is_directory)),
713715
}
714716
}
715717
}
@@ -994,89 +996,77 @@ pub fn remove_dir_all(path: &Path) -> io::Result<()> {
994996
if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {
995997
return Err(io::Error::from_raw_os_error(c::ERROR_DIRECTORY as _));
996998
}
997-
let mut delete: fn(&File) -> io::Result<()> = File::posix_delete;
998-
let result = match delete(&file) {
999-
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {
1000-
match remove_dir_all_recursive(&file, delete) {
1001-
// Return unexpected errors.
1002-
Err(e) if e.kind() != io::ErrorKind::DirectoryNotEmpty => return Err(e),
1003-
result => result,
1004-
}
1005-
}
1006-
// If POSIX delete is not supported for this filesystem then fallback to win32 delete.
1007-
Err(e)
1008-
if e.raw_os_error() == Some(c::ERROR_NOT_SUPPORTED as i32)
1009-
|| e.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as i32) =>
1010-
{
1011-
delete = File::win32_delete;
1012-
Err(e)
1013-
}
1014-
result => result,
1015-
};
1016-
if result.is_ok() {
1017-
Ok(())
1018-
} else {
1019-
// This is a fallback to make sure the directory is actually deleted.
1020-
// Otherwise this function is prone to failing with `DirectoryNotEmpty`
1021-
// due to possible delays between marking a file for deletion and the
1022-
// file actually being deleted from the filesystem.
1023-
//
1024-
// So we retry a few times before giving up.
1025-
for _ in 0..5 {
1026-
match remove_dir_all_recursive(&file, delete) {
1027-
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {}
1028-
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)
10291014
}
10301015
}
1031-
// Try one last time.
1032-
delete(&file)
1016+
ok => ok,
10331017
}
10341018
}
10351019

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

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

0 commit comments

Comments
 (0)