1 use std::ffi::OsString;
2 use std::fs::{self, File, OpenOptions};
3 use std::os::windows::prelude::*;
4 use std::path::{Path, PathBuf};
5 use std::{io, ptr};
6
7 use winapi::shared::minwindef::*;
8 use winapi::shared::winerror::*;
9 use winapi::um::errhandlingapi::*;
10 use winapi::um::fileapi::*;
11 use winapi::um::minwinbase::*;
12 use winapi::um::winbase::*;
13 use winapi::um::winnt::*;
14
15 pub const VOLUME_NAME_DOS: DWORD = 0x0;
16
17 struct RmdirContext<'a> {
18 base_dir: &'a Path,
19 readonly: bool,
20 counter: u64,
21 }
22
23 /// Reliably removes a directory and all of its children.
24 ///
25 /// ```rust
26 /// extern crate remove_dir_all;
27 ///
28 /// use std::fs;
29 /// use remove_dir_all::*;
30 ///
31 /// fn main() {
32 /// fs::create_dir("./temp/").unwrap();
33 /// remove_dir_all("./temp/").unwrap();
34 /// }
35 /// ```
remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()>36 pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
37 // On Windows it is not enough to just recursively remove the contents of a
38 // directory and then the directory itself. Deleting does not happen
39 // instantaneously, but is scheduled.
40 // To work around this, we move the file or directory to some `base_dir`
41 // right before deletion to avoid races.
42 //
43 // As `base_dir` we choose the parent dir of the directory we want to
44 // remove. We very probably have permission to create files here, as we
45 // already need write permission in this dir to delete the directory. And it
46 // should be on the same volume.
47 //
48 // To handle files with names like `CON` and `morse .. .`, and when a
49 // directory structure is so deep it needs long path names the path is first
50 // converted to a `//?/`-path with `get_path()`.
51 //
52 // To make sure we don't leave a moved file laying around if the process
53 // crashes before we can delete the file, we do all operations on an file
54 // handle. By opening a file with `FILE_FLAG_DELETE_ON_CLOSE` Windows will
55 // always delete the file when the handle closes.
56 //
57 // All files are renamed to be in the `base_dir`, and have their name
58 // changed to "rm-<counter>". After every rename the counter is increased.
59 // Rename should not overwrite possibly existing files in the base dir. So
60 // if it fails with `AlreadyExists`, we just increase the counter and try
61 // again.
62 //
63 // For read-only files and directories we first have to remove the read-only
64 // attribute before we can move or delete them. This also removes the
65 // attribute from possible hardlinks to the file, so just before closing we
66 // restore the read-only attribute.
67 //
68 // If 'path' points to a directory symlink or junction we should not
69 // recursively remove the target of the link, but only the link itself.
70 //
71 // Moving and deleting is guaranteed to succeed if we are able to open the
72 // file with `DELETE` permission. If others have the file open we only have
73 // `DELETE` permission if they have specified `FILE_SHARE_DELETE`. We can
74 // also delete the file now, but it will not disappear until all others have
75 // closed the file. But no-one can open the file after we have flagged it
76 // for deletion.
77
78 // Open the path once to get the canonical path, file type and attributes.
79 let (path, metadata) = {
80 let path = path.as_ref();
81 let mut opts = OpenOptions::new();
82 opts.access_mode(FILE_READ_ATTRIBUTES);
83 opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT);
84 let file = opts.open(path)?;
85 (get_path(&file)?, path.metadata()?)
86 };
87
88 let mut ctx = RmdirContext {
89 base_dir: match path.parent() {
90 Some(dir) => dir,
91 None => {
92 return Err(io::Error::new(
93 io::ErrorKind::PermissionDenied,
94 "Can't delete root directory",
95 ))
96 }
97 },
98 readonly: metadata.permissions().readonly(),
99 counter: 0,
100 };
101
102 let filetype = metadata.file_type();
103 if filetype.is_dir() {
104 if !filetype.is_symlink() {
105 remove_dir_all_recursive(path.as_ref(), &mut ctx)
106 } else {
107 remove_item(path.as_ref(), &mut ctx)
108 }
109 } else {
110 Err(io::Error::new(
111 io::ErrorKind::PermissionDenied,
112 "Not a directory",
113 ))
114 }
115 }
116
remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()>117 fn remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> {
118 if ctx.readonly {
119 // remove read-only permision
120 let mut permissions = path.metadata()?.permissions();
121 permissions.set_readonly(false);
122
123 fs::set_permissions(path, permissions)?;
124 }
125
126 let mut opts = OpenOptions::new();
127 opts.access_mode(DELETE);
128 opts.custom_flags(
129 FILE_FLAG_BACKUP_SEMANTICS | // delete directory
130 FILE_FLAG_OPEN_REPARSE_POINT | // delete symlink
131 FILE_FLAG_DELETE_ON_CLOSE,
132 );
133 let file = opts.open(path)?;
134 move_item(&file, ctx)?;
135
136 if ctx.readonly {
137 // restore read-only flag just in case there are other hard links
138 match fs::metadata(&path) {
139 Ok(metadata) => {
140 let mut perm = metadata.permissions();
141 perm.set_readonly(true);
142 fs::set_permissions(&path, perm)?;
143 }
144 Err(ref err) if err.kind() == io::ErrorKind::NotFound => {}
145 err => return err.map(|_| ()),
146 }
147 }
148
149 Ok(())
150 }
151
move_item(file: &File, ctx: &mut RmdirContext) -> io::Result<()>152 fn move_item(file: &File, ctx: &mut RmdirContext) -> io::Result<()> {
153 let mut tmpname = ctx.base_dir.join(format! {"rm-{}", ctx.counter});
154 ctx.counter += 1;
155
156 // Try to rename the file. If it already exists, just retry with an other
157 // filename.
158 while let Err(err) = rename(file, &tmpname, false) {
159 if err.kind() != io::ErrorKind::AlreadyExists {
160 return Err(err);
161 };
162 tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter));
163 ctx.counter += 1;
164 }
165
166 Ok(())
167 }
168
rename(file: &File, new: &Path, replace: bool) -> io::Result<()>169 fn rename(file: &File, new: &Path, replace: bool) -> io::Result<()> {
170 // &self must be opened with DELETE permission
171 use std::iter;
172 #[cfg(target_pointer_width = "32")]
173 const STRUCT_SIZE: usize = 12;
174 #[cfg(target_pointer_width = "64")]
175 const STRUCT_SIZE: usize = 20;
176
177 // FIXME: check for internal NULs in 'new'
178 let mut data: Vec<u16> = iter::repeat(0u16)
179 .take(STRUCT_SIZE / 2)
180 .chain(new.as_os_str().encode_wide())
181 .collect();
182 data.push(0);
183 let size = data.len() * 2;
184
185 unsafe {
186 // Thanks to alignment guarantees on Windows this works
187 // (8 for 32-bit and 16 for 64-bit)
188 let info = data.as_mut_ptr() as *mut FILE_RENAME_INFO;
189 // The type of ReplaceIfExists is BOOL, but it actually expects a
190 // BOOLEAN. This means true is -1, not c::TRUE.
191 (*info).ReplaceIfExists = if replace { -1 } else { FALSE };
192 (*info).RootDirectory = ptr::null_mut();
193 (*info).FileNameLength = (size - STRUCT_SIZE) as DWORD;
194 let result = SetFileInformationByHandle(
195 file.as_raw_handle(),
196 FileRenameInfo,
197 data.as_mut_ptr() as *mut _ as *mut _,
198 size as DWORD,
199 );
200
201 if result == 0 {
202 Err(io::Error::last_os_error())
203 } else {
204 Ok(())
205 }
206 }
207 }
208
get_path(f: &File) -> io::Result<PathBuf>209 fn get_path(f: &File) -> io::Result<PathBuf> {
210 fill_utf16_buf(
211 |buf, sz| unsafe { GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, VOLUME_NAME_DOS) },
212 |buf| PathBuf::from(OsString::from_wide(buf)),
213 )
214 }
215
remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) -> io::Result<()>216 fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> {
217 let dir_readonly = ctx.readonly;
218 for child in fs::read_dir(path)? {
219 let child = child?;
220 let child_type = child.file_type()?;
221 ctx.readonly = child.metadata()?.permissions().readonly();
222 if child_type.is_dir() {
223 remove_dir_all_recursive(&child.path(), ctx)?;
224 } else {
225 remove_item(&child.path().as_ref(), ctx)?;
226 }
227 }
228 ctx.readonly = dir_readonly;
229 remove_item(path, ctx)
230 }
231
fill_utf16_buf<F1, F2, T>(mut f1: F1, f2: F2) -> io::Result<T> where F1: FnMut(*mut u16, DWORD) -> DWORD, F2: FnOnce(&[u16]) -> T,232 fn fill_utf16_buf<F1, F2, T>(mut f1: F1, f2: F2) -> io::Result<T>
233 where
234 F1: FnMut(*mut u16, DWORD) -> DWORD,
235 F2: FnOnce(&[u16]) -> T,
236 {
237 // Start off with a stack buf but then spill over to the heap if we end up
238 // needing more space.
239 let mut stack_buf = [0u16; 512];
240 let mut heap_buf = Vec::new();
241 unsafe {
242 let mut n = stack_buf.len();
243
244 loop {
245 let buf = if n <= stack_buf.len() {
246 &mut stack_buf[..]
247 } else {
248 let extra = n - heap_buf.len();
249 heap_buf.reserve(extra);
250 heap_buf.set_len(n);
251 &mut heap_buf[..]
252 };
253
254 // This function is typically called on windows API functions which
255 // will return the correct length of the string, but these functions
256 // also return the `0` on error. In some cases, however, the
257 // returned "correct length" may actually be 0!
258 //
259 // To handle this case we call `SetLastError` to reset it to 0 and
260 // then check it again if we get the "0 error value". If the "last
261 // error" is still 0 then we interpret it as a 0 length buffer and
262 // not an actual error.
263 SetLastError(0);
264 let k = match f1(buf.as_mut_ptr(), n as DWORD) {
265 0 if GetLastError() == 0 => 0,
266 0 => return Err(io::Error::last_os_error()),
267 n => n,
268 } as usize;
269 if k == n && GetLastError() == ERROR_INSUFFICIENT_BUFFER {
270 n *= 2;
271 } else if k >= n {
272 n = k;
273 } else {
274 return Ok(f2(&buf[..k]));
275 }
276 }
277 }
278 }
279