1 use std::{
2 fs::{self, OpenOptions},
3 io,
4 mem::size_of,
5 os::windows::prelude::*,
6 path::{Path, PathBuf},
7 };
8
9 use rayon::prelude::*;
10 use winapi::shared::minwindef::*;
11 use winapi::um::fileapi::*;
12 use winapi::um::minwinbase::*;
13 use winapi::um::winbase::*;
14 use winapi::um::winnt::*;
15
16 /// Reliably removes a directory and all of its children.
17 ///
18 /// ```rust
19 /// use std::fs;
20 /// use remove_dir_all::*;
21 ///
22 /// fs::create_dir("./temp/").unwrap();
23 /// remove_dir_all("./temp/").unwrap();
24 /// ```
remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()>25 pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
26 // On Windows it is not enough to just recursively remove the contents of a
27 // directory and then the directory itself. Deleting does not happen
28 // instantaneously, but is delayed by IO being completed in the fs stack.
29 //
30 // Further, typical Windows machines can handle many more concurrent IOs
31 // than a single threaded application is capable of submitting: the
32 // overlapped (async) calls available do not cover the operations needed to
33 // perform directory removal.
34 //
35 // To work around this, we use a work stealing scheduler and submit
36 // deletions concurrently with directory scanning, and delete sibling
37 // directories in parallel. This allows the slight latency of
38 // STATUS_DELETE_PENDING to only have logarithmic effect: a very deep tree
39 // will pay wall clock time for that overhead per level as the tree traverse
40 // completes, but not for every interior not as a simple recursive deletion
41 // would result in.
42 //
43 // Earlier versions of this crate moved the contents of the directory being
44 // deleted to become siblings of `base_dir`, which required write access to
45 // the parent directory under all circumstances; this is no longer required
46 // - though it may be re-instated if in-use files turn out to be handled
47 // very poorly with this new threaded implementation.
48 //
49 // There is a single small race condition where external side effects may be
50 // left: when deleting a hard linked readonly file, the syscalls required
51 // are:
52 // - open
53 // - set rw
54 // - unlink (SetFileDispositionDelete)
55 // - set ro
56 //
57 // A crash or power failure could lead to the loss of the readonly bit on
58 // the hardlinked inode.
59 //
60 // To handle files with names like `CON` and `morse .. .`, and when a
61 // directory structure is so deep it needs long path names the path is first
62 // converted to the Win32 file namespace by calling `canonicalize()`.
63
64 let path = _remove_dir_contents(path)?;
65 let metadata = path.metadata()?;
66 if metadata.permissions().readonly() {
67 delete_readonly(metadata, &path)?;
68 } else {
69 log::trace!("removing {}", &path.display());
70 fs::remove_dir(&path).map_err(|e| {
71 log::debug!("error removing {}", &path.display());
72 e
73 })?;
74 log::trace!("removed {}", &path.display());
75 }
76 Ok(())
77 }
78
79 /// Returns the canonicalised path, for one of caller's convenience.
_remove_dir_contents<P: AsRef<Path>>(path: P) -> io::Result<PathBuf>80 pub fn _remove_dir_contents<P: AsRef<Path>>(path: P) -> io::Result<PathBuf> {
81 let path = path.as_ref().canonicalize()?;
82 _delete_dir_contents(&path)?;
83 Ok(path)
84 }
85
_delete_dir_contents(path: &PathBuf) -> io::Result<()>86 fn _delete_dir_contents(path: &PathBuf) -> io::Result<()> {
87 log::trace!("scanning {}", &path.display());
88 let iter = path.read_dir()?.par_bridge();
89 iter.try_for_each(|dir_entry| -> io::Result<()> {
90 let dir_entry = dir_entry?;
91 let metadata = dir_entry.metadata()?;
92 let is_dir = dir_entry.file_type()?.is_dir();
93 let dir_path = dir_entry.path();
94 if is_dir {
95 _delete_dir_contents(&dir_path)?;
96 }
97 log::trace!("removing {}", &dir_path.display());
98 if metadata.permissions().readonly() {
99 delete_readonly(metadata, &dir_path).map_err(|e| {
100 log::debug!("error removing {}", &dir_path.display());
101 e
102 })?;
103 } else if is_dir {
104 fs::remove_dir(&dir_path).map_err(|e| {
105 log::debug!("error removing {}", &dir_path.display());
106 e
107 })?;
108 } else {
109 fs::remove_file(&dir_path).map_err(|e| {
110 log::debug!("error removing {}", &dir_path.display());
111 e
112 })?;
113 }
114 log::trace!("removed {}", &dir_path.display());
115 Ok(())
116 })?;
117 log::trace!("scanned {}", &path.display());
118 Ok(())
119 }
120
121 // Delete a file or directory that is readonly
delete_readonly(metadata: fs::Metadata, path: &Path) -> io::Result<()>122 fn delete_readonly(metadata: fs::Metadata, path: &Path) -> io::Result<()> {
123 // Open, drop the readonly bit, set delete-on-close, close.
124 let mut opts = OpenOptions::new();
125 opts.access_mode(DELETE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES);
126 opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
127
128 let file = opts.open(path)?;
129 let mut perms = metadata.permissions();
130 perms.set_readonly(false);
131 file.set_permissions(perms)?;
132
133 let mut info = FILE_DISPOSITION_INFO {
134 DeleteFile: TRUE as u8,
135 };
136 let result = unsafe {
137 SetFileInformationByHandle(
138 file.as_raw_handle(),
139 FileDispositionInfo,
140 &mut info as *mut FILE_DISPOSITION_INFO as LPVOID,
141 size_of::<FILE_DISPOSITION_INFO>() as u32,
142 )
143 };
144
145 if result == 0 {
146 return Err(io::Error::last_os_error());
147 }
148
149 file.set_permissions(metadata.permissions())?;
150 drop(file);
151 Ok(())
152 }
153