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>(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-". 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 = 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 { 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(mut f1: F1, f2: F2) -> io::Result 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])); } } } }