#![allow(unsafe_code)]
use std::os::windows::io::AsRawHandle;
use std::sync::Arc;
use crate::Result;
use crate::errors::PagedbError;
use crate::vfs::blocking::offload;
use crate::vfs::traits::{
OverlappedStart, VfsFile, checked_overlapped_read_start, checked_overlapped_start,
checked_read_count, checked_readfile_len, checked_signed_file_len, checked_write_progress,
};
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: Arc<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: Arc::new(file),
writable,
_key: key,
port,
}
}
fn shared(&self) -> (Arc<std::fs::File>, Port) {
(
Arc::clone(&self.file),
Port {
inner: Arc::clone(&self.port),
},
)
}
fn writefile_len(len: usize) -> Result<u32> {
u32::try_from(len).map_err(|_| {
PagedbError::Io(std::io::Error::other(
"buffer too large for u32 in WriteFile",
))
})
}
unsafe fn submit_overlapped<F>(port: &Port, offset: u64, op: F) -> std::io::Result<u32>
where
F: FnOnce(&mut OVERLAPPED) -> i32,
{
unsafe { Self::submit_overlapped_impl(port, offset, false, op) }
}
unsafe fn submit_read_overlapped<F>(port: &Port, offset: u64, op: F) -> std::io::Result<u32>
where
F: FnOnce(&mut OVERLAPPED) -> i32,
{
unsafe { Self::submit_overlapped_impl(port, offset, true, op) }
}
unsafe fn submit_overlapped_impl<F>(
port: &Port,
offset: u64,
eof_is_empty_read: bool,
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);
let err_code = if rc == 0 {
unsafe { windows_sys::Win32::Foundation::GetLastError() }
} else {
0
};
let start = if eof_is_empty_read {
checked_overlapped_read_start(rc, err_code, ERROR_IO_PENDING, ERROR_HANDLE_EOF)
} else {
checked_overlapped_start(rc, err_code, ERROR_IO_PENDING)
}
.map_err(|error| match error {
PagedbError::Io(io) => io,
other => std::io::Error::other(other.to_string()),
})?;
if start == OverlappedStart::EmptyRead {
return Ok(0);
}
let (bytes, _key, completed) = unsafe { port.dequeue() }.or_else(|error| {
if error.raw_os_error() == Some(ERROR_HANDLE_EOF as i32) {
Ok((0u32, 0usize, std::ptr::null_mut::<OVERLAPPED>()))
} else {
Err(error)
}
})?;
let expected = (&raw mut overlapped).cast::<OVERLAPPED>();
if !completed.is_null() && completed != expected {
return Err(std::io::Error::other(
"IOCP completion did not match the submitted OVERLAPPED",
));
}
Ok(bytes)
}
fn read_locked(handle: HANDLE, port: &Port, offset: u64, buf: &mut [u8]) -> Result<usize> {
let len = checked_readfile_len(buf.len())?;
let buf_ptr = buf.as_mut_ptr();
let bytes = unsafe {
Self::submit_read_overlapped(port, offset, |ov| {
ReadFile(handle, buf_ptr.cast(), len, std::ptr::null_mut(), ov)
})
}
.map_err(PagedbError::Io)?;
checked_read_count(bytes as usize, buf.len())
}
fn write_locked(handle: HANDLE, port: &Port, offset: u64, buf: &[u8]) -> Result<usize> {
let len = Self::writefile_len(buf.len())?;
let buf_ptr = buf.as_ptr();
let bytes = unsafe {
Self::submit_overlapped(port, offset, |ov| {
WriteFile(handle, buf_ptr, len, std::ptr::null_mut(), ov)
})
}
.map_err(PagedbError::Io)?;
usize::try_from(bytes).map_err(|_| {
PagedbError::Io(std::io::Error::other(
"WriteFile reported byte count that does not fit in usize",
))
})
}
}
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 len = buf.len();
checked_readfile_len(len)?;
let (file, port) = self.shared();
let (scratch, read) = offload(move || {
let handle = file.as_raw_handle() as HANDLE;
let mut scratch = vec![0u8; len];
let _guard = port.lock();
let read = IocpFile::read_locked(handle, &port, offset, &mut scratch)?;
Ok((scratch, read))
})
.await?;
buf[..read].copy_from_slice(&scratch[..read]);
Ok(read)
}
async fn read_at_vectored(&self, reqs: &mut [ReadReq<'_>]) -> Result<()> {
if reqs.is_empty() {
return Ok(());
}
let mut plan: Vec<(u64, usize)> = Vec::with_capacity(reqs.len());
for req in reqs.iter() {
checked_readfile_len(req.buf.len())?;
plan.push((req.offset, req.buf.len()));
}
let (file, port) = self.shared();
let completed = offload(move || {
let handle = file.as_raw_handle() as HANDLE;
let mut out: Vec<(Vec<u8>, usize)> = Vec::with_capacity(plan.len());
let _guard = port.lock();
for (offset, len) in plan {
let mut scratch = vec![0u8; len];
let read = IocpFile::read_locked(handle, &port, offset, &mut scratch)?;
out.push((scratch, read));
}
Ok(out)
})
.await?;
for (req, (scratch, read)) in reqs.iter_mut().zip(completed) {
req.buf[..read].copy_from_slice(&scratch[..read]);
for b in &mut req.buf[read..] {
*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);
}
Self::writefile_len(buf.len())?;
let (file, port) = self.shared();
let data = buf.to_vec();
offload(move || {
let handle = file.as_raw_handle() as HANDLE;
let _guard = port.lock();
IocpFile::write_locked(handle, &port, offset, &data)
})
.await
}
async fn write_at_vectored(&mut self, reqs: &[WriteReq<'_>]) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
if reqs.is_empty() {
return Ok(());
}
let mut batch: Vec<(u64, Vec<u8>)> = Vec::with_capacity(reqs.len());
for req in reqs {
Self::writefile_len(req.buf.len())?;
batch.push((req.offset, req.buf.to_vec()));
}
let (file, port) = self.shared();
offload(move || {
let handle = file.as_raw_handle() as HANDLE;
let _guard = port.lock();
for (start, data) in &batch {
let mut offset = *start;
let mut remaining = data.as_slice();
while !remaining.is_empty() {
let written = IocpFile::write_locked(handle, &port, offset, remaining)?;
let consumed = checked_write_progress(&mut offset, written, remaining.len())?;
remaining = &remaining[consumed..];
}
}
Ok(())
})
.await
}
async fn sync(&mut self) -> Result<()> {
let file = Arc::clone(&self.file);
offload(move || {
let handle = file.as_raw_handle() as HANDLE;
let rc = unsafe { FlushFileBuffers(handle) };
if rc == 0 {
return Err(PagedbError::Io(std::io::Error::last_os_error()));
}
Ok(())
})
.await
}
async fn truncate(&mut self, len: u64) -> Result<()> {
if !self.writable {
return Err(PagedbError::ReadOnly);
}
let len = checked_signed_file_len(len, "SetFilePointerEx")?;
let file = Arc::clone(&self.file);
offload(move || {
let handle = file.as_raw_handle() as HANDLE;
let rc = unsafe { SetFilePointerEx(handle, len, 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(())
})
.await
}
async fn len(&self) -> Result<u64> {
let file = Arc::clone(&self.file);
offload(move || Ok(file.metadata().map_err(PagedbError::Io)?.len())).await
}
async fn is_empty(&self) -> Result<bool> {
Ok(self.len().await? == 0)
}
fn supports_direct_io(&self) -> bool {
false
}
}