1use std::collections::HashMap;
29use std::io;
30use std::os::windows::io::OwnedHandle;
31use std::sync::Arc;
32use std::time::Duration;
33
34use win_sync_sys::{Access, duplicate_handle_with_access};
35use windows_ioring_sys::{
36 Batch, FlushCoverage, FlushMode, IoBuf, IoBufMut, IoRing, IoRingErrorExt, OperationId, Owned,
37 PushOptions, PushRefused, RegisteredBuffers, RegisteredFile, RegisteredSpan, RingCondition,
38 SharedHandle,
39};
40
41use crate::dioring::{register_buffers, register_files};
42use crate::error_code::{WINDOWS_IORING, windows_fault};
43use durable_ioring::IoTarget;
44use durable_ioring::backend::{
45 Completion, DuplicateTarget, IoBackend, RefusalCause, Refused, RegisteredRegion, Submission,
46 SubmissionData, SubmissionOp, SubmissionToken,
47};
48use durable_ioring::{IoFault, IoTargetKey, WriteCaching};
49
50mod file_target;
51mod opened_disk;
52mod opened_file;
53mod opened_volume;
54pub(crate) mod pool;
55pub(crate) mod signalled;
56mod wait;
57
58pub use wait::CompletionEvent;
59
60pub use file_target::Win32FileTarget;
61pub use opened_disk::{DiskPath, OpenedDisk};
62pub use opened_file::{Buffering, Cached, FileOptions, OpenedFile, RemoteDevice, Unbuffered};
63pub use opened_volume::{
64 OpenedVolume, VolumeOptions, VolumePath, VolumePathError, disk_flush_domain,
65};
66
67#[cfg(test)]
68mod tests;
69
70pub(crate) fn transfer(
75 result: io::Result<usize>,
76 write: bool,
77 requested: u32,
78) -> Result<u32, IoFault> {
79 match result {
80 Ok(transferred) => {
82 let transferred = u32::try_from(transferred).unwrap_or(u32::MAX);
83 if write && transferred < requested {
84 Err(IoFault {
85 source: WINDOWS_IORING,
86 code: None,
87 error: Arc::new(io::Error::other(format!(
88 "the write completed short: {transferred} of {requested} bytes"
89 ))),
90 })
91 } else {
92 Ok(transferred)
93 }
94 }
95 Err(error) => Err(windows_fault(error)),
96 }
97}
98
99pub(crate) fn refusal_cause(error: io::Error) -> RefusalCause {
106 match error.ring_condition() {
107 Some(RingCondition::SubmissionQueueFull) => RefusalCause::SubmitNeeded,
108 Some(RingCondition::CompletionQueueTooFull) => RefusalCause::DrainNeeded,
109 Some(RingCondition::SubmitInProgress) => RefusalCause::Busy,
110 Some(RingCondition::Corrupt) => RefusalCause::Unusable(windows_fault(error)),
111 Some(_) => RefusalCause::Unknown(windows_fault(error)),
112 None => match error.kind() {
113 io::ErrorKind::Unsupported => RefusalCause::Unsupported,
114 io::ErrorKind::InvalidInput => RefusalCause::Invalid,
115 _ => RefusalCause::Unknown(windows_fault(error)),
116 },
117 }
118}
119
120#[derive(Clone, Copy, Debug)]
122pub(crate) struct Sidecar {
123 token: SubmissionToken,
124 target: IoTargetKey,
125 kind: Kind,
126}
127
128#[derive(Clone, Copy, Debug)]
130enum Kind {
131 Write(u32),
133 Read,
134 Commit,
135}
136
137struct Slot {
139 target: Win32FileTarget,
140 registered: Option<RegisteredFile>,
142}
143
144#[cfg(test)]
146pub(crate) type RingHook<B> = Box<dyn FnOnce(&IoRing<B, Sidecar>)>;
147
148pub(crate) struct Construction<B> {
154 pub(crate) registration_bound: Duration,
156 #[cfg(test)]
159 pub(crate) on_ring: Option<RingHook<B>>,
160 ring: std::marker::PhantomData<fn(B)>,
162}
163
164impl<B> Default for Construction<B> {
165 fn default() -> Self {
167 Self {
168 registration_bound: crate::dioring::REGISTRATION_BOUND,
169 #[cfg(test)]
170 on_ring: None,
171 ring: std::marker::PhantomData,
172 }
173 }
174}
175
176#[cfg(test)]
177impl<B> Construction<B> {
178 pub(crate) fn faulted(
182 registration_bound: Duration,
183 ) -> (Self, windows_ioring_sys::sys::CallFaultControl) {
184 let (faults, control) = windows_ioring_sys::sys::call_faults();
185 let on_ring: RingHook<B> = Box::new(move |ring| {
186 let _ = windows_ioring_sys::sys::register(ring, Box::new(faults));
187 });
188 let construction = Self {
189 registration_bound,
190 on_ring: Some(on_ring),
191 ring: std::marker::PhantomData,
192 };
193 (construction, control)
194 }
195}
196
197pub struct WindowsBackendSetup<R> {
199 pub submission_queue_size: u32,
201 pub completion_queue_size: u32,
203 pub targets: Vec<(IoTargetKey, Win32FileTarget)>,
205 pub buffers: Vec<R>,
207}
208
209#[derive(Debug)]
211pub enum WindowsBackendRefusal {
212 DuplicateKey(IoTargetKey),
214 Ring(io::Error),
216}
217
218pub struct WindowsBackendError<R> {
221 pub reason: WindowsBackendRefusal,
223 pub targets: Vec<(IoTargetKey, Win32FileTarget)>,
225 pub buffers: Option<Vec<R>>,
227}
228
229impl<R> std::fmt::Debug for WindowsBackendError<R> {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 f.debug_struct("WindowsBackendError")
232 .field("reason", &self.reason)
233 .field("targets", &self.targets.len())
234 .field("buffers", &self.buffers.as_ref().map(Vec::len))
235 .finish()
236 }
237}
238
239pub struct WindowsBackend<B: IoBuf, R: IoBufMut = Vec<u8>> {
247 ring: IoRing<B, Sidecar, Owned>,
250 registered: Option<RegisteredBuffers<R>>,
251 targets: HashMap<IoTargetKey, Slot>,
252 owned_read: Option<OwnedRead<B>>,
254 event: OwnedHandle,
256 queued: bool,
258 #[cfg(feature = "fault-injection")]
261 injected: Vec<(IoTargetKey, u32)>,
262}
263
264impl<B: IoBufMut, R: IoBufMut> WindowsBackend<B, R> {
265 pub fn new(setup: WindowsBackendSetup<R>) -> Result<Self, WindowsBackendError<R>> {
272 let mut backend = Self::without_owned_reads(setup)?;
273 backend.enable_owned_reads();
274 Ok(backend)
275 }
276
277 pub(crate) fn enable_owned_reads(&mut self) {
279 self.owned_read = Some(push_owned_read::<B>);
280 }
281}
282
283impl<B: IoBuf, R: IoBufMut> WindowsBackend<B, R> {
284 pub fn without_owned_reads(
292 setup: WindowsBackendSetup<R>,
293 ) -> Result<Self, WindowsBackendError<R>> {
294 Self::construct(setup, Construction::default())
295 }
296
297 pub(crate) fn construct(
303 setup: WindowsBackendSetup<R>,
304 construction: Construction<B>,
305 ) -> Result<Self, WindowsBackendError<R>> {
306 let Construction {
307 registration_bound,
308 #[cfg(test)]
309 on_ring,
310 ..
311 } = construction;
312 let WindowsBackendSetup {
313 submission_queue_size,
314 completion_queue_size,
315 targets,
316 buffers,
317 } = setup;
318 if let Some(key) = durable_ioring::driver::first_repeat(targets.iter().map(|(key, _)| *key))
319 {
320 return Err(WindowsBackendError {
321 reason: WindowsBackendRefusal::DuplicateKey(key),
322 targets,
323 buffers: Some(buffers),
324 });
325 }
326 let fail = |error, targets, buffers| WindowsBackendError {
327 reason: WindowsBackendRefusal::Ring(error),
328 targets,
329 buffers,
330 };
331 let mut ring = match IoRing::with_inventory(submission_queue_size, completion_queue_size) {
332 Ok(ring) => ring,
333 Err(error) => return Err(fail(error, targets, Some(buffers))),
334 };
335 #[cfg(test)]
336 if let Some(on_ring) = on_ring {
337 on_ring(&ring);
338 }
339 let handles = targets
340 .iter()
341 .map(|(_, target)| target.handle().clone())
342 .collect();
343 let files = match register_files(&mut ring, handles, registration_bound) {
344 Ok(files) => files,
345 Err(error) => return Err(fail(error, targets, Some(buffers))),
346 };
347 let registered = match register_buffers(&mut ring, buffers, registration_bound) {
348 Ok(registered) => registered,
349 Err((error, buffers)) => return Err(fail(error, targets, buffers)),
350 };
351 let event = match ring.completion_event() {
352 Ok(event) => event,
353 Err(error) => return Err(fail(error, targets, None)),
354 };
355 let ring = ring.into_owned().unwrap_or_else(|_| {
359 panic!(
360 "a registration was left in flight on the backend's ring: it cannot be sealed \
361 against operations the ring holds nothing for"
362 )
363 });
364 let slots = targets
365 .into_iter()
366 .enumerate()
367 .map(|(i, (key, target))| {
368 let index = files
369 .as_ref()
370 .and_then(|files| files.get(u32::try_from(i).ok()?))
371 .expect("every target given at construction was registered, in order");
372 (
373 key,
374 Slot {
375 target,
376 registered: Some(index),
377 },
378 )
379 })
380 .collect();
381 Ok(Self {
382 ring,
383 registered,
384 targets: slots,
385 owned_read: None,
386 event,
387 queued: false,
388 #[cfg(feature = "fault-injection")]
389 injected: Vec::new(),
390 })
391 }
392
393 pub(crate) fn wake(
404 &mut self,
405 on_wake: impl Fn() + Send + Sync + 'static,
406 env: Option<&mut windows_threadpool_sys::callback_env::CallbackEnviron<'_>>,
407 ) -> io::Result<windows_ioring_sys::CompletionWake> {
408 windows_ioring_sys::CompletionWake::new(&mut self.ring, on_wake, env)
409 }
410
411 pub(crate) fn completion_wait_handle(&self) -> io::Result<OwnedHandle> {
419 duplicate_handle_with_access(&self.event, Access::SYNCHRONIZE)
420 }
421
422 #[cfg(test)]
426 pub(crate) fn submit_faults(&self) -> windows_ioring_sys::sys::SubmitFaultControl {
427 let (faults, control) = windows_ioring_sys::sys::submit_faults();
428 let _ = windows_ioring_sys::sys::register(&self.ring, Box::new(faults));
429 control
430 }
431
432 #[cfg(test)]
436 pub(crate) fn call_faults(&self) -> windows_ioring_sys::sys::CallFaultControl {
437 let (faults, control) = windows_ioring_sys::sys::call_faults();
438 let _ = windows_ioring_sys::sys::register(&self.ring, Box::new(faults));
439 control
440 }
441
442 #[must_use]
444 pub fn target(&self, key: IoTargetKey) -> Option<&Win32FileTarget> {
445 self.targets.get(&key).map(|slot| &slot.target)
446 }
447
448 #[cfg(test)]
451 pub(crate) fn registration_index(&self, key: IoTargetKey) -> Option<u32> {
452 self.targets
453 .get(&key)
454 .and_then(|slot| slot.registered)
455 .map(RegisteredFile::index)
456 }
457
458 pub fn registered_buffer(&mut self, index: u32) -> io::Result<&[u8]> {
464 self.registration()?.get(index)
465 }
466
467 pub fn registered_buffer_mut(&mut self, index: u32) -> io::Result<&mut [u8]> {
473 self.registration()?.get_mut(index)
474 }
475
476 fn registration(&mut self) -> io::Result<&mut RegisteredBuffers<R>> {
477 self.registered.as_mut().ok_or_else(|| {
478 io::Error::new(
479 io::ErrorKind::InvalidInput,
480 "this backend was built without registered buffers",
481 )
482 })
483 }
484
485 fn admits(&self, target: IoTargetKey, op: &SubmissionOp<B>) -> Result<(), RefusalCause> {
491 let Some(slot) = self.targets.get(&target) else {
492 return Err(RefusalCause::UnknownTarget);
493 };
494 if self.owned_read.is_none()
495 && matches!(
496 op,
497 SubmissionOp::Read {
498 data: SubmissionData::Owned(_),
499 ..
500 }
501 )
502 {
503 return Err(RefusalCause::Unsupported);
504 }
505 let (offset, data) = match op {
506 SubmissionOp::Write { offset, data, .. } | SubmissionOp::Read { offset, data } => {
507 (*offset, data)
508 }
509 SubmissionOp::Commit => return Ok(()),
510 };
511 let geometry = slot.target.geometry();
512 let admitted = match data {
513 SubmissionData::Owned(buffer) => {
514 let length = u64::try_from(buffer.bytes_len()).unwrap_or(u64::MAX);
515 u32::try_from(length).is_ok()
518 && geometry.admits(offset, length)
519 && geometry.admits_buffer(buffer.stable_ptr() as usize)
520 }
521 SubmissionData::Registered(region) => {
522 if self.registered.is_none() {
523 return Err(RefusalCause::Unsupported);
524 }
525 geometry.admits(offset, u64::from(region.length))
526 }
527 };
528 if admitted {
529 Ok(())
530 } else {
531 Err(RefusalCause::Invalid)
532 }
533 }
534}
535
536macro_rules! push_to {
539 ($batch:expr, $file:expr, $via:expr, $read:expr, $registered:expr, $op:expr, $sidecar:expr) => {{
540 let options = PushOptions::new();
541 match $op {
542 Op::Write {
543 offset,
544 data: SubmissionData::Owned(buffer),
545 caching,
546 } => $batch.write_owned($file, buffer, $sidecar, offset, options, caching),
547 Op::Write {
548 offset,
549 data: SubmissionData::Registered(region),
550 caching,
551 } => $batch.write_registered_owned(
552 $file,
553 $registered.expect("refusal() checked for a registration"),
554 span(region),
555 $sidecar,
556 offset,
557 options,
558 caching,
559 ),
560 Op::Read {
561 offset,
562 data: SubmissionData::Owned(buffer),
563 } => $read.expect("admits() checked for an owned read")(
564 &mut $batch,
565 $via,
566 buffer,
567 $sidecar,
568 offset,
569 ),
570 Op::Read {
571 offset,
572 data: SubmissionData::Registered(region),
573 } => $batch.read_registered_owned(
574 $file,
575 $registered.expect("refusal() checked for a registration"),
576 span(region),
577 $sidecar,
578 offset,
579 options,
580 ),
581 Op::Commit => $batch.flush_owned(
582 $file,
583 $sidecar,
584 FlushCoverage::Unordered,
585 FlushMode::Default,
586 ),
587 }
588 }};
589}
590
591#[derive(Clone, Copy)]
593enum Via<'a> {
594 Registered(&'a RegisteredFile),
595 Shared(&'a SharedHandle),
596}
597
598type OwnedRead<B> = for<'r, 'v> fn(
601 &mut Batch<'r, B, Sidecar, Owned>,
602 Via<'v>,
603 B,
604 Sidecar,
605 u64,
606) -> Result<OperationId, PushRefused<B, Sidecar>>;
607
608fn push_owned_read<B: IoBufMut>(
610 batch: &mut Batch<'_, B, Sidecar, Owned>,
611 via: Via<'_>,
612 buffer: B,
613 sidecar: Sidecar,
614 offset: u64,
615) -> Result<OperationId, PushRefused<B, Sidecar>> {
616 let options = PushOptions::new();
617 match via {
618 Via::Registered(index) => batch.read_owned(index, buffer, sidecar, offset, options),
619 Via::Shared(handle) => batch.read_owned(handle, buffer, sidecar, offset, options),
620 }
621}
622
623enum Op<B> {
625 Write {
626 offset: u64,
627 data: SubmissionData<B>,
628 caching: windows_ioring_sys::WriteCaching,
629 },
630 Read {
631 offset: u64,
632 data: SubmissionData<B>,
633 },
634 Commit,
635}
636
637fn span(region: RegisteredRegion) -> RegisteredSpan {
639 RegisteredSpan {
640 buffer_index: region.index,
641 offset: region.offset,
642 len: region.length,
643 }
644}
645
646fn ring_caching(caching: WriteCaching) -> windows_ioring_sys::WriteCaching {
648 match caching {
649 WriteCaching::Cached => windows_ioring_sys::WriteCaching::Cached,
650 WriteCaching::WriteThrough => windows_ioring_sys::WriteCaching::WriteThrough,
651 }
652}
653
654fn rebuilt<B>(
656 token: SubmissionToken,
657 target: IoTargetKey,
658 shape: Shape,
659 payload: Option<B>,
660) -> Submission<B> {
661 let data = |region| match (payload, region) {
662 (Some(buffer), _) => SubmissionData::Owned(buffer),
663 (None, Some(region)) => SubmissionData::Registered(region),
664 (None, None) => unreachable!("a refused owned push hands its buffer back"),
665 };
666 let op = match shape {
667 Shape::Write {
668 offset,
669 region,
670 caching,
671 } => SubmissionOp::Write {
672 offset,
673 data: data(region),
674 caching,
675 },
676 Shape::Read { offset, region } => SubmissionOp::Read {
677 offset,
678 data: data(region),
679 },
680 Shape::Commit => SubmissionOp::Commit,
681 };
682 Submission { token, target, op }
683}
684
685#[derive(Clone, Copy)]
687enum Shape {
688 Write {
689 offset: u64,
690 region: Option<RegisteredRegion>,
691 caching: WriteCaching,
692 },
693 Read {
694 offset: u64,
695 region: Option<RegisteredRegion>,
696 },
697 Commit,
698}
699
700impl Shape {
701 fn of<B>(op: &SubmissionOp<B>) -> Self {
702 let region = |data: &SubmissionData<B>| match data {
703 SubmissionData::Owned(_) => None,
704 SubmissionData::Registered(region) => Some(*region),
705 };
706 match op {
707 SubmissionOp::Write {
708 offset,
709 data,
710 caching,
711 } => Shape::Write {
712 offset: *offset,
713 region: region(data),
714 caching: *caching,
715 },
716 SubmissionOp::Read { offset, data } => Shape::Read {
717 offset: *offset,
718 region: region(data),
719 },
720 SubmissionOp::Commit => Shape::Commit,
721 }
722 }
723}
724
725fn kind<B: IoBuf>(op: &SubmissionOp<B>) -> Kind {
727 match op {
728 SubmissionOp::Write { data, .. } => Kind::Write(match data {
729 SubmissionData::Owned(buffer) => u32::try_from(buffer.bytes_len()).unwrap_or(u32::MAX),
730 SubmissionData::Registered(region) => region.length,
731 }),
732 SubmissionOp::Read { .. } => Kind::Read,
733 SubmissionOp::Commit => Kind::Commit,
734 }
735}
736
737impl<B: IoBuf, R: IoBufMut> IoBackend for WindowsBackend<B, R> {
738 type Target = Win32FileTarget;
739 type Buffer = B;
740 type Notification = CompletionEvent;
741
742 fn attach(
743 &mut self,
744 key: IoTargetKey,
745 target: Win32FileTarget,
746 ) -> Result<(), DuplicateTarget<Win32FileTarget>> {
747 if self.targets.contains_key(&key) {
748 return Err(DuplicateTarget { key, target });
749 }
750 self.targets.insert(
751 key,
752 Slot {
753 target,
754 registered: None,
755 },
756 );
757 Ok(())
758 }
759
760 fn detach(&mut self, key: IoTargetKey) -> Option<Win32FileTarget> {
761 self.targets.remove(&key).map(|slot| slot.target)
762 }
763
764 fn registered_buffers(&self) -> u32 {
765 self.registered.as_ref().map_or(0, RegisteredBuffers::len)
766 }
767
768 fn check(&self, target: IoTargetKey, op: &SubmissionOp<B>) -> Result<(), RefusalCause> {
769 self.admits(target, op)?;
770 let region = match op {
773 SubmissionOp::Write {
774 data: SubmissionData::Registered(region),
775 ..
776 }
777 | SubmissionOp::Read {
778 data: SubmissionData::Registered(region),
779 ..
780 } => *region,
781 _ => return Ok(()),
782 };
783 match &self.registered {
784 Some(registered) => registered.check_span(span(region)).map_err(refusal_cause),
785 None => Err(RefusalCause::Unsupported),
786 }
787 }
788
789 fn push(&mut self, submission: Submission<B>) -> Result<(), Refused<B>> {
790 if let Err(cause) = self.admits(submission.target, &submission.op) {
791 return Err(Refused { cause, submission });
792 }
793 let Submission { token, target, op } = submission;
794 let shape = Shape::of(&op);
795 let sidecar = Sidecar {
796 token,
797 target,
798 kind: kind(&op),
799 };
800 let op = match op {
801 SubmissionOp::Write {
802 offset,
803 data,
804 caching,
805 } => Op::Write {
806 offset,
807 data,
808 caching: ring_caching(caching),
809 },
810 SubmissionOp::Read { offset, data } => Op::Read { offset, data },
811 SubmissionOp::Commit => Op::Commit,
812 };
813 let slot = &self.targets[&target];
814 let registered = self.registered.as_ref();
815 let read = self.owned_read;
816 let mut batch = Batch::new(&mut self.ring);
817 let pushed: Result<OperationId, PushRefused<B, Sidecar>> = match &slot.registered {
818 Some(index) => {
819 let via = Via::Registered(index);
820 push_to!(batch, index, via, read, registered, op, sidecar)
821 }
822 None => {
823 let handle = slot.target.handle();
824 let via = Via::Shared(handle);
825 push_to!(batch, handle, via, read, registered, op, sidecar)
826 }
827 };
828 match pushed {
829 Ok(_) => {
830 batch.leave_queued();
833 self.queued = true;
834 Ok(())
835 }
836 Err(refused) => {
837 batch.leave_queued();
840 let PushRefused { error, payload, .. } = refused;
841 Err(Refused {
842 cause: refusal_cause(error),
843 submission: rebuilt(token, target, shape, payload),
844 })
845 }
846 }
847 }
848
849 fn submit(&mut self) -> Result<(), RefusalCause> {
850 if !self.queued {
851 return Ok(());
852 }
853 match Batch::new(&mut self.ring).submit() {
854 Ok(_) => {
855 self.queued = false;
856 Ok(())
857 }
858 Err(error) => Err(refusal_cause(error)),
859 }
860 }
861
862 fn pop(&mut self) -> Result<Option<Completion<B>>, IoFault> {
863 let Some((completion, buffer, sidecar)) = self.ring.try_pop().map_err(windows_fault)?
866 else {
867 return Ok(None);
868 };
869 #[cfg(feature = "fault-injection")]
870 let completion = match sidecar.kind {
871 Kind::Commit => match self
872 .injected
873 .iter()
874 .position(|(target, _)| *target == sidecar.target)
875 {
876 Some(index) => {
877 let (_, code) = self.injected.remove(index);
878 completion
879 .with_injected_failure(windows_ioring_sys::InjectedFailure::Win32(code))
880 }
881 None => completion,
882 },
883 Kind::Write(_) | Kind::Read => completion,
884 };
885 let result = match sidecar.kind {
886 Kind::Write(requested) => transfer(completion.result(), true, requested),
887 Kind::Read => transfer(completion.result(), false, 0),
888 Kind::Commit => completion.result().map(|_| 0).map_err(windows_fault),
889 };
890 Ok(Some(Completion {
891 token: sidecar.token,
892 result,
893 buffer,
894 }))
895 }
896
897 fn notification(&self) -> Result<CompletionEvent, IoFault> {
898 self.event
899 .try_clone()
900 .map(CompletionEvent::new)
901 .map_err(windows_fault)
902 }
903}
904
905#[cfg(feature = "fault-injection")]
909impl<B: IoBuf, R: IoBufMut> WindowsBackend<B, R> {
910 pub fn fail_next_commit(&mut self, target: IoTargetKey, code: u32) {
913 self.injected.push((target, code));
914 }
915}