use super::{err, interface::FileInterface};
use crate::{error::FrozenResult, hints};
use std::{sync::atomic, time::Duration};
use windows_sys::Win32::{
Foundation::{
CloseHandle, ERROR_ACCESS_DENIED, ERROR_ALREADY_EXISTS, ERROR_BAD_PATHNAME,
ERROR_DISK_FULL, ERROR_FILE_EXISTS, ERROR_FILE_NOT_FOUND, ERROR_HANDLE_DISK_FULL,
ERROR_HANDLE_EOF, ERROR_INVALID_HANDLE, ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER,
ERROR_IO_DEVICE, ERROR_LOCK_VIOLATION, ERROR_NOT_SUPPORTED, ERROR_PATH_NOT_FOUND,
ERROR_SHARING_BUFFER_EXCEEDED, ERROR_SHARING_VIOLATION, GENERIC_READ, GENERIC_WRITE,
HANDLE, INVALID_HANDLE_VALUE,
},
Storage::FileSystem::{
CREATE_NEW, CreateFileW, DELETE, DeleteFileW, FILE_ALLOCATION_INFO, FILE_ATTRIBUTE_NORMAL,
FILE_DISPOSITION_FLAG_DELETE, FILE_DISPOSITION_FLAG_POSIX_SEMANTICS, FILE_DISPOSITION_INFO,
FILE_DISPOSITION_INFO_EX, FILE_END_OF_FILE_INFO, FILE_FLAG_BACKUP_SEMANTICS,
FILE_FLAG_RANDOM_ACCESS, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
FileAllocationInfo, FileDispositionInfo, FileDispositionInfoEx, FileEndOfFileInfo,
FlushFileBuffers, GetFileAttributesW, GetFileSizeEx, INVALID_FILE_ATTRIBUTES,
LOCKFILE_EXCLUSIVE_LOCK, LOCKFILE_FAIL_IMMEDIATELY, LockFileEx, OPEN_ALWAYS, OPEN_EXISTING,
ReadFile, SetFileInformationByHandle, WriteFile,
},
System::IO::{OVERLAPPED, OVERLAPPED_0, OVERLAPPED_0_0},
};
pub(super) type FileId = isize;
pub(in crate::file) const CLOSED_HANDLE: FileId = -1isize;
const MAX_RETRIES: usize = 0x80;
#[derive(Debug)]
pub(super) struct WINFile {
handle: atomic::AtomicIsize,
}
impl FileInterface for WINFile {
type Id = FileId;
const CLOSED_ID: Self::Id = CLOSED_HANDLE;
#[inline(always)]
fn fd(&self) -> FileId {
self.handle.load(atomic::Ordering::Acquire)
}
fn exists(path: &std::path::Path) -> FrozenResult<bool> {
let wide = path_to_wide(path)?;
let attrs = unsafe { GetFileAttributesW(wide.as_ptr()) };
if attrs != INVALID_FILE_ATTRIBUTES {
return Ok(true);
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => Ok(false),
ERROR_ACCESS_DENIED => err::raw_error(err::PRM, err_msg),
ERROR_INVALID_NAME | ERROR_BAD_PATHNAME => err::raw_error(err::INV, err_msg),
_ => err::raw_error(err::UNK, err_msg),
}
}
fn create(path: &std::path::Path) -> FrozenResult<Self> {
let handle = create_file_raw(path, CREATE_NEW)?;
let file = Self { handle: atomic::AtomicIsize::new(handle) };
if let Err(mut e) = sync_parent_dir(path) {
if let Err(close_err) = file.close() {
e.add_suppressed(close_err);
}
return Err(e);
}
Ok(file)
}
fn open(path: &std::path::Path) -> FrozenResult<Self> {
let handle = create_file_raw(path, OPEN_EXISTING)?;
Ok(Self { handle: atomic::AtomicIsize::new(handle) })
}
fn new(path: &std::path::Path) -> FrozenResult<Self> {
let handle = create_file_raw(path, OPEN_ALWAYS)?;
let file = Self { handle: atomic::AtomicIsize::new(handle) };
if let Err(mut e) = sync_parent_dir(path) {
if let Err(close_err) = file.close() {
e.add_suppressed(close_err);
}
return Err(e);
}
Ok(file)
}
fn flock(&self) -> FrozenResult<()> {
lock_raw(self.fd() as usize as HANDLE)
}
fn close(self) -> FrozenResult<()> {
let h = self.handle.swap(CLOSED_HANDLE, atomic::Ordering::AcqRel);
if h == CLOSED_HANDLE {
return Ok(());
}
close_raw(h as usize as HANDLE)
}
fn unlink(self, path: &std::path::Path) -> FrozenResult<()> {
let h = self.handle.swap(CLOSED_HANDLE, atomic::Ordering::AcqRel);
if h == CLOSED_HANDLE {
return err::raw_error(err::INV, "file handle is already closed");
}
let handle = h as usize as HANDLE;
let disposition_res = set_disposition_raw(handle);
let close_res = close_raw(handle);
match disposition_res {
Ok(()) => {
close_res?;
sync_parent_dir(path)
}
Err(e) => {
if e.reason == err::INV.reason || e.reason == err::UNK.reason {
let wide = path_to_wide(path)?;
let ok = unsafe { DeleteFileW(wide.as_ptr()) };
if ok != 0 {
let _ = close_res;
return sync_parent_dir(path);
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => {
err::raw_error(err::INV, err_msg)
}
ERROR_SHARING_VIOLATION | ERROR_LOCK_VIOLATION => {
err::raw_error(err::LCK, err_msg)
}
ERROR_ACCESS_DENIED => err::raw_error(err::PRM, err_msg),
ERROR_INVALID_NAME | ERROR_BAD_PATHNAME => {
err::raw_error(err::INV, err_msg)
}
_ => err::raw_error(err::UNK, err_msg),
}
} else {
let mut err = e;
if let Err(c_err) = close_res {
err.add_suppressed(c_err);
}
Err(err)
}
}
}
}
fn length(&self) -> FrozenResult<usize> {
get_size_raw(self.fd() as usize as HANDLE)
}
#[inline(always)]
fn grow(&self, curr_len: usize, len_to_add: usize) -> FrozenResult<()> {
if len_to_add == 0 {
return Ok(());
}
let new_len = match curr_len.checked_add(len_to_add) {
Some(len) => match i64::try_from(len) {
Ok(l) if l >= 0 => l,
_ => {
return err::raw_error(err::GRW, "target file size exceeds i64 capacity");
}
},
None => {
return err::raw_error(err::GRW, "file growth calculation overflowed usize");
}
};
let h = self.fd() as usize as HANDLE;
let alloc_res = set_alloc_raw(h, new_len);
if let Err(ref e) = alloc_res {
if e.reason == err::NSP.reason {
return alloc_res;
}
}
set_eof_raw(h, new_len)
}
fn sync(&self) -> FrozenResult<()> {
flush_raw(self.fd() as usize as HANDLE)
}
#[inline(always)]
fn pread(&self, buf: &mut [u8], offset: usize) -> FrozenResult<()> {
if buf.is_empty() {
return Ok(());
}
let h = self.fd() as usize as HANDLE;
let mut read = 0usize;
let mut retries = 0usize;
while read < buf.len() {
let cur_offset = match offset.checked_add(read) {
Some(off) => off,
None => return err::raw_error(err::INV, "read offset overflow"),
};
let off_lo = cur_offset as u32;
let off_hi = (cur_offset >> 0x20) as u32;
let to_read = (buf.len() - read).min(u32::MAX as usize) as u32;
let mut overlapped = overlapped_at(off_lo, off_hi);
let mut bytes_read: u32 = 0;
let ok = unsafe {
ReadFile(h, buf[read..].as_mut_ptr(), to_read, &mut bytes_read, &mut overlapped)
};
if ok != 0 {
if hints::unlikely(bytes_read == 0) {
return err::default_error(err::HCF);
}
read += bytes_read as usize;
retries = 0;
continue;
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_HANDLE_EOF => return err::default_error(err::HCF),
ERROR_SHARING_VIOLATION | ERROR_LOCK_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return err::raw_error(err::UNK, err_msg);
}
ERROR_ACCESS_DENIED => return err::raw_error(err::RED, err_msg),
ERROR_INVALID_HANDLE => return err::raw_error(err::HCF, err_msg),
ERROR_IO_DEVICE => return err::raw_error(err::HCF, err_msg),
_ => return err::raw_error(err::UNK, err_msg),
}
}
Ok(())
}
#[inline(always)]
fn pwrite(&self, buf: &[u8], offset: usize) -> FrozenResult<()> {
if buf.is_empty() {
return Ok(());
}
let h = self.fd() as usize as HANDLE;
let mut written = 0usize;
let mut retries = 0usize;
while written < buf.len() {
let cur_offset = match offset.checked_add(written) {
Some(off) => off,
None => return err::raw_error(err::INV, "write offset overflow"),
};
let off_lo = cur_offset as u32;
let off_hi = (cur_offset >> 0x20) as u32;
let to_write = (buf.len() - written).min(u32::MAX as usize) as u32;
let mut overlapped = overlapped_at(off_lo, off_hi);
let mut bytes_written: u32 = 0;
let ok = unsafe {
WriteFile(h, buf[written..].as_ptr(), to_write, &mut bytes_written, &mut overlapped)
};
if ok != 0 {
if bytes_written == 0 {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return err::default_error(err::HCF);
}
written += bytes_written as usize;
retries = 0;
continue;
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_SHARING_VIOLATION | ERROR_LOCK_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return err::raw_error(err::UNK, err_msg);
}
ERROR_ACCESS_DENIED => return err::raw_error(err::WRT, err_msg),
ERROR_HANDLE_DISK_FULL | ERROR_DISK_FULL => {
return err::raw_error(err::NSP, err_msg);
}
ERROR_INVALID_HANDLE => return err::raw_error(err::HCF, err_msg),
ERROR_IO_DEVICE => return err::raw_error(err::HCF, err_msg),
_ => return err::raw_error(err::UNK, err_msg),
}
}
Ok(())
}
}
impl Drop for WINFile {
fn drop(&mut self) {
let h = self.handle.swap(CLOSED_HANDLE, atomic::Ordering::AcqRel);
if h != CLOSED_HANDLE {
let _ = close_raw(h as usize as HANDLE);
}
}
}
fn create_file_raw(path: &std::path::Path, disposition: u32) -> FrozenResult<FileId> {
let wide = path_to_wide(path)?;
let mut retries = 0; loop {
let h = unsafe {
CreateFileW(
wide.as_ptr(),
GENERIC_READ | GENERIC_WRITE | DELETE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
core::ptr::null(),
disposition,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_RANDOM_ACCESS,
core::ptr::null_mut(),
)
};
if h != INVALID_HANDLE_VALUE {
return Ok(h as isize);
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_SHARING_VIOLATION => {
if retries < MAX_RETRIES {
retries += 1;
std::thread::sleep(Duration::from_millis(1));
continue;
}
return err::raw_error(err::LCK, err_msg);
}
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => {
return err::raw_error(err::INV, err_msg);
}
ERROR_FILE_EXISTS | ERROR_ALREADY_EXISTS => {
return err::raw_error(err::EXS, err_msg);
}
ERROR_ACCESS_DENIED => return err::raw_error(err::PRM, err_msg),
ERROR_INVALID_NAME | ERROR_BAD_PATHNAME => {
return err::raw_error(err::INV, err_msg);
}
ERROR_HANDLE_DISK_FULL | ERROR_DISK_FULL => {
return err::raw_error(err::NSP, err_msg);
}
_ => return err::raw_error(err::UNK, err_msg),
}
}
}
fn close_raw(handle: HANDLE) -> FrozenResult<()> {
let ok = unsafe { CloseHandle(handle) };
if ok != 0 {
return Ok(());
}
let code = last_error();
let err_msg = err_msg(code);
if code == ERROR_INVALID_HANDLE {
return err::raw_error(err::HCF, err_msg);
}
err::raw_error(err::UNK, err_msg)
}
fn flush_raw(handle: HANDLE) -> FrozenResult<()> {
let ok = unsafe { FlushFileBuffers(handle) };
if ok != 0 {
return Ok(());
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_INVALID_HANDLE => err::raw_error(err::HCF, err_msg),
ERROR_IO_DEVICE => err::raw_error(err::SYN, err_msg),
ERROR_ACCESS_DENIED => err::raw_error(err::PRM, err_msg),
_ => err::raw_error(err::SYN, err_msg),
}
}
fn lock_raw(handle: HANDLE) -> FrozenResult<()> {
let mut overlapped = overlapped_at(0, 0);
let ok = unsafe {
LockFileEx(
handle,
LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY,
0,
0xFFFF_FFFF, 0xFFFF_FFFF, &mut overlapped,
)
};
if ok != 0 {
return Ok(());
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_LOCK_VIOLATION | ERROR_SHARING_VIOLATION => err::raw_error(err::LCK, err_msg),
ERROR_SHARING_BUFFER_EXCEEDED => err::raw_error(err::LEX, err_msg),
ERROR_INVALID_HANDLE => err::raw_error(err::HCF, err_msg),
_ => err::raw_error(err::UNK, err_msg),
}
}
fn set_eof_raw(handle: HANDLE, new_len: i64) -> FrozenResult<()> {
let info = FILE_END_OF_FILE_INFO { EndOfFile: new_len };
let ok = unsafe {
SetFileInformationByHandle(
handle,
FileEndOfFileInfo,
&info as *const _ as *const core::ffi::c_void,
core::mem::size_of::<FILE_END_OF_FILE_INFO>() as u32,
)
};
if ok != 0 {
return Ok(());
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_HANDLE_DISK_FULL | ERROR_DISK_FULL => err::raw_error(err::NSP, err_msg),
ERROR_INVALID_HANDLE => err::raw_error(err::HCF, err_msg),
ERROR_ACCESS_DENIED => err::raw_error(err::PRM, err_msg),
_ => err::raw_error(err::GRW, err_msg),
}
}
fn set_alloc_raw(handle: HANDLE, new_len: i64) -> FrozenResult<()> {
let info = FILE_ALLOCATION_INFO { AllocationSize: new_len };
let ok = unsafe {
SetFileInformationByHandle(
handle,
FileAllocationInfo,
&info as *const _ as *const core::ffi::c_void,
core::mem::size_of::<FILE_ALLOCATION_INFO>() as u32,
)
};
if ok != 0 {
return Ok(());
}
let code = last_error();
let err_msg = err_msg(code);
match code {
ERROR_NOT_SUPPORTED | ERROR_INVALID_PARAMETER => Ok(()),
ERROR_HANDLE_DISK_FULL | ERROR_DISK_FULL => err::raw_error(err::NSP, err_msg),
ERROR_INVALID_HANDLE => err::raw_error(err::HCF, err_msg),
ERROR_ACCESS_DENIED => err::raw_error(err::PRM, err_msg),
_ => err::raw_error(err::GRW, err_msg),
}
}
fn set_disposition_raw(handle: HANDLE) -> FrozenResult<()> {
let info_ex = FILE_DISPOSITION_INFO_EX {
Flags: FILE_DISPOSITION_FLAG_DELETE | FILE_DISPOSITION_FLAG_POSIX_SEMANTICS,
};
let ok = unsafe {
SetFileInformationByHandle(
handle,
FileDispositionInfoEx,
&info_ex as *const _ as *const core::ffi::c_void,
core::mem::size_of::<FILE_DISPOSITION_INFO_EX>() as u32,
)
};
if ok != 0 {
return Ok(());
}
let code = last_error();
if code == ERROR_NOT_SUPPORTED || code == ERROR_INVALID_PARAMETER {
let info = FILE_DISPOSITION_INFO { DeleteFile: 1 };
let ok_classic = unsafe {
SetFileInformationByHandle(
handle,
FileDispositionInfo,
&info as *const _ as *const core::ffi::c_void,
core::mem::size_of::<FILE_DISPOSITION_INFO>() as u32,
)
};
if ok_classic != 0 {
return Ok(());
}
let code_classic = last_error();
let err_msg = err_msg(code_classic);
return match code_classic {
ERROR_ACCESS_DENIED => err::raw_error(err::PRM, err_msg),
ERROR_INVALID_HANDLE => err::raw_error(err::HCF, err_msg),
ERROR_SHARING_VIOLATION | ERROR_LOCK_VIOLATION => err::raw_error(err::LCK, err_msg),
ERROR_NOT_SUPPORTED | ERROR_INVALID_PARAMETER => err::raw_error(err::INV, err_msg),
_ => err::raw_error(err::UNK, err_msg),
};
}
let err_msg = err_msg(code);
match code {
ERROR_ACCESS_DENIED => err::raw_error(err::PRM, err_msg),
ERROR_INVALID_HANDLE => err::raw_error(err::HCF, err_msg),
ERROR_SHARING_VIOLATION | ERROR_LOCK_VIOLATION => err::raw_error(err::LCK, err_msg),
_ => err::raw_error(err::UNK, err_msg),
}
}
fn get_size_raw(handle: HANDLE) -> FrozenResult<usize> {
let mut size: i64 = 0;
let ok = unsafe { GetFileSizeEx(handle, &mut size) };
if ok == 0 {
let code = last_error();
let err_msg = err_msg(code);
if code == ERROR_INVALID_HANDLE {
return err::raw_error(err::HCF, err_msg);
}
return err::raw_error(err::UNK, err_msg);
}
if hints::unlikely(size < 0) {
return err::raw_error(err::HCF, "filesystem reported negative file size");
}
match usize::try_from(size) {
Ok(sz) => Ok(sz),
Err(_) => err::raw_error(err::HCF, "file size exceeds usize address space"),
}
}
fn sync_parent_dir(path: &std::path::Path) -> FrozenResult<()> {
let parent = extract_parent_dir(path);
let wide = match path_to_wide(&parent) {
Ok(w) => w,
Err(_) => return Ok(()), };
let h = unsafe {
CreateFileW(
wide.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if h == INVALID_HANDLE_VALUE {
return Ok(());
}
let _ = flush_raw(h);
let _ = close_raw(h);
Ok(())
}
fn path_to_wide(path: &std::path::Path) -> FrozenResult<Vec<u16>> {
use std::os::windows::ffi::OsStrExt;
let mut wide: Vec<u16> = path.as_os_str().encode_wide().collect();
if wide.contains(&0u16) {
return err::raw_error(err::INV, "path contains embedded NUL character");
}
wide.push(0); Ok(wide)
}
#[inline]
fn last_error() -> u32 {
unsafe { windows_sys::Win32::Foundation::GetLastError() }
}
#[inline]
fn err_msg(code: u32) -> String {
std::io::Error::from_raw_os_error(code as i32).to_string()
}
fn extract_parent_dir(path: &std::path::Path) -> std::path::PathBuf {
match path.parent() {
Some(p) if !p.as_os_str().is_empty() => p.to_path_buf(),
_ => std::path::Path::new(".").to_path_buf(),
}
}
#[inline(always)]
fn overlapped_at(offset_lo: u32, offset_hi: u32) -> OVERLAPPED {
OVERLAPPED {
Internal: 0,
InternalHigh: 0,
Anonymous: OVERLAPPED_0 {
Anonymous: OVERLAPPED_0_0 { Offset: offset_lo, OffsetHigh: offset_hi },
},
hEvent: core::ptr::null_mut(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::{path::PathBuf, sync::Arc};
fn tmp_path() -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tmp_file");
(dir, path)
}
mod file_new_close {
use super::*;
#[test]
fn ok_new_close_cycle() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
assert!(path.exists());
file.close().unwrap();
}
#[test]
fn ok_new_close_cycle_on_existing() {
let (_dir, path) = tmp_path();
let file1 = WINFile::new(&path).unwrap();
file1.close().unwrap();
let file2 = WINFile::new(&path).unwrap();
file2.close().unwrap();
}
#[test]
fn err_new_on_missing_parent_dir() {
let (_dir, path) = tmp_path();
let missing = path.join("missing\\sub\\dir\\file");
let err = WINFile::new(&missing).unwrap_err();
assert_eq!(err.reason, err::INV.reason);
}
}
mod file_create_open {
use super::*;
#[test]
fn ok_create_close_cycle() {
let (_dir, path) = tmp_path();
let file = WINFile::create(&path).unwrap();
assert!(path.exists());
file.close().unwrap();
}
#[test]
fn err_create_when_already_exists() {
let (_dir, path) = tmp_path();
let file = WINFile::create(&path).unwrap();
let err = WINFile::create(&path).unwrap_err();
assert_eq!(err.reason, err::EXS.reason);
file.close().unwrap();
}
#[test]
fn err_create_on_missing_parent_dir() {
let (_dir, path) = tmp_path();
let missing = path.join("missing\\sub\\file");
let err = WINFile::create(&missing).unwrap_err();
assert_eq!(err.reason, err::INV.reason);
}
#[test]
fn ok_open_existing_file() {
let (_dir, path) = tmp_path();
let file = WINFile::create(&path).unwrap();
file.close().unwrap();
let opened = WINFile::open(&path).unwrap();
opened.close().unwrap();
}
#[test]
fn err_open_on_missing_file() {
let (_dir, path) = tmp_path();
let err = WINFile::open(&path).unwrap_err();
assert_eq!(err.reason, err::INV.reason);
}
}
mod file_unlink {
use super::*;
#[test]
fn ok_unlink_existing() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
assert!(path.exists());
file.unlink(&path).unwrap();
assert!(!path.exists());
}
#[test]
fn ok_unlink_while_locked_and_immediate_recreate() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.flock().unwrap();
file.unlink(&path).unwrap();
assert!(!path.exists());
let new_file = WINFile::create(&path).unwrap();
assert!(path.exists());
new_file.close().unwrap();
}
#[test]
fn err_open_after_unlink_grown_file() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0x2000).unwrap();
assert_eq!(file.length().unwrap(), 0x2000);
file.unlink(&path).unwrap();
assert!(!path.exists());
let err = WINFile::open(&path).unwrap_err();
assert_eq!(err.reason, err::INV.reason);
}
#[test]
fn ok_unlink_concurrent_reader_handles() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
let reader = WINFile::open(&path).unwrap();
file.unlink(&path).unwrap();
reader.close().unwrap();
assert!(!path.exists());
}
#[test]
fn err_concurrent_open_during_and_after_unlink() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0x1000).unwrap();
let running = Arc::new(atomic::AtomicBool::new(true));
let opener = {
let path = path.clone();
let running = running.clone();
std::thread::spawn(move || {
let mut attempts = 0;
while running.load(atomic::Ordering::Relaxed) || attempts < 0x32 {
attempts += 1;
let res = WINFile::open(&path);
if let Err(e) = res {
assert!(
e.reason == err::INV.reason || e.reason == err::PRM.reason,
"Unexpected error reason: {:?}",
e
);
}
std::thread::yield_now();
}
})
};
std::thread::sleep(std::time::Duration::from_millis(5));
file.unlink(&path).unwrap();
running.store(false, atomic::Ordering::Relaxed);
opener.join().unwrap();
let err = WINFile::open(&path).unwrap_err();
assert_eq!(err.reason, err::INV.reason);
}
#[test]
fn ok_concurrent_new_fails_before_unlink_and_succeeds_after() {
let (_dir, path) = tmp_path();
let file = WINFile::create(&path).unwrap();
file.grow(0, 0x1000).unwrap();
let unlinked = Arc::new(atomic::AtomicBool::new(false));
let creator = {
let path = path.clone();
let unlinked = unlinked.clone();
std::thread::spawn(move || {
let mut created = None;
while created.is_none() {
match WINFile::create(&path) {
Ok(f) => {
assert!(
unlinked.load(atomic::Ordering::Acquire),
"WINFile::create succeeded before file.unlink completed!"
);
created = Some(f);
}
Err(e) => {
assert!(
e.reason == err::EXS.reason || e.reason == err::PRM.reason,
"Unexpected error reason during race: {:?}",
e
);
std::thread::yield_now();
}
}
}
created.unwrap()
})
};
std::thread::sleep(std::time::Duration::from_millis(0x0A));
unlinked.store(true, atomic::Ordering::Release);
file.unlink(&path).unwrap();
let new_file = creator.join().unwrap();
assert!(WINFile::exists(&path).unwrap());
new_file.close().unwrap();
}
#[test]
fn err_unlink_missing() {
let (_dir, path) = tmp_path();
let file = WINFile::create(&path).unwrap();
let missing_path = path.with_file_name("definitely_does_not_exist.db");
let inner_file = WINFile { handle: atomic::AtomicIsize::new(CLOSED_HANDLE) };
let err = inner_file.unlink(&missing_path).unwrap_err();
assert_eq!(err.reason, err::INV.reason);
file.close().unwrap();
}
}
mod file_lock {
use super::*;
#[test]
fn ok_flock_acquires_exclusive_lock() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.flock().unwrap();
file.close().unwrap();
}
#[test]
fn err_flock_when_already_locked() {
let (_dir, path) = tmp_path();
let file1 = WINFile::new(&path).unwrap();
file1.flock().unwrap();
let file2 = WINFile::new(&path).unwrap();
let err = file2.flock().unwrap_err();
assert_eq!(err.reason, err::LCK.reason);
file1.close().unwrap();
file2.close().unwrap();
}
#[test]
fn ok_flock_released_after_close() {
let (_dir, path) = tmp_path();
let file1 = WINFile::new(&path).unwrap();
file1.flock().unwrap();
file1.close().unwrap();
let file2 = WINFile::new(&path).unwrap();
file2.flock().unwrap();
file2.close().unwrap();
}
}
mod file_grow {
use super::*;
#[test]
fn ok_grow() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
let initial = file.length().unwrap();
assert_eq!(initial, 0);
file.grow(0, 0x1000).unwrap();
let new_len = file.length().unwrap();
assert_eq!(new_len, 0x1000);
file.close().unwrap();
}
#[test]
fn ok_grow_extends_with_zero() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0x500).unwrap();
let mut buf = vec![0u8; 0x500];
file.pread(&mut buf, 0).unwrap();
assert!(buf.iter().all(|b| *b == 0));
file.close().unwrap();
}
#[test]
fn ok_grow_zero_noop() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0).unwrap();
assert_eq!(file.length().unwrap(), 0);
file.close().unwrap();
}
#[test]
fn ok_grow_multiple_times() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0x1000).unwrap();
file.grow(0x1000, 0x2000).unwrap();
assert_eq!(file.length().unwrap(), 0x3000);
file.close().unwrap();
}
}
mod file_sync {
use super::*;
#[test]
fn ok_sync() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0x1000).unwrap();
file.sync().unwrap();
file.close().unwrap();
}
}
mod file_pread_pwrite {
use super::*;
#[test]
fn ok_pwrite_and_pread() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0x1000).unwrap();
let data = [0xABu8; 0x100];
file.pwrite(&data, 0x200).unwrap();
let mut buf = [0u8; 0x100];
file.pread(&mut buf, 0x200).unwrap();
assert_eq!(buf, data);
file.close().unwrap();
}
#[test]
fn ok_pwrite_empty_noop() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.pwrite(&[], 0).unwrap();
file.close().unwrap();
}
#[test]
fn ok_pread_empty_noop() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.pread(&mut [], 0).unwrap();
file.close().unwrap();
}
#[test]
fn ok_pwrite_at_multiple_offsets() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.grow(0, 0x1000).unwrap();
file.pwrite(&[0x11u8; 0x100], 0x000).unwrap();
file.pwrite(&[0x22u8; 0x100], 0x100).unwrap();
file.pwrite(&[0x33u8; 0x100], 0x200).unwrap();
let mut a = [0u8; 0x100];
let mut b = [0u8; 0x100];
let mut c = [0u8; 0x100];
file.pread(&mut a, 0x000).unwrap();
file.pread(&mut b, 0x100).unwrap();
file.pread(&mut c, 0x200).unwrap();
assert_eq!(a, [0x11u8; 0x100]);
assert_eq!(b, [0x22u8; 0x100]);
assert_eq!(c, [0x33u8; 0x100]);
file.close().unwrap();
}
}
mod file_exists {
use super::*;
#[test]
fn ok_exists_true_for_existing_file() {
let (_dir, path) = tmp_path();
let file = WINFile::create(&path).unwrap();
file.close().unwrap();
assert!(WINFile::exists(&path).unwrap());
}
#[test]
fn ok_exists_false_for_missing_file() {
let (_dir, path) = tmp_path();
assert!(!WINFile::exists(&path).unwrap());
}
}
mod file_fd {
use super::*;
#[test]
fn ok_fd_not_closed_after_open() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
assert_ne!(file.fd(), CLOSED_HANDLE);
assert!(!file.is_closed());
file.close().unwrap();
}
#[test]
fn ok_is_closed_after_close() {
let (_dir, path) = tmp_path();
let file = WINFile::new(&path).unwrap();
file.close().unwrap();
}
}
}