Skip to main content

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(&registered_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}