use std::collections::HashMap;
use std::io;
use std::os::windows::io::OwnedHandle;
use std::sync::Arc;
use std::time::Duration;
use win_sync_sys::{Access, duplicate_handle_with_access};
use windows_ioring_sys::{
Batch, FlushCoverage, FlushMode, IoBuf, IoBufMut, IoRing, IoRingErrorExt, OperationId, Owned,
PushOptions, PushRefused, RegisteredBuffers, RegisteredFile, RegisteredSpan, RingCondition,
SharedHandle,
};
use crate::dioring::{register_buffers, register_files};
use crate::error_code::{WINDOWS_IORING, windows_fault};
use durable_ioring::IoTarget;
use durable_ioring::backend::{
Completion, DuplicateTarget, IoBackend, RefusalCause, Refused, RegisteredRegion, Submission,
SubmissionData, SubmissionOp, SubmissionToken,
};
use durable_ioring::{IoFault, IoTargetKey, WriteCaching};
mod file_target;
mod opened_disk;
mod opened_file;
mod opened_volume;
pub(crate) mod pool;
pub(crate) mod signalled;
mod wait;
pub use wait::CompletionEvent;
pub use file_target::Win32FileTarget;
pub use opened_disk::{DiskPath, OpenedDisk};
pub use opened_file::{Buffering, Cached, FileOptions, OpenedFile, RemoteDevice, Unbuffered};
pub use opened_volume::{
OpenedVolume, VolumeOptions, VolumePath, VolumePathError, disk_flush_domain,
};
#[cfg(test)]
mod tests;
pub(crate) fn transfer(
result: io::Result<usize>,
write: bool,
requested: u32,
) -> Result<u32, IoFault> {
match result {
Ok(transferred) => {
let transferred = u32::try_from(transferred).unwrap_or(u32::MAX);
if write && transferred < requested {
Err(IoFault {
source: WINDOWS_IORING,
code: None,
error: Arc::new(io::Error::other(format!(
"the write completed short: {transferred} of {requested} bytes"
))),
})
} else {
Ok(transferred)
}
}
Err(error) => Err(windows_fault(error)),
}
}
pub(crate) fn refusal_cause(error: io::Error) -> RefusalCause {
match error.ring_condition() {
Some(RingCondition::SubmissionQueueFull) => RefusalCause::SubmitNeeded,
Some(RingCondition::CompletionQueueTooFull) => RefusalCause::DrainNeeded,
Some(RingCondition::SubmitInProgress) => RefusalCause::Busy,
Some(RingCondition::Corrupt) => RefusalCause::Unusable(windows_fault(error)),
Some(_) => RefusalCause::Unknown(windows_fault(error)),
None => match error.kind() {
io::ErrorKind::Unsupported => RefusalCause::Unsupported,
io::ErrorKind::InvalidInput => RefusalCause::Invalid,
_ => RefusalCause::Unknown(windows_fault(error)),
},
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Sidecar {
token: SubmissionToken,
target: IoTargetKey,
kind: Kind,
}
#[derive(Clone, Copy, Debug)]
enum Kind {
Write(u32),
Read,
Commit,
}
struct Slot {
target: Win32FileTarget,
registered: Option<RegisteredFile>,
}
#[cfg(test)]
pub(crate) type RingHook<B> = Box<dyn FnOnce(&IoRing<B, Sidecar>)>;
pub(crate) struct Construction<B> {
pub(crate) registration_bound: Duration,
#[cfg(test)]
pub(crate) on_ring: Option<RingHook<B>>,
ring: std::marker::PhantomData<fn(B)>,
}
impl<B> Default for Construction<B> {
fn default() -> Self {
Self {
registration_bound: crate::dioring::REGISTRATION_BOUND,
#[cfg(test)]
on_ring: None,
ring: std::marker::PhantomData,
}
}
}
#[cfg(test)]
impl<B> Construction<B> {
pub(crate) fn faulted(
registration_bound: Duration,
) -> (Self, windows_ioring_sys::sys::CallFaultControl) {
let (faults, control) = windows_ioring_sys::sys::call_faults();
let on_ring: RingHook<B> = Box::new(move |ring| {
let _ = windows_ioring_sys::sys::register(ring, Box::new(faults));
});
let construction = Self {
registration_bound,
on_ring: Some(on_ring),
ring: std::marker::PhantomData,
};
(construction, control)
}
}
pub struct WindowsBackendSetup<R> {
pub submission_queue_size: u32,
pub completion_queue_size: u32,
pub targets: Vec<(IoTargetKey, Win32FileTarget)>,
pub buffers: Vec<R>,
}
#[derive(Debug)]
pub enum WindowsBackendRefusal {
DuplicateKey(IoTargetKey),
Ring(io::Error),
}
pub struct WindowsBackendError<R> {
pub reason: WindowsBackendRefusal,
pub targets: Vec<(IoTargetKey, Win32FileTarget)>,
pub buffers: Option<Vec<R>>,
}
impl<R> std::fmt::Debug for WindowsBackendError<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WindowsBackendError")
.field("reason", &self.reason)
.field("targets", &self.targets.len())
.field("buffers", &self.buffers.as_ref().map(Vec::len))
.finish()
}
}
pub struct WindowsBackend<B: IoBuf, R: IoBufMut = Vec<u8>> {
ring: IoRing<B, Sidecar, Owned>,
registered: Option<RegisteredBuffers<R>>,
targets: HashMap<IoTargetKey, Slot>,
owned_read: Option<OwnedRead<B>>,
event: OwnedHandle,
queued: bool,
#[cfg(feature = "fault-injection")]
injected: Vec<(IoTargetKey, u32)>,
}
impl<B: IoBufMut, R: IoBufMut> WindowsBackend<B, R> {
pub fn new(setup: WindowsBackendSetup<R>) -> Result<Self, WindowsBackendError<R>> {
let mut backend = Self::without_owned_reads(setup)?;
backend.enable_owned_reads();
Ok(backend)
}
pub(crate) fn enable_owned_reads(&mut self) {
self.owned_read = Some(push_owned_read::<B>);
}
}
impl<B: IoBuf, R: IoBufMut> WindowsBackend<B, R> {
pub fn without_owned_reads(
setup: WindowsBackendSetup<R>,
) -> Result<Self, WindowsBackendError<R>> {
Self::construct(setup, Construction::default())
}
pub(crate) fn construct(
setup: WindowsBackendSetup<R>,
construction: Construction<B>,
) -> Result<Self, WindowsBackendError<R>> {
let Construction {
registration_bound,
#[cfg(test)]
on_ring,
..
} = construction;
let WindowsBackendSetup {
submission_queue_size,
completion_queue_size,
targets,
buffers,
} = setup;
if let Some(key) = durable_ioring::driver::first_repeat(targets.iter().map(|(key, _)| *key))
{
return Err(WindowsBackendError {
reason: WindowsBackendRefusal::DuplicateKey(key),
targets,
buffers: Some(buffers),
});
}
let fail = |error, targets, buffers| WindowsBackendError {
reason: WindowsBackendRefusal::Ring(error),
targets,
buffers,
};
let mut ring = match IoRing::with_inventory(submission_queue_size, completion_queue_size) {
Ok(ring) => ring,
Err(error) => return Err(fail(error, targets, Some(buffers))),
};
#[cfg(test)]
if let Some(on_ring) = on_ring {
on_ring(&ring);
}
let handles = targets
.iter()
.map(|(_, target)| target.handle().clone())
.collect();
let files = match register_files(&mut ring, handles, registration_bound) {
Ok(files) => files,
Err(error) => return Err(fail(error, targets, Some(buffers))),
};
let registered = match register_buffers(&mut ring, buffers, registration_bound) {
Ok(registered) => registered,
Err((error, buffers)) => return Err(fail(error, targets, buffers)),
};
let event = match ring.completion_event() {
Ok(event) => event,
Err(error) => return Err(fail(error, targets, None)),
};
let ring = ring.into_owned().unwrap_or_else(|_| {
panic!(
"a registration was left in flight on the backend's ring: it cannot be sealed \
against operations the ring holds nothing for"
)
});
let slots = targets
.into_iter()
.enumerate()
.map(|(i, (key, target))| {
let index = files
.as_ref()
.and_then(|files| files.get(u32::try_from(i).ok()?))
.expect("every target given at construction was registered, in order");
(
key,
Slot {
target,
registered: Some(index),
},
)
})
.collect();
Ok(Self {
ring,
registered,
targets: slots,
owned_read: None,
event,
queued: false,
#[cfg(feature = "fault-injection")]
injected: Vec::new(),
})
}
pub(crate) fn wake(
&mut self,
on_wake: impl Fn() + Send + Sync + 'static,
env: Option<&mut windows_threadpool_sys::callback_env::CallbackEnviron<'_>>,
) -> io::Result<windows_ioring_sys::CompletionWake> {
windows_ioring_sys::CompletionWake::new(&mut self.ring, on_wake, env)
}
pub(crate) fn completion_wait_handle(&self) -> io::Result<OwnedHandle> {
duplicate_handle_with_access(&self.event, Access::SYNCHRONIZE)
}
#[cfg(test)]
pub(crate) fn submit_faults(&self) -> windows_ioring_sys::sys::SubmitFaultControl {
let (faults, control) = windows_ioring_sys::sys::submit_faults();
let _ = windows_ioring_sys::sys::register(&self.ring, Box::new(faults));
control
}
#[cfg(test)]
pub(crate) fn call_faults(&self) -> windows_ioring_sys::sys::CallFaultControl {
let (faults, control) = windows_ioring_sys::sys::call_faults();
let _ = windows_ioring_sys::sys::register(&self.ring, Box::new(faults));
control
}
#[must_use]
pub fn target(&self, key: IoTargetKey) -> Option<&Win32FileTarget> {
self.targets.get(&key).map(|slot| &slot.target)
}
#[cfg(test)]
pub(crate) fn registration_index(&self, key: IoTargetKey) -> Option<u32> {
self.targets
.get(&key)
.and_then(|slot| slot.registered)
.map(RegisteredFile::index)
}
pub fn registered_buffer(&mut self, index: u32) -> io::Result<&[u8]> {
self.registration()?.get(index)
}
pub fn registered_buffer_mut(&mut self, index: u32) -> io::Result<&mut [u8]> {
self.registration()?.get_mut(index)
}
fn registration(&mut self) -> io::Result<&mut RegisteredBuffers<R>> {
self.registered.as_mut().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidInput,
"this backend was built without registered buffers",
)
})
}
fn admits(&self, target: IoTargetKey, op: &SubmissionOp<B>) -> Result<(), RefusalCause> {
let Some(slot) = self.targets.get(&target) else {
return Err(RefusalCause::UnknownTarget);
};
if self.owned_read.is_none()
&& matches!(
op,
SubmissionOp::Read {
data: SubmissionData::Owned(_),
..
}
)
{
return Err(RefusalCause::Unsupported);
}
let (offset, data) = match op {
SubmissionOp::Write { offset, data, .. } | SubmissionOp::Read { offset, data } => {
(*offset, data)
}
SubmissionOp::Commit => return Ok(()),
};
let geometry = slot.target.geometry();
let admitted = match data {
SubmissionData::Owned(buffer) => {
let length = u64::try_from(buffer.bytes_len()).unwrap_or(u64::MAX);
u32::try_from(length).is_ok()
&& geometry.admits(offset, length)
&& geometry.admits_buffer(buffer.stable_ptr() as usize)
}
SubmissionData::Registered(region) => {
if self.registered.is_none() {
return Err(RefusalCause::Unsupported);
}
geometry.admits(offset, u64::from(region.length))
}
};
if admitted {
Ok(())
} else {
Err(RefusalCause::Invalid)
}
}
}
macro_rules! push_to {
($batch:expr, $file:expr, $via:expr, $read:expr, $registered:expr, $op:expr, $sidecar:expr) => {{
let options = PushOptions::new();
match $op {
Op::Write {
offset,
data: SubmissionData::Owned(buffer),
caching,
} => $batch.write_owned($file, buffer, $sidecar, offset, options, caching),
Op::Write {
offset,
data: SubmissionData::Registered(region),
caching,
} => $batch.write_registered_owned(
$file,
$registered.expect("refusal() checked for a registration"),
span(region),
$sidecar,
offset,
options,
caching,
),
Op::Read {
offset,
data: SubmissionData::Owned(buffer),
} => $read.expect("admits() checked for an owned read")(
&mut $batch,
$via,
buffer,
$sidecar,
offset,
),
Op::Read {
offset,
data: SubmissionData::Registered(region),
} => $batch.read_registered_owned(
$file,
$registered.expect("refusal() checked for a registration"),
span(region),
$sidecar,
offset,
options,
),
Op::Commit => $batch.flush_owned(
$file,
$sidecar,
FlushCoverage::Unordered,
FlushMode::Default,
),
}
}};
}
#[derive(Clone, Copy)]
enum Via<'a> {
Registered(&'a RegisteredFile),
Shared(&'a SharedHandle),
}
type OwnedRead<B> = for<'r, 'v> fn(
&mut Batch<'r, B, Sidecar, Owned>,
Via<'v>,
B,
Sidecar,
u64,
) -> Result<OperationId, PushRefused<B, Sidecar>>;
fn push_owned_read<B: IoBufMut>(
batch: &mut Batch<'_, B, Sidecar, Owned>,
via: Via<'_>,
buffer: B,
sidecar: Sidecar,
offset: u64,
) -> Result<OperationId, PushRefused<B, Sidecar>> {
let options = PushOptions::new();
match via {
Via::Registered(index) => batch.read_owned(index, buffer, sidecar, offset, options),
Via::Shared(handle) => batch.read_owned(handle, buffer, sidecar, offset, options),
}
}
enum Op<B> {
Write {
offset: u64,
data: SubmissionData<B>,
caching: windows_ioring_sys::WriteCaching,
},
Read {
offset: u64,
data: SubmissionData<B>,
},
Commit,
}
fn span(region: RegisteredRegion) -> RegisteredSpan {
RegisteredSpan {
buffer_index: region.index,
offset: region.offset,
len: region.length,
}
}
fn ring_caching(caching: WriteCaching) -> windows_ioring_sys::WriteCaching {
match caching {
WriteCaching::Cached => windows_ioring_sys::WriteCaching::Cached,
WriteCaching::WriteThrough => windows_ioring_sys::WriteCaching::WriteThrough,
}
}
fn rebuilt<B>(
token: SubmissionToken,
target: IoTargetKey,
shape: Shape,
payload: Option<B>,
) -> Submission<B> {
let data = |region| match (payload, region) {
(Some(buffer), _) => SubmissionData::Owned(buffer),
(None, Some(region)) => SubmissionData::Registered(region),
(None, None) => unreachable!("a refused owned push hands its buffer back"),
};
let op = match shape {
Shape::Write {
offset,
region,
caching,
} => SubmissionOp::Write {
offset,
data: data(region),
caching,
},
Shape::Read { offset, region } => SubmissionOp::Read {
offset,
data: data(region),
},
Shape::Commit => SubmissionOp::Commit,
};
Submission { token, target, op }
}
#[derive(Clone, Copy)]
enum Shape {
Write {
offset: u64,
region: Option<RegisteredRegion>,
caching: WriteCaching,
},
Read {
offset: u64,
region: Option<RegisteredRegion>,
},
Commit,
}
impl Shape {
fn of<B>(op: &SubmissionOp<B>) -> Self {
let region = |data: &SubmissionData<B>| match data {
SubmissionData::Owned(_) => None,
SubmissionData::Registered(region) => Some(*region),
};
match op {
SubmissionOp::Write {
offset,
data,
caching,
} => Shape::Write {
offset: *offset,
region: region(data),
caching: *caching,
},
SubmissionOp::Read { offset, data } => Shape::Read {
offset: *offset,
region: region(data),
},
SubmissionOp::Commit => Shape::Commit,
}
}
}
fn kind<B: IoBuf>(op: &SubmissionOp<B>) -> Kind {
match op {
SubmissionOp::Write { data, .. } => Kind::Write(match data {
SubmissionData::Owned(buffer) => u32::try_from(buffer.bytes_len()).unwrap_or(u32::MAX),
SubmissionData::Registered(region) => region.length,
}),
SubmissionOp::Read { .. } => Kind::Read,
SubmissionOp::Commit => Kind::Commit,
}
}
impl<B: IoBuf, R: IoBufMut> IoBackend for WindowsBackend<B, R> {
type Target = Win32FileTarget;
type Buffer = B;
type Notification = CompletionEvent;
fn attach(
&mut self,
key: IoTargetKey,
target: Win32FileTarget,
) -> Result<(), DuplicateTarget<Win32FileTarget>> {
if self.targets.contains_key(&key) {
return Err(DuplicateTarget { key, target });
}
self.targets.insert(
key,
Slot {
target,
registered: None,
},
);
Ok(())
}
fn detach(&mut self, key: IoTargetKey) -> Option<Win32FileTarget> {
self.targets.remove(&key).map(|slot| slot.target)
}
fn registered_buffers(&self) -> u32 {
self.registered.as_ref().map_or(0, RegisteredBuffers::len)
}
fn check(&self, target: IoTargetKey, op: &SubmissionOp<B>) -> Result<(), RefusalCause> {
self.admits(target, op)?;
let region = match op {
SubmissionOp::Write {
data: SubmissionData::Registered(region),
..
}
| SubmissionOp::Read {
data: SubmissionData::Registered(region),
..
} => *region,
_ => return Ok(()),
};
match &self.registered {
Some(registered) => registered.check_span(span(region)).map_err(refusal_cause),
None => Err(RefusalCause::Unsupported),
}
}
fn push(&mut self, submission: Submission<B>) -> Result<(), Refused<B>> {
if let Err(cause) = self.admits(submission.target, &submission.op) {
return Err(Refused { cause, submission });
}
let Submission { token, target, op } = submission;
let shape = Shape::of(&op);
let sidecar = Sidecar {
token,
target,
kind: kind(&op),
};
let op = match op {
SubmissionOp::Write {
offset,
data,
caching,
} => Op::Write {
offset,
data,
caching: ring_caching(caching),
},
SubmissionOp::Read { offset, data } => Op::Read { offset, data },
SubmissionOp::Commit => Op::Commit,
};
let slot = &self.targets[&target];
let registered = self.registered.as_ref();
let read = self.owned_read;
let mut batch = Batch::new(&mut self.ring);
let pushed: Result<OperationId, PushRefused<B, Sidecar>> = match &slot.registered {
Some(index) => {
let via = Via::Registered(index);
push_to!(batch, index, via, read, registered, op, sidecar)
}
None => {
let handle = slot.target.handle();
let via = Via::Shared(handle);
push_to!(batch, handle, via, read, registered, op, sidecar)
}
};
match pushed {
Ok(_) => {
batch.leave_queued();
self.queued = true;
Ok(())
}
Err(refused) => {
batch.leave_queued();
let PushRefused { error, payload, .. } = refused;
Err(Refused {
cause: refusal_cause(error),
submission: rebuilt(token, target, shape, payload),
})
}
}
}
fn submit(&mut self) -> Result<(), RefusalCause> {
if !self.queued {
return Ok(());
}
match Batch::new(&mut self.ring).submit() {
Ok(_) => {
self.queued = false;
Ok(())
}
Err(error) => Err(refusal_cause(error)),
}
}
fn pop(&mut self) -> Result<Option<Completion<B>>, IoFault> {
let Some((completion, buffer, sidecar)) = self.ring.try_pop().map_err(windows_fault)?
else {
return Ok(None);
};
#[cfg(feature = "fault-injection")]
let completion = match sidecar.kind {
Kind::Commit => match self
.injected
.iter()
.position(|(target, _)| *target == sidecar.target)
{
Some(index) => {
let (_, code) = self.injected.remove(index);
completion
.with_injected_failure(windows_ioring_sys::InjectedFailure::Win32(code))
}
None => completion,
},
Kind::Write(_) | Kind::Read => completion,
};
let result = match sidecar.kind {
Kind::Write(requested) => transfer(completion.result(), true, requested),
Kind::Read => transfer(completion.result(), false, 0),
Kind::Commit => completion.result().map(|_| 0).map_err(windows_fault),
};
Ok(Some(Completion {
token: sidecar.token,
result,
buffer,
}))
}
fn notification(&self) -> Result<CompletionEvent, IoFault> {
self.event
.try_clone()
.map(CompletionEvent::new)
.map_err(windows_fault)
}
}
#[cfg(feature = "fault-injection")]
impl<B: IoBuf, R: IoBufMut> WindowsBackend<B, R> {
pub fn fail_next_commit(&mut self, target: IoTargetKey, code: u32) {
self.injected.push((target, code));
}
}