• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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