blob: 730582b8ab889c08f71bc7d991dc70f67bf36c34 [file] [log] [blame]
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]))
}
}
}
}