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,
}
pub fn remove_dir_all<P: AsRef<Path>>(path: P) -> io::Result<()> {
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 {
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 | FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_DELETE_ON_CLOSE,
);
let file = opts.open(path)?;
move_item(&file, ctx)?;
if ctx.readonly {
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;
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<()> {
use std::iter;
#[cfg(target_pointer_width = "32")]
const STRUCT_SIZE: usize = 12;
#[cfg(target_pointer_width = "64")]
const STRUCT_SIZE: usize = 20;
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 {
let info = data.as_mut_ptr() as *mut FILE_RENAME_INFO;
(*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,
{
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[..]
};
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]));
}
}
}
}