#![allow(unsafe_code)]
use std::os::windows::io::AsRawHandle;
use std::sync::Arc;
use crate::Result;
use crate::errors::PagedbError;
use crate::vfs::traits::VfsFile;
use crate::vfs::types::{ReadReq, WriteReq};
use super::port::{Port, PortInner};
use windows_sys::Win32::Foundation::{ERROR_HANDLE_EOF, ERROR_IO_PENDING, HANDLE};
use windows_sys::Win32::Storage::FileSystem::{
FILE_BEGIN, FlushFileBuffers, ReadFile, SetEndOfFile, SetFilePointerEx, WriteFile,
};
use windows_sys::Win32::System::IO::OVERLAPPED;
pub struct IocpFile {
file: std::fs::File,
writable: bool,
_key: usize,
port: Arc<PortInner>,
}
impl IocpFile {
pub(crate) fn new(
file: std::fs::File,
writable: bool,
key: usize,
port: Arc<PortInner>,
) -> Self {
Self {
file,
writable,
_key: key,
port,
}
}
fn handle(&self) -> HANDLE {
self.file.as_raw_handle() as HANDLE
}
unsafe fn submit_overlapped<F>(port: &Port, offset: u64, op: F) -> std::io::Result<u32>
where
F: FnOnce(&mut OVERLAPPED) -> i32,
{
let mut overlapped: OVERLAPPED = unsafe { std::mem::zeroed() };
#[allow(clippy::cast_possible_truncation)]
{
overlapped.Anonymous.Anonymous.Offset = offset as u32;
overlapped.Anonymous.Anonymous.OffsetHigh = (offset >> 32) as u32;
}
let rc = op(&mut overlapped);
if rc != 0 {
#[allow(clippy::cast_possible_truncation)]
return Ok(overlapped.InternalHigh as u32);
}
let err_code = unsafe { windows_sys::Win32::Foundation::GetLastError() };
if err_code != ERROR_IO_PENDING {
return Err(std::io::Error::from_raw_os_error(err_code as i32));
}
let (bytes, _key, _ov) = unsafe { port.dequeue() }.or_else(|e| {
if e.raw_os_error() == Some(ERROR_HANDLE_EOF as i32) {
Ok((0u32, 0usize, std::ptr::null_mut::<OVERLAPPED>()))
} else {
Err(e)
}
})?;
Ok(bytes)
}
}
unsafe impl Send for IocpFile {}
impl VfsFile for IocpFile {
async fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let handle = self.handle();
let len = u32::try_from(buf.len()).map_err(|_| {
PagedbError::Io(std::io::Error::other(
"buffer too large for u32 in ReadFile",
))
})?;
let port = Port {
inner: Arc::clone(&self.port),
};
let _guard = port.lock();
let buf_ptr = buf.as_mut_ptr();
let bytes = unsafe {
IocpFile::submit_overlapped(&port, offset, |ov| {
let mut bytes_read: u32 = 0;
ReadFile(handle, buf_ptr.cast(), len, &mut bytes_read, ov)
})
}
.map_err(PagedbError::Io)?;
Ok(bytes as usize)
}
async fn read_at_vectored(&self, reqs: &mut [ReadReq<'_>]) -> Result<()> {
if reqs.is_empty() {
return Ok(());
}
let handle = self.handle();
let port = Port {
inner: Arc::clone(&self.port),
};
let _guard = port.lock();
for req in reqs.iter_mut() {
let len = u32::try_from(req.buf.len()).map_err(|_| {
PagedbError::Io(std::io::Error::other(
"buffer too large for u32 in ReadFile",
))
})?;
let buf_ptr = req.buf.as_mut_ptr();
let bytes = unsafe {
IocpFile::submit_overlapped(&port, req.offset, |ov| {
let mut bytes_read: u32 = 0;
ReadFile(handle, buf_ptr.cast(), len, &mut bytes_read, ov)
})
}
.map_err(PagedbError::Io)? as usize;
for b in &mut req.buf[bytes..] {
*b = 0;
}
}
Ok(())
}
async fn write_at(&mut self, offset: u64, buf: &[u8]) -> Result<usize> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
if buf.is_empty() {
return Ok(0);
}
let handle = self.handle();
let len = u32::try_from(buf.len()).map_err(|_| {
PagedbError::Io(std::io::Error::other(
"buffer too large for u32 in WriteFile",
))
})?;
let port = Port {
inner: Arc::clone(&self.port),
};
let _guard = port.lock();
let buf_ptr = buf.as_ptr();
let bytes = unsafe {
IocpFile::submit_overlapped(&port, offset, |ov| {
let mut bytes_written: u32 = 0;
WriteFile(handle, buf_ptr, len, &mut bytes_written, ov)
})
}
.map_err(PagedbError::Io)?;
Ok(bytes as usize)
}
async fn write_at_vectored(&mut self, reqs: &[WriteReq<'_>]) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
if reqs.is_empty() {
return Ok(());
}
let handle = self.handle();
let port = Port {
inner: Arc::clone(&self.port),
};
let _guard = port.lock();
for req in reqs {
let len = u32::try_from(req.buf.len()).map_err(|_| {
PagedbError::Io(std::io::Error::other(
"buffer too large for u32 in WriteFile",
))
})?;
let buf_ptr = req.buf.as_ptr();
unsafe {
IocpFile::submit_overlapped(&port, req.offset, |ov| {
let mut bytes_written: u32 = 0;
WriteFile(handle, buf_ptr, len, &mut bytes_written, ov)
})
}
.map_err(PagedbError::Io)?;
}
Ok(())
}
async fn sync(&mut self) -> Result<()> {
let handle = self.handle();
let rc = unsafe { FlushFileBuffers(handle) };
if rc == 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
Ok(())
}
async fn truncate(&mut self, len: u64) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
let handle = self.handle();
#[allow(clippy::cast_possible_wrap)]
let rc = unsafe { SetFilePointerEx(handle, len as i64, std::ptr::null_mut(), FILE_BEGIN) };
if rc == 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
let rc = unsafe { SetEndOfFile(handle) };
if rc == 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
Ok(())
}
async fn len(&self) -> Result<u64> {
let meta = self.file.metadata().map_err(PagedbError::Io)?;
Ok(meta.len())
}
async fn is_empty(&self) -> Result<bool> {
Ok(self.len().await? == 0)
}
fn supports_direct_io(&self) -> bool {
false
}
}