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.
This commit is contained in:
parent
7417110cef
commit
8b1f85caed
@ -680,7 +680,7 @@ fn new(buffer: &'a DirBuff) -> Self {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> Iterator for DirBuffIter<'a> {
|
impl<'a> Iterator for DirBuffIter<'a> {
|
||||||
type Item = &'a [u16];
|
type Item = (&'a [u16], bool);
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
use crate::mem::size_of;
|
use crate::mem::size_of;
|
||||||
let buffer = &self.buffer?[self.cursor..];
|
let buffer = &self.buffer?[self.cursor..];
|
||||||
@ -689,14 +689,16 @@ fn next(&mut self) -> Option<Self::Item> {
|
|||||||
// SAFETY: The buffer contains a `FILE_ID_BOTH_DIR_INFO` struct but the
|
// SAFETY: The buffer contains a `FILE_ID_BOTH_DIR_INFO` struct but the
|
||||||
// last field (the file name) is unsized. So an offset has to be
|
// last field (the file name) is unsized. So an offset has to be
|
||||||
// used to get the file name slice.
|
// used to get the file name slice.
|
||||||
let (name, next_entry) = unsafe {
|
let (name, is_directory, next_entry) = unsafe {
|
||||||
let info = buffer.as_ptr().cast::<c::FILE_ID_BOTH_DIR_INFO>();
|
let info = buffer.as_ptr().cast::<c::FILE_ID_BOTH_DIR_INFO>();
|
||||||
let next_entry = (*info).NextEntryOffset as usize;
|
let next_entry = (*info).NextEntryOffset as usize;
|
||||||
let name = crate::slice::from_raw_parts(
|
let name = crate::slice::from_raw_parts(
|
||||||
(*info).FileName.as_ptr().cast::<u16>(),
|
(*info).FileName.as_ptr().cast::<u16>(),
|
||||||
(*info).FileNameLength as usize / size_of::<u16>(),
|
(*info).FileNameLength as usize / size_of::<u16>(),
|
||||||
);
|
);
|
||||||
(name, next_entry)
|
let is_directory = ((*info).FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||||
|
|
||||||
|
(name, is_directory, next_entry)
|
||||||
};
|
};
|
||||||
|
|
||||||
if next_entry == 0 {
|
if next_entry == 0 {
|
||||||
@ -709,7 +711,7 @@ fn next(&mut self) -> Option<Self::Item> {
|
|||||||
const DOT: u16 = b'.' as u16;
|
const DOT: u16 = b'.' as u16;
|
||||||
match name {
|
match name {
|
||||||
[DOT] | [DOT, DOT] => self.next(),
|
[DOT] | [DOT, DOT] => self.next(),
|
||||||
_ => Some(name),
|
_ => Some((name, is_directory)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -994,89 +996,77 @@ pub fn remove_dir_all(path: &Path) -> io::Result<()> {
|
|||||||
if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {
|
if (file.basic_info()?.FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) == 0 {
|
||||||
return Err(io::Error::from_raw_os_error(c::ERROR_DIRECTORY as _));
|
return Err(io::Error::from_raw_os_error(c::ERROR_DIRECTORY as _));
|
||||||
}
|
}
|
||||||
let mut delete: fn(&File) -> io::Result<()> = File::posix_delete;
|
|
||||||
let result = match delete(&file) {
|
match remove_dir_all_iterative(&file, File::posix_delete) {
|
||||||
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {
|
Err(e) => {
|
||||||
match remove_dir_all_recursive(&file, delete) {
|
if let Some(code) = e.raw_os_error() {
|
||||||
// Return unexpected errors.
|
match code as u32 {
|
||||||
Err(e) if e.kind() != io::ErrorKind::DirectoryNotEmpty => return Err(e),
|
|
||||||
result => result,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If POSIX delete is not supported for this filesystem then fallback to win32 delete.
|
// If POSIX delete is not supported for this filesystem then fallback to win32 delete.
|
||||||
Err(e)
|
c::ERROR_NOT_SUPPORTED
|
||||||
if e.raw_os_error() == Some(c::ERROR_NOT_SUPPORTED as i32)
|
| c::ERROR_INVALID_FUNCTION
|
||||||
|| e.raw_os_error() == Some(c::ERROR_INVALID_PARAMETER as i32) =>
|
| c::ERROR_INVALID_PARAMETER => {
|
||||||
{
|
remove_dir_all_iterative(&file, File::win32_delete)
|
||||||
delete = File::win32_delete;
|
}
|
||||||
Err(e)
|
_ => Err(e),
|
||||||
}
|
}
|
||||||
result => result,
|
|
||||||
};
|
|
||||||
if result.is_ok() {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
} else {
|
||||||
// This is a fallback to make sure the directory is actually deleted.
|
Err(e)
|
||||||
// Otherwise this function is prone to failing with `DirectoryNotEmpty`
|
|
||||||
// due to possible delays between marking a file for deletion and the
|
|
||||||
// file actually being deleted from the filesystem.
|
|
||||||
//
|
|
||||||
// So we retry a few times before giving up.
|
|
||||||
for _ in 0..5 {
|
|
||||||
match remove_dir_all_recursive(&file, delete) {
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {}
|
|
||||||
result => return result,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Try one last time.
|
ok => ok,
|
||||||
delete(&file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_dir_all_recursive(f: &File, delete: fn(&File) -> io::Result<()>) -> io::Result<()> {
|
fn remove_dir_all_iterative(f: &File, delete: fn(&File) -> io::Result<()>) -> io::Result<()> {
|
||||||
let mut buffer = DirBuff::new();
|
let mut buffer = DirBuff::new();
|
||||||
let mut restart = true;
|
let mut dirlist = vec![f.duplicate()?];
|
||||||
|
|
||||||
|
// FIXME: This is a hack so we can push to the dirlist vec after borrowing from it.
|
||||||
|
fn copy_handle(f: &File) -> mem::ManuallyDrop<File> {
|
||||||
|
unsafe { mem::ManuallyDrop::new(File::from_raw_handle(f.as_raw_handle())) }
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(dir) = dirlist.last() {
|
||||||
|
let dir = copy_handle(dir);
|
||||||
|
|
||||||
// Fill the buffer and iterate the entries.
|
// Fill the buffer and iterate the entries.
|
||||||
while f.fill_dir_buff(&mut buffer, restart)? {
|
let more_data = dir.fill_dir_buff(&mut buffer, false)?;
|
||||||
for name in buffer.iter() {
|
for (name, is_directory) in buffer.iter() {
|
||||||
// Open the file without following symlinks and try deleting it.
|
if is_directory {
|
||||||
// We try opening will all needed permissions and if that is denied
|
let child_dir = open_link_no_reparse(
|
||||||
// fallback to opening without `FILE_LIST_DIRECTORY` permission.
|
&dir,
|
||||||
// Note `SYNCHRONIZE` permission is needed for synchronous access.
|
name,
|
||||||
let mut result =
|
c::SYNCHRONIZE | c::DELETE | c::FILE_LIST_DIRECTORY,
|
||||||
open_link_no_reparse(&f, name, c::SYNCHRONIZE | c::DELETE | c::FILE_LIST_DIRECTORY);
|
)?;
|
||||||
if matches!(&result, Err(e) if e.kind() == io::ErrorKind::PermissionDenied) {
|
dirlist.push(child_dir);
|
||||||
result = open_link_no_reparse(&f, name, c::SYNCHRONIZE | c::DELETE);
|
} else {
|
||||||
}
|
const MAX_RETRIES: u32 = 10;
|
||||||
|
for i in 1..=MAX_RETRIES {
|
||||||
|
let result = open_link_no_reparse(&dir, name, c::SYNCHRONIZE | c::DELETE);
|
||||||
match result {
|
match result {
|
||||||
Ok(file) => match delete(&file) {
|
Ok(f) => delete(&f)?,
|
||||||
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {
|
// Already deleted, so skip.
|
||||||
// Iterate the directory's files.
|
Err(e) if e.kind() == io::ErrorKind::NotFound => break,
|
||||||
// Ignore `DirectoryNotEmpty` errors here. They will be
|
// Retry a few times if the file is locked or a delete is already in progress.
|
||||||
// caught when `remove_dir_all` tries to delete the top
|
|
||||||
// level directory. It can then decide if to retry or not.
|
|
||||||
match remove_dir_all_recursive(&file, delete) {
|
|
||||||
Err(e) if e.kind() == io::ErrorKind::DirectoryNotEmpty => {}
|
|
||||||
result => result?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result => result?,
|
|
||||||
},
|
|
||||||
// Ignore error if a delete is already in progress or the file
|
|
||||||
// has already been deleted. It also ignores sharing violations
|
|
||||||
// (where a file is locked by another process) as these are
|
|
||||||
// usually temporary.
|
|
||||||
Err(e)
|
Err(e)
|
||||||
if e.raw_os_error() == Some(c::ERROR_DELETE_PENDING as _)
|
if i < MAX_RETRIES
|
||||||
|| e.kind() == io::ErrorKind::NotFound
|
&& (e.raw_os_error() == Some(c::ERROR_DELETE_PENDING as _)
|
||||||
|| e.raw_os_error() == Some(c::ERROR_SHARING_VIOLATION as _) => {}
|
|| e.raw_os_error()
|
||||||
|
== Some(c::ERROR_SHARING_VIOLATION as _)) => {}
|
||||||
|
// Otherwise return the error.
|
||||||
Err(e) => return Err(e),
|
Err(e) => return Err(e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Continue reading directory entries without restarting from the beginning,
|
|
||||||
restart = false;
|
|
||||||
}
|
}
|
||||||
delete(&f)
|
}
|
||||||
|
// If there were no more files then delete the directory.
|
||||||
|
if !more_data {
|
||||||
|
if let Some(dir) = dirlist.pop() {
|
||||||
|
delete(&dir)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn readlink(path: &Path) -> io::Result<PathBuf> {
|
pub fn readlink(path: &Path) -> io::Result<PathBuf> {
|
||||||
|
Loading…
Reference in New Issue
Block a user