use std::{ffi::CString, io, ptr};
use windows_sys::Win32::{
Foundation::{
CloseHandle, GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE,
},
Storage::FileSystem::{
CreateFileA, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED, FILE_FLAG_RANDOM_ACCESS,
FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
},
};
use super::DWORD;
pub const FILE_ATTRIBUTE_READONLY: DWORD = 0x00000001;
pub enum AccessMode {
Read,
Write,
ReadWrite,
}
pub enum ShareMode {
None,
Read,
Write,
Delete,
}
pub struct FileHandle {
pub handle: HANDLE,
}
unsafe impl Send for FileHandle {}
unsafe impl Sync for FileHandle {}
impl FileHandle {
pub unsafe fn new(
file_name: &str,
access_mode: AccessMode,
share_mode: ShareMode,
) -> io::Result<Self> {
let file_name_c = CString::new(file_name).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("Invalid file name. {}", file_name),
)
})?;
let dw_desired_access = match access_mode {
AccessMode::Read => GENERIC_READ,
AccessMode::Write => GENERIC_WRITE,
AccessMode::ReadWrite => GENERIC_READ | GENERIC_WRITE,
};
let dw_share_mode = match share_mode {
ShareMode::None => 0,
ShareMode::Read => FILE_SHARE_READ,
ShareMode::Write => FILE_SHARE_WRITE,
ShareMode::Delete => FILE_SHARE_DELETE,
};
let dw_flags_and_attributes = FILE_ATTRIBUTE_READONLY
| FILE_FLAG_NO_BUFFERING
| FILE_FLAG_OVERLAPPED
| FILE_FLAG_RANDOM_ACCESS;
let handle = unsafe {
CreateFileA(
Self::as_windows_pcstr(&file_name_c),
dw_desired_access,
dw_share_mode,
ptr::null_mut(),
OPEN_EXISTING,
dw_flags_and_attributes,
std::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
let error_code = unsafe { GetLastError() };
Err(io::Error::from_raw_os_error(error_code as i32))
} else {
Ok(Self { handle })
}
}
fn as_windows_pcstr(str: &CString) -> ::windows_sys::core::PCSTR {
str.as_ptr() as ::windows_sys::core::PCSTR
}
}
impl Drop for FileHandle {
fn drop(&mut self) {
let result = unsafe { CloseHandle(self.handle) };
if result == 0 {
let error_code = unsafe { GetLastError() };
let error = io::Error::from_raw_os_error(error_code as i32);
tracing::warn!("Error when dropping FileHandle: {:?}", error);
}
}
}
impl Default for FileHandle {
fn default() -> Self {
FileHandle {
handle: INVALID_HANDLE_VALUE,
}
}
}
#[cfg(test)]
mod tests {
use std::{fs::File, path::Path};
use super::*;
#[test]
fn test_create_file() {
let dummy_file_path = "dummy_file.txt";
{
let _file = File::create(dummy_file_path).expect("Failed to create dummy file.");
}
let path = Path::new(dummy_file_path);
{
let file_handle = unsafe {
FileHandle::new(path.to_str().unwrap(), AccessMode::Read, ShareMode::Read)
};
assert!(file_handle.is_ok());
}
match std::fs::remove_file(dummy_file_path) {
Ok(()) => (), Err(e) => panic!("Failed to delete file: {}", e), }
}
#[test]
fn test_file_not_found() {
let path = Path::new("non_existent_file.txt");
let file_handle =
unsafe { FileHandle::new(path.to_str().unwrap(), AccessMode::Read, ShareMode::Read) };
assert!(file_handle.is_err());
}
}