use std::{ fs::{self, OpenOptions}, io, mem::size_of, os::windows::prelude::*, path::{Path, PathBuf}, }; use rayon::prelude::*; use winapi::shared::minwindef::*; use winapi::um::fileapi::*; use winapi::um::minwinbase::*; use winapi::um::winbase::*; use winapi::um::winnt::*; /// Reliably removes a directory and all of its children. /// /// ```rust /// use std::fs; /// use remove_dir_all::*; /// /// fs::create_dir("./temp/").unwrap(); /// remove_dir_all("./temp/").unwrap(); /// ``` pub fn remove_dir_all>(path: P) -> io::Result<()> { // On Windows it is not enough to just recursively remove the contents of a // directory and then the directory itself. Deleting does not happen // instantaneously, but is delayed by IO being completed in the fs stack. // // Further, typical Windows machines can handle many more concurrent IOs // than a single threaded application is capable of submitting: the // overlapped (async) calls available do not cover the operations needed to // perform directory removal. // // To work around this, we use a work stealing scheduler and submit // deletions concurrently with directory scanning, and delete sibling // directories in parallel. This allows the slight latency of // STATUS_DELETE_PENDING to only have logarithmic effect: a very deep tree // will pay wall clock time for that overhead per level as the tree traverse // completes, but not for every interior not as a simple recursive deletion // would result in. // // Earlier versions of this crate moved the contents of the directory being // deleted to become siblings of `base_dir`, which required write access to // the parent directory under all circumstances; this is no longer required // - though it may be re-instated if in-use files turn out to be handled // very poorly with this new threaded implementation. // // There is a single small race condition where external side effects may be // left: when deleting a hard linked readonly file, the syscalls required // are: // - open // - set rw // - unlink (SetFileDispositionDelete) // - set ro // // A crash or power failure could lead to the loss of the readonly bit on // the hardlinked inode. // // To handle files with names like `CON` and `morse .. .`, and when a // directory structure is so deep it needs long path names the path is first // converted to the Win32 file namespace by calling `canonicalize()`. let path = _remove_dir_contents(path)?; let metadata = path.metadata()?; if metadata.permissions().readonly() { delete_readonly(metadata, &path)?; } else { log::trace!("removing {}", &path.display()); fs::remove_dir(&path).map_err(|e| { log::debug!("error removing {}", &path.display()); e })?; log::trace!("removed {}", &path.display()); } Ok(()) } /// Returns the canonicalised path, for one of caller's convenience. pub fn _remove_dir_contents>(path: P) -> io::Result { let path = path.as_ref().canonicalize()?; _delete_dir_contents(&path)?; Ok(path) } fn _delete_dir_contents(path: &PathBuf) -> io::Result<()> { log::trace!("scanning {}", &path.display()); let iter = path.read_dir()?.par_bridge(); iter.try_for_each(|dir_entry| -> io::Result<()> { let dir_entry = dir_entry?; let metadata = dir_entry.metadata()?; let is_dir = dir_entry.file_type()?.is_dir(); let dir_path = dir_entry.path(); if is_dir { _delete_dir_contents(&dir_path)?; } log::trace!("removing {}", &dir_path.display()); if metadata.permissions().readonly() { delete_readonly(metadata, &dir_path).map_err(|e| { log::debug!("error removing {}", &dir_path.display()); e })?; } else if is_dir { fs::remove_dir(&dir_path).map_err(|e| { log::debug!("error removing {}", &dir_path.display()); e })?; } else { fs::remove_file(&dir_path).map_err(|e| { log::debug!("error removing {}", &dir_path.display()); e })?; } log::trace!("removed {}", &dir_path.display()); Ok(()) })?; log::trace!("scanned {}", &path.display()); Ok(()) } // Delete a file or directory that is readonly fn delete_readonly(metadata: fs::Metadata, path: &Path) -> io::Result<()> { // Open, drop the readonly bit, set delete-on-close, close. let mut opts = OpenOptions::new(); opts.access_mode(DELETE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES); opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); let file = opts.open(path)?; let mut perms = metadata.permissions(); perms.set_readonly(false); file.set_permissions(perms)?; let mut info = FILE_DISPOSITION_INFO { DeleteFile: TRUE as u8, }; let result = unsafe { SetFileInformationByHandle( file.as_raw_handle(), FileDispositionInfo, &mut info as *mut FILE_DISPOSITION_INFO as LPVOID, size_of::() as u32, ) }; if result == 0 { return Err(io::Error::last_os_error()); } file.set_permissions(metadata.permissions())?; drop(file); Ok(()) }