use crate::read::physical_read_request::PhysicalReadRequest;
use crate::read::physical_read_results::PhysicalReadResultEntry;
use eyre::bail;
use std::any::type_name;
use std::ptr::null_mut;
use tracing::trace;
use tracing::warn;
use uom::si::information::byte;
use windows::Win32::Foundation::ERROR_IO_PENDING;
use windows::Win32::Foundation::HANDLE;
use windows::Win32::Storage::FileSystem::ReadFile;
use windows::Win32::System::IO::GetQueuedCompletionStatus;
use windows::Win32::System::IO::OVERLAPPED;
#[repr(C)]
pub struct ActivePhysicalReadRequest {
pub overlapped: OVERLAPPED,
pub buffer: Vec<u8>,
pub response_index: usize,
pub original: PhysicalReadRequest,
}
impl std::fmt::Debug for ActivePhysicalReadRequest {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(type_name::<ActivePhysicalReadRequest>())
.field("file_offset", &self.original.offset)
.field("length", &self.original.length)
.field("response_index", &self.response_index)
.field("buffer length", &self.buffer.len())
.field("overlapped", &format_args!("{:p}", &self.overlapped))
.finish_non_exhaustive()
}
}
impl ActivePhysicalReadRequest {
pub fn new(request: PhysicalReadRequest, response_index: usize) -> Self {
let overlapped = {
let mut overlapped = OVERLAPPED::default();
let offset = request.offset.get::<byte>();
if request.offset.get::<byte>() & 0x1FF != 0 {
warn!(
?request.offset,
?request.length,
"Constructing {} - not 512-byte aligned",
type_name::<PhysicalReadRequest>(),
);
}
if request.length.get::<byte>() & 0x1FF != 0 {
warn!(
?request.offset,
?request.length,
"Constructing {} - not 512-byte multiple",
type_name::<PhysicalReadRequest>(),
);
}
let offset_low =
u32::try_from(offset & 0xFFFF_FFFF).expect("offset low bits fit into u32");
let offset_high =
u32::try_from((offset >> 32) & 0xFFFF_FFFF).expect("offset high bits fit into u32");
overlapped.Anonymous.Anonymous.Offset = offset_low;
overlapped.Anonymous.Anonymous.OffsetHigh = offset_high;
overlapped
};
Self {
overlapped,
buffer: vec![0; request.length.get::<byte>()],
response_index,
original: request,
}
}
pub fn send(self, file_handle: HANDLE) -> eyre::Result<()> {
let mut boxed = Box::new(self);
let overlapped_ptr: *mut OVERLAPPED = &raw mut boxed.overlapped;
match unsafe {
ReadFile(
file_handle,
Some(&mut *boxed.buffer),
None,
Some(overlapped_ptr),
)
} {
Ok(()) => {}
Err(e) => {
if e.code() != ERROR_IO_PENDING.into() {
bail!("ReadFile failed to queue request {boxed:?}: {e:?}");
}
}
}
let _ = Box::into_raw(boxed);
Ok(())
}
pub fn receive(completion_port: HANDLE) -> eyre::Result<(PhysicalReadResultEntry, usize)> {
let mut bytes_transferred: u32 = 0;
let mut completion_key: usize = 0;
let mut lp_overlapped: *mut OVERLAPPED = null_mut();
trace!("Waiting for IOCP read completion");
let res = unsafe {
GetQueuedCompletionStatus(
completion_port,
&raw mut bytes_transferred,
&raw mut completion_key,
&raw mut lp_overlapped,
u32::MAX,
)
};
match res {
Ok(()) => {
if lp_overlapped.is_null() {
bail!("IOCP returned success but OVERLAPPED ptr was null");
}
let req_ptr = lp_overlapped.cast::<ActivePhysicalReadRequest>();
let boxed_req = unsafe { Box::from_raw(req_ptr) };
trace!(?boxed_req, bytes_transferred, "Completed IOCP read",);
let mut data = boxed_req.buffer;
let copy_len =
(bytes_transferred as usize).min(boxed_req.original.length.get::<byte>());
if copy_len < data.len() {
data.truncate(copy_len);
}
Ok((
PhysicalReadResultEntry {
request: boxed_req.original,
data,
},
boxed_req.response_index,
))
}
Err(e) => {
if lp_overlapped.is_null() {
Err(eyre::eyre!("GetQueuedCompletionStatus failed: {e:?}"))
} else {
let req_ptr = lp_overlapped.cast::<ActivePhysicalReadRequest>();
let boxed_req = unsafe { Box::from_raw(req_ptr) };
Err(eyre::eyre!("I/O operation failed for {boxed_req:?}: {e:?}"))
}
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use std::ptr::from_mut;
use uom::si::usize::Information;
#[test]
fn assert_pointer_alignment() {
let mut dummy = Box::new(ActivePhysicalReadRequest {
overlapped: OVERLAPPED::default(),
buffer: Vec::new(),
response_index: 0,
original: PhysicalReadRequest::new(
Information::new::<byte>(0),
Information::new::<byte>(0),
),
});
let req_ref = dummy.as_mut();
let parent_ptr = from_mut(req_ref) as usize;
let child_ptr = (&raw mut req_ref.overlapped) as usize;
assert_eq!(
parent_ptr, child_ptr,
"ReadRequest.overlapped must be the first field (offset 0)"
);
drop(dummy);
}
}