blob: 0e37c66579c0ca7eb4c8138f65d309285e438b4a [file] [log] [blame]
use std::fs::File;
use std::io::{Error, Result};
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::io::{AsRawHandle, FromRawHandle};
use std::path::Path;
use std::ptr;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::winerror::ERROR_LOCK_VIOLATION;
use winapi::um::fileapi::{FILE_ALLOCATION_INFO, FILE_STANDARD_INFO, GetDiskFreeSpaceW};
use winapi::um::fileapi::{GetVolumePathNameW, LockFileEx, UnlockFile, SetFileInformationByHandle};
use winapi::um::handleapi::DuplicateHandle;
use winapi::um::minwinbase::{FileAllocationInfo, FileStandardInfo};
use winapi::um::minwinbase::{LOCKFILE_FAIL_IMMEDIATELY, LOCKFILE_EXCLUSIVE_LOCK};
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::winbase::GetFileInformationByHandleEx;
use winapi::um::winnt::DUPLICATE_SAME_ACCESS;
use FsStats;
pub fn duplicate(file: &File) -> Result<File> {
unsafe {
let mut handle = ptr::null_mut();
let current_process = GetCurrentProcess();
let ret = DuplicateHandle(current_process,
file.as_raw_handle(),
current_process,
&mut handle,
0,
true as BOOL,
DUPLICATE_SAME_ACCESS);
if ret == 0 {
Err(Error::last_os_error())
} else {
Ok(File::from_raw_handle(handle))
}
}
}
pub fn allocated_size(file: &File) -> Result<u64> {
unsafe {
let mut info: FILE_STANDARD_INFO = mem::zeroed();
let ret = GetFileInformationByHandleEx(
file.as_raw_handle(),
FileStandardInfo,
&mut info as *mut _ as *mut _,
mem::size_of::<FILE_STANDARD_INFO>() as DWORD);
if ret == 0 {
Err(Error::last_os_error())
} else {
Ok(*info.AllocationSize.QuadPart() as u64)
}
}
}
pub fn allocate(file: &File, len: u64) -> Result<()> {
if try!(allocated_size(file)) < len {
unsafe {
let mut info: FILE_ALLOCATION_INFO = mem::zeroed();
*info.AllocationSize.QuadPart_mut() = len as i64;
let ret = SetFileInformationByHandle(
file.as_raw_handle(),
FileAllocationInfo,
&mut info as *mut _ as *mut _,
mem::size_of::<FILE_ALLOCATION_INFO>() as DWORD);
if ret == 0 {
return Err(Error::last_os_error());
}
}
}
if try!(file.metadata()).len() < len {
file.set_len(len)
} else {
Ok(())
}
}
pub fn lock_shared(file: &File) -> Result<()> {
lock_file(file, 0)
}
pub fn lock_exclusive(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
}
pub fn try_lock_shared(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)
}
pub fn try_lock_exclusive(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
}
pub fn unlock(file: &File) -> Result<()> {
unsafe {
let ret = UnlockFile(file.as_raw_handle(), 0, 0, !0, !0);
if ret == 0 { Err(Error::last_os_error()) } else { Ok(()) }
}
}
pub fn lock_error() -> Error {
Error::from_raw_os_error(ERROR_LOCK_VIOLATION as i32)
}
fn lock_file(file: &File, flags: DWORD) -> Result<()> {
unsafe {
let mut overlapped = mem::zeroed();
let ret = LockFileEx(file.as_raw_handle(), flags, 0, !0, !0, &mut overlapped);
if ret == 0 { Err(Error::last_os_error()) } else { Ok(()) }
}
}
fn volume_path(path: &Path, volume_path: &mut [u16]) -> Result<()> {
let path_utf8: Vec<u16> = path.as_os_str().encode_wide().chain(Some(0)).collect();
unsafe {
let ret = GetVolumePathNameW(path_utf8.as_ptr(),
volume_path.as_mut_ptr(),
volume_path.len() as DWORD);
if ret == 0 { Err(Error::last_os_error()) } else { Ok(())
}
}
}
pub fn statvfs(path: &Path) -> Result<FsStats> {
let root_path: &mut [u16] = &mut [0; 261];
try!(volume_path(path, root_path));
unsafe {
let mut sectors_per_cluster = 0;
let mut bytes_per_sector = 0;
let mut number_of_free_clusters = 0;
let mut total_number_of_clusters = 0;
let ret = GetDiskFreeSpaceW(root_path.as_ptr(),
&mut sectors_per_cluster,
&mut bytes_per_sector,
&mut number_of_free_clusters,
&mut total_number_of_clusters);
if ret == 0 {
Err(Error::last_os_error())
} else {
let bytes_per_cluster = sectors_per_cluster as u64 * bytes_per_sector as u64;
let free_space = bytes_per_cluster * number_of_free_clusters as u64;
let total_space = bytes_per_cluster * total_number_of_clusters as u64;
Ok(FsStats {
free_space: free_space,
available_space: free_space,
total_space: total_space,
allocation_granularity: bytes_per_cluster,
})
}
}
}
#[cfg(test)]
mod test {
extern crate tempdir;
use std::fs;
use std::os::windows::io::AsRawHandle;
use {FileExt, lock_contended_error};
/// The duplicate method returns a file with a new file handle.
#[test]
fn duplicate_new_handle() {
let tempdir = tempdir::TempDir::new("fs2").unwrap();
let path = tempdir.path().join("fs2");
let file1 = fs::OpenOptions::new().write(true).create(true).open(&path).unwrap();
let file2 = file1.duplicate().unwrap();
assert!(file1.as_raw_handle() != file2.as_raw_handle());
}
/// A duplicated file handle does not have access to the original handle's locks.
#[test]
fn lock_duplicate_handle_independence() {
let tempdir = tempdir::TempDir::new("fs2").unwrap();
let path = tempdir.path().join("fs2");
let file1 = fs::OpenOptions::new().read(true).write(true).create(true).open(&path).unwrap();
let file2 = file1.duplicate().unwrap();
// Locking the original file handle will block the duplicate file handle from opening a lock.
file1.lock_shared().unwrap();
assert_eq!(file2.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
// Once the original file handle is unlocked, the duplicate handle can proceed with a lock.
file1.unlock().unwrap();
file2.lock_exclusive().unwrap();
}
/// A file handle may not be exclusively locked multiple times, or exclusively locked and then
/// shared locked.
#[test]
fn lock_non_reentrant() {
let tempdir = tempdir::TempDir::new("fs2").unwrap();
let path = tempdir.path().join("fs2");
let file = fs::OpenOptions::new().read(true).write(true).create(true).open(&path).unwrap();
// Multiple exclusive locks fails.
file.lock_exclusive().unwrap();
assert_eq!(file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
file.unlock().unwrap();
// Shared then Exclusive locks fails.
file.lock_shared().unwrap();
assert_eq!(file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
}
/// A file handle can hold an exclusive lock and any number of shared locks, all of which must
/// be unlocked independently.
#[test]
fn lock_layering() {
let tempdir = tempdir::TempDir::new("fs2").unwrap();
let path = tempdir.path().join("fs2");
let file = fs::OpenOptions::new().read(true).write(true).create(true).open(&path).unwrap();
// Open two shared locks on the file, and then try and fail to open an exclusive lock.
file.lock_exclusive().unwrap();
file.lock_shared().unwrap();
file.lock_shared().unwrap();
assert_eq!(file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
// Pop one of the shared locks and try again.
file.unlock().unwrap();
assert_eq!(file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
// Pop the second shared lock and try again.
file.unlock().unwrap();
assert_eq!(file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
// Pop the exclusive lock and finally succeed.
file.unlock().unwrap();
file.lock_exclusive().unwrap();
}
/// A file handle with multiple open locks will have all locks closed on drop.
#[test]
fn lock_layering_cleanup() {
let tempdir = tempdir::TempDir::new("fs2").unwrap();
let path = tempdir.path().join("fs2");
let file1 = fs::OpenOptions::new().read(true).write(true).create(true).open(&path).unwrap();
let file2 = fs::OpenOptions::new().read(true).write(true).create(true).open(&path).unwrap();
// Open two shared locks on the file, and then try and fail to open an exclusive lock.
file1.lock_shared().unwrap();
assert_eq!(file2.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
drop(file1);
file2.lock_exclusive().unwrap();
}
/// A file handle's locks will not be released until the original handle and all of its
/// duplicates have been closed. This on really smells like a bug in Windows.
#[test]
fn lock_duplicate_cleanup() {
let tempdir = tempdir::TempDir::new("fs2").unwrap();
let path = tempdir.path().join("fs2");
let file1 = fs::OpenOptions::new().read(true).write(true).create(true).open(&path).unwrap();
let file2 = file1.duplicate().unwrap();
// Open a lock on the original handle, then close it.
file1.lock_shared().unwrap();
drop(file1);
// Attempting to create a lock on the file with the duplicate handle will fail.
assert_eq!(file2.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error());
}
}