use std::fs::{File, OpenOptions};
use std::io::{Error, Result};
use std::mem;
use std::os::windows::ffi::OsStrExt;
use std::os::windows::fs::OpenOptionsExt;
use std::os::windows::io::{AsRawHandle, FromRawHandle};
use std::path::Path;
use std::ptr;
use super::FsStats;
use winapi::ctypes::c_void;
use winapi::shared::minwindef::{BOOL, DWORD};
use winapi::shared::winerror::ERROR_LOCK_VIOLATION;
use winapi::um::fileapi::{GetDiskFreeSpaceW, FILE_ALLOCATION_INFO, FILE_STANDARD_INFO};
use winapi::um::fileapi::{GetVolumePathNameW, LockFileEx, SetFileInformationByHandle, UnlockFile};
use winapi::um::handleapi::DuplicateHandle;
use winapi::um::minwinbase::{FileAllocationInfo, FileStandardInfo};
use winapi::um::minwinbase::{LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY};
use winapi::um::processthreadsapi::GetCurrentProcess;
use winapi::um::winbase::{
GetFileInformationByHandleEx, FILE_FLAG_BACKUP_SEMANTICS, FILE_FLAG_DELETE_ON_CLOSE,
FILE_FLAG_FIRST_PIPE_INSTANCE, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OPEN_NO_RECALL,
FILE_FLAG_OPEN_REPARSE_POINT, FILE_FLAG_OPEN_REQUIRING_OPLOCK, FILE_FLAG_OVERLAPPED,
FILE_FLAG_POSIX_SEMANTICS, FILE_FLAG_RANDOM_ACCESS, FILE_FLAG_SEQUENTIAL_SCAN,
FILE_FLAG_SESSION_AWARE, FILE_FLAG_WRITE_THROUGH, SECURITY_IDENTIFICATION,
};
use winapi::um::winnt::{
DUPLICATE_SAME_ACCESS, FILE_ATTRIBUTE_ARCHIVE, FILE_ATTRIBUTE_COMPRESSED, FILE_ATTRIBUTE_DEVICE,
FILE_ATTRIBUTE_DIRECTORY, FILE_ATTRIBUTE_ENCRYPTED, FILE_ATTRIBUTE_HIDDEN,
FILE_ATTRIBUTE_INTEGRITY_STREAM, FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
FILE_ATTRIBUTE_NO_SCRUB_DATA, FILE_ATTRIBUTE_OFFLINE, FILE_ATTRIBUTE_PINNED,
FILE_ATTRIBUTE_READONLY, FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS, FILE_ATTRIBUTE_RECALL_ON_OPEN,
FILE_ATTRIBUTE_REPARSE_POINT, FILE_ATTRIBUTE_SPARSE_FILE, FILE_ATTRIBUTE_SYSTEM,
FILE_ATTRIBUTE_TEMPORARY, FILE_ATTRIBUTE_UNPINNED, FILE_ATTRIBUTE_VIRTUAL, FILE_SHARE_DELETE,
FILE_SHARE_READ, FILE_SHARE_WRITE,
};
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileAttribute {
Hidden = FILE_ATTRIBUTE_HIDDEN,
Compressed = FILE_ATTRIBUTE_COMPRESSED,
Archive = FILE_ATTRIBUTE_ARCHIVE,
Device = FILE_ATTRIBUTE_DEVICE,
Directory = FILE_ATTRIBUTE_DIRECTORY,
SparseFile = FILE_ATTRIBUTE_SPARSE_FILE,
Offline = FILE_ATTRIBUTE_OFFLINE,
Encrypted = FILE_ATTRIBUTE_ENCRYPTED,
IntegrityStream = FILE_ATTRIBUTE_INTEGRITY_STREAM,
Normal = FILE_ATTRIBUTE_NORMAL,
NotContentIndexed = FILE_ATTRIBUTE_NOT_CONTENT_INDEXED,
NoScrubData = FILE_ATTRIBUTE_NO_SCRUB_DATA,
Pinned = FILE_ATTRIBUTE_PINNED,
Readonly = FILE_ATTRIBUTE_READONLY,
RecallOnDataAccess = FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS,
RecallOnOpen = FILE_ATTRIBUTE_RECALL_ON_OPEN,
ReparsePoint = FILE_ATTRIBUTE_REPARSE_POINT,
System = FILE_ATTRIBUTE_SYSTEM,
Temporary = FILE_ATTRIBUTE_TEMPORARY,
UnPinned = FILE_ATTRIBUTE_UNPINNED,
Virtual = FILE_ATTRIBUTE_VIRTUAL,
Empty = 0,
}
#[repr(u32)]
#[derive(Debug, Clone,Copy)]
pub enum FileShare {
Read = FILE_SHARE_READ,
Write = FILE_SHARE_WRITE,
Delete = FILE_SHARE_DELETE,
ReadWrite = FILE_SHARE_READ | FILE_SHARE_WRITE,
All = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
Empty = 0,
}
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileAccess {
Empty = 0,
}
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileSecurity {
Identification = SECURITY_IDENTIFICATION,
Empty = 0,
}
#[repr(u32)]
#[derive(Debug, Clone)]
pub enum FileCustom {
WriteThrough = FILE_FLAG_WRITE_THROUGH,
SessionAware = FILE_FLAG_SESSION_AWARE,
SequentialScan = FILE_FLAG_SEQUENTIAL_SCAN,
RandomAccess = FILE_FLAG_RANDOM_ACCESS,
PosixSeantics = FILE_FLAG_POSIX_SEMANTICS,
Overlapped = FILE_FLAG_OVERLAPPED,
OpenRequiringOplock = FILE_FLAG_OPEN_REQUIRING_OPLOCK,
OpenReparsePoint = FILE_FLAG_OPEN_REPARSE_POINT,
OpenOnRecall = FILE_FLAG_OPEN_NO_RECALL,
NoBuffering = FILE_FLAG_NO_BUFFERING,
FirstPipeInstance = FILE_FLAG_FIRST_PIPE_INSTANCE,
DeleteOnClose = FILE_FLAG_DELETE_ON_CLOSE,
BackupSemantics = FILE_FLAG_BACKUP_SEMANTICS,
Empty = 0,
}
pub(crate) fn options_lock_share(option: &mut OpenOptions, mode: FileShare) -> &mut OpenOptions {
option.share_mode(mode as u32)
}
pub(crate) fn options_lock_access(
option: &mut OpenOptions,
access: FileAccess,
) -> &mut OpenOptions {
option.access_mode(access as u32)
}
pub(crate) fn options_attributes(
option: &mut OpenOptions,
attributes: impl IntoIterator<Item = FileAttribute>,
) -> &mut OpenOptions {
let mut sum = 0;
for v in attributes {
sum = sum | v as u32;
}
option.attributes(sum)
}
pub(crate) fn options_security_qos_flags(
option: &mut OpenOptions,
flags: impl IntoIterator<Item = FileSecurity>,
) -> &mut OpenOptions {
let mut sum = 0;
for v in flags {
sum = sum | v as u32;
}
option.security_qos_flags(sum)
}
pub(crate) fn options_custom_flags(
option: &mut OpenOptions,
flags: impl IntoIterator<Item = FileCustom>,
) -> &mut OpenOptions {
let mut sum = 0;
for v in flags {
sum = sum | v as u32;
}
option.custom_flags(sum)
}
pub(crate) 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() as *mut c_void,
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 as *mut std::ffi::c_void))
}
}
}
pub(crate) fn allocated_size(file: &File) -> Result<u64> {
unsafe {
let mut info: FILE_STANDARD_INFO = mem::zeroed();
let ret = GetFileInformationByHandleEx(
file.as_raw_handle() as *mut c_void,
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(crate) fn allocate(file: &File, len: u64) -> Result<()> {
if 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() as *mut c_void,
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 file.metadata()?.len() < len {
file.set_len(len)
} else {
Ok(())
}
}
pub(crate) fn lock_shared(file: &File) -> Result<()> {
lock_file(file, 0)
}
pub(crate) fn lock_exclusive(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK)
}
pub(crate) fn try_lock_shared(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_FAIL_IMMEDIATELY)
}
pub(crate) fn try_lock_exclusive(file: &File) -> Result<()> {
lock_file(file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY)
}
pub(crate) fn unlock(file: &File) -> Result<()> {
unsafe {
let ret = UnlockFile(file.as_raw_handle() as *mut c_void, 0, 0, !0, !0);
if ret == 0 {
Err(Error::last_os_error())
} else {
Ok(())
}
}
}
pub(crate) 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() as *mut c_void, 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(crate) fn statvfs(path: &Path) -> Result<FsStats> {
let root_path: &mut [u16] = &mut [0; 261];
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 {
use std::fs;
use std::os::windows::io::AsRawHandle;
use crate::fs::{options::{lock_contended_error, FileExt}, temp_file};
#[test]
fn duplicate_new_handle() {
let path = temp_file("fs2", "fs2", "").unwrap();
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());
}
#[test]
fn lock_duplicate_handle_independence() {
let path = temp_file("fs2", "fs2", "").unwrap();
let file1 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.unwrap();
let file2 = file1.duplicate().unwrap();
file1.lock_shared().unwrap();
assert_eq!(
file2.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error()
);
file1.unlock().unwrap();
file2.lock_exclusive().unwrap();
}
#[test]
fn lock_non_reentrant() {
let path = temp_file("fs2", "fs2", "").unwrap();
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.unwrap();
file.lock_exclusive().unwrap();
assert_eq!(
file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error()
);
file.unlock().unwrap();
file.lock_shared().unwrap();
assert_eq!(
file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error()
);
}
#[test]
fn lock_layering() {
let path = temp_file("fs2", "fs2", "").unwrap();
let file = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.unwrap();
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()
);
file.unlock().unwrap();
assert_eq!(
file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error()
);
file.unlock().unwrap();
assert_eq!(
file.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error()
);
file.unlock().unwrap();
file.lock_exclusive().unwrap();
}
#[test]
fn lock_layering_cleanup() {
let path = temp_file("fs2", "fs2", "").unwrap();
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();
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();
}
#[test]
fn lock_duplicate_cleanup() {
let path = temp_file("fs2", "fs2", "").unwrap();
let file1 = fs::OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open(&path)
.unwrap();
let file2 = file1.duplicate().unwrap();
file1.lock_shared().unwrap();
drop(file1);
assert_eq!(
file2.try_lock_exclusive().unwrap_err().raw_os_error(),
lock_contended_error().raw_os_error()
);
}
}