From 5569ca99a93ce24610e2541fb68f36b8f8697fdb Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 28 Mar 2017 11:43:33 -0700 Subject: [PATCH 1/3] Add aggressive remove_dir_all --- Cargo.lock | 1 + src/rustup-utils/Cargo.toml | 1 + src/rustup-utils/src/lib.rs | 3 + src/rustup-utils/src/remove_dir_all.rs | 815 +++++++++++++++++++++++++ 4 files changed, 820 insertions(+) create mode 100644 src/rustup-utils/src/remove_dir_all.rs diff --git a/Cargo.lock b/Cargo.lock index cfe51004df..5d810fcb5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -606,6 +606,7 @@ dependencies = [ "download 0.3.0", "error-chain 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "ole32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/src/rustup-utils/Cargo.toml b/src/rustup-utils/Cargo.toml index e6e4775c3c..cd2da3afb8 100644 --- a/src/rustup-utils/Cargo.toml +++ b/src/rustup-utils/Cargo.toml @@ -22,6 +22,7 @@ url = "1.1" toml = "0.1.27" semver = "0.4.0" download = { path = "../download" } +lazy_static = "0.1.15" [target."cfg(windows)".dependencies] winapi = "0.2.8" diff --git a/src/rustup-utils/src/lib.rs b/src/rustup-utils/src/lib.rs index 7825fdd343..c5edb7809b 100644 --- a/src/rustup-utils/src/lib.rs +++ b/src/rustup-utils/src/lib.rs @@ -10,6 +10,8 @@ extern crate url; extern crate toml; extern crate download; extern crate semver; +#[macro_use] +extern crate lazy_static; #[cfg(windows)] extern crate winapi; @@ -35,6 +37,7 @@ pub mod raw; pub mod tty; pub mod utils; pub mod toml_utils; +mod remove_dir_all; pub use errors::*; pub use notifications::{Notification}; diff --git a/src/rustup-utils/src/remove_dir_all.rs b/src/rustup-utils/src/remove_dir_all.rs new file mode 100644 index 0000000000..3d20300209 --- /dev/null +++ b/src/rustup-utils/src/remove_dir_all.rs @@ -0,0 +1,815 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use winapi::{ + FileBasicInfo, + FILE_BASIC_INFO, + FALSE, + FileRenameInfo, + FILE_RENAME_INFO, + c_ushort, + c_uint, + FILETIME, + FILE_ATTRIBUTE_READONLY, + FILE_ATTRIBUTE_REPARSE_POINT, + FILE_ATTRIBUTE_DIRECTORY, + WIN32_FIND_DATAW, + ERROR_NO_MORE_FILES, + OPEN_EXISTING, + OPEN_ALWAYS, + TRUNCATE_EXISTING, + CREATE_ALWAYS, + CREATE_NEW, + GENERIC_READ, + GENERIC_WRITE, + FILE_GENERIC_WRITE, + FILE_WRITE_DATA, + FILE_SHARE_READ, + FILE_SHARE_WRITE, + FILE_SHARE_DELETE, + FILE_FLAG_DELETE_ON_CLOSE, + DELETE, + FILE_WRITE_ATTRIBUTES, + FILE_INFO_BY_HANDLE_CLASS, + HANDLE, + ERROR_INSUFFICIENT_BUFFER, + FILE_READ_ATTRIBUTES, + FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_OPEN_REPARSE_POINT, + ERROR_CALL_NOT_IMPLEMENTED, + DWORD, + BOOL, + LPVOID, + INVALID_HANDLE_VALUE, + LPCWSTR, + SECURITY_SQOS_PRESENT, + FSCTL_GET_REPARSE_POINT, + BY_HANDLE_FILE_INFORMATION, +}; + +use kernel32::{ + CreateFileW, + GetFileInformationByHandle, + CloseHandle, + GetLastError, + SetLastError, + DeviceIoControl, + GetModuleHandleW, + GetProcAddress, + FindNextFileW, + FindFirstFileW, +}; + +use std::ptr; +use std::sync::Arc; +use std::path::{PathBuf, Path}; +use std::mem; +use std::io; +use std::ffi::{OsStr, OsString}; +use std::os::windows::ffi::{OsStrExt, OsStringExt}; + +pub fn remove_dir_all(path: &Path) -> 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 mut opts = OpenOptions::new(); + opts.access_mode(FILE_READ_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT); + let file = try!(File::open(path, &opts)); + (try!(get_path(&file)), try!(file.file_attr())) + }; + + 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.perm().readonly(), + counter: 0, + }; + + let filetype = metadata.file_type(); + if filetype.is_dir() { + remove_dir_all_recursive(path.as_ref(), &mut ctx) + } else if filetype.is_symlink_dir() { + remove_item(path.as_ref(), &mut ctx) + } else { + Err(io::Error::new(io::ErrorKind::PermissionDenied, "Not a directory")) + } +} + + +pub fn readdir(p: &Path) -> io::Result { + let root = p.to_path_buf(); + let star = p.join("*"); + let path = try!(to_u16s(&star)); + + unsafe { + let mut wfd = mem::zeroed(); + let find_handle = FindFirstFileW(path.as_ptr(), &mut wfd); + if find_handle != INVALID_HANDLE_VALUE { + Ok(ReadDir { + handle: FindNextFileHandle(find_handle), + root: Arc::new(root), + first: Some(wfd), + }) + } else { + Err(io::Error::last_os_error()) + } + } +} + +struct RmdirContext<'a> { + base_dir: &'a Path, + readonly: bool, + counter: u64, +} + +fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) + -> io::Result<()> { + let dir_readonly = ctx.readonly; + for child in try!(readdir(path)) { + let child = try!(child); + let child_type = try!(child.file_type()); + ctx.readonly = try!(child.metadata()).perm().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 remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { + if !ctx.readonly { + 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 = try!(File::open(path, &opts)); + move_item(&file, ctx) + } else { + // remove read-only permision + try!(set_perm(&path, FilePermissions::new())); + // move and delete file, similar to !readonly. + // only the access mode is different. + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE | FILE_WRITE_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT | + FILE_FLAG_DELETE_ON_CLOSE); + let file = try!(File::open(path, &opts)); + try!(move_item(&file, ctx)); + // restore read-only flag just in case there are other hard links + let mut perm = FilePermissions::new(); + perm.set_readonly(true); + let _ = file.set_perm(perm); // ignore if this fails + Ok(()) + } +} + +macro_rules! compat_fn { + ($module:ident: $( + pub fn $symbol:ident($($argname:ident: $argtype:ty),*) + -> $rettype:ty { + $($body:expr);* + } + )*) => ($( + #[allow(unused_variables)] + pub unsafe fn $symbol($($argname: $argtype),*) -> $rettype { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::mem; + use std::ffi::CString; + type F = unsafe extern "system" fn($($argtype),*) -> $rettype; + + lazy_static! { static ref PTR: AtomicUsize = AtomicUsize::new(0);} + + pub fn lookup(module: &str, symbol: &str) -> Option { + let mut module: Vec = module.encode_utf16().collect(); + module.push(0); + let symbol = CString::new(symbol).unwrap(); + unsafe { + let handle = GetModuleHandleW(module.as_ptr()); + match GetProcAddress(handle, symbol.as_ptr()) as usize { + 0 => None, + n => Some(n), + } + } + } + + pub fn store_func(ptr: &AtomicUsize, module: &str, symbol: &str, + fallback: usize) -> usize { + let value = lookup(module, symbol).unwrap_or(fallback); + ptr.store(value, Ordering::SeqCst); + value + } + + fn load() -> usize { + store_func(&PTR, stringify!($module), stringify!($symbol), fallback as usize) + } + unsafe extern "system" fn fallback($($argname: $argtype),*) + -> $rettype { + $($body);* + } + + let addr = match PTR.load(Ordering::SeqCst) { + 0 => load(), + n => n, + }; + mem::transmute::(addr)($($argname),*) + } + )*) +} + +compat_fn! { + kernel32: + pub fn GetFinalPathNameByHandleW(_hFile: HANDLE, + _lpszFilePath: LPCWSTR, + _cchFilePath: DWORD, + _dwFlags: DWORD) -> DWORD { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 + } + pub fn SetFileInformationByHandle(_hFile: HANDLE, + _FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, + _lpFileInformation: LPVOID, + _dwBufferSize: DWORD) -> BOOL { + SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 + } +} + +fn cvt(i: i32) -> io::Result { + if i == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(i) + } +} + +pub fn to_u16s>(s: S) -> io::Result> { + fn inner(s: &OsStr) -> io::Result> { + let mut maybe_result: Vec = s.encode_wide().collect(); + if maybe_result.iter().any(|&u| u == 0) { + return Err(io::Error::new(io::ErrorKind::InvalidInput, + "strings passed to WinAPI cannot contain NULs")); + } + maybe_result.push(0); + Ok(maybe_result) + } + inner(s.as_ref()) +} + +pub fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] { + match v.iter().position(|c| *c == 0) { + // don't include the 0 + Some(i) => &v[..i], + None => v + } +} + +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])) + } + } + } +} + +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct FilePermissions { readonly: bool } + +impl FilePermissions { + pub fn new() -> FilePermissions { Default::default() } + pub fn readonly(&self) -> bool { self.readonly } + pub fn set_readonly(&mut self, readonly: bool) { self.readonly = readonly } +} + +#[derive(Clone)] +pub struct OpenOptions { + // generic + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, + // system-specific + custom_flags: u32, + access_mode: Option, + attributes: DWORD, + share_mode: DWORD, + security_qos_flags: DWORD, + security_attributes: usize, // FIXME: should be a reference +} + +impl OpenOptions { + pub fn new() -> OpenOptions { + OpenOptions { + // generic + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + // system-specific + custom_flags: 0, + access_mode: None, + share_mode: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + attributes: 0, + security_qos_flags: 0, + security_attributes: 0, + } + } + pub fn custom_flags(&mut self, flags: u32) { self.custom_flags = flags; } + pub fn access_mode(&mut self, access_mode: u32) { self.access_mode = Some(access_mode); } + + fn get_access_mode(&self) -> io::Result { + const ERROR_INVALID_PARAMETER: i32 = 87; + + match (self.read, self.write, self.append, self.access_mode) { + (_, _, _, Some(mode)) => Ok(mode), + (true, false, false, None) => Ok(GENERIC_READ), + (false, true, false, None) => Ok(GENERIC_WRITE), + (true, true, false, None) => Ok(GENERIC_READ | GENERIC_WRITE), + (false, _, true, None) => Ok(FILE_GENERIC_WRITE & !FILE_WRITE_DATA), + (true, _, true, None) => Ok(GENERIC_READ | + (FILE_GENERIC_WRITE & !FILE_WRITE_DATA)), + (false, false, false, None) => Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)), + } + } + + fn get_creation_mode(&self) -> io::Result { + const ERROR_INVALID_PARAMETER: i32 = 87; + + match (self.write, self.append) { + (true, false) => {} + (false, false) => + if self.truncate || self.create || self.create_new { + return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); + }, + (_, true) => + if self.truncate && !self.create_new { + return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); + }, + } + + Ok(match (self.create, self.truncate, self.create_new) { + (false, false, false) => OPEN_EXISTING, + (true, false, false) => OPEN_ALWAYS, + (false, true, false) => TRUNCATE_EXISTING, + (true, true, false) => CREATE_ALWAYS, + (_, _, true) => CREATE_NEW, + }) + } + + fn get_flags_and_attributes(&self) -> DWORD { + self.custom_flags | + self.attributes | + self.security_qos_flags | + if self.security_qos_flags != 0 { SECURITY_SQOS_PRESENT } else { 0 } | + if self.create_new { FILE_FLAG_OPEN_REPARSE_POINT } else { 0 } + } +} + +pub struct File { handle: Handle } + +impl File { + pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { + let path = try!(to_u16s(path)); + let handle = unsafe { + CreateFileW(path.as_ptr(), + try!(opts.get_access_mode()), + opts.share_mode, + opts.security_attributes as *mut _, + try!(opts.get_creation_mode()), + opts.get_flags_and_attributes(), + ptr::null_mut()) + }; + if handle == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(File { handle: Handle::new(handle) }) + } + } + + pub fn file_attr(&self) -> io::Result { + unsafe { + let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed(); + try!(cvt(GetFileInformationByHandle(self.handle.raw(), + &mut info))); + let mut attr = FileAttr { + attributes: info.dwFileAttributes, + creation_time: info.ftCreationTime, + last_access_time: info.ftLastAccessTime, + last_write_time: info.ftLastWriteTime, + file_size: ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64), + reparse_tag: 0, + }; + if attr.is_reparse_point() { + let mut b = [0; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + if let Ok((_, buf)) = self.reparse_point(&mut b) { + attr.reparse_tag = buf.ReparseTag; + } + } + Ok(attr) + } + } + + pub fn set_attributes(&self, attr: DWORD) -> io::Result<()> { + let mut info = FILE_BASIC_INFO { + CreationTime: 0, // do not change + LastAccessTime: 0, // do not change + LastWriteTime: 0, // do not change + ChangeTime: 0, // do not change + FileAttributes: attr, + }; + let size = mem::size_of_val(&info); + try!(cvt(unsafe { + SetFileInformationByHandle(self.handle.raw(), + FileBasicInfo, + &mut info as *mut _ as *mut _, + size as DWORD) + })); + Ok(()) + } + + pub fn rename(&self, 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 = 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 mut 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; + try!(cvt(SetFileInformationByHandle(self.handle().raw(), + FileRenameInfo, + data.as_mut_ptr() as *mut _ as *mut _, + size as DWORD))); + Ok(()) + } + } + pub fn set_perm(&self, perm: FilePermissions) -> io::Result<()> { + let attr = try!(self.file_attr()).attributes; + if perm.readonly == (attr & FILE_ATTRIBUTE_READONLY != 0) { + Ok(()) + } else if perm.readonly { + self.set_attributes(attr | FILE_ATTRIBUTE_READONLY) + } else { + self.set_attributes(attr & !FILE_ATTRIBUTE_READONLY) + } + } + + pub fn handle(&self) -> &Handle { &self.handle } + + fn reparse_point<'a>(&self, + space: &'a mut [u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]) + -> io::Result<(DWORD, &'a REPARSE_DATA_BUFFER)> { + unsafe { + let mut bytes = 0; + try!(cvt({ + DeviceIoControl(self.handle.raw(), + FSCTL_GET_REPARSE_POINT, + ptr::null_mut(), + 0, + space.as_mut_ptr() as *mut _, + space.len() as DWORD, + &mut bytes, + ptr::null_mut()) + })); + Ok((bytes, &*(space.as_ptr() as *const REPARSE_DATA_BUFFER))) + } + } +} + + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub enum FileType { + Dir, File, SymlinkFile, SymlinkDir, ReparsePoint, MountPoint, +} + +impl FileType { + fn new(attrs: DWORD, reparse_tag: DWORD) -> FileType { + match (attrs & FILE_ATTRIBUTE_DIRECTORY != 0, + attrs & FILE_ATTRIBUTE_REPARSE_POINT != 0, + reparse_tag) { + (false, false, _) => FileType::File, + (true, false, _) => FileType::Dir, + (false, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkFile, + (true, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkDir, + (true, true, IO_REPARSE_TAG_MOUNT_POINT) => FileType::MountPoint, + (_, true, _) => FileType::ReparsePoint, + // Note: if a _file_ has a reparse tag of the type IO_REPARSE_TAG_MOUNT_POINT it is + // invalid, as junctions always have to be dirs. We set the filetype to ReparsePoint + // to indicate it is something symlink-like, but not something you can follow. + } + } + + pub fn is_dir(&self) -> bool { *self == FileType::Dir } + pub fn is_symlink_dir(&self) -> bool { + *self == FileType::SymlinkDir || *self == FileType::MountPoint + } +} + +impl DirEntry { + fn new(root: &Arc, wfd: &WIN32_FIND_DATAW) -> Option { + let first_bytes = &wfd.cFileName[0..3]; + if first_bytes.starts_with(&[46, 0]) || first_bytes.starts_with(&[46, 46, 0]) { + None + } else { + Some(DirEntry { + root: root.clone(), + data: *wfd, + }) + } + } + + pub fn path(&self) -> PathBuf { + self.root.join(&self.file_name()) + } + + pub fn file_name(&self) -> OsString { + let filename = truncate_utf16_at_nul(&self.data.cFileName); + OsString::from_wide(filename) + } + + pub fn file_type(&self) -> io::Result { + Ok(FileType::new(self.data.dwFileAttributes, + /* reparse_tag = */ self.data.dwReserved0)) + } + + pub fn metadata(&self) -> io::Result { + Ok(FileAttr { + attributes: self.data.dwFileAttributes, + creation_time: self.data.ftCreationTime, + last_access_time: self.data.ftLastAccessTime, + last_write_time: self.data.ftLastWriteTime, + file_size: ((self.data.nFileSizeHigh as u64) << 32) | (self.data.nFileSizeLow as u64), + reparse_tag: if self.data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + // reserved unless this is a reparse point + self.data.dwReserved0 + } else { + 0 + }, + }) + } +} + + + +pub struct DirEntry { + root: Arc, + data: WIN32_FIND_DATAW, +} + +pub struct ReadDir { + handle: FindNextFileHandle, + root: Arc, + first: Option, +} + +impl Iterator for ReadDir { + type Item = io::Result; + fn next(&mut self) -> Option> { + if let Some(first) = self.first.take() { + if let Some(e) = DirEntry::new(&self.root, &first) { + return Some(Ok(e)); + } + } + unsafe { + let mut wfd = mem::zeroed(); + loop { + if FindNextFileW(self.handle.0, &mut wfd) == 0 { + if GetLastError() == ERROR_NO_MORE_FILES { + return None + } else { + return Some(Err(io::Error::last_os_error())) + } + } + if let Some(e) = DirEntry::new(&self.root, &wfd) { + return Some(Ok(e)) + } + } + } + } +} + + +#[derive(Clone)] +pub struct FileAttr { + attributes: DWORD, + creation_time: FILETIME, + last_access_time: FILETIME, + last_write_time: FILETIME, + file_size: u64, + reparse_tag: DWORD, +} + +impl FileAttr { + pub fn perm(&self) -> FilePermissions { + FilePermissions { + readonly: self.attributes & FILE_ATTRIBUTE_READONLY != 0 + } + } + + pub fn file_type(&self) -> FileType { + FileType::new(self.attributes, self.reparse_tag) + } + + fn is_reparse_point(&self) -> bool { + self.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + } +} + +#[repr(C)] +pub struct REPARSE_DATA_BUFFER { + pub ReparseTag: c_uint, + pub ReparseDataLength: c_ushort, + pub Reserved: c_ushort, + pub rest: (), +} + +pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; + + +/// An owned container for `HANDLE` object, closing them on Drop. +/// +/// All methods are inherited through a `Deref` impl to `RawHandle` +pub struct Handle(RawHandle); + +use std::ops::Deref; + +/// A wrapper type for `HANDLE` objects to give them proper Send/Sync inference +/// as well as Rust-y methods. +/// +/// This does **not** drop the handle when it goes out of scope, use `Handle` +/// instead for that. +#[derive(Copy, Clone)] +pub struct RawHandle(HANDLE); + +unsafe impl Send for RawHandle {} +unsafe impl Sync for RawHandle {} + +impl Handle { + pub fn new(handle: HANDLE) -> Handle { + Handle(RawHandle::new(handle)) + } +} + +impl Deref for Handle { + type Target = RawHandle; + fn deref(&self) -> &RawHandle { &self.0 } +} + +impl Drop for Handle { + fn drop(&mut self) { + unsafe { let _ = CloseHandle(self.raw()); } + } +} + +impl RawHandle { + pub fn new(handle: HANDLE) -> RawHandle { + RawHandle(handle) + } + + pub fn raw(&self) -> HANDLE { self.0 } +} + +struct FindNextFileHandle(HANDLE); + +fn get_path(f: &File) -> io::Result { + fill_utf16_buf(|buf, sz| unsafe { + GetFinalPathNameByHandleW(f.handle.raw(), buf, sz, + VOLUME_NAME_DOS) + }, |buf| { + PathBuf::from(OsString::from_wide(buf)) + }) +} + +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) = file.rename(tmpname.as_ref(), false) { + if err.kind() != io::ErrorKind::AlreadyExists { return Err(err) }; + tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter)); + ctx.counter += 1; + } + Ok(()) +} + +pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.access_mode(FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); + let file = try!(File::open(path, &opts)); + file.set_perm(perm) +} + +pub const VOLUME_NAME_DOS: DWORD = 0x0; From 2f129baa1ba3ccf411b053e35b17c6d977dd4239 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 28 Mar 2017 11:46:30 -0700 Subject: [PATCH 2/3] Use custom remove_dir_all --- src/rustup-utils/src/raw.rs | 53 +++----------------------- src/rustup-utils/src/remove_dir_all.rs | 6 +++ 2 files changed, 11 insertions(+), 48 deletions(-) diff --git a/src/rustup-utils/src/raw.rs b/src/rustup-utils/src/raw.rs index a9d3a721af..5c91abb221 100644 --- a/src/rustup-utils/src/raw.rs +++ b/src/rustup-utils/src/raw.rs @@ -320,54 +320,11 @@ pub fn remove_dir(path: &Path) -> io::Result<()> { fs::remove_file(path) } } else { - let mut result = Ok(()); - - // The implementation of `remove_dir_all` is broken on windows, - // so may need to try multiple times! - for _ in 0..5 { - result = rm_rf(path); - if !is_directory(path) { - return Ok(()); - } - thread::sleep(Duration::from_millis(16)); - } - result - } -} - -// Again because remove_dir all doesn't delete write-only files on windows, -// this is a custom implementation, more-or-less copied from cargo. -// cc rust-lang/rust#31944 -// cc https://github.com/rust-lang/cargo/blob/master/tests/support/paths.rs#L52-L80 -fn rm_rf(path: &Path) -> io::Result<()> { - if path.exists() { - for file in fs::read_dir(path).unwrap() { - let file = try!(file); - let is_dir = try!(file.file_type()).is_dir(); - let ref file = file.path(); - - if is_dir { - try!(rm_rf(file)); - } else { - // On windows we can't remove a readonly file, and git will - // often clone files as readonly. As a result, we have some - // special logic to remove readonly files on windows. - match fs::remove_file(file) { - Ok(()) => {} - Err(ref e) if cfg!(windows) && - e.kind() == io::ErrorKind::PermissionDenied => { - let mut p = file.metadata().unwrap().permissions(); - p.set_readonly(false); - fs::set_permissions(file, p).unwrap(); - try!(fs::remove_file(file)); - } - Err(e) => return Err(e) - } - } - } - fs::remove_dir(path) - } else { - Ok(()) + // Again because remove_dir all doesn't delete write-only files on windows, + // this is a custom implementation, more-or-less copied from cargo. + // cc rust-lang/rust#31944 + // cc https://github.com/rust-lang/cargo/blob/master/tests/support/paths.rs#L52 + ::remove_dir_all::remove_dir_all(path) } } diff --git a/src/rustup-utils/src/remove_dir_all.rs b/src/rustup-utils/src/remove_dir_all.rs index 3d20300209..8333b6c23e 100644 --- a/src/rustup-utils/src/remove_dir_all.rs +++ b/src/rustup-utils/src/remove_dir_all.rs @@ -75,6 +75,12 @@ use std::io; use std::ffi::{OsStr, OsString}; use std::os::windows::ffi::{OsStrExt, OsStringExt}; +#[cfg(not(windows))] +pub fn remove_dir_all(path: &Path) -> io::Result<()> { + std::fs::remove_dir_all(path) +} + +#[cfg(windows)] pub fn remove_dir_all(path: &Path) -> 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 From b61370a19a6c10d549ccdceb3fc2f01d4f9cf8f7 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 28 Mar 2017 13:57:32 -0700 Subject: [PATCH 3/3] Remove pub modifiers from remove_dir_all module --- src/rustup-utils/src/remove_dir_all.rs | 1358 ++++++++++++------------ 1 file changed, 686 insertions(+), 672 deletions(-) diff --git a/src/rustup-utils/src/remove_dir_all.rs b/src/rustup-utils/src/remove_dir_all.rs index 8333b6c23e..778b3a1ae5 100644 --- a/src/rustup-utils/src/remove_dir_all.rs +++ b/src/rustup-utils/src/remove_dir_all.rs @@ -8,814 +8,828 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use winapi::{ - FileBasicInfo, - FILE_BASIC_INFO, - FALSE, - FileRenameInfo, - FILE_RENAME_INFO, - c_ushort, - c_uint, - FILETIME, - FILE_ATTRIBUTE_READONLY, - FILE_ATTRIBUTE_REPARSE_POINT, - FILE_ATTRIBUTE_DIRECTORY, - WIN32_FIND_DATAW, - ERROR_NO_MORE_FILES, - OPEN_EXISTING, - OPEN_ALWAYS, - TRUNCATE_EXISTING, - CREATE_ALWAYS, - CREATE_NEW, - GENERIC_READ, - GENERIC_WRITE, - FILE_GENERIC_WRITE, - FILE_WRITE_DATA, - FILE_SHARE_READ, - FILE_SHARE_WRITE, - FILE_SHARE_DELETE, - FILE_FLAG_DELETE_ON_CLOSE, - DELETE, - FILE_WRITE_ATTRIBUTES, - FILE_INFO_BY_HANDLE_CLASS, - HANDLE, - ERROR_INSUFFICIENT_BUFFER, - FILE_READ_ATTRIBUTES, - FILE_FLAG_BACKUP_SEMANTICS, - FILE_FLAG_OPEN_REPARSE_POINT, - ERROR_CALL_NOT_IMPLEMENTED, - DWORD, - BOOL, - LPVOID, - INVALID_HANDLE_VALUE, - LPCWSTR, - SECURITY_SQOS_PRESENT, - FSCTL_GET_REPARSE_POINT, - BY_HANDLE_FILE_INFORMATION, -}; - -use kernel32::{ - CreateFileW, - GetFileInformationByHandle, - CloseHandle, - GetLastError, - SetLastError, - DeviceIoControl, - GetModuleHandleW, - GetProcAddress, - FindNextFileW, - FindFirstFileW, -}; - -use std::ptr; -use std::sync::Arc; -use std::path::{PathBuf, Path}; -use std::mem; +#![allow(non_snake_case)] + +use std::path::Path; use std::io; -use std::ffi::{OsStr, OsString}; -use std::os::windows::ffi::{OsStrExt, OsStringExt}; #[cfg(not(windows))] pub fn remove_dir_all(path: &Path) -> io::Result<()> { - std::fs::remove_dir_all(path) + ::std::fs::remove_dir_all(path) } #[cfg(windows)] pub fn remove_dir_all(path: &Path) -> 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 mut opts = OpenOptions::new(); - opts.access_mode(FILE_READ_ATTRIBUTES); - opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | - FILE_FLAG_OPEN_REPARSE_POINT); - let file = try!(File::open(path, &opts)); - (try!(get_path(&file)), try!(file.file_attr())) - }; + win::remove_dir_all(path) +} - 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.perm().readonly(), - counter: 0, +#[cfg(windows)] +mod win { + use winapi::{ + FileBasicInfo, + FILE_BASIC_INFO, + FALSE, + FileRenameInfo, + FILE_RENAME_INFO, + c_ushort, + c_uint, + FILETIME, + FILE_ATTRIBUTE_READONLY, + FILE_ATTRIBUTE_REPARSE_POINT, + FILE_ATTRIBUTE_DIRECTORY, + WIN32_FIND_DATAW, + ERROR_NO_MORE_FILES, + OPEN_EXISTING, + OPEN_ALWAYS, + TRUNCATE_EXISTING, + CREATE_ALWAYS, + CREATE_NEW, + GENERIC_READ, + GENERIC_WRITE, + FILE_GENERIC_WRITE, + FILE_WRITE_DATA, + FILE_SHARE_READ, + FILE_SHARE_WRITE, + FILE_SHARE_DELETE, + FILE_FLAG_DELETE_ON_CLOSE, + DELETE, + FILE_WRITE_ATTRIBUTES, + FILE_INFO_BY_HANDLE_CLASS, + HANDLE, + ERROR_INSUFFICIENT_BUFFER, + FILE_READ_ATTRIBUTES, + FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_OPEN_REPARSE_POINT, + ERROR_CALL_NOT_IMPLEMENTED, + DWORD, + BOOL, + LPVOID, + INVALID_HANDLE_VALUE, + LPCWSTR, + SECURITY_SQOS_PRESENT, + FSCTL_GET_REPARSE_POINT, + BY_HANDLE_FILE_INFORMATION, + IO_REPARSE_TAG_SYMLINK, + IO_REPARSE_TAG_MOUNT_POINT, }; - let filetype = metadata.file_type(); - if filetype.is_dir() { - remove_dir_all_recursive(path.as_ref(), &mut ctx) - } else if filetype.is_symlink_dir() { - remove_item(path.as_ref(), &mut ctx) - } else { - Err(io::Error::new(io::ErrorKind::PermissionDenied, "Not a directory")) - } -} + use kernel32::{ + CreateFileW, + GetFileInformationByHandle, + CloseHandle, + GetLastError, + SetLastError, + DeviceIoControl, + GetModuleHandleW, + GetProcAddress, + FindNextFileW, + FindFirstFileW, + }; + use std::ptr; + use std::sync::Arc; + use std::path::{PathBuf, Path}; + use std::mem; + use std::io; + use std::ffi::{OsStr, OsString}; + use std::os::windows::ffi::{OsStrExt, OsStringExt}; + + pub fn remove_dir_all(path: &Path) -> 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 mut opts = OpenOptions::new(); + opts.access_mode(FILE_READ_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT); + let file = try!(File::open(path, &opts)); + (try!(get_path(&file)), try!(file.file_attr())) + }; -pub fn readdir(p: &Path) -> io::Result { - let root = p.to_path_buf(); - let star = p.join("*"); - let path = try!(to_u16s(&star)); + 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.perm().readonly(), + counter: 0, + }; - unsafe { - let mut wfd = mem::zeroed(); - let find_handle = FindFirstFileW(path.as_ptr(), &mut wfd); - if find_handle != INVALID_HANDLE_VALUE { - Ok(ReadDir { - handle: FindNextFileHandle(find_handle), - root: Arc::new(root), - first: Some(wfd), - }) + let filetype = metadata.file_type(); + if filetype.is_dir() { + remove_dir_all_recursive(path.as_ref(), &mut ctx) + } else if filetype.is_symlink_dir() { + remove_item(path.as_ref(), &mut ctx) } else { - Err(io::Error::last_os_error()) + Err(io::Error::new(io::ErrorKind::PermissionDenied, "Not a directory")) } } -} -struct RmdirContext<'a> { - base_dir: &'a Path, - readonly: bool, - counter: u64, -} -fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) - -> io::Result<()> { - let dir_readonly = ctx.readonly; - for child in try!(readdir(path)) { - let child = try!(child); - let child_type = try!(child.file_type()); - ctx.readonly = try!(child.metadata()).perm().readonly(); - if child_type.is_dir() { - try!(remove_dir_all_recursive(&child.path(), ctx)); - } else { - try!(remove_item(&child.path().as_ref(), ctx)); + fn readdir(p: &Path) -> io::Result { + let root = p.to_path_buf(); + let star = p.join("*"); + let path = try!(to_u16s(&star)); + + unsafe { + let mut wfd = mem::zeroed(); + let find_handle = FindFirstFileW(path.as_ptr(), &mut wfd); + if find_handle != INVALID_HANDLE_VALUE { + Ok(ReadDir { + handle: FindNextFileHandle(find_handle), + root: Arc::new(root), + first: Some(wfd), + }) + } else { + Err(io::Error::last_os_error()) + } } } - ctx.readonly = dir_readonly; - remove_item(path, ctx) -} -fn remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { - if !ctx.readonly { - 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 = try!(File::open(path, &opts)); - move_item(&file, ctx) - } else { - // remove read-only permision - try!(set_perm(&path, FilePermissions::new())); - // move and delete file, similar to !readonly. - // only the access mode is different. - let mut opts = OpenOptions::new(); - opts.access_mode(DELETE | FILE_WRITE_ATTRIBUTES); - opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | - FILE_FLAG_OPEN_REPARSE_POINT | - FILE_FLAG_DELETE_ON_CLOSE); - let file = try!(File::open(path, &opts)); - try!(move_item(&file, ctx)); - // restore read-only flag just in case there are other hard links - let mut perm = FilePermissions::new(); - perm.set_readonly(true); - let _ = file.set_perm(perm); // ignore if this fails - Ok(()) + struct RmdirContext<'a> { + base_dir: &'a Path, + readonly: bool, + counter: u64, } -} -macro_rules! compat_fn { - ($module:ident: $( - pub fn $symbol:ident($($argname:ident: $argtype:ty),*) - -> $rettype:ty { - $($body:expr);* - } - )*) => ($( - #[allow(unused_variables)] - pub unsafe fn $symbol($($argname: $argtype),*) -> $rettype { - use std::sync::atomic::{AtomicUsize, Ordering}; - use std::mem; - use std::ffi::CString; - type F = unsafe extern "system" fn($($argtype),*) -> $rettype; - - lazy_static! { static ref PTR: AtomicUsize = AtomicUsize::new(0);} - - pub fn lookup(module: &str, symbol: &str) -> Option { - let mut module: Vec = module.encode_utf16().collect(); - module.push(0); - let symbol = CString::new(symbol).unwrap(); - unsafe { - let handle = GetModuleHandleW(module.as_ptr()); - match GetProcAddress(handle, symbol.as_ptr()) as usize { - 0 => None, - n => Some(n), + fn remove_dir_all_recursive(path: &Path, ctx: &mut RmdirContext) + -> io::Result<()> { + let dir_readonly = ctx.readonly; + for child in try!(readdir(path)) { + let child = try!(child); + let child_type = try!(child.file_type()); + ctx.readonly = try!(child.metadata()).perm().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 remove_item(path: &Path, ctx: &mut RmdirContext) -> io::Result<()> { + if !ctx.readonly { + 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 = try!(File::open(path, &opts)); + move_item(&file, ctx) + } else { + // remove read-only permision + try!(set_perm(&path, FilePermissions::new())); + // move and delete file, similar to !readonly. + // only the access mode is different. + let mut opts = OpenOptions::new(); + opts.access_mode(DELETE | FILE_WRITE_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS | + FILE_FLAG_OPEN_REPARSE_POINT | + FILE_FLAG_DELETE_ON_CLOSE); + let file = try!(File::open(path, &opts)); + try!(move_item(&file, ctx)); + // restore read-only flag just in case there are other hard links + let mut perm = FilePermissions::new(); + perm.set_readonly(true); + let _ = file.set_perm(perm); // ignore if this fails + Ok(()) + } + } + + macro_rules! compat_fn { + ($module:ident: $( + fn $symbol:ident($($argname:ident: $argtype:ty),*) + -> $rettype:ty { + $($body:expr);* + } + )*) => ($( + #[allow(unused_variables)] + unsafe fn $symbol($($argname: $argtype),*) -> $rettype { + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::mem; + use std::ffi::CString; + type F = unsafe extern "system" fn($($argtype),*) -> $rettype; + + lazy_static! { static ref PTR: AtomicUsize = AtomicUsize::new(0);} + + fn lookup(module: &str, symbol: &str) -> Option { + let mut module: Vec = module.encode_utf16().collect(); + module.push(0); + let symbol = CString::new(symbol).unwrap(); + unsafe { + let handle = GetModuleHandleW(module.as_ptr()); + match GetProcAddress(handle, symbol.as_ptr()) as usize { + 0 => None, + n => Some(n), + } } } - } - pub fn store_func(ptr: &AtomicUsize, module: &str, symbol: &str, + fn store_func(ptr: &AtomicUsize, module: &str, symbol: &str, fallback: usize) -> usize { - let value = lookup(module, symbol).unwrap_or(fallback); - ptr.store(value, Ordering::SeqCst); - value - } + let value = lookup(module, symbol).unwrap_or(fallback); + ptr.store(value, Ordering::SeqCst); + value + } - fn load() -> usize { - store_func(&PTR, stringify!($module), stringify!($symbol), fallback as usize) - } - unsafe extern "system" fn fallback($($argname: $argtype),*) - -> $rettype { + fn load() -> usize { + store_func(&PTR, stringify!($module), stringify!($symbol), fallback as usize) + } + unsafe extern "system" fn fallback($($argname: $argtype),*) + -> $rettype { $($body);* } - let addr = match PTR.load(Ordering::SeqCst) { - 0 => load(), - n => n, - }; - mem::transmute::(addr)($($argname),*) - } - )*) -} + let addr = match PTR.load(Ordering::SeqCst) { + 0 => load(), + n => n, + }; + mem::transmute::(addr)($($argname),*) + } + )*) + } -compat_fn! { - kernel32: - pub fn GetFinalPathNameByHandleW(_hFile: HANDLE, + compat_fn! { + kernel32: + fn GetFinalPathNameByHandleW(_hFile: HANDLE, _lpszFilePath: LPCWSTR, _cchFilePath: DWORD, _dwFlags: DWORD) -> DWORD { - SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 - } - pub fn SetFileInformationByHandle(_hFile: HANDLE, + SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 + } + fn SetFileInformationByHandle(_hFile: HANDLE, _FileInformationClass: FILE_INFO_BY_HANDLE_CLASS, _lpFileInformation: LPVOID, _dwBufferSize: DWORD) -> BOOL { - SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 + SetLastError(ERROR_CALL_NOT_IMPLEMENTED as DWORD); 0 + } } -} -fn cvt(i: i32) -> io::Result { - if i == 0 { - Err(io::Error::last_os_error()) - } else { - Ok(i) + fn cvt(i: i32) -> io::Result { + if i == 0 { + Err(io::Error::last_os_error()) + } else { + Ok(i) + } } -} -pub fn to_u16s>(s: S) -> io::Result> { - fn inner(s: &OsStr) -> io::Result> { - let mut maybe_result: Vec = s.encode_wide().collect(); - if maybe_result.iter().any(|&u| u == 0) { - return Err(io::Error::new(io::ErrorKind::InvalidInput, - "strings passed to WinAPI cannot contain NULs")); + fn to_u16s>(s: S) -> io::Result> { + fn inner(s: &OsStr) -> io::Result> { + let mut maybe_result: Vec = s.encode_wide().collect(); + if maybe_result.iter().any(|&u| u == 0) { + return Err(io::Error::new(io::ErrorKind::InvalidInput, + "strings passed to WinAPI cannot contain NULs")); + } + maybe_result.push(0); + Ok(maybe_result) } - maybe_result.push(0); - Ok(maybe_result) + inner(s.as_ref()) } - inner(s.as_ref()) -} -pub fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] { - match v.iter().position(|c| *c == 0) { - // don't include the 0 - Some(i) => &v[..i], - None => v + fn truncate_utf16_at_nul<'a>(v: &'a [u16]) -> &'a [u16] { + match v.iter().position(|c| *c == 0) { + // don't include the 0 + Some(i) => &v[..i], + None => v + } } -} - -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])) + 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])) + } } } } -} - -#[derive(Clone, PartialEq, Eq, Debug, Default)] -pub struct FilePermissions { readonly: bool } - -impl FilePermissions { - pub fn new() -> FilePermissions { Default::default() } - pub fn readonly(&self) -> bool { self.readonly } - pub fn set_readonly(&mut self, readonly: bool) { self.readonly = readonly } -} -#[derive(Clone)] -pub struct OpenOptions { - // generic - read: bool, - write: bool, - append: bool, - truncate: bool, - create: bool, - create_new: bool, - // system-specific - custom_flags: u32, - access_mode: Option, - attributes: DWORD, - share_mode: DWORD, - security_qos_flags: DWORD, - security_attributes: usize, // FIXME: should be a reference -} - -impl OpenOptions { - pub fn new() -> OpenOptions { - OpenOptions { - // generic - read: false, - write: false, - append: false, - truncate: false, - create: false, - create_new: false, - // system-specific - custom_flags: 0, - access_mode: None, - share_mode: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - attributes: 0, - security_qos_flags: 0, - security_attributes: 0, + #[derive(Clone, PartialEq, Eq, Debug, Default)] + struct FilePermissions { readonly: bool } + + impl FilePermissions { + fn new() -> FilePermissions { Default::default() } + fn readonly(&self) -> bool { self.readonly } + fn set_readonly(&mut self, readonly: bool) { self.readonly = readonly } + } + + #[derive(Clone)] + struct OpenOptions { + // generic + read: bool, + write: bool, + append: bool, + truncate: bool, + create: bool, + create_new: bool, + // system-specific + custom_flags: u32, + access_mode: Option, + attributes: DWORD, + share_mode: DWORD, + security_qos_flags: DWORD, + security_attributes: usize, // FIXME: should be a reference + } + + impl OpenOptions { + fn new() -> OpenOptions { + OpenOptions { + // generic + read: false, + write: false, + append: false, + truncate: false, + create: false, + create_new: false, + // system-specific + custom_flags: 0, + access_mode: None, + share_mode: FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + attributes: 0, + security_qos_flags: 0, + security_attributes: 0, + } } - } - pub fn custom_flags(&mut self, flags: u32) { self.custom_flags = flags; } - pub fn access_mode(&mut self, access_mode: u32) { self.access_mode = Some(access_mode); } - - fn get_access_mode(&self) -> io::Result { - const ERROR_INVALID_PARAMETER: i32 = 87; - - match (self.read, self.write, self.append, self.access_mode) { - (_, _, _, Some(mode)) => Ok(mode), - (true, false, false, None) => Ok(GENERIC_READ), - (false, true, false, None) => Ok(GENERIC_WRITE), - (true, true, false, None) => Ok(GENERIC_READ | GENERIC_WRITE), - (false, _, true, None) => Ok(FILE_GENERIC_WRITE & !FILE_WRITE_DATA), - (true, _, true, None) => Ok(GENERIC_READ | - (FILE_GENERIC_WRITE & !FILE_WRITE_DATA)), - (false, false, false, None) => Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)), + fn custom_flags(&mut self, flags: u32) { self.custom_flags = flags; } + fn access_mode(&mut self, access_mode: u32) { self.access_mode = Some(access_mode); } + + fn get_access_mode(&self) -> io::Result { + const ERROR_INVALID_PARAMETER: i32 = 87; + + match (self.read, self.write, self.append, self.access_mode) { + (_, _, _, Some(mode)) => Ok(mode), + (true, false, false, None) => Ok(GENERIC_READ), + (false, true, false, None) => Ok(GENERIC_WRITE), + (true, true, false, None) => Ok(GENERIC_READ | GENERIC_WRITE), + (false, _, true, None) => Ok(FILE_GENERIC_WRITE & !FILE_WRITE_DATA), + (true, _, true, None) => Ok(GENERIC_READ | + (FILE_GENERIC_WRITE & !FILE_WRITE_DATA)), + (false, false, false, None) => Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)), + } } - } - fn get_creation_mode(&self) -> io::Result { - const ERROR_INVALID_PARAMETER: i32 = 87; + fn get_creation_mode(&self) -> io::Result { + const ERROR_INVALID_PARAMETER: i32 = 87; - match (self.write, self.append) { - (true, false) => {} - (false, false) => - if self.truncate || self.create || self.create_new { - return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); - }, + match (self.write, self.append) { + (true, false) => {} + (false, false) => + if self.truncate || self.create || self.create_new { + return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); + }, (_, true) => if self.truncate && !self.create_new { return Err(io::Error::from_raw_os_error(ERROR_INVALID_PARAMETER)); }, - } + } - Ok(match (self.create, self.truncate, self.create_new) { - (false, false, false) => OPEN_EXISTING, - (true, false, false) => OPEN_ALWAYS, - (false, true, false) => TRUNCATE_EXISTING, - (true, true, false) => CREATE_ALWAYS, - (_, _, true) => CREATE_NEW, - }) - } + Ok(match (self.create, self.truncate, self.create_new) { + (false, false, false) => OPEN_EXISTING, + (true, false, false) => OPEN_ALWAYS, + (false, true, false) => TRUNCATE_EXISTING, + (true, true, false) => CREATE_ALWAYS, + (_, _, true) => CREATE_NEW, + }) + } - fn get_flags_and_attributes(&self) -> DWORD { - self.custom_flags | + fn get_flags_and_attributes(&self) -> DWORD { + self.custom_flags | self.attributes | self.security_qos_flags | if self.security_qos_flags != 0 { SECURITY_SQOS_PRESENT } else { 0 } | - if self.create_new { FILE_FLAG_OPEN_REPARSE_POINT } else { 0 } - } -} - -pub struct File { handle: Handle } - -impl File { - pub fn open(path: &Path, opts: &OpenOptions) -> io::Result { - let path = try!(to_u16s(path)); - let handle = unsafe { - CreateFileW(path.as_ptr(), - try!(opts.get_access_mode()), - opts.share_mode, - opts.security_attributes as *mut _, - try!(opts.get_creation_mode()), - opts.get_flags_and_attributes(), - ptr::null_mut()) - }; - if handle == INVALID_HANDLE_VALUE { - Err(io::Error::last_os_error()) - } else { - Ok(File { handle: Handle::new(handle) }) + if self.create_new { FILE_FLAG_OPEN_REPARSE_POINT } else { 0 } } } - pub fn file_attr(&self) -> io::Result { - unsafe { - let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed(); - try!(cvt(GetFileInformationByHandle(self.handle.raw(), - &mut info))); - let mut attr = FileAttr { - attributes: info.dwFileAttributes, - creation_time: info.ftCreationTime, - last_access_time: info.ftLastAccessTime, - last_write_time: info.ftLastWriteTime, - file_size: ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64), - reparse_tag: 0, + struct File { handle: Handle } + + impl File { + fn open(path: &Path, opts: &OpenOptions) -> io::Result { + let path = try!(to_u16s(path)); + let handle = unsafe { + CreateFileW(path.as_ptr(), + try!(opts.get_access_mode()), + opts.share_mode, + opts.security_attributes as *mut _, + try!(opts.get_creation_mode()), + opts.get_flags_and_attributes(), + ptr::null_mut()) }; - if attr.is_reparse_point() { - let mut b = [0; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; - if let Ok((_, buf)) = self.reparse_point(&mut b) { - attr.reparse_tag = buf.ReparseTag; + if handle == INVALID_HANDLE_VALUE { + Err(io::Error::last_os_error()) + } else { + Ok(File { handle: Handle::new(handle) }) + } + } + + fn file_attr(&self) -> io::Result { + unsafe { + let mut info: BY_HANDLE_FILE_INFORMATION = mem::zeroed(); + try!(cvt(GetFileInformationByHandle(self.handle.raw(), + &mut info))); + let mut attr = FileAttr { + attributes: info.dwFileAttributes, + creation_time: info.ftCreationTime, + last_access_time: info.ftLastAccessTime, + last_write_time: info.ftLastWriteTime, + file_size: ((info.nFileSizeHigh as u64) << 32) | (info.nFileSizeLow as u64), + reparse_tag: 0, + }; + if attr.is_reparse_point() { + let mut b = [0; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; + if let Ok((_, buf)) = self.reparse_point(&mut b) { + attr.reparse_tag = buf.ReparseTag; + } } + Ok(attr) } - Ok(attr) } - } - pub fn set_attributes(&self, attr: DWORD) -> io::Result<()> { - let mut info = FILE_BASIC_INFO { - CreationTime: 0, // do not change - LastAccessTime: 0, // do not change - LastWriteTime: 0, // do not change - ChangeTime: 0, // do not change - FileAttributes: attr, - }; - let size = mem::size_of_val(&info); - try!(cvt(unsafe { - SetFileInformationByHandle(self.handle.raw(), - FileBasicInfo, - &mut info as *mut _ as *mut _, - size as DWORD) - })); - Ok(()) - } + fn set_attributes(&self, attr: DWORD) -> io::Result<()> { + let mut info = FILE_BASIC_INFO { + CreationTime: 0, // do not change + LastAccessTime: 0, // do not change + LastWriteTime: 0, // do not change + ChangeTime: 0, // do not change + FileAttributes: attr, + }; + let size = mem::size_of_val(&info); + try!(cvt(unsafe { + SetFileInformationByHandle(self.handle.raw(), + FileBasicInfo, + &mut info as *mut _ as *mut _, + size as DWORD) + })); + Ok(()) + } - pub fn rename(&self, 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 = iter::repeat(0u16).take(STRUCT_SIZE/2) - .chain(new.as_os_str().encode_wide()) - .collect(); - data.push(0); - let size = data.len() * 2; + fn rename(&self, 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 = 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 mut 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; - try!(cvt(SetFileInformationByHandle(self.handle().raw(), - FileRenameInfo, - data.as_mut_ptr() as *mut _ as *mut _, - size as DWORD))); - Ok(()) + unsafe { + // Thanks to alignment guarantees on Windows this works + // (8 for 32-bit and 16 for 64-bit) + let mut 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; + try!(cvt(SetFileInformationByHandle(self.handle().raw(), + FileRenameInfo, + data.as_mut_ptr() as *mut _ as *mut _, + size as DWORD))); + Ok(()) + } } - } - pub fn set_perm(&self, perm: FilePermissions) -> io::Result<()> { - let attr = try!(self.file_attr()).attributes; - if perm.readonly == (attr & FILE_ATTRIBUTE_READONLY != 0) { - Ok(()) - } else if perm.readonly { - self.set_attributes(attr | FILE_ATTRIBUTE_READONLY) - } else { - self.set_attributes(attr & !FILE_ATTRIBUTE_READONLY) + fn set_perm(&self, perm: FilePermissions) -> io::Result<()> { + let attr = try!(self.file_attr()).attributes; + if perm.readonly == (attr & FILE_ATTRIBUTE_READONLY != 0) { + Ok(()) + } else if perm.readonly { + self.set_attributes(attr | FILE_ATTRIBUTE_READONLY) + } else { + self.set_attributes(attr & !FILE_ATTRIBUTE_READONLY) + } } - } - pub fn handle(&self) -> &Handle { &self.handle } + fn handle(&self) -> &Handle { &self.handle } - fn reparse_point<'a>(&self, - space: &'a mut [u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]) - -> io::Result<(DWORD, &'a REPARSE_DATA_BUFFER)> { + fn reparse_point<'a>(&self, + space: &'a mut [u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE]) + -> io::Result<(DWORD, &'a REPARSE_DATA_BUFFER)> { unsafe { let mut bytes = 0; try!(cvt({ DeviceIoControl(self.handle.raw(), - FSCTL_GET_REPARSE_POINT, - ptr::null_mut(), - 0, - space.as_mut_ptr() as *mut _, - space.len() as DWORD, - &mut bytes, - ptr::null_mut()) + FSCTL_GET_REPARSE_POINT, + ptr::null_mut(), + 0, + space.as_mut_ptr() as *mut _, + space.len() as DWORD, + &mut bytes, + ptr::null_mut()) })); Ok((bytes, &*(space.as_ptr() as *const REPARSE_DATA_BUFFER))) } } -} + } -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub enum FileType { - Dir, File, SymlinkFile, SymlinkDir, ReparsePoint, MountPoint, -} + #[derive(Copy, Clone, PartialEq, Eq, Hash)] + enum FileType { + Dir, File, SymlinkFile, SymlinkDir, ReparsePoint, MountPoint, + } -impl FileType { - fn new(attrs: DWORD, reparse_tag: DWORD) -> FileType { - match (attrs & FILE_ATTRIBUTE_DIRECTORY != 0, - attrs & FILE_ATTRIBUTE_REPARSE_POINT != 0, - reparse_tag) { - (false, false, _) => FileType::File, - (true, false, _) => FileType::Dir, - (false, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkFile, - (true, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkDir, - (true, true, IO_REPARSE_TAG_MOUNT_POINT) => FileType::MountPoint, - (_, true, _) => FileType::ReparsePoint, - // Note: if a _file_ has a reparse tag of the type IO_REPARSE_TAG_MOUNT_POINT it is - // invalid, as junctions always have to be dirs. We set the filetype to ReparsePoint - // to indicate it is something symlink-like, but not something you can follow. + impl FileType { + fn new(attrs: DWORD, reparse_tag: DWORD) -> FileType { + match (attrs & FILE_ATTRIBUTE_DIRECTORY != 0, + attrs & FILE_ATTRIBUTE_REPARSE_POINT != 0, + reparse_tag) { + (false, false, _) => FileType::File, + (true, false, _) => FileType::Dir, + (false, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkFile, + (true, true, IO_REPARSE_TAG_SYMLINK) => FileType::SymlinkDir, + (true, true, IO_REPARSE_TAG_MOUNT_POINT) => FileType::MountPoint, + (_, true, _) => FileType::ReparsePoint, + // Note: if a _file_ has a reparse tag of the type IO_REPARSE_TAG_MOUNT_POINT it is + // invalid, as junctions always have to be dirs. We set the filetype to ReparsePoint + // to indicate it is something symlink-like, but not something you can follow. + } } - } - pub fn is_dir(&self) -> bool { *self == FileType::Dir } - pub fn is_symlink_dir(&self) -> bool { - *self == FileType::SymlinkDir || *self == FileType::MountPoint + fn is_dir(&self) -> bool { *self == FileType::Dir } + fn is_symlink_dir(&self) -> bool { + *self == FileType::SymlinkDir || *self == FileType::MountPoint + } } -} -impl DirEntry { - fn new(root: &Arc, wfd: &WIN32_FIND_DATAW) -> Option { - let first_bytes = &wfd.cFileName[0..3]; - if first_bytes.starts_with(&[46, 0]) || first_bytes.starts_with(&[46, 46, 0]) { - None - } else { - Some(DirEntry { - root: root.clone(), - data: *wfd, - }) + impl DirEntry { + fn new(root: &Arc, wfd: &WIN32_FIND_DATAW) -> Option { + let first_bytes = &wfd.cFileName[0..3]; + if first_bytes.starts_with(&[46, 0]) || first_bytes.starts_with(&[46, 46, 0]) { + None + } else { + Some(DirEntry { + root: root.clone(), + data: *wfd, + }) + } } - } - pub fn path(&self) -> PathBuf { - self.root.join(&self.file_name()) - } + fn path(&self) -> PathBuf { + self.root.join(&self.file_name()) + } - pub fn file_name(&self) -> OsString { - let filename = truncate_utf16_at_nul(&self.data.cFileName); - OsString::from_wide(filename) - } + fn file_name(&self) -> OsString { + let filename = truncate_utf16_at_nul(&self.data.cFileName); + OsString::from_wide(filename) + } - pub fn file_type(&self) -> io::Result { - Ok(FileType::new(self.data.dwFileAttributes, - /* reparse_tag = */ self.data.dwReserved0)) - } + fn file_type(&self) -> io::Result { + Ok(FileType::new(self.data.dwFileAttributes, + /* reparse_tag = */ self.data.dwReserved0)) + } - pub fn metadata(&self) -> io::Result { - Ok(FileAttr { - attributes: self.data.dwFileAttributes, - creation_time: self.data.ftCreationTime, - last_access_time: self.data.ftLastAccessTime, - last_write_time: self.data.ftLastWriteTime, - file_size: ((self.data.nFileSizeHigh as u64) << 32) | (self.data.nFileSizeLow as u64), - reparse_tag: if self.data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { - // reserved unless this is a reparse point - self.data.dwReserved0 - } else { - 0 - }, - }) + fn metadata(&self) -> io::Result { + Ok(FileAttr { + attributes: self.data.dwFileAttributes, + creation_time: self.data.ftCreationTime, + last_access_time: self.data.ftLastAccessTime, + last_write_time: self.data.ftLastWriteTime, + file_size: ((self.data.nFileSizeHigh as u64) << 32) | (self.data.nFileSizeLow as u64), + reparse_tag: if self.data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 { + // reserved unless this is a reparse point + self.data.dwReserved0 + } else { + 0 + }, + }) + } } -} -pub struct DirEntry { - root: Arc, - data: WIN32_FIND_DATAW, -} + struct DirEntry { + root: Arc, + data: WIN32_FIND_DATAW, + } -pub struct ReadDir { - handle: FindNextFileHandle, - root: Arc, - first: Option, -} + struct ReadDir { + handle: FindNextFileHandle, + root: Arc, + first: Option, + } -impl Iterator for ReadDir { - type Item = io::Result; - fn next(&mut self) -> Option> { - if let Some(first) = self.first.take() { - if let Some(e) = DirEntry::new(&self.root, &first) { - return Some(Ok(e)); + impl Iterator for ReadDir { + type Item = io::Result; + fn next(&mut self) -> Option> { + if let Some(first) = self.first.take() { + if let Some(e) = DirEntry::new(&self.root, &first) { + return Some(Ok(e)); + } } - } - unsafe { - let mut wfd = mem::zeroed(); - loop { - if FindNextFileW(self.handle.0, &mut wfd) == 0 { - if GetLastError() == ERROR_NO_MORE_FILES { - return None - } else { - return Some(Err(io::Error::last_os_error())) + unsafe { + let mut wfd = mem::zeroed(); + loop { + if FindNextFileW(self.handle.0, &mut wfd) == 0 { + if GetLastError() == ERROR_NO_MORE_FILES { + return None + } else { + return Some(Err(io::Error::last_os_error())) + } + } + if let Some(e) = DirEntry::new(&self.root, &wfd) { + return Some(Ok(e)) } - } - if let Some(e) = DirEntry::new(&self.root, &wfd) { - return Some(Ok(e)) } } } } -} -#[derive(Clone)] -pub struct FileAttr { - attributes: DWORD, - creation_time: FILETIME, - last_access_time: FILETIME, - last_write_time: FILETIME, - file_size: u64, - reparse_tag: DWORD, -} + #[derive(Clone)] + struct FileAttr { + attributes: DWORD, + creation_time: FILETIME, + last_access_time: FILETIME, + last_write_time: FILETIME, + file_size: u64, + reparse_tag: DWORD, + } -impl FileAttr { - pub fn perm(&self) -> FilePermissions { - FilePermissions { - readonly: self.attributes & FILE_ATTRIBUTE_READONLY != 0 + impl FileAttr { + fn perm(&self) -> FilePermissions { + FilePermissions { + readonly: self.attributes & FILE_ATTRIBUTE_READONLY != 0 + } } - } - pub fn file_type(&self) -> FileType { - FileType::new(self.attributes, self.reparse_tag) - } + fn file_type(&self) -> FileType { + FileType::new(self.attributes, self.reparse_tag) + } - fn is_reparse_point(&self) -> bool { - self.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + fn is_reparse_point(&self) -> bool { + self.attributes & FILE_ATTRIBUTE_REPARSE_POINT != 0 + } } -} -#[repr(C)] -pub struct REPARSE_DATA_BUFFER { - pub ReparseTag: c_uint, - pub ReparseDataLength: c_ushort, - pub Reserved: c_ushort, - pub rest: (), -} + #[repr(C)] + struct REPARSE_DATA_BUFFER { + ReparseTag: c_uint, + ReparseDataLength: c_ushort, + Reserved: c_ushort, + rest: (), + } -pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; + const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: usize = 16 * 1024; -/// An owned container for `HANDLE` object, closing them on Drop. -/// -/// All methods are inherited through a `Deref` impl to `RawHandle` -pub struct Handle(RawHandle); + /// An owned container for `HANDLE` object, closing them on Drop. + /// + /// All methods are inherited through a `Deref` impl to `RawHandle` + struct Handle(RawHandle); -use std::ops::Deref; + use std::ops::Deref; -/// A wrapper type for `HANDLE` objects to give them proper Send/Sync inference -/// as well as Rust-y methods. -/// -/// This does **not** drop the handle when it goes out of scope, use `Handle` -/// instead for that. -#[derive(Copy, Clone)] -pub struct RawHandle(HANDLE); + /// A wrapper type for `HANDLE` objects to give them proper Send/Sync inference + /// as well as Rust-y methods. + /// + /// This does **not** drop the handle when it goes out of scope, use `Handle` + /// instead for that. + #[derive(Copy, Clone)] + struct RawHandle(HANDLE); -unsafe impl Send for RawHandle {} -unsafe impl Sync for RawHandle {} + unsafe impl Send for RawHandle {} + unsafe impl Sync for RawHandle {} -impl Handle { - pub fn new(handle: HANDLE) -> Handle { - Handle(RawHandle::new(handle)) + impl Handle { + fn new(handle: HANDLE) -> Handle { + Handle(RawHandle::new(handle)) + } } -} - -impl Deref for Handle { - type Target = RawHandle; - fn deref(&self) -> &RawHandle { &self.0 } -} -impl Drop for Handle { - fn drop(&mut self) { - unsafe { let _ = CloseHandle(self.raw()); } + impl Deref for Handle { + type Target = RawHandle; + fn deref(&self) -> &RawHandle { &self.0 } } -} -impl RawHandle { - pub fn new(handle: HANDLE) -> RawHandle { - RawHandle(handle) + impl Drop for Handle { + fn drop(&mut self) { + unsafe { let _ = CloseHandle(self.raw()); } + } } - pub fn raw(&self) -> HANDLE { self.0 } -} + impl RawHandle { + fn new(handle: HANDLE) -> RawHandle { + RawHandle(handle) + } -struct FindNextFileHandle(HANDLE); + fn raw(&self) -> HANDLE { self.0 } + } -fn get_path(f: &File) -> io::Result { - fill_utf16_buf(|buf, sz| unsafe { - GetFinalPathNameByHandleW(f.handle.raw(), buf, sz, - VOLUME_NAME_DOS) - }, |buf| { - PathBuf::from(OsString::from_wide(buf)) - }) -} + struct FindNextFileHandle(HANDLE); -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) = file.rename(tmpname.as_ref(), false) { - if err.kind() != io::ErrorKind::AlreadyExists { return Err(err) }; - tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter)); + fn get_path(f: &File) -> io::Result { + fill_utf16_buf(|buf, sz| unsafe { + GetFinalPathNameByHandleW(f.handle.raw(), buf, sz, + VOLUME_NAME_DOS) + }, |buf| { + PathBuf::from(OsString::from_wide(buf)) + }) + } + + 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) = file.rename(tmpname.as_ref(), false) { + if err.kind() != io::ErrorKind::AlreadyExists { return Err(err) }; + tmpname = ctx.base_dir.join(format!("rm-{}", ctx.counter)); + ctx.counter += 1; + } + Ok(()) } - Ok(()) -} -pub fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { - let mut opts = OpenOptions::new(); - opts.access_mode(FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES); - opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); - let file = try!(File::open(path, &opts)); - file.set_perm(perm) -} + fn set_perm(path: &Path, perm: FilePermissions) -> io::Result<()> { + let mut opts = OpenOptions::new(); + opts.access_mode(FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES); + opts.custom_flags(FILE_FLAG_BACKUP_SEMANTICS); + let file = try!(File::open(path, &opts)); + file.set_perm(perm) + } -pub const VOLUME_NAME_DOS: DWORD = 0x0; + const VOLUME_NAME_DOS: DWORD = 0x0; +}