use std::ffi::OsString; | |
use std::fs::{self, File, OpenOptions}; | |
use std::{io, ptr}; | |
use std::os::windows::prelude::*; | |
use std::path::{Path, PathBuf}; | |
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 directory and all of it's children. | |
/// ```rust | |
/// extern crate remove_dir_all; | |
/// | |
/// use remove_dir_all::*; | |
/// | |
/// fn main() { | |
/// 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_arch = "x86")] | |
const STRUCT_SIZE: usize = 12; | |
#[cfg(target_arch = "x86_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 try!(fs::read_dir(path)) { | |
let child = try!(child); | |
let child_type = try!(child.file_type()); | |
ctx.readonly = try!(child.metadata()).permissions().readonly(); | |
if child_type.is_dir() { | |
try!(remove_dir_all_recursive(&child.path(), ctx)); | |
} else { | |
try!(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])) | |
} | |
} | |
} | |
} | |