windows_ioring_sys/batch/push.rs
1// Copyright (c) 2026 Mike Grier
2// Split from batch.rs at f6e92aa7.
3//! The push surface: the calls that queue one operation onto a ring.
4//!
5//! Separated from [`super`], which keeps the type definitions, the private
6//! plumbing every push shares (`require`, `begin_owned`, `finish_owned`,
7//! `check_registration_ring`) and the submit path.
8//!
9//! The cut runs this way round because Rust's privacy is asymmetric: a child
10//! module can see its ancestors' private items, a parent cannot see its
11//! children's. Moving the *definitions* down would have broken the parent's
12//! use of `PushOptions::sqe_flags`, `WriteCaching::raw` and the rest, all of
13//! which are private; moving the *consumers* down costs nothing.
14
15use super::*;
16
17/// Unwrap a check made before the build, or return a [`PushRefused`] handing
18/// back what the push was given (M30.1, [D-80](../../DESIGN-NOTES.md#d-80)).
19///
20/// `payload` and `extra` are moved only on the refusing arm, which returns, so
21/// the push keeps both on the arm that continues.
22macro_rules! refuse {
23 ($result:expr, $payload:expr, $extra:expr) => {
24 match $result {
25 Ok(value) => value,
26 Err(error) => return Err(PushRefused::new(error, $payload, $extra)),
27 }
28 };
29}
30
31impl<'ring, T, X, K> Batch<'ring, T, X, K> {
32 /// Queue a write of `buffer.bytes_len()` bytes to `file` at `offset`,
33 /// whose buffer the **ring** holds (`D-71`, `D-73`).
34 ///
35 /// The raw counterpart to [`Batch::write_owned`]: prefer that unless `file`
36 /// needs to address a raw `FileRef` directly, without a `SharedHandle`'s
37 /// shared-ownership bookkeeping.
38 ///
39 /// # Safety
40 ///
41 /// If `file` is [`FileRef::Raw`], the handle must be valid, opened with
42 /// write access, and must remain valid -- not closed, not reused for a
43 /// different object -- until this operation's completion is observed (via a
44 /// popped [`Completion`]) or until the ring runs down (M8, PR #20 review
45 /// response). A [`FileRef::Registered`] target needs none of this.
46 ///
47 /// # Errors
48 ///
49 /// [`io::ErrorKind::Unsupported`] if the ring was not probed as supporting
50 /// [`Op::Write`]; [`io::ErrorKind::InvalidInput`] if the buffer is longer
51 /// than `u32::MAX`; an [`crate::IoRingError`] wrapping
52 /// `IORING_E_SUBMISSION_QUEUE_FULL` if the queue has no room (M3.3, not
53 /// auto-flushed -- see [`Batch`]'s own docs); or any other error from
54 /// `BuildIoRingWriteFile`. On any error the buffer and `extra` are handed
55 /// back in the [`PushRefused`] ([D-80](../../DESIGN-NOTES.md#d-80)).
56 pub unsafe fn write_raw_owned(
57 &mut self,
58 file: impl Into<FileRef>,
59 buffer: T,
60 extra: X,
61 offset: u64,
62 options: PushOptions,
63 caching: WriteCaching,
64 ) -> Result<OperationId, PushRefused<T, X>>
65 where
66 T: IoBuf,
67 {
68 refuse!(self.require(Op::Write), Some(buffer), extra);
69 let len = refuse!(checked_len(buffer.bytes_len()), Some(buffer), extra);
70 let address = buffer.stable_ptr().cast::<c_void>().cast_mut();
71 let target = refuse!(
72 handle_ref(file.into(), self.ring.ring_id()),
73 Some(buffer),
74 extra
75 );
76 let (user_data, id) = refuse!(self.begin_owned(), Some(buffer), extra);
77 // SAFETY: as `write_raw` -- `address` is `IoBuf`'s promised stable
78 // pointer, valid for `len` bytes, and stays valid across the move into
79 // the inventory because that stability is the trait's contract rather
80 // than a property of where the value lives.
81 let hr = unsafe {
82 crate::sys::build_write(
83 self.ring.ring_id(),
84 self.ring.raw_handle(),
85 target,
86 raw_buffer_ref(address),
87 len,
88 offset,
89 caching.raw(),
90 user_data,
91 options.sqe_flags(),
92 )
93 };
94 self.finish_owned(hr, id, Some(buffer), extra, Held::default())
95 }
96
97 /// Queue a read against a guarded file, with the **ring** holding both the
98 /// buffer and the guard (`D-73`).
99 ///
100 /// The guarded counterpart to [`Batch::read_raw_owned`]. The guard goes into
101 /// the ring's own slot rather than into `T`, which is what keeps the
102 /// caller's parameters free of this crate's internals.
103 ///
104 /// # Errors
105 ///
106 /// As [`Batch::read_raw_owned`].
107 pub fn read_owned<F: FileTarget>(
108 &mut self,
109 file: &F,
110 mut buffer: T,
111 extra: X,
112 offset: u64,
113 options: PushOptions,
114 ) -> Result<OperationId, PushRefused<T, X>>
115 where
116 T: IoBufMut,
117 {
118 refuse!(self.require(Op::Read), Some(buffer), extra);
119 let len = refuse!(checked_len(buffer.bytes_len()), Some(buffer), extra);
120 let address = buffer.stable_mut_ptr().cast::<c_void>();
121 let target = refuse!(
122 handle_ref(file.as_file_ref(), self.ring.ring_id()),
123 Some(buffer),
124 extra
125 );
126 let (user_data, id) = refuse!(self.begin_owned(), Some(buffer), extra);
127 let held = Held {
128 guard: Some(file.guard().into()),
129 registration: None,
130 };
131 // SAFETY: as `read` -- `target` stays valid at least as long as the
132 // operation, because the ring holds `file`'s guard until the pop that
133 // completes it.
134 let hr = unsafe {
135 crate::sys::build_read(
136 self.ring.ring_id(),
137 self.ring.raw_handle(),
138 target,
139 raw_buffer_ref(address),
140 len,
141 offset,
142 user_data,
143 options.sqe_flags(),
144 )
145 };
146 self.finish_owned(hr, id, Some(buffer), extra, held)
147 }
148
149 /// Queue a write against a guarded file, with the **ring** holding both
150 /// the buffer and the guard (`D-73`).
151 ///
152 /// The guarded counterpart to [`Batch::write_raw_owned`].
153 ///
154 /// # Errors
155 ///
156 /// As [`Batch::write_raw_owned`].
157 pub fn write_owned<F: FileTarget>(
158 &mut self,
159 file: &F,
160 buffer: T,
161 extra: X,
162 offset: u64,
163 options: PushOptions,
164 caching: WriteCaching,
165 ) -> Result<OperationId, PushRefused<T, X>>
166 where
167 T: IoBuf,
168 {
169 refuse!(self.require(Op::Write), Some(buffer), extra);
170 let len = refuse!(checked_len(buffer.bytes_len()), Some(buffer), extra);
171 let address = buffer.stable_ptr().cast::<c_void>().cast_mut();
172 let target = refuse!(
173 handle_ref(file.as_file_ref(), self.ring.ring_id()),
174 Some(buffer),
175 extra
176 );
177 let (user_data, id) = refuse!(self.begin_owned(), Some(buffer), extra);
178 let held = Held {
179 guard: Some(file.guard().into()),
180 registration: None,
181 };
182 // SAFETY: as `write`.
183 let hr = unsafe {
184 crate::sys::build_write(
185 self.ring.ring_id(),
186 self.ring.raw_handle(),
187 target,
188 raw_buffer_ref(address),
189 len,
190 offset,
191 caching.raw(),
192 user_data,
193 options.sqe_flags(),
194 )
195 };
196 self.finish_owned(hr, id, Some(buffer), extra, held)
197 }
198
199 /// Queue a read whose buffer the **ring** holds, returning the operation's
200 /// name (`D-71`, `D-73`).
201 ///
202 /// The raw counterpart to [`Batch::read_owned`]: prefer that unless `file`
203 /// needs to address a raw `FileRef` directly, without a `SharedHandle`'s
204 /// shared-ownership bookkeeping. The caller hands over the buffer and its sidecar and
205 /// receives an [`OperationId`], which names the operation and grants
206 /// nothing; the buffer comes back from [`IoRing::try_pop`] and from nowhere
207 /// else. A consumer that never holds a token cannot lose one, which is the
208 /// whole of `D-55`.
209 ///
210 /// # Safety
211 ///
212 /// If `file` is [`FileRef::Raw`], the handle must be valid, opened with
213 /// read access, and must remain valid -- not closed, not reused for a
214 /// different object -- until this operation's completion is observed (via a
215 /// popped [`Completion`]) or until the ring runs down (M8, PR #20 review
216 /// response). A [`FileRef::Registered`] target needs none of this.
217 ///
218 /// # Errors
219 ///
220 /// [`io::ErrorKind::Unsupported`] if the ring was not probed as supporting
221 /// [`Op::Read`]; [`io::ErrorKind::InvalidInput`] if the buffer is longer
222 /// than `u32::MAX`; an [`crate::IoRingError`] wrapping
223 /// `IORING_E_SUBMISSION_QUEUE_FULL` if the queue has no room (M3.3, not
224 /// auto-flushed -- see [`Batch`]'s own docs); or any other error from
225 /// `BuildIoRingReadFile`. On any error the buffer and `extra` are **handed
226 /// back** in the [`PushRefused`] ([D-80](../../DESIGN-NOTES.md#d-80)): a
227 /// refused push queued no SQE, so nothing will ever complete to return
228 /// them, and the refusal is the only way back.
229 pub unsafe fn read_raw_owned(
230 &mut self,
231 file: impl Into<FileRef>,
232 mut buffer: T,
233 extra: X,
234 offset: u64,
235 options: PushOptions,
236 ) -> Result<OperationId, PushRefused<T, X>>
237 where
238 T: IoBufMut,
239 {
240 refuse!(self.require(Op::Read), Some(buffer), extra);
241 let len = refuse!(checked_len(buffer.bytes_len()), Some(buffer), extra);
242 let address = buffer.stable_mut_ptr().cast::<c_void>();
243 let target = refuse!(
244 handle_ref(file.into(), self.ring.ring_id()),
245 Some(buffer),
246 extra
247 );
248 let (user_data, id) = refuse!(self.begin_owned(), Some(buffer), extra);
249 // SAFETY: as `read_raw` -- `address` is `IoBufMut`'s promised stable
250 // pointer, valid for `len` bytes, and it stays valid across the move
251 // into the inventory below because that stability is the trait's
252 // contract rather than a property of where the value lives.
253 let hr = unsafe {
254 crate::sys::build_read(
255 self.ring.ring_id(),
256 self.ring.raw_handle(),
257 target,
258 raw_buffer_ref(address),
259 len,
260 offset,
261 user_data,
262 options.sqe_flags(),
263 )
264 };
265 self.finish_owned(hr, id, Some(buffer), extra, Held::default())
266 }
267
268 /// Queue a flush of `file`'s buffered data.
269 ///
270 /// `coverage` decides whether this flush covers the operations queued
271 /// before it. It is required rather than defaulted because there is no
272 /// safe default -- see [`FlushCoverage`] and the measured contract below.
273 ///
274 /// `mode` is `FILE_FLUSH_MODE`; [`FlushMode::Default`] is the durability
275 /// barrier. Note that [`FlushMode::NoSync`] issues no device sync and so
276 /// makes nothing durable, whatever `coverage` says.
277 ///
278 /// There is no buffer, so this returns the raw `UserData` identity
279 /// rather than an inventory entry: nothing owns a buffer for a completion to
280 /// hand back. Prefer [`Batch::flush_owned`] unless `file` needs to address a
281 /// raw `FileRef` directly.
282 ///
283 /// # A flush is the ring's only durability primitive
284 ///
285 /// **The ring has no FUA.** `BuildIoRingWriteFile`'s entire flag set is
286 /// `{FILE_WRITE_FLAGS_NONE, FILE_WRITE_FLAGS_WRITE_THROUGH}`, and
287 /// write-through is a cache-bypass directive to the OS rather than a
288 /// device-level durability guarantee -- whether it becomes a Force Unit
289 /// Access bit depends on the driver, the volume, and whether the device's
290 /// write cache is enabled (see [`WriteCaching`]). So this operation is the
291 /// only way the ring makes anything durable, and only with
292 /// [`FlushCoverage::CoversPrecedingOperations`] and a syncing
293 /// [`FlushMode`].
294 ///
295 /// # The measured contract (D-23 in `DESIGN-NOTES.md`)
296 ///
297 /// **An unflagged flush does not cover preceding writes.** It is an
298 /// ordinary operation competing with them, and it frequently wins: a
299 /// flush pushed after a batch of writes with no barrier was observed
300 /// completing while 17, and on another run 23, of 32 of those writes were
301 /// still outstanding. A caller that reads such a completion as "the
302 /// writes before it are now durable" has lost data it believes it has
303 /// committed, and nothing reports the loss until power fails.
304 ///
305 /// Which *direction* the reordering shows in is device-dependent, and a
306 /// machine where the flush happens to land last anyway proves nothing:
307 /// that is incidental behavior of one device stack, not a guarantee. Only
308 /// the barrier makes it one.
309 ///
310 /// Durability on this ring is therefore a property of an **epoch**, never
311 /// of an individual write, because there is no per-write primitive to
312 /// make it one: stream the writes unflagged, close the epoch with one
313 /// covering flush, and wait on that flush rather than on the writes.
314 /// "Durability on the ring" in `DESIGN-NOTES.md` has the full
315 /// construction, and the three ways to pay for the barrier's ring-wide
316 /// wait.
317 ///
318 /// # Safety
319 ///
320 /// As [`Batch::read_raw_owned`]'s, for a [`FileRef::Raw`] target.
321 ///
322 /// # Errors
323 ///
324 /// [`io::ErrorKind::Unsupported`] if the ring was not probed as
325 /// supporting [`Op::Flush`]; an [`crate::IoRingError`] wrapping
326 /// `IORING_E_SUBMISSION_QUEUE_FULL` if the queue has no room; or any
327 /// other error from `BuildIoRingFlushFile`.
328 pub unsafe fn flush_raw(
329 &mut self,
330 file: impl Into<FileRef>,
331 coverage: FlushCoverage,
332 mode: FlushMode,
333 ) -> io::Result<usize>
334 where
335 K: AllowsRaw,
336 {
337 self.require(Op::Flush)?;
338 let target = handle_ref(file.into(), self.ring.ring_id())?;
339 let user_data = self.ring.reserve_user_data()?;
340 // SAFETY: `self.ring`'s handle is live; `file` is the caller's to
341 // keep alive, forwarded from this function's own contract; there is
342 // no buffer.
343 let hr = unsafe {
344 crate::sys::build_flush(
345 self.ring.ring_id(),
346 self.ring.raw_handle(),
347 target,
348 mode.raw(),
349 user_data,
350 coverage.sqe_flags(),
351 )
352 };
353 if let Err(error) = check(hr) {
354 self.ring.cancel_reservation(user_data);
355 return Err(error);
356 }
357 Ok(user_data)
358 }
359
360 /// Queue a raw flush, with the **ring** holding the sidecar (`D-73`).
361 ///
362 /// The inventory counterpart to [`Batch::flush_raw`]. There is no buffer
363 /// and no guard -- a raw handle is the caller's to keep alive, exactly as
364 /// for [`Batch::flush_raw`] -- so the entry exists purely to carry
365 /// `extra`. That is not a degenerate case: a flush that cannot say which
366 /// group of writes it belongs to forces the caller back into the
367 /// side-table this API exists to remove.
368 ///
369 /// # Safety
370 ///
371 /// As [`Batch::flush_raw`].
372 ///
373 /// # Errors
374 ///
375 /// As [`Batch::flush_raw`].
376 pub unsafe fn flush_raw_owned(
377 &mut self,
378 file: impl Into<FileRef>,
379 extra: X,
380 coverage: FlushCoverage,
381 mode: FlushMode,
382 ) -> Result<OperationId, PushRefused<T, X>> {
383 refuse!(self.require(Op::Flush), None, extra);
384 let target = refuse!(handle_ref(file.into(), self.ring.ring_id()), None, extra);
385 let (user_data, id) = refuse!(self.begin_owned(), None, extra);
386 // SAFETY: as `flush_raw` -- `file` is the caller's to keep alive,
387 // forwarded from this function's own contract; there is no buffer.
388 let hr = unsafe {
389 crate::sys::build_flush(
390 self.ring.ring_id(),
391 self.ring.raw_handle(),
392 target,
393 mode.raw(),
394 user_data,
395 coverage.sqe_flags(),
396 )
397 };
398 self.finish_owned(
399 hr,
400 id,
401 None,
402 extra,
403 Held {
404 guard: None,
405 registration: None,
406 },
407 )
408 }
409
410 /// Queue a flush, with the **ring** holding the file guard (`D-73`).
411 ///
412 /// The guarded counterpart to [`Batch::flush_raw_owned`]. This push stows an
413 /// inventory entry, so the pop that observes its completion returns that
414 /// entry -- [`IoRing::try_pop`] yields `Some((completion, Some((None, extra))))`.
415 /// Only the inner **payload** is `None`, because there is no buffer; the
416 /// sidecar still arrives. An outer `None` is what a `_raw` flush produces
417 /// instead -- see [`IoRing::try_pop`]'s "What each `None` means". The guard
418 /// still has to outlive the operation, and the ring is what holds it.
419 ///
420 /// # Errors
421 ///
422 /// As [`Batch::flush_raw_owned`].
423 pub fn flush_owned<F: FileTarget>(
424 &mut self,
425 file: &F,
426 extra: X,
427 coverage: FlushCoverage,
428 mode: FlushMode,
429 ) -> Result<OperationId, PushRefused<T, X>> {
430 refuse!(self.require(Op::Flush), None, extra);
431 let target = refuse!(
432 handle_ref(file.as_file_ref(), self.ring.ring_id()),
433 None,
434 extra
435 );
436 let (user_data, id) = refuse!(self.begin_owned(), None, extra);
437 let held = Held {
438 guard: Some(file.guard().into()),
439 registration: None,
440 };
441 // SAFETY: as `flush` -- `target` stays valid at least as long as the
442 // ring's hold on `file`'s guard, which lasts until the pop that
443 // completes this operation.
444 let hr = unsafe {
445 crate::sys::build_flush(
446 self.ring.ring_id(),
447 self.ring.raw_handle(),
448 target,
449 mode.raw(),
450 user_data,
451 coverage.sqe_flags(),
452 )
453 };
454 self.finish_owned(hr, id, None, extra, held)
455 }
456
457 /// Queue a cancellation of `target`, with the **ring** holding the file
458 /// guard (`D-73`).
459 ///
460 /// The inventory counterpart to [`Batch::cancel_raw`].
461 ///
462 /// `target` is an [`OperationId`] rather than a bare `UserData`, and this
463 /// checks that **this** ring minted it. Every ring hands out `UserData`
464 /// from its own counter starting at the same value, so one integer
465 /// legitimately names a different operation on each ring -- which is why an
466 /// [`OperationId`] carries the ring that minted it alongside the
467 /// `UserData`, and why that half must not be discarded. Accepting the bare
468 /// integer
469 /// would therefore let an identity from one ring cancel an unrelated
470 /// operation on another, silently, and most readily when both address the
471 /// same file. This is the same check [`Batch`] already applies to a
472 /// [`RegisteredFile`] belonging to the wrong ring.
473 ///
474 /// To cancel a `UserData` this ring never minted an [`OperationId`] for,
475 /// use [`Batch::cancel_owned_raw`], which says so at the call site.
476 ///
477 /// # Errors
478 ///
479 /// [`io::ErrorKind::InvalidInput`] if `target` was minted by a different
480 /// ring; otherwise as [`Batch::cancel_raw`].
481 pub fn cancel_owned<F: FileTarget>(
482 &mut self,
483 file: &F,
484 target: OperationId,
485 extra: X,
486 ) -> Result<OperationId, PushRefused<T, X>> {
487 if target.ring_id() != self.ring.ring_id() {
488 return Err(PushRefused::new(
489 io::Error::new(
490 io::ErrorKind::InvalidInput,
491 "this OperationId was minted by a different IoRing",
492 ),
493 None,
494 extra,
495 ));
496 }
497 self.cancel_owned_raw(file, target.user_data(), extra)
498 }
499
500 /// [`Batch::cancel_owned`] against a raw `UserData`, with no ring check.
501 ///
502 /// For the targets an [`OperationId`] cannot express: a `UserData` observed
503 /// out of band, or -- as this crate's own tests use it -- one chosen
504 /// precisely *because* nothing is outstanding under it, to exercise the
505 /// `ERROR_NOT_FOUND` the kernel reports through the cancel's own
506 /// completion.
507 ///
508 /// Safe rather than `unsafe`, because a cancel returns no memory: naming
509 /// the wrong operation costs a wrong cancellation, not a use-after-free.
510 /// It is a separate method so that cost is visible at the call site instead
511 /// of hidden inside an integer conversion.
512 ///
513 /// # Errors
514 ///
515 /// As [`Batch::cancel_raw`].
516 pub fn cancel_owned_raw<F: FileTarget>(
517 &mut self,
518 file: &F,
519 target: usize,
520 extra: X,
521 ) -> Result<OperationId, PushRefused<T, X>> {
522 refuse!(self.require(Op::Cancel), None, extra);
523 let handle = refuse!(
524 handle_ref(file.as_file_ref(), self.ring.ring_id()),
525 None,
526 extra
527 );
528 let (user_data, id) = refuse!(self.begin_owned(), None, extra);
529 let held = Held {
530 guard: Some(file.guard().into()),
531 registration: None,
532 };
533 // SAFETY: as `cancel_raw`.
534 let hr = unsafe {
535 crate::sys::build_cancel(
536 self.ring.ring_id(),
537 self.ring.raw_handle(),
538 handle,
539 target,
540 user_data,
541 )
542 };
543 self.finish_owned(hr, id, None, extra, held)
544 }
545
546 /// Queue cancellation of the operation identified by `target` (the
547 /// `usize` a prior push returned), against `file`.
548 ///
549 /// A cancel is itself an operation: it completes on its own `UserData`,
550 /// returned here, independently of whether `target` was actually
551 /// outstanding. Cancelling a target that has already completed -- or
552 /// was never outstanding -- reports `ERROR_NOT_FOUND` through *this*
553 /// completion rather than failing to build (M3.6). Prefer
554 /// [`Batch::cancel_owned`] unless `file` needs to address a raw `FileRef`
555 /// directly.
556 ///
557 /// # Safety
558 ///
559 /// As [`Batch::read_raw_owned`]'s, for a [`FileRef::Raw`] target.
560 ///
561 /// # Errors
562 ///
563 /// [`io::ErrorKind::Unsupported`] if the ring was not probed as
564 /// supporting [`Op::Cancel`]; an [`crate::IoRingError`] wrapping
565 /// `IORING_E_SUBMISSION_QUEUE_FULL` if the queue has no room; or any
566 /// other error from `BuildIoRingCancelRequest`.
567 pub unsafe fn cancel_raw(
568 &mut self,
569 file: impl Into<FileRef>,
570 target: usize,
571 ) -> io::Result<usize>
572 where
573 K: AllowsRaw,
574 {
575 self.require(Op::Cancel)?;
576 let handle = handle_ref(file.into(), self.ring.ring_id())?;
577 let user_data = self.ring.reserve_user_data()?;
578 // SAFETY: `self.ring`'s handle is live; `file` is the caller's to
579 // keep alive, forwarded from this function's own contract;
580 // `BuildIoRingCancelRequest` takes no SQE-flags parameter.
581 let hr = unsafe {
582 crate::sys::build_cancel(
583 self.ring.ring_id(),
584 self.ring.raw_handle(),
585 handle,
586 target,
587 user_data,
588 )
589 };
590 if let Err(error) = check(hr) {
591 self.ring.cancel_reservation(user_data);
592 return Err(error);
593 }
594 Ok(user_data)
595 }
596
597 /// Queue registration of `handles` as a ring's file-handle table (M5.1).
598 ///
599 /// `BuildIoRingRegisterFileHandles` *replaces* the ring's entire
600 /// file-handle table rather than appending to it (Win32 docs: "If a
601 /// previous registration exists, this replaces the previous
602 /// registration completely"), which would silently invalidate every
603 /// [`RegisteredFile`] index a prior registration handed out. Rather than
604 /// track and resubmit that whole prior table transparently, this method
605 /// refuses a second registration outright: a ring accepts at most one
606 /// file-handle registration *that assigned an index* in its lifetime.
607 ///
608 /// Two consequences of that rule being enforced against
609 /// [`crate::IoRing::registered_file_count`] rather than a flag (M10.1):
610 /// a zero-length `handles` does not spend the ring's one registration,
611 /// since it hands out no index for a later replacement to invalidate;
612 /// and the count advances when this call *queues*, not when its
613 /// completion succeeds (D-14), so a registration whose completion
614 /// reports failure has still spent it. There is no retry -- a consumer
615 /// whose registration fails must build the registration on a new ring.
616 ///
617 /// The `handles` *slice* only needs to stay valid for this call. The
618 /// kernel reads the array when the registration op *runs*, during a later
619 /// `SubmitIoRing`, not when `BuildIoRingRegisterFileHandles` returns
620 /// (D-32, measured) -- so this copies it into the ring, which holds the
621 /// copy until it can prove the op has run, and builds the SQE from that.
622 /// The handles themselves must still stay open for as long as the
623 /// registration is used -- this crate does not take ownership of them,
624 /// only of their assigned indices' bookkeeping.
625 ///
626 /// Before PR #113 this said the opposite, that the kernel reads the array
627 /// synchronously, and built the SQE from the caller's slice. A temporary
628 /// `&[handle]` then registered whatever its reused stack slot held by the
629 /// time of the submit: correct in a debug build by accident, and
630 /// `ERROR_INVALID_HANDLE` on the first read in a release one.
631 ///
632 /// # Safety
633 ///
634 /// Every handle in `handles` must be valid, and must remain valid for
635 /// as long as the resulting registration is used -- for the ring's
636 /// remaining life, since Win32 has no unregister call (M8, PR #20
637 /// review response). Prefer [`Batch::register_shared_files`], the safe
638 /// counterpart, which discharges this by having the ring own the handles
639 /// for its whole life ([D-81](../../DESIGN-NOTES.md#d-81)). This method
640 /// remains for a caller whose handles cannot be `SharedHandle`s.
641 ///
642 /// # Errors
643 ///
644 /// [`io::ErrorKind::AlreadyExists`] if this ring already has a
645 /// file-handle registration (see above); [`io::ErrorKind::Unsupported`]
646 /// if the ring was not probed as
647 /// supporting [`Op::RegisterFiles`](crate::Op::RegisterFiles);
648 /// [`io::ErrorKind::InvalidInput`] if `handles` has more than
649 /// `u32::MAX` entries; an [`crate::IoRingError`] wrapping
650 /// `IORING_E_SUBMISSION_QUEUE_FULL` if the queue has no room; or any
651 /// other error from `BuildIoRingRegisterFileHandles`.
652 pub unsafe fn register_files(
653 &mut self,
654 handles: &[HANDLE],
655 ) -> io::Result<PendingFileRegistration>
656 where
657 K: AllowsRaw,
658 {
659 self.require(Op::RegisterFiles)?;
660 if self.ring.registered_file_count() > 0 {
661 return Err(io::Error::new(
662 io::ErrorKind::AlreadyExists,
663 "this ring already has a file-handle registration; BuildIoRingRegisterFileHandles \
664 replaces the whole table, so a second call would invalidate every RegisteredFile \
665 index already handed out",
666 ));
667 }
668 let count = checked_len(handles.len())?;
669 let base_index = self.ring.registered_file_count();
670 let user_data = self.ring.reserve_user_data()?;
671 // The kernel reads the array when the op runs, during a later submit
672 // (D-32, measured), so the SQE is built from the ring's copy, never
673 // from the caller's slice.
674 let handles_ptr = self.ring.hold_registered_file_handles(handles.to_vec());
675 // SAFETY: `self.ring`'s handle is live; `handles_ptr` addresses the
676 // copy the ring now holds, which outlives every submit that could run
677 // this SQE.
678 let hr = unsafe {
679 crate::sys::build_register_files(
680 self.ring.ring_id(),
681 self.ring.raw_handle(),
682 count,
683 handles_ptr,
684 user_data,
685 )
686 };
687 if let Err(error) = check(hr) {
688 self.ring.cancel_reservation(user_data);
689 self.ring.release_unqueued_file_handles();
690 return Err(error);
691 }
692 self.ring.reserve_registered_files(count);
693 Ok(PendingFileRegistration {
694 user_data,
695 base_index,
696 count,
697 ring_id: self.ring.ring_id(),
698 })
699 }
700
701 /// Queue registration of `files` as a ring's file-handle table, with the
702 /// **ring** holding every file for the rest of its life (M30.2,
703 /// [D-81](../../DESIGN-NOTES.md#d-81)).
704 ///
705 /// The safe counterpart to [`Batch::register_files`], and the same
706 /// registration in every other respect: one per ring, shared with that
707 /// method, so a ring has at most one file registration whichever call
708 /// made it; a zero-length `files` spends nothing; the `i`-th file is
709 /// [`RegisteredFiles::get`]`(i)` once the registration is claimed.
710 ///
711 /// `register_files` is `unsafe` because Win32 has no unregister, so the
712 /// handles must stay open for the ring's remaining life, and nothing a
713 /// single push holds lasts that long. The ring itself does: it keeps
714 /// `files` beside the arrays the kernel reads late, and its `Drop`
715 /// releases them only after `CloseIoRing`, and only once rundown has
716 /// proved nothing is outstanding -- otherwise it leaks them, as it does
717 /// every other thing the kernel may still be using
718 /// ([D-73](../../DESIGN-NOTES.md#d-73)). A caller may drop its own clones
719 /// as soon as this returns.
720 ///
721 /// A registration whose *completion* reports failure has still spent the
722 /// ring's one registration, and the ring keeps the files anyway: it cannot
723 /// prove the kernel took no reference to them.
724 ///
725 /// # Errors
726 ///
727 /// As [`Batch::register_files`]. On any error `files` is handed back in
728 /// the [`PushRefused`]: a refused registration queued nothing, so the
729 /// kernel never saw a handle.
730 pub fn register_shared_files(
731 &mut self,
732 files: Vec<SharedHandle>,
733 ) -> Result<PendingFileRegistration, PushRefused<Vec<SharedHandle>, ()>>
734 where
735 K: AllowsRaw,
736 {
737 let handles: Vec<HANDLE> = files.iter().map(SharedHandle::as_raw_handle).collect();
738 // SAFETY: every handle belongs to a `SharedHandle` in `files`, which the
739 // ring takes below and holds until `Drop` has closed the ring -- or
740 // forever, if rundown cannot prove the kernel is finished. The ring
741 // therefore outlives every use of the registration, which is the
742 // whole of `register_files`' contract.
743 match unsafe { self.register_files(&handles) } {
744 Ok(pending) => {
745 self.ring.hold_registered_shared_files(files);
746 Ok(pending)
747 }
748 Err(error) => Err(PushRefused::new(error, Some(files), ())),
749 }
750 }
751
752 /// Queue registration of `buffers` as a ring's registered-buffer table
753 /// (M5.2).
754 ///
755 /// As [`Batch::register_files`]: `BuildIoRingRegisterBuffers` replaces
756 /// the ring's entire buffer table rather than appending to it, so this
757 /// method refuses a second registration outright -- a ring accepts at
758 /// most one buffer registration *that assigned an index* in its
759 /// lifetime, with the same zero-length and failed-registration
760 /// consequences [`Batch::register_files`] spells out (M10.1).
761 ///
762 /// Two things must outlive this call (D-32):
763 ///
764 /// - the *bytes each entry points at* -- the registration case `IoBuf`'s
765 /// contract was extended to cover (D-11), so `buffers` is taken by
766 /// value and kept inside the returned [`RegisteredBuffers`] once
767 /// claimed;
768 /// - the `IORING_BUFFER_INFO` array itself, which the kernel reads when
769 /// the registration op runs, during a later `SubmitIoRing` -- as it
770 /// does [`Batch::register_files`]' handle array. This crate builds that
771 /// array and hands it to the ring, which holds it until it can prove the
772 /// op has run, so a caller has nothing to do -- but it is why
773 /// [`crate::IoRing::push_raw`] callers building either registration op
774 /// themselves must not pass a temporary.
775 ///
776 /// # Errors
777 ///
778 /// As [`Batch::register_files`], for [`Op::RegisterBuffers`](crate::Op::RegisterBuffers)
779 /// and `BuildIoRingRegisterBuffers`, plus [`io::ErrorKind::InvalidInput`]
780 /// if any buffer is longer than `u32::MAX`. On any error `buffers` is
781 /// handed back whole in the [`PushRefused`]
782 /// ([D-80](../../DESIGN-NOTES.md#d-80)): the kernel never saw an address
783 /// from a refused registration.
784 pub fn register_buffers<B: IoBufMut>(
785 &mut self,
786 mut buffers: Vec<B>,
787 ) -> Result<PendingBufferRegistration<B>, PushRefused<Vec<B>, ()>>
788 where
789 K: AllowsRaw,
790 {
791 refuse!(self.require(Op::RegisterBuffers), Some(buffers), ());
792 if self.ring.registered_buffer_count() > 0 {
793 return Err(PushRefused::new(
794 io::Error::new(
795 io::ErrorKind::AlreadyExists,
796 "this ring already has a buffer registration; BuildIoRingRegisterBuffers \
797 replaces the whole table, so a second call would invalidate every buffer \
798 index already handed out",
799 ),
800 Some(buffers),
801 (),
802 ));
803 }
804 let count = refuse!(checked_len(buffers.len()), Some(buffers), ());
805 let base_index = self.ring.registered_buffer_count();
806 // Every length is checked before any address is taken, so a refusal
807 // can still hand `buffers` back: nothing borrows it yet.
808 let registered_lens = refuse!(
809 buffers
810 .iter()
811 .map(|buffer| checked_len(buffer.bytes_len()))
812 .collect::<io::Result<Vec<u32>>>(),
813 Some(buffers),
814 ()
815 );
816 let infos: Vec<IORING_BUFFER_INFO> = buffers
817 .iter_mut()
818 .zip(®istered_lens)
819 .map(|(buffer, &length)| IORING_BUFFER_INFO {
820 Address: buffer.stable_mut_ptr().cast::<c_void>(),
821 Length: length,
822 })
823 .collect();
824 let user_data = refuse!(self.ring.reserve_user_data(), Some(buffers), ());
825 // The array must outlive this call: the kernel reads it when the
826 // registration op runs, during a later `SubmitIoRing`, not here
827 // (D-32, measured). Hand it to the ring, which holds it until it can
828 // prove the op has run, and build the SQE from *that* pointer rather
829 // than from the local `Vec` about to go out of scope.
830 let infos_ptr = self.ring.hold_registered_buffer_infos(infos);
831 // SAFETY: `self.ring`'s handle is live; `infos_ptr` addresses the
832 // array the ring now owns, which outlives every submit that could run
833 // this SQE; each `Address` points into `buffers`, which the caller
834 // keeps alive via the returned `PendingBufferRegistration` and, once
835 // claimed, `RegisteredBuffers`.
836 let hr = unsafe {
837 crate::sys::build_register_buffers(
838 self.ring.ring_id(),
839 self.ring.raw_handle(),
840 count,
841 infos_ptr,
842 user_data,
843 )
844 };
845 if let Err(error) = check(hr) {
846 self.ring.cancel_reservation(user_data);
847 self.ring.release_unqueued_buffer_infos();
848 return Err(PushRefused::new(error, Some(buffers), ()));
849 }
850 self.ring.reserve_registered_buffers(count);
851 Ok(PendingBufferRegistration {
852 user_data,
853 base_index,
854 buffers: ManuallyDrop::new(buffers),
855 registered_lens,
856 ring_id: self.ring.ring_id(),
857 })
858 }
859
860 /// Queue a read of `span.len` bytes from `file` at `file_offset`, into
861 /// `span`'s byte offset of `registration`'s buffer at `span.buffer_index`,
862 /// instead of handing over a fresh owned buffer (M5.2), with the **ring**
863 /// holding the registration's use count (`D-73`).
864 ///
865 /// The raw counterpart to [`Batch::read_registered_owned`]: prefer that
866 /// unless `file` needs to address a raw `FileRef` directly.
867 ///
868 /// The buffer belongs to the registration rather than to the caller, so
869 /// there is no payload to give back -- what the ring holds is the *use*,
870 /// which keeps the registration from being torn down while the kernel is
871 /// writing into it. That is `Held.registration`, released at the pop that
872 /// completes this operation (M5.3). Read the transferred bytes back from
873 /// `registration` itself afterward, for example via a caller-side accessor
874 /// into the buffer it was constructed from.
875 ///
876 /// # Safety
877 ///
878 /// As [`Batch::read_raw_owned`]'s.
879 ///
880 /// # Errors
881 ///
882 /// As [`Batch::read_raw_owned`], plus [`io::ErrorKind::InvalidInput`] if
883 /// `span.buffer_index` is out of range for `registration`.
884 pub unsafe fn read_registered_raw_owned<B: IoBufMut>(
885 &mut self,
886 file: impl Into<FileRef>,
887 registration: &RegisteredBuffers<B>,
888 span: RegisteredSpan,
889 extra: X,
890 file_offset: u64,
891 options: PushOptions,
892 ) -> Result<OperationId, PushRefused<T, X>> {
893 refuse!(self.require(Op::Read), None, extra);
894 refuse!(self.check_registration_ring(registration), None, extra);
895 let target = refuse!(handle_ref(file.into(), self.ring.ring_id()), None, extra);
896 let index = refuse!(registration.checked_span(span), None, extra);
897 let (user_data, id) = refuse!(self.begin_owned(), None, extra);
898 let held = Held {
899 guard: None,
900 registration: Some(registration.begin_use(span, KernelAccess::WritesBuffer)),
901 };
902 // SAFETY: as `read_registered_raw`.
903 let hr = unsafe {
904 crate::sys::build_read(
905 self.ring.ring_id(),
906 self.ring.raw_handle(),
907 target,
908 registered_buffer_ref(index, span.offset),
909 span.len,
910 file_offset,
911 user_data,
912 options.sqe_flags(),
913 )
914 };
915 self.finish_owned(hr, id, None, extra, held)
916 }
917
918 /// Queue a read into a registered buffer against a guarded file, with the
919 /// **ring** holding both the use and the guard (`D-73`).
920 ///
921 /// The guarded counterpart to [`Batch::read_registered_raw_owned`], and the
922 /// only shape that fills both halves of `Held`.
923 ///
924 /// # Errors
925 ///
926 /// As [`Batch::read_registered_raw_owned`].
927 pub fn read_registered_owned<B: IoBufMut, F: FileTarget>(
928 &mut self,
929 file: &F,
930 registration: &RegisteredBuffers<B>,
931 span: RegisteredSpan,
932 extra: X,
933 file_offset: u64,
934 options: PushOptions,
935 ) -> Result<OperationId, PushRefused<T, X>> {
936 refuse!(self.require(Op::Read), None, extra);
937 refuse!(self.check_registration_ring(registration), None, extra);
938 let target = refuse!(
939 handle_ref(file.as_file_ref(), self.ring.ring_id()),
940 None,
941 extra
942 );
943 let index = refuse!(registration.checked_span(span), None, extra);
944 let (user_data, id) = refuse!(self.begin_owned(), None, extra);
945 let held = Held {
946 guard: Some(file.guard().into()),
947 registration: Some(registration.begin_use(span, KernelAccess::WritesBuffer)),
948 };
949 // SAFETY: as `read_registered`.
950 let hr = unsafe {
951 crate::sys::build_read(
952 self.ring.ring_id(),
953 self.ring.raw_handle(),
954 target,
955 registered_buffer_ref(index, span.offset),
956 span.len,
957 file_offset,
958 user_data,
959 options.sqe_flags(),
960 )
961 };
962 self.finish_owned(hr, id, None, extra, held)
963 }
964
965 /// Queue a write of `span.len` bytes to `file` at `file_offset`, from
966 /// `span`'s byte offset of `registration`'s buffer at `span.buffer_index`
967 /// (M5.2).
968 ///
969 /// As [`Batch::read_registered_raw_owned`], but for `BuildIoRingWriteFile`.
970 /// Prefer [`Batch::write_registered_owned`] unless `file` needs to address a
971 /// raw `FileRef` directly.
972 ///
973 /// # Safety
974 ///
975 /// As [`Batch::read_registered_raw_owned`]'s.
976 ///
977 /// # Errors
978 ///
979 /// As [`Batch::read_registered_raw_owned`].
980 ///
981 /// # Why the long parameter list
982 ///
983 /// These are `BuildIoRingWriteFile`'s own parameters, plus the sidecar the
984 /// inventory carries. Collapsing them into a struct would hide which are
985 /// the kernel's and which are this crate's, which is the distinction a
986 /// reader of a push most needs. `sys.rs` takes the same view for the same
987 /// reason.
988 #[allow(
989 clippy::too_many_arguments,
990 reason = "mirrors the Win32 call plus the inventory sidecar"
991 )]
992 pub unsafe fn write_registered_raw_owned<B: IoBufMut>(
993 &mut self,
994 file: impl Into<FileRef>,
995 registration: &RegisteredBuffers<B>,
996 span: RegisteredSpan,
997 extra: X,
998 file_offset: u64,
999 options: PushOptions,
1000 caching: WriteCaching,
1001 ) -> Result<OperationId, PushRefused<T, X>> {
1002 refuse!(self.require(Op::Write), None, extra);
1003 refuse!(self.check_registration_ring(registration), None, extra);
1004 let target = refuse!(handle_ref(file.into(), self.ring.ring_id()), None, extra);
1005 let index = refuse!(registration.checked_span(span), None, extra);
1006 let (user_data, id) = refuse!(self.begin_owned(), None, extra);
1007 let held = Held {
1008 guard: None,
1009 registration: Some(registration.begin_use(span, KernelAccess::ReadsBuffer)),
1010 };
1011 // SAFETY: as `write_registered_raw`.
1012 let hr = unsafe {
1013 crate::sys::build_write(
1014 self.ring.ring_id(),
1015 self.ring.raw_handle(),
1016 target,
1017 registered_buffer_ref(index, span.offset),
1018 span.len,
1019 file_offset,
1020 caching.raw(),
1021 user_data,
1022 options.sqe_flags(),
1023 )
1024 };
1025 self.finish_owned(hr, id, None, extra, held)
1026 }
1027
1028 /// Queue a write from a registered buffer against a guarded file, with the
1029 /// **ring** holding both the use and the guard (`D-73`).
1030 ///
1031 /// The guarded counterpart to [`Batch::write_registered_raw_owned`].
1032 ///
1033 /// # Errors
1034 ///
1035 /// As [`Batch::write_registered_raw_owned`].
1036 ///
1037 /// # Why the long parameter list
1038 ///
1039 /// These are `BuildIoRingWriteFile`'s own parameters, plus the sidecar the
1040 /// inventory carries. Collapsing them into a struct would hide which are
1041 /// the kernel's and which are this crate's, which is the distinction a
1042 /// reader of a push most needs. `sys.rs` takes the same view for the same
1043 /// reason.
1044 #[allow(
1045 clippy::too_many_arguments,
1046 reason = "mirrors the Win32 call plus the inventory sidecar"
1047 )]
1048 pub fn write_registered_owned<B: IoBufMut, F: FileTarget>(
1049 &mut self,
1050 file: &F,
1051 registration: &RegisteredBuffers<B>,
1052 span: RegisteredSpan,
1053 extra: X,
1054 file_offset: u64,
1055 options: PushOptions,
1056 caching: WriteCaching,
1057 ) -> Result<OperationId, PushRefused<T, X>> {
1058 refuse!(self.require(Op::Write), None, extra);
1059 refuse!(self.check_registration_ring(registration), None, extra);
1060 let target = refuse!(
1061 handle_ref(file.as_file_ref(), self.ring.ring_id()),
1062 None,
1063 extra
1064 );
1065 let index = refuse!(registration.checked_span(span), None, extra);
1066 let (user_data, id) = refuse!(self.begin_owned(), None, extra);
1067 let held = Held {
1068 guard: Some(file.guard().into()),
1069 registration: Some(registration.begin_use(span, KernelAccess::ReadsBuffer)),
1070 };
1071 // SAFETY: as `write_registered`.
1072 let hr = unsafe {
1073 crate::sys::build_write(
1074 self.ring.ring_id(),
1075 self.ring.raw_handle(),
1076 target,
1077 registered_buffer_ref(index, span.offset),
1078 span.len,
1079 file_offset,
1080 caching.raw(),
1081 user_data,
1082 options.sqe_flags(),
1083 )
1084 };
1085 self.finish_owned(hr, id, None, extra, held)
1086 }
1087}