diff options
Diffstat (limited to 'vendor/remove_dir_all/src/fs.rs')
-rw-r--r-- | vendor/remove_dir_all/src/fs.rs | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/vendor/remove_dir_all/src/fs.rs b/vendor/remove_dir_all/src/fs.rs new file mode 100644 index 0000000..c63e817 --- /dev/null +++ b/vendor/remove_dir_all/src/fs.rs @@ -0,0 +1,278 @@ +use std::ffi::OsString; +use std::fs::{self, File, OpenOptions}; +use std::os::windows::prelude::*; +use std::path::{Path, PathBuf}; +use std::{io, ptr}; + +use winapi::shared::minwindef::*; +use winapi::shared::winerror::*; +use winapi::um::errhandlingapi::*; +use winapi::um::fileapi::*; +use winapi::um::minwinbase::*; +use winapi::um::winbase::*; +use winapi::um::winnt::*; + +pub const VOLUME_NAME_DOS: DWORD = 0x0; + +struct RmdirContext<'a> { + base_dir: &'a Path, + readonly: bool, + counter: u64, +} + +/// Reliably removes a directory and all of its children. +/// +/// ```rust +/// extern crate remove_dir_all; +/// +/// use std::fs; +/// use remove_dir_all::*; +/// +/// fn main() { +/// fs::create_dir("./temp/").unwrap(); +/// remove_dir_all("./temp/").unwrap(); +/// } +/// ``` +pub fn remove_dir_all<P: AsRef<Path>>(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 scheduled. + // To work around this, we move the file or directory to some `base_dir` + // right before deletion to avoid races. + // + // As `base_dir` we choose the parent dir of the directory we want to + // remove. We very probably have permission to create files here, as we + // already need write permission in this dir to delete the directory. And it + // should be on the same volume. + // + // 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 a `//?/`-path with `get_path()`. + // + // To make sure we don't leave a moved file laying around if the process + // crashes before we can delete the file, we do all operations on an file + // handle. By opening a file with `FILE_FLAG_DELETE_ON_CLOSE` Windows will + // always delete the file when the handle closes. + // + // All files are renamed to be in the `base_dir`, and have their name + // changed to "rm-<counter>". After every rename the counter is increased. + // Rename should not overwrite possibly existing files in the base dir. So + // if it fails with `AlreadyExists`, we just increase the counter and try + // again. + // + // For read-only files and directories we first have to remove the read-only + // attribute before we can move or delete them. This also removes the + // attribute from possible hardlinks to the file, so just before closing we + // restore the read-only attribute. + // + // If 'path' points to a directory symlink or junction we should not + // recursively remove the target of the link, but only the link itself. + // + // Moving and deleting is guaranteed to succeed if we are able to open the + // file with `DELETE` permission. If others have the file open we only have + // `DELETE` permission if they have specified `FILE_SHARE_DELETE`. We can + // also delete the file now, but it will not disappear until all others have + // closed the file. But no-one can open the file after we have flagged it + // for deletion. + + // Open the path once to get the canonical path, file type and attributes. + let (path, metadata) = { + let path = path.as_ref(); + let mut opts = OpenOptions::new(); + opts.access_mode(FILE_READ_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT); + let file = opts.open(path)?; + (get_path(&file)?, path.metadata()?) + }; + + let mut ctx = RmdirContext { + base_dir: match path.parent() { + Some(dir) => dir, + None => { + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Can't delete root directory", + )) + } + }, + readonly: metadata.permissions().readonly(), + counter: 0, + }; + + let filetype = metadata.file_type(); + if filetype.is_dir() { + if !filetype.is_symlink() { + remove_dir_all_recursive(path.as_ref(), &mut ctx) + } else { + remove_item(path.as_ref(), &mut ctx) + } + } else { + Err(io::Error::new( + io::ErrorKind::PermissionDenied, + "Not a directory", + )) + } +} + +fn remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { + if ctx.readonly { + // remove read-only permision + let mut permissions = path.metadata()?.permissions(); + permissions.set_readonly(false); + + fs::set_permissions(path, permissions)?; + } + + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE); + opts.custom_flags( + FILE_FLAG_BACKUP_SEMANTICS | // delete directory + FILE_FLAG_OPEN_REPARSE_POINT | // delete symlink + FILE_FLAG_DELETE_ON_CLOSE, + ); + let file = opts.open(path)?; + move_item(&file, ctx)?; + + if ctx.readonly { + // restore read-only flag just in case there are other hard links + match fs::metadata(&path) { + Ok(metadata) => { + let mut perm = metadata.permissions(); + perm.set_readonly(true); + fs::set_permissions(&path, perm)?; + } + Err(ref err) if err.kind() == io::ErrorKind::NotFound => {} + err => return err.map(|_| ()), + } + } + + Ok(()) +} + +fn move_item(file: &File, ctx: &mut RmdirContext) -> io::Result<()> { + let mut tmpname = ctx.base_dir.join(format! {"rm-{}", ctx.counter}); + ctx.counter += 1; + + // Try to rename the file. If it already exists, just retry with an other + // filename. + while let Err(err) = rename(file, &tmpname, false) { + if err.kind() != io::ErrorKind::AlreadyExists { + return Err(err); + }; + tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter)); + ctx.counter += 1; + } + + Ok(()) +} + +fn rename(file: &File, new: &Path, replace: bool) -> io::Result<()> { + // &self must be opened with DELETE permission + use std::iter; + #[cfg(target_pointer_width = "32")] + const STRUCT_SIZE: usize = 12; + #[cfg(target_pointer_width = "64")] + const STRUCT_SIZE: usize = 20; + + // FIXME: check for internal NULs in 'new' + let mut data: Vec<u16> = iter::repeat(0u16) + .take(STRUCT_SIZE / 2) + .chain(new.as_os_str().encode_wide()) + .collect(); + data.push(0); + let size = data.len() * 2; + + unsafe { + // Thanks to alignment guarantees on Windows this works + // (8 for 32-bit and 16 for 64-bit) + let info = data.as_mut_ptr() as *mut FILE_RENAME_INFO; + // The type of ReplaceIfExists is BOOL, but it actually expects a + // BOOLEAN. This means true is -1, not c::TRUE. + (*info).ReplaceIfExists = if replace { -1 } else { FALSE }; + (*info).RootDirectory = ptr::null_mut(); + (*info).FileNameLength = (size - STRUCT_SIZE) as DWORD; + let result = SetFileInformationByHandle( + file.as_raw_handle(), + FileRenameInfo, + data.as_mut_ptr() as *mut _ as *mut _, + size as DWORD, + ); + + if result == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(()) + } + } +} + +fn get_path(f: &File) -> io::Result<PathBuf> { + fill_utf16_buf( + |buf, sz| unsafe { GetFinalPathNameByHandleW(f.as_raw_handle(), buf, sz, VOLUME_NAME_DOS) }, + |buf| PathBuf::from(OsString::from_wide(buf)), + ) +} + +fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { + let dir_readonly = ctx.readonly; + for child in fs::read_dir(path)? { + let child = child?; + let child_type = child.file_type()?; + ctx.readonly = child.metadata()?.permissions().readonly(); + if child_type.is_dir() { + remove_dir_all_recursive(&child.path(), ctx)?; + } else { + remove_item(&child.path().as_ref(), ctx)?; + } + } + ctx.readonly = dir_readonly; + remove_item(path, ctx) +} + +fn 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, +{ + // Start off with a stack buf but then spill over to the heap if we end up + // needing more space. + let mut stack_buf = [0u16; 512]; + let mut heap_buf = Vec::new(); + unsafe { + let mut n = stack_buf.len(); + + loop { + let buf = if n <= stack_buf.len() { + &mut stack_buf[..] + } else { + let extra = n - heap_buf.len(); + heap_buf.reserve(extra); + heap_buf.set_len(n); + &mut heap_buf[..] + }; + + // This function is typically called on windows API functions which + // will return the correct length of the string, but these functions + // also return the `0` on error. In some cases, however, the + // returned "correct length" may actually be 0! + // + // To handle this case we call `SetLastError` to reset it to 0 and + // then check it again if we get the "0 error value". If the "last + // error" is still 0 then we interpret it as a 0 length buffer and + // not an actual error. + SetLastError(0); + let k = match f1(buf.as_mut_ptr(), n as DWORD) { + 0 if GetLastError() == 0 => 0, + 0 => return Err(io::Error::last_os_error()), + n => n, + } as usize; + if k == n && GetLastError() == ERROR_INSUFFICIENT_BUFFER { + n *= 2; + } else if k >= n { + n = k; + } else { + return Ok(f2(&buf[..k])); + } + } + } +} |