1use std::collections::HashMap;
29use std::io;
30use std::os::windows::io::OwnedHandle;
31use std::sync::Arc;
32use std::time::Duration;
33
34use win_handles_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 disposition;
51mod file_attributes;
52mod file_id;
53mod file_rights;
54mod file_target;
55mod opened_disk;
56mod opened_file;
57mod opened_volume;
58pub(crate) mod pool;
59mod raw_open;
60mod share_mode;
61pub(crate) mod signalled;
62mod wait;
63
64pub use wait::CompletionEvent;
65
66pub use disposition::Disposition;
68pub use durable_ioring::{
69 CanRead, CanWrite, DataAccess, HasData, NoData, ReadOnly, ReadWrite, WriteOnly,
70};
71pub use file_attributes::FileAttributes;
72pub use file_id::{FileId, FileIdentifier};
73pub use file_rights::FileRights;
74pub use file_target::Win32FileTarget;
75pub use opened_disk::{DiskOptions, DiskPath, OpenedDisk};
76pub use opened_file::{
77 Buffering, ByIdOptions, ByIdRequest, Cached, FileOptions, IdentityMismatch, OpenRequest,
78 OpenedBy, OpenedFile, RemoteDevice, ReopenOptions, ReopenRequest, SharingViolation, Unbuffered,
79 UnknownLocation,
80};
81pub use opened_volume::{
82 OpenedVolume, VolumeOptions, VolumePath, VolumePathError, disk_flush_domain,
83};
84pub use share_mode::ShareMode;
85pub use win_volume_sys::DiskExtent;
87pub use win_authorization_sys::{SecurityCaptureError, SecurityDescriptor};
90
91#[cfg(test)]
92mod tests;
93
94pub(crate) fn transfer(
99 result: io::Result<usize>,
100 write: bool,
101 requested: u32,
102) -> Result<u32, IoFault> {
103 match result {
104 Ok(transferred) => {
106 let transferred = u32::try_from(transferred).unwrap_or(u32::MAX);
107 if write && transferred < requested {
108 Err(IoFault {
109 source: WINDOWS_IORING,
110 code: None,
111 error: Arc::new(io::Error::other(format!(
112 "the write completed short: {transferred} of {requested} bytes"
113 ))),
114 })
115 } else {
116 Ok(transferred)
117 }
118 }
119 Err(error) => Err(windows_fault(error)),
120 }
121}
122
123pub(crate) fn refusal_cause(error: io::Error) -> RefusalCause {
130 match error.ring_condition() {
131 Some(RingCondition::SubmissionQueueFull) => RefusalCause::SubmitNeeded,
132 Some(RingCondition::CompletionQueueTooFull) => RefusalCause::DrainNeeded,
133 Some(RingCondition::SubmitInProgress) => RefusalCause::Busy,
134 Some(RingCondition::Corrupt) => RefusalCause::Unusable(windows_fault(error)),
135 Some(_) => RefusalCause::Unknown(windows_fault(error)),
136 None => match error.kind() {
137 io::ErrorKind::Unsupported => RefusalCause::Unsupported,
138 io::ErrorKind::InvalidInput => RefusalCause::Invalid,
139 _ => RefusalCause::Unknown(windows_fault(error)),
140 },
141 }
142}
143
144#[derive(Clone, Copy, Debug)]
146pub(crate) struct Sidecar {
147 token: SubmissionToken,
148 target: IoTargetKey,
149 kind: Kind,
150}
151
152#[derive(Clone, Copy, Debug)]
154enum Kind {
155 Write(u32),
157 Read,
158 Commit,
159}
160
161struct Slot {
163 target: Win32FileTarget,
164 registered: Option<RegisteredFile>,
166}
167
168#[cfg(test)]
170pub(crate) type RingHook<B> = Box<dyn FnOnce(&IoRing<B, Sidecar>)>;
171
172pub(crate) struct Construction<B> {
178 pub(crate) registration_bound: Duration,
180 #[cfg(test)]
183 pub(crate) on_ring: Option<RingHook<B>>,
184 ring: std::marker::PhantomData<fn(B)>,
186}
187
188impl<B> Default for Construction<B> {
189 fn default() -> Self {
191 Self {
192 registration_bound: crate::dioring::REGISTRATION_BOUND,
193 #[cfg(test)]
194 on_ring: None,
195 ring: std::marker::PhantomData,
196 }
197 }
198}
199
200#[cfg(test)]
201impl<B> Construction<B> {
202 pub(crate) fn faulted(
206 registration_bound: Duration,
207 ) -> (Self, windows_ioring_sys::sys::CallFaultControl) {
208 let (faults, control) = windows_ioring_sys::sys::call_faults();
209 let on_ring: RingHook<B> = Box::new(move |ring| {
210 let _ = windows_ioring_sys::sys::register(ring, Box::new(faults));
211 });
212 let construction = Self {
213 registration_bound,
214 on_ring: Some(on_ring),
215 ring: std::marker::PhantomData,
216 };
217 (construction, control)
218 }
219}
220
221pub struct WindowsBackendSetup<R> {
223 pub submission_queue_size: u32,
225 pub completion_queue_size: u32,
227 pub targets: Vec<(IoTargetKey, Win32FileTarget)>,
229 pub buffers: Vec<R>,
231}
232
233#[derive(Debug)]
235pub enum WindowsBackendRefusal {
236 DuplicateKey(IoTargetKey),
238 Ring(io::Error),
240}
241
242pub struct WindowsBackendError<R> {
245 pub reason: WindowsBackendRefusal,
247 pub targets: Vec<(IoTargetKey, Win32FileTarget)>,
249 pub buffers: Option<Vec<R>>,
251}
252
253impl<R> std::fmt::Debug for WindowsBackendError<R> {
254 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255 f.debug_struct("WindowsBackendError")
256 .field("reason", &self.reason)
257 .field("targets", &self.targets.len())
258 .field("buffers", &self.buffers.as_ref().map(Vec::len))
259 .finish()
260 }
261}
262
263pub struct WindowsBackend<B: IoBuf, R: IoBufMut = Vec<u8>> {
271 ring: IoRing<B, Sidecar, Owned>,
274 registered: Option<RegisteredBuffers<R>>,
275 targets: HashMap<IoTargetKey, Slot>,
276 owned_read: Option<OwnedRead<B>>,
278 event: OwnedHandle,
280 queued: bool,
282 #[cfg(feature = "fault-injection")]
285 injected: Vec<(IoTargetKey, u32)>,
286}
287
288impl<B: IoBufMut, R: IoBufMut> WindowsBackend<B, R> {
289 pub fn new(setup: WindowsBackendSetup<R>) -> Result<Self, WindowsBackendError<R>> {
296 let mut backend = Self::without_owned_reads(setup)?;
297 backend.enable_owned_reads();
298 Ok(backend)
299 }
300
301 pub(crate) fn enable_owned_reads(&mut self) {
303 self.owned_read = Some(push_owned_read::<B>);
304 }
305}
306
307impl<B: IoBuf, R: IoBufMut> WindowsBackend<B, R> {
308 pub fn without_owned_reads(
316 setup: WindowsBackendSetup<R>,
317 ) -> Result<Self, WindowsBackendError<R>> {
318 Self::construct(setup, Construction::default())
319 }
320
321 pub(crate) fn construct(
327 setup: WindowsBackendSetup<R>,
328 construction: Construction<B>,
329 ) -> Result<Self, WindowsBackendError<R>> {
330 let Construction {
331 registration_bound,
332 #[cfg(test)]
333 on_ring,
334 ..
335 } = construction;
336 let WindowsBackendSetup {
337 submission_queue_size,
338 completion_queue_size,
339 targets,
340 buffers,
341 } = setup;
342 if let Some(key) = durable_ioring::driver::first_repeat(targets.iter().map(|(key, _)| *key))
343 {
344 return Err(WindowsBackendError {
345 reason: WindowsBackendRefusal::DuplicateKey(key),
346 targets,
347 buffers: Some(buffers),
348 });
349 }
350 let fail = |error, targets, buffers| WindowsBackendError {
351 reason: WindowsBackendRefusal::Ring(error),
352 targets,
353 buffers,
354 };
355 let mut ring = match IoRing::with_inventory(submission_queue_size, completion_queue_size) {
356 Ok(ring) => ring,
357 Err(error) => return Err(fail(error, targets, Some(buffers))),
358 };
359 #[cfg(test)]
360 if let Some(on_ring) = on_ring {
361 on_ring(&ring);
362 }
363 let handles = targets
364 .iter()
365 .map(|(_, target)| target.handle().clone())
366 .collect();
367 let files = match register_files(&mut ring, handles, registration_bound) {
368 Ok(files) => files,
369 Err(error) => return Err(fail(error, targets, Some(buffers))),
370 };
371 let registered = match register_buffers(&mut ring, buffers, registration_bound) {
372 Ok(registered) => registered,
373 Err((error, buffers)) => return Err(fail(error, targets, buffers)),
374 };
375 let event = match ring.completion_event() {
376 Ok(event) => event,
377 Err(error) => return Err(fail(error, targets, None)),
378 };
379 let ring = ring.into_owned().unwrap_or_else(|_| {
383 panic!(
384 "a registration was left in flight on the backend's ring: it cannot be sealed \
385 against operations the ring holds nothing for"
386 )
387 });
388 let slots = targets
389 .into_iter()
390 .enumerate()
391 .map(|(i, (key, target))| {
392 let index = files
393 .as_ref()
394 .and_then(|files| files.get(u32::try_from(i).ok()?))
395 .expect("every target given at construction was registered, in order");
396 (
397 key,
398 Slot {
399 target,
400 registered: Some(index),
401 },
402 )
403 })
404 .collect();
405 Ok(Self {
406 ring,
407 registered,
408 targets: slots,
409 owned_read: None,
410 event,
411 queued: false,
412 #[cfg(feature = "fault-injection")]
413 injected: Vec::new(),
414 })
415 }
416
417 pub(crate) fn wake(
428 &mut self,
429 on_wake: impl Fn() + Send + Sync + 'static,
430 env: Option<&mut windows_threadpool_sys::callback_env::CallbackEnviron<'_>>,
431 ) -> io::Result<windows_ioring_sys::CompletionWake> {
432 windows_ioring_sys::CompletionWake::new(&mut self.ring, on_wake, env)
433 }
434
435 pub(crate) fn completion_wait_handle(&self) -> io::Result<OwnedHandle> {
443 duplicate_handle_with_access(&self.event, Access::SYNCHRONIZE)
444 }
445
446 #[cfg(test)]
450 pub(crate) fn submit_faults(&self) -> windows_ioring_sys::sys::SubmitFaultControl {
451 let (faults, control) = windows_ioring_sys::sys::submit_faults();
452 let _ = windows_ioring_sys::sys::register(&self.ring, Box::new(faults));
453 control
454 }
455
456 #[cfg(test)]
460 pub(crate) fn call_faults(&self) -> windows_ioring_sys::sys::CallFaultControl {
461 let (faults, control) = windows_ioring_sys::sys::call_faults();
462 let _ = windows_ioring_sys::sys::register(&self.ring, Box::new(faults));
463 control
464 }
465
466 #[must_use]
468 pub fn target(&self, key: IoTargetKey) -> Option<&Win32FileTarget> {
469 self.targets.get(&key).map(|slot| &slot.target)
470 }
471
472 #[cfg(test)]
475 pub(crate) fn registration_index(&self, key: IoTargetKey) -> Option<u32> {
476 self.targets
477 .get(&key)
478 .and_then(|slot| slot.registered)
479 .map(RegisteredFile::index)
480 }
481
482 pub fn registered_buffer(&mut self, index: u32) -> io::Result<&[u8]> {
488 self.registration()?.get(index)
489 }
490
491 pub fn registered_buffer_mut(&mut self, index: u32) -> io::Result<&mut [u8]> {
497 self.registration()?.get_mut(index)
498 }
499
500 fn registration(&mut self) -> io::Result<&mut RegisteredBuffers<R>> {
501 self.registered.as_mut().ok_or_else(|| {
502 io::Error::new(
503 io::ErrorKind::InvalidInput,
504 "this backend was built without registered buffers",
505 )
506 })
507 }
508
509 fn admits(&self, target: IoTargetKey, op: &SubmissionOp<B>) -> Result<(), RefusalCause> {
516 let Some(slot) = self.targets.get(&target) else {
517 return Err(RefusalCause::UnknownTarget);
518 };
519 let access = slot.target.access();
521 let covered = match op {
522 SubmissionOp::Write { .. } => access.can_write(),
523 SubmissionOp::Read { .. } => access.can_read(),
524 SubmissionOp::Commit => true,
525 };
526 if !covered {
527 return Err(RefusalCause::AccessDenied);
528 }
529 if self.owned_read.is_none()
530 && matches!(
531 op,
532 SubmissionOp::Read {
533 data: SubmissionData::Owned(_),
534 ..
535 }
536 )
537 {
538 return Err(RefusalCause::Unsupported);
539 }
540 let (offset, data) = match op {
541 SubmissionOp::Write { offset, data, .. } | SubmissionOp::Read { offset, data } => {
542 (*offset, data)
543 }
544 SubmissionOp::Commit => return Ok(()),
545 };
546 let geometry = slot.target.geometry();
547 let admitted = match data {
548 SubmissionData::Owned(buffer) => {
549 let length = u64::try_from(buffer.bytes_len()).unwrap_or(u64::MAX);
550 u32::try_from(length).is_ok()
553 && geometry.admits(offset, length)
554 && geometry.admits_buffer(buffer.stable_ptr() as usize)
555 }
556 SubmissionData::Registered(region) => {
557 if self.registered.is_none() {
558 return Err(RefusalCause::Unsupported);
559 }
560 geometry.admits(offset, u64::from(region.length))
561 }
562 };
563 if admitted {
564 Ok(())
565 } else {
566 Err(RefusalCause::Invalid)
567 }
568 }
569}
570
571macro_rules! push_to {
574 ($batch:expr, $file:expr, $via:expr, $read:expr, $registered:expr, $op:expr, $sidecar:expr) => {{
575 let options = PushOptions::new();
576 match $op {
577 Op::Write {
578 offset,
579 data: SubmissionData::Owned(buffer),
580 caching,
581 } => $batch.write_owned($file, buffer, $sidecar, offset, options, caching),
582 Op::Write {
583 offset,
584 data: SubmissionData::Registered(region),
585 caching,
586 } => $batch.write_registered_owned(
587 $file,
588 $registered.expect("refusal() checked for a registration"),
589 span(region),
590 $sidecar,
591 offset,
592 options,
593 caching,
594 ),
595 Op::Read {
596 offset,
597 data: SubmissionData::Owned(buffer),
598 } => $read.expect("admits() checked for an owned read")(
599 &mut $batch,
600 $via,
601 buffer,
602 $sidecar,
603 offset,
604 ),
605 Op::Read {
606 offset,
607 data: SubmissionData::Registered(region),
608 } => $batch.read_registered_owned(
609 $file,
610 $registered.expect("refusal() checked for a registration"),
611 span(region),
612 $sidecar,
613 offset,
614 options,
615 ),
616 Op::Commit => $batch.flush_owned(
617 $file,
618 $sidecar,
619 FlushCoverage::Unordered,
620 FlushMode::Default,
621 ),
622 }
623 }};
624}
625
626#[derive(Clone, Copy)]
628enum Via<'a> {
629 Registered(&'a RegisteredFile),
630 Shared(&'a SharedHandle),
631}
632
633type OwnedRead<B> = for<'r, 'v> fn(
636 &mut Batch<'r, B, Sidecar, Owned>,
637 Via<'v>,
638 B,
639 Sidecar,
640 u64,
641) -> Result<OperationId, PushRefused<B, Sidecar>>;
642
643fn push_owned_read<B: IoBufMut>(
645 batch: &mut Batch<'_, B, Sidecar, Owned>,
646 via: Via<'_>,
647 buffer: B,
648 sidecar: Sidecar,
649 offset: u64,
650) -> Result<OperationId, PushRefused<B, Sidecar>> {
651 let options = PushOptions::new();
652 match via {
653 Via::Registered(index) => batch.read_owned(index, buffer, sidecar, offset, options),
654 Via::Shared(handle) => batch.read_owned(handle, buffer, sidecar, offset, options),
655 }
656}
657
658enum Op<B> {
660 Write {
661 offset: u64,
662 data: SubmissionData<B>,
663 caching: windows_ioring_sys::WriteCaching,
664 },
665 Read {
666 offset: u64,
667 data: SubmissionData<B>,
668 },
669 Commit,
670}
671
672fn span(region: RegisteredRegion) -> RegisteredSpan {
674 RegisteredSpan {
675 buffer_index: region.index,
676 offset: region.offset,
677 len: region.length,
678 }
679}
680
681fn ring_caching(caching: WriteCaching) -> windows_ioring_sys::WriteCaching {
683 match caching {
684 WriteCaching::Cached => windows_ioring_sys::WriteCaching::Cached,
685 WriteCaching::WriteThrough => windows_ioring_sys::WriteCaching::WriteThrough,
686 }
687}
688
689fn rebuilt<B>(
691 token: SubmissionToken,
692 target: IoTargetKey,
693 shape: Shape,
694 payload: Option<B>,
695) -> Submission<B> {
696 let data = |region| match (payload, region) {
697 (Some(buffer), _) => SubmissionData::Owned(buffer),
698 (None, Some(region)) => SubmissionData::Registered(region),
699 (None, None) => unreachable!("a refused owned push hands its buffer back"),
700 };
701 let op = match shape {
702 Shape::Write {
703 offset,
704 region,
705 caching,
706 } => SubmissionOp::Write {
707 offset,
708 data: data(region),
709 caching,
710 },
711 Shape::Read { offset, region } => SubmissionOp::Read {
712 offset,
713 data: data(region),
714 },
715 Shape::Commit => SubmissionOp::Commit,
716 };
717 Submission { token, target, op }
718}
719
720#[derive(Clone, Copy)]
722enum Shape {
723 Write {
724 offset: u64,
725 region: Option<RegisteredRegion>,
726 caching: WriteCaching,
727 },
728 Read {
729 offset: u64,
730 region: Option<RegisteredRegion>,
731 },
732 Commit,
733}
734
735impl Shape {
736 fn of<B>(op: &SubmissionOp<B>) -> Self {
737 let region = |data: &SubmissionData<B>| match data {
738 SubmissionData::Owned(_) => None,
739 SubmissionData::Registered(region) => Some(*region),
740 };
741 match op {
742 SubmissionOp::Write {
743 offset,
744 data,
745 caching,
746 } => Shape::Write {
747 offset: *offset,
748 region: region(data),
749 caching: *caching,
750 },
751 SubmissionOp::Read { offset, data } => Shape::Read {
752 offset: *offset,
753 region: region(data),
754 },
755 SubmissionOp::Commit => Shape::Commit,
756 }
757 }
758}
759
760fn kind<B: IoBuf>(op: &SubmissionOp<B>) -> Kind {
762 match op {
763 SubmissionOp::Write { data, .. } => Kind::Write(match data {
764 SubmissionData::Owned(buffer) => u32::try_from(buffer.bytes_len()).unwrap_or(u32::MAX),
765 SubmissionData::Registered(region) => region.length,
766 }),
767 SubmissionOp::Read { .. } => Kind::Read,
768 SubmissionOp::Commit => Kind::Commit,
769 }
770}
771
772impl<B: IoBuf, R: IoBufMut> IoBackend for WindowsBackend<B, R> {
773 type Target = Win32FileTarget;
774 type Buffer = B;
775 type Notification = CompletionEvent;
776
777 fn attach(
778 &mut self,
779 key: IoTargetKey,
780 target: Win32FileTarget,
781 ) -> Result<(), DuplicateTarget<Win32FileTarget>> {
782 if self.targets.contains_key(&key) {
783 return Err(DuplicateTarget { key, target });
784 }
785 self.targets.insert(
786 key,
787 Slot {
788 target,
789 registered: None,
790 },
791 );
792 Ok(())
793 }
794
795 fn detach(&mut self, key: IoTargetKey) -> Option<Win32FileTarget> {
796 self.targets.remove(&key).map(|slot| slot.target)
797 }
798
799 fn registered_buffers(&self) -> u32 {
800 self.registered.as_ref().map_or(0, RegisteredBuffers::len)
801 }
802
803 fn check(&self, target: IoTargetKey, op: &SubmissionOp<B>) -> Result<(), RefusalCause> {
804 self.admits(target, op)?;
805 let region = match op {
808 SubmissionOp::Write {
809 data: SubmissionData::Registered(region),
810 ..
811 }
812 | SubmissionOp::Read {
813 data: SubmissionData::Registered(region),
814 ..
815 } => *region,
816 _ => return Ok(()),
817 };
818 match &self.registered {
819 Some(registered) => registered.check_span(span(region)).map_err(refusal_cause),
820 None => Err(RefusalCause::Unsupported),
821 }
822 }
823
824 fn push(&mut self, submission: Submission<B>) -> Result<(), Refused<B>> {
825 if let Err(cause) = self.admits(submission.target, &submission.op) {
826 return Err(Refused { cause, submission });
827 }
828 let Submission { token, target, op } = submission;
829 let shape = Shape::of(&op);
830 let sidecar = Sidecar {
831 token,
832 target,
833 kind: kind(&op),
834 };
835 let op = match op {
836 SubmissionOp::Write {
837 offset,
838 data,
839 caching,
840 } => Op::Write {
841 offset,
842 data,
843 caching: ring_caching(caching),
844 },
845 SubmissionOp::Read { offset, data } => Op::Read { offset, data },
846 SubmissionOp::Commit => Op::Commit,
847 };
848 let slot = &self.targets[&target];
849 let registered = self.registered.as_ref();
850 let read = self.owned_read;
851 let mut batch = Batch::new(&mut self.ring);
852 let pushed: Result<OperationId, PushRefused<B, Sidecar>> = match &slot.registered {
853 Some(index) => {
854 let via = Via::Registered(index);
855 push_to!(batch, index, via, read, registered, op, sidecar)
856 }
857 None => {
858 let handle = slot.target.handle();
859 let via = Via::Shared(handle);
860 push_to!(batch, handle, via, read, registered, op, sidecar)
861 }
862 };
863 match pushed {
864 Ok(_) => {
865 batch.leave_queued();
868 self.queued = true;
869 Ok(())
870 }
871 Err(refused) => {
872 batch.leave_queued();
875 let PushRefused { error, payload, .. } = refused;
876 Err(Refused {
877 cause: refusal_cause(error),
878 submission: rebuilt(token, target, shape, payload),
879 })
880 }
881 }
882 }
883
884 fn submit(&mut self) -> Result<(), RefusalCause> {
885 if !self.queued {
886 return Ok(());
887 }
888 match Batch::new(&mut self.ring).submit() {
889 Ok(_) => {
890 self.queued = false;
891 Ok(())
892 }
893 Err(error) => Err(refusal_cause(error)),
894 }
895 }
896
897 fn pop(&mut self) -> Result<Option<Completion<B>>, IoFault> {
898 let Some((completion, buffer, sidecar)) = self.ring.try_pop().map_err(windows_fault)?
901 else {
902 return Ok(None);
903 };
904 #[cfg(feature = "fault-injection")]
905 let completion = match sidecar.kind {
906 Kind::Commit => match self
907 .injected
908 .iter()
909 .position(|(target, _)| *target == sidecar.target)
910 {
911 Some(index) => {
912 let (_, code) = self.injected.remove(index);
913 completion
914 .with_injected_failure(windows_ioring_sys::InjectedFailure::Win32(code))
915 }
916 None => completion,
917 },
918 Kind::Write(_) | Kind::Read => completion,
919 };
920 let result = match sidecar.kind {
921 Kind::Write(requested) => transfer(completion.result(), true, requested),
922 Kind::Read => transfer(completion.result(), false, 0),
923 Kind::Commit => completion.result().map(|_| 0).map_err(windows_fault),
924 };
925 Ok(Some(Completion {
926 token: sidecar.token,
927 result,
928 buffer,
929 }))
930 }
931
932 fn notification(&self) -> Result<CompletionEvent, IoFault> {
933 self.event
934 .try_clone()
935 .map(CompletionEvent::new)
936 .map_err(windows_fault)
937 }
938}
939
940#[cfg(feature = "fault-injection")]
944impl<B: IoBuf, R: IoBufMut> WindowsBackend<B, R> {
945 pub fn fail_next_commit(&mut self, target: IoTargetKey, code: u32) {
948 self.injected.push((target, code));
949 }
950}