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