rustix_uring/
opcode.rs

1//! Operation codes that can be used to construct [`squeue::Entry`](crate::squeue::Entry)s.
2
3#![allow(clippy::new_without_default)]
4
5use core::convert::TryInto;
6use core::mem;
7
8use rustix::fd::RawFd;
9
10use crate::squeue::Entry;
11use crate::squeue::Entry128;
12use crate::sys;
13use crate::types::{self, sealed};
14
15macro_rules! assign_fd {
16    ( $sqe:ident . fd = $opfd:expr ) => {
17        match $opfd {
18            sealed::Target::Fd(fd) => $sqe.fd = fd,
19            sealed::Target::Fixed(idx) => {
20                $sqe.fd = idx as _;
21                $sqe.flags |= sys::IoringSqeFlags::FIXED_FILE;
22            }
23        }
24    };
25}
26
27macro_rules! opcode {
28    (@type impl sealed::UseFixed ) => {
29        sealed::Target
30    };
31    (@type impl sealed::UseFd ) => {
32        RawFd
33    };
34    (@type $name:ty ) => {
35        $name
36    };
37    (
38        $( #[$outer:meta] )*
39        pub struct $name:ident {
40            $( #[$new_meta:meta] )*
41
42            $( $field:ident : { $( $tnt:tt )+ } ),*
43
44            $(,)?
45
46            ;;
47
48            $(
49                $( #[$opt_meta:meta] )*
50                $opt_field:ident : $opt_tname:ty = $default:expr
51            ),*
52
53            $(,)?
54        }
55
56        pub const CODE = $opcode:expr;
57
58        $( #[$build_meta:meta] )*
59        pub fn build($self:ident) -> $entry:ty $build_block:block
60    ) => {
61        $( #[$outer] )*
62        pub struct $name {
63            $( $field : opcode!(@type $( $tnt )*), )*
64            $( $opt_field : $opt_tname, )*
65        }
66
67        impl $name {
68            $( #[$new_meta] )*
69            #[inline]
70            pub fn new($( $field : $( $tnt )* ),*) -> Self {
71                $name {
72                    $( $field: $field.into(), )*
73                    $( $opt_field: $default, )*
74                }
75            }
76
77            /// The opcode of the operation. This can be passed to
78            /// [`Probe::is_supported`](crate::Probe::is_supported) to check if this operation is
79            /// supported with the current kernel.
80            pub const CODE: sys::IoringOp = $opcode as _;
81
82            $(
83                $( #[$opt_meta] )*
84                #[inline]
85                pub const fn $opt_field(mut self, $opt_field: $opt_tname) -> Self {
86                    self.$opt_field = $opt_field;
87                    self
88                }
89            )*
90
91            $( #[$build_meta] )*
92            #[inline]
93            pub fn build($self) -> $entry $build_block
94        }
95    }
96}
97
98/// inline zeroed to improve codegen
99#[inline(always)]
100fn sqe_zeroed() -> sys::io_uring_sqe {
101    Default::default()
102}
103
104opcode! {
105    /// Do not perform any I/O.
106    ///
107    /// This is useful for testing the performance of the io_uring implementation itself.
108    #[derive(Debug)]
109    pub struct Nop { ;; }
110
111    pub const CODE = sys::IoringOp::Nop;
112
113    pub fn build(self) -> Entry {
114        let Nop {} = self;
115
116        let mut sqe = sqe_zeroed();
117        sqe.opcode = Self::CODE;
118        sqe.fd = -1;
119        Entry(sqe)
120    }
121}
122
123opcode! {
124    /// Vectored read, equivalent to `preadv2(2)`.
125    #[derive(Debug)]
126    pub struct Readv {
127        fd: { impl sealed::UseFixed },
128        iovec: { *const sys::iovec },
129        len: { u32 },
130        ;;
131        ioprio: u16 = 0,
132        offset: u64 = 0,
133        /// specified for read operations, contains a bitwise OR of per-I/O flags,
134        /// as described in the `preadv2(2)` man page.
135        rw_flags: types::RwFlags = types::RwFlags::empty(),
136        buf_group: u16 = 0
137    }
138
139    pub const CODE = sys::IoringOp::Readv;
140
141    pub fn build(self) -> Entry {
142        let Readv {
143            fd,
144            iovec, len, offset,
145            ioprio, rw_flags,
146            buf_group
147        } = self;
148
149        let mut sqe = sqe_zeroed();
150        sqe.opcode = Self::CODE;
151        assign_fd!(sqe.fd = fd);
152        sqe.ioprio.ioprio = ioprio;
153        sqe.addr_or_splice_off_in.addr.ptr = iovec as _;
154        sqe.len.len = len;
155        sqe.off_or_addr2.off = offset as _;
156        sqe.op_flags.rw_flags = rw_flags;
157        sqe.buf.buf_group = buf_group;
158        Entry(sqe)
159    }
160}
161
162opcode! {
163    /// Vectored write, equivalent to `pwritev2(2)`.
164    #[derive(Debug)]
165    pub struct Writev {
166        fd: { impl sealed::UseFixed },
167        iovec: { *const sys::iovec },
168        len: { u32 },
169        ;;
170        ioprio: u16 = 0,
171        offset: u64 = 0,
172        /// specified for write operations, contains a bitwise OR of per-I/O flags,
173        /// as described in the `preadv2(2)` man page.
174        rw_flags: types::RwFlags = types::RwFlags::empty()
175    }
176
177    pub const CODE = sys::IoringOp::Writev;
178
179    pub fn build(self) -> Entry {
180        let Writev {
181            fd,
182            iovec, len, offset,
183            ioprio, rw_flags
184        } = self;
185
186        let mut sqe = sqe_zeroed();
187        sqe.opcode = Self::CODE;
188        assign_fd!(sqe.fd = fd);
189        sqe.ioprio.ioprio = ioprio;
190        sqe.addr_or_splice_off_in.addr.ptr = iovec as _;
191        sqe.len.len = len;
192        sqe.off_or_addr2.off = offset as _;
193        sqe.op_flags.rw_flags = rw_flags;
194        Entry(sqe)
195    }
196}
197
198opcode! {
199    /// File sync, equivalent to `fsync(2)`.
200    ///
201    /// Note that, while I/O is initiated in the order in which it appears in the submission queue,
202    /// completions are unordered. For example, an application which places a write I/O followed by
203    /// an fsync in the submission queue cannot expect the fsync to apply to the write. The two
204    /// operations execute in parallel, so the fsync may complete before the write is issued to the
205    /// storage. The same is also true for previously issued writes that have not completed prior to
206    /// the fsync.
207    #[derive(Debug)]
208    pub struct Fsync {
209        fd: { impl sealed::UseFixed },
210        ;;
211        /// The `flags` bit mask may contain either 0, for a normal file integrity sync,
212        /// or [types::FsyncFlags::DATASYNC] to provide data sync only semantics.
213        /// See the descriptions of `O_SYNC` and `O_DSYNC` in the `open(2)` manual page for more information.
214        flags: types::FsyncFlags = types::FsyncFlags::empty()
215    }
216
217    pub const CODE = sys::IoringOp::Fsync;
218
219    pub fn build(self) -> Entry {
220        let Fsync { fd, flags } = self;
221
222        let mut sqe = sqe_zeroed();
223        sqe.opcode = Self::CODE;
224        assign_fd!(sqe.fd = fd);
225        sqe.op_flags.fsync_flags = sys::IoringFsyncFlags::from_bits_retain(flags.bits());
226        Entry(sqe)
227    }
228}
229
230opcode! {
231    /// Read from a file into a fixed buffer that has been previously registered with
232    /// [`Submitter::register_buffers`](crate::Submitter::register_buffers).
233    ///
234    /// The return values match those documented in the `preadv2(2)` man pages.
235    #[derive(Debug)]
236    pub struct ReadFixed {
237        fd: { impl sealed::UseFixed },
238        buf: { *mut u8 },
239        len: { u32 },
240        buf_index: { u16 },
241        ;;
242        ioprio: u16 = 0,
243        /// The offset of the file to read from.
244        offset: u64 = 0,
245        /// Specified for read operations, contains a bitwise OR of per-I/O flags, as described in
246        /// the `preadv2(2)` man page.
247        rw_flags: types::RwFlags = types::RwFlags::empty()
248    }
249
250    pub const CODE = sys::IoringOp::ReadFixed;
251
252    pub fn build(self) -> Entry {
253        let ReadFixed {
254            fd,
255            buf, len, offset,
256            buf_index,
257            ioprio, rw_flags
258        } = self;
259
260        let mut sqe = sqe_zeroed();
261        sqe.opcode = Self::CODE;
262        assign_fd!(sqe.fd = fd);
263        sqe.ioprio.ioprio = ioprio;
264        sqe.addr_or_splice_off_in.addr.ptr = buf as _;
265        sqe.len.len = len;
266        sqe.off_or_addr2.off = offset as _;
267        sqe.op_flags.rw_flags = rw_flags;
268        sqe.buf.buf_index = buf_index;
269        Entry(sqe)
270    }
271}
272
273opcode! {
274    /// Write to a file from a fixed buffer that have been previously registered with
275    /// [`Submitter::register_buffers`](crate::Submitter::register_buffers).
276    ///
277    /// The return values match those documented in the `pwritev2(2)` man pages.
278    #[derive(Debug)]
279    pub struct WriteFixed {
280        fd: { impl sealed::UseFixed },
281        buf: { *const u8 },
282        len: { u32 },
283        buf_index: { u16 },
284        ;;
285        ioprio: u16 = 0,
286        /// The offset of the file to write to.
287        offset: u64 = 0,
288        /// Specified for write operations, contains a bitwise OR of per-I/O flags, as described in
289        /// the `pwritev2(2)` man page.
290        rw_flags: types::RwFlags = types::RwFlags::empty()
291    }
292
293    pub const CODE = sys::IoringOp::WriteFixed;
294
295    pub fn build(self) -> Entry {
296        let WriteFixed {
297            fd,
298            buf, len, offset,
299            buf_index,
300            ioprio, rw_flags
301        } = self;
302
303        let mut sqe = sqe_zeroed();
304        sqe.opcode = Self::CODE;
305        assign_fd!(sqe.fd = fd);
306        sqe.ioprio.ioprio = ioprio;
307        sqe.addr_or_splice_off_in.addr.ptr = buf as _;
308        sqe.len.len = len;
309        sqe.off_or_addr2.off = offset as _;
310        sqe.op_flags.rw_flags = rw_flags;
311        sqe.buf.buf_index = buf_index;
312        Entry(sqe)
313    }
314}
315
316opcode! {
317    /// Poll the specified fd.
318    ///
319    /// Unlike poll or epoll without `EPOLLONESHOT`, this interface defaults to work in one shot mode.
320    /// That is, once the poll operation is completed, it will have to be resubmitted.
321    ///
322    /// If multi is set, the poll will work in multi shot mode instead. That means it will
323    /// repeatedly trigger when the requested event becomes true, and hence multiple CQEs can be
324    /// generated from this single submission. The CQE flags field will have IORING_CQE_F_MORE set
325    /// on completion if the application should expect further CQE entries from the original
326    /// request. If this flag isn't set on completion, then the poll request has been terminated
327    /// and no further events will be generated. This mode is available since 5.13.
328    #[derive(Debug)]
329    pub struct PollAdd {
330        /// The bits that may be set in `flags` are defined in `<poll.h>`,
331        /// and documented in `poll(2)`.
332        fd: { impl sealed::UseFixed },
333        flags: { u32 },
334        ;;
335        multi: bool = false
336    }
337
338    pub const CODE = sys::IoringOp::PollAdd;
339
340    pub fn build(self) -> Entry {
341        let PollAdd { fd, flags, multi } = self;
342
343        let mut sqe = sqe_zeroed();
344        sqe.opcode = Self::CODE;
345        assign_fd!(sqe.fd = fd);
346        if multi {
347            sqe.len.poll_flags = sys::IoringPollFlags::ADD_MULTI;
348        }
349
350        #[cfg(target_endian = "little")] {
351            sqe.op_flags.poll32_events = flags;
352        }
353
354        #[cfg(target_endian = "big")] {
355            let x = flags << 16;
356            let y = flags >> 16;
357            let flags = x | y;
358            sqe.op_flags.poll32_events = flags;
359        }
360
361        Entry(sqe)
362    }
363}
364
365opcode! {
366    /// Remove an existing [poll](PollAdd) request.
367    ///
368    /// If found, the `result` method of the `cqueue::Entry` will return 0.
369    /// If not found, `result` will return `-libc::ENOENT`.
370    #[derive(Debug)]
371    pub struct PollRemove {
372        user_data: { sys::io_uring_user_data }
373        ;;
374    }
375
376    pub const CODE = sys::IoringOp::PollRemove;
377
378    pub fn build(self) -> Entry {
379        let PollRemove { user_data } = self;
380
381        let mut sqe = sqe_zeroed();
382        sqe.opcode = Self::CODE;
383        sqe.fd = -1;
384        sqe.addr_or_splice_off_in.user_data = user_data;
385        Entry(sqe)
386    }
387}
388
389opcode! {
390    /// Sync a file segment with disk, equivalent to `sync_file_range(2)`.
391    #[derive(Debug)]
392    pub struct SyncFileRange {
393        fd: { impl sealed::UseFixed },
394        len: { u32 },
395        ;;
396        /// the offset method holds the offset in bytes
397        offset: u64 = 0,
398        /// the flags method holds the flags for the command
399        flags: u32 = 0
400    }
401
402    pub const CODE = sys::IoringOp::SyncFileRange;
403
404    pub fn build(self) -> Entry {
405        let SyncFileRange {
406            fd,
407            len, offset,
408            flags
409        } = self;
410
411        let mut sqe = sqe_zeroed();
412        sqe.opcode = Self::CODE;
413        assign_fd!(sqe.fd = fd);
414        sqe.len.len = len as _;
415        sqe.off_or_addr2.off = offset as _;
416        sqe.op_flags.sync_range_flags = flags;
417        Entry(sqe)
418    }
419}
420
421opcode! {
422    /// Send a message on a socket, equivalent to `send(2)`.
423    ///
424    /// fd must be set to the socket file descriptor, addr must contains a pointer to the MsgHdr
425    /// structure, and flags holds the flags associated with the system call.
426    #[derive(Debug)]
427    pub struct SendMsg {
428        fd: { impl sealed::UseFixed },
429        msg: { *const sys::MsgHdr },
430        ;;
431        ioprio: u16 = 0,
432        flags: sys::SendFlags = sys::SendFlags::empty()
433    }
434
435    pub const CODE = sys::IoringOp::Sendmsg;
436
437    pub fn build(self) -> Entry {
438        let SendMsg { fd, msg, ioprio, flags } = self;
439
440        let mut sqe = sqe_zeroed();
441        sqe.opcode = Self::CODE;
442        assign_fd!(sqe.fd = fd);
443        sqe.ioprio.ioprio = ioprio;
444        sqe.addr_or_splice_off_in.addr.ptr = msg as _;
445        sqe.len.len = 1;
446        sqe.op_flags.send_flags = flags;
447        Entry(sqe)
448    }
449}
450
451opcode! {
452    /// Receive a message on a socket, equivalent to `recvmsg(2)`.
453    ///
454    /// See also the description of [`SendMsg`].
455    #[derive(Debug)]
456    pub struct RecvMsg {
457        fd: { impl sealed::UseFixed },
458        msg: { *mut sys::MsgHdr },
459        ;;
460        ioprio: u16 = 0,
461        flags: sys::RecvFlags = sys::RecvFlags::empty(),
462        buf_group: u16 = 0
463    }
464
465    pub const CODE = sys::IoringOp::Recvmsg;
466
467    pub fn build(self) -> Entry {
468        let RecvMsg { fd, msg, ioprio, flags, buf_group } = self;
469
470        let mut sqe = sqe_zeroed();
471        sqe.opcode = Self::CODE;
472        assign_fd!(sqe.fd = fd);
473        sqe.ioprio.ioprio = ioprio;
474        sqe.addr_or_splice_off_in.addr.ptr = msg as _;
475        sqe.len.len = 1;
476        sqe.op_flags.recv_flags = flags;
477        sqe.buf.buf_group = buf_group;
478        Entry(sqe)
479    }
480}
481
482opcode! {
483    /// Receive multiple messages on a socket, equivalent to `recvmsg(2)`.
484    ///
485    /// Parameters:
486    ///     msg:       For this multishot variant of ResvMsg, only the msg_namelen and msg_controllen
487    ///                fields are relevant.
488    ///     buf_group: The id of the provided buffer pool to use for each received message.
489    ///
490    /// See also the description of [`SendMsg`] and [`types::RecvMsgOut`].
491    ///
492    /// The multishot version allows the application to issue a single receive request, which
493    /// repeatedly posts a CQE when data is available. It requires the MSG_WAITALL flag is not set.
494    /// Each CQE will take a buffer out of a provided buffer pool for receiving. The application
495    /// should check the flags of each CQE, regardless of its result. If a posted CQE does not have
496    /// the IORING_CQE_F_MORE flag set then the multishot receive will be done and the application
497    /// should issue a new request.
498    ///
499    /// Unlike [`RecvMsg`], this multishot recvmsg will prepend a struct which describes the layout
500    /// of the rest of the buffer in combination with the initial MsgHdr structure submitted with
501    /// the request. Use [`types::RecvMsgOut`] to parse the data received and access its
502    /// components.
503    ///
504    /// The recvmsg multishot variant is available since kernel 6.0.
505    #[derive(Debug)]
506    pub struct RecvMsgMulti {
507        fd: { impl sealed::UseFixed },
508        msg: { *const sys::MsgHdr },
509        buf_group: { u16 },
510        ;;
511        ioprio: sys::IoringRecvFlags = sys::IoringRecvFlags::empty(),
512        flags: sys::RecvFlags = sys::RecvFlags::empty()
513    }
514
515    pub const CODE = sys::IoringOp::Recvmsg;
516
517    pub fn build(self) -> Entry {
518        let RecvMsgMulti { fd, msg, buf_group, ioprio, flags } = self;
519
520        let mut sqe = sqe_zeroed();
521        sqe.opcode = Self::CODE;
522        assign_fd!(sqe.fd = fd);
523        sqe.addr_or_splice_off_in.addr.ptr = msg as _;
524        sqe.len.len = 1;
525        sqe.op_flags.recv_flags = flags;
526        sqe.buf.buf_group = buf_group;
527        sqe.flags |= sys::IoringSqeFlags::BUFFER_SELECT;
528        sqe.ioprio.recv_flags = ioprio | sys::IoringRecvFlags::MULTISHOT;
529        Entry(sqe)
530    }
531}
532
533opcode! {
534    /// Register a timeout operation.
535    ///
536    /// A timeout will trigger a wakeup event on the completion ring for anyone waiting for events.
537    /// A timeout condition is met when either the specified timeout expires, or the specified number of events have completed.
538    /// Either condition will trigger the event.
539    /// The request will complete with `-ETIME` if the timeout got completed through expiration of the timer,
540    /// or 0 if the timeout got completed through requests completing on their own.
541    /// If the timeout was cancelled before it expired, the request will complete with `-ECANCELED`.
542    #[derive(Debug)]
543    pub struct Timeout {
544        timespec: { *const types::Timespec },
545        ;;
546        /// `count` may contain a completion event count.
547        count: u32 = 0,
548
549        /// `flags` may contain [types::TimeoutFlags::ABS] for an absolute timeout value, or 0 for a relative timeout.
550        flags: types::TimeoutFlags = types::TimeoutFlags::empty()
551    }
552
553    pub const CODE = sys::IoringOp::Timeout;
554
555    pub fn build(self) -> Entry {
556        let Timeout { timespec, count, flags } = self;
557
558        let mut sqe = sqe_zeroed();
559        sqe.opcode = Self::CODE;
560        sqe.fd = -1;
561        sqe.addr_or_splice_off_in.addr.ptr = timespec as _;
562        sqe.len.len = 1;
563        sqe.off_or_addr2.off = count as _;
564        sqe.op_flags.timeout_flags = sys::IoringTimeoutFlags::from_bits_retain(flags.bits());
565        Entry(sqe)
566    }
567}
568
569// === 5.5 ===
570
571opcode! {
572    /// Attempt to remove an existing [timeout operation](Timeout).
573    pub struct TimeoutRemove {
574        user_data: { sys::io_uring_user_data }
575        ;;
576    }
577
578    pub const CODE = sys::IoringOp::TimeoutRemove;
579
580    pub fn build(self) -> Entry {
581        let TimeoutRemove { user_data } = self;
582
583        let mut sqe = sqe_zeroed();
584        sqe.opcode = Self::CODE;
585        sqe.fd = -1;
586        sqe.addr_or_splice_off_in.user_data = user_data;
587        Entry(sqe)
588    }
589}
590
591opcode! {
592    /// Attempt to update an existing [timeout operation](Timeout) with a new timespec.
593    /// The optional `count` value of the original timeout value cannot be updated.
594    pub struct TimeoutUpdate {
595        user_data: { sys::io_uring_user_data },
596        timespec: { *const types::Timespec },
597        ;;
598        flags: types::TimeoutFlags = types::TimeoutFlags::empty()
599    }
600
601    pub const CODE = sys::IoringOp::TimeoutRemove;
602
603    pub fn build(self) -> Entry {
604        let TimeoutUpdate { user_data, timespec, flags } = self;
605
606        let mut sqe = sqe_zeroed();
607        sqe.opcode = Self::CODE;
608        sqe.fd = -1;
609        sqe.off_or_addr2.addr2.ptr = timespec as _;
610        sqe.addr_or_splice_off_in.user_data = user_data;
611        sqe.op_flags.timeout_flags = sys::IoringTimeoutFlags::from_bits_retain(flags.bits()) | sys::IoringTimeoutFlags::UPDATE;
612        Entry(sqe)
613    }
614}
615
616opcode! {
617    /// Accept a new connection on a socket, equivalent to `accept4(2)`.
618    pub struct Accept {
619        fd: { impl sealed::UseFixed },
620        addr: { *mut sys::SocketAddrOpaque },
621        addrlen: { *mut sys::SocketAddrLen },
622        ;;
623        file_index: Option<types::DestinationSlot> = None,
624        flags: sys::SocketFlags = sys::SocketFlags::empty()
625    }
626
627    pub const CODE = sys::IoringOp::Accept;
628
629    pub fn build(self) -> Entry {
630        let Accept { fd, addr, addrlen, file_index, flags } = self;
631
632        let mut sqe = sqe_zeroed();
633        sqe.opcode = Self::CODE;
634        assign_fd!(sqe.fd = fd);
635        sqe.addr_or_splice_off_in.addr.ptr = addr as _;
636        sqe.off_or_addr2.addr2.ptr = addrlen as _;
637        sqe.op_flags.accept_flags = flags;
638        if let Some(dest) = file_index {
639            sqe.splice_fd_in_or_file_index_or_addr_len.file_index = dest.kernel_index_arg();
640        }
641        Entry(sqe)
642    }
643}
644
645opcode! {
646    /// Attempt to cancel an already issued request.
647    pub struct AsyncCancel {
648        user_data: { sys::io_uring_user_data }
649        ;;
650
651        // TODO flags
652    }
653
654    pub const CODE = sys::IoringOp::AsyncCancel;
655
656    pub fn build(self) -> Entry {
657        let AsyncCancel { user_data } = self;
658
659        let mut sqe = sqe_zeroed();
660        sqe.opcode = Self::CODE;
661        sqe.fd = -1;
662        sqe.addr_or_splice_off_in.user_data = user_data;
663        Entry(sqe)
664    }
665}
666
667opcode! {
668    /// This request must be linked with another request through
669    /// [`Flags::IO_LINK`](crate::squeue::Flags::IO_LINK) which is described below.
670    /// Unlike [`Timeout`], [`LinkTimeout`] acts on the linked request, not the completion queue.
671    pub struct LinkTimeout {
672        timespec: { *const types::Timespec },
673        ;;
674        flags: types::TimeoutFlags = types::TimeoutFlags::empty()
675    }
676
677    pub const CODE = sys::IoringOp::LinkTimeout;
678
679    pub fn build(self) -> Entry {
680        let LinkTimeout { timespec, flags } = self;
681
682        let mut sqe = sqe_zeroed();
683        sqe.opcode = Self::CODE;
684        sqe.fd = -1;
685        sqe.addr_or_splice_off_in.addr.ptr = timespec as _;
686        sqe.len.len = 1;
687        sqe.op_flags.timeout_flags = sys::IoringTimeoutFlags::from_bits_retain(flags.bits());
688        Entry(sqe)
689    }
690}
691
692opcode! {
693    /// Connect a socket, equivalent to `connect(2)`.
694    pub struct Connect {
695        fd: { impl sealed::UseFixed },
696        addr: { *const sys::SocketAddrOpaque },
697        addrlen: { sys::SocketAddrLen }
698        ;;
699    }
700
701    pub const CODE = sys::IoringOp::Connect;
702
703    pub fn build(self) -> Entry {
704        let Connect { fd, addr, addrlen } = self;
705
706        let mut sqe = sqe_zeroed();
707        sqe.opcode = Self::CODE;
708        assign_fd!(sqe.fd = fd);
709        sqe.addr_or_splice_off_in.addr.ptr = addr as _;
710        sqe.off_or_addr2.off = addrlen as _;
711        Entry(sqe)
712    }
713}
714
715// === 5.6 ===
716
717opcode! {
718    /// Preallocate or deallocate space to a file, equivalent to `fallocate(2)`.
719    pub struct Fallocate {
720        fd: { impl sealed::UseFixed },
721        len: { u64 },
722        ;;
723        offset: u64 = 0,
724        mode: i32 = 0
725    }
726
727    pub const CODE = sys::IoringOp::Fallocate;
728
729    pub fn build(self) -> Entry {
730        let Fallocate { fd, len, offset, mode } = self;
731
732        let mut sqe = sqe_zeroed();
733        sqe.opcode = Self::CODE;
734        assign_fd!(sqe.fd = fd);
735        sqe.addr_or_splice_off_in.addr.ptr = len as _;
736        sqe.len.len = mode as _;
737        sqe.off_or_addr2.off = offset as _;
738        Entry(sqe)
739    }
740}
741
742opcode! {
743    /// Open a file, equivalent to `openat(2)`.
744    pub struct OpenAt {
745        dirfd: { impl sealed::UseFd },
746        pathname: { *const sys::c_char },
747        ;;
748        file_index: Option<types::DestinationSlot> = None,
749        flags: sys::OFlags = sys::OFlags::empty(),
750        mode: sys::Mode = sys::Mode::empty()
751    }
752
753    pub const CODE = sys::IoringOp::Openat;
754
755    pub fn build(self) -> Entry {
756        let OpenAt { dirfd, pathname, file_index, flags, mode } = self;
757
758        let mut sqe = sqe_zeroed();
759        sqe.opcode = Self::CODE;
760        sqe.fd = dirfd;
761        sqe.addr_or_splice_off_in.addr.ptr = pathname as _;
762        sqe.len.len = mode.bits();
763        sqe.op_flags.open_flags = flags;
764        if let Some(dest) = file_index {
765            sqe.splice_fd_in_or_file_index_or_addr_len.file_index = dest.kernel_index_arg();
766        }
767        Entry(sqe)
768    }
769}
770
771opcode! {
772    /// Close a file descriptor, equivalent to `close(2)`.
773    ///
774    /// Use a types::Fixed(fd) argument to close an io_uring direct descriptor.
775    pub struct Close {
776        fd: { impl sealed::UseFixed },
777        ;;
778    }
779
780    pub const CODE = sys::IoringOp::Close;
781
782    pub fn build(self) -> Entry {
783        let Close { fd } = self;
784
785        let mut sqe = sqe_zeroed();
786        sqe.opcode = Self::CODE;
787        match fd {
788            sealed::Target::Fd(fd) => sqe.fd = fd,
789            sealed::Target::Fixed(idx) => {
790                sqe.fd = 0;
791                sqe.splice_fd_in_or_file_index_or_addr_len.file_index = idx + 1;
792            }
793        }
794        Entry(sqe)
795    }
796}
797
798opcode! {
799    /// This command is an alternative to using
800    /// [`Submitter::register_files_update`](crate::Submitter::register_files_update) which then
801    /// works in an async fashion, like the rest of the io_uring commands.
802    pub struct FilesUpdate {
803        fds: { *const RawFd },
804        len: { u32 },
805        ;;
806        offset: i32 = 0
807    }
808
809    pub const CODE = sys::IoringOp::FilesUpdate;
810
811    pub fn build(self) -> Entry {
812        let FilesUpdate { fds, len, offset } = self;
813
814        let mut sqe = sqe_zeroed();
815        sqe.opcode = Self::CODE;
816        sqe.fd = -1;
817        sqe.addr_or_splice_off_in.addr.ptr = fds as _;
818        sqe.len.len = len;
819        sqe.off_or_addr2.off = offset as _;
820        Entry(sqe)
821    }
822}
823
824opcode! {
825    /// Get file status, equivalent to `statx(2)`.
826    pub struct Statx {
827        dirfd: { impl sealed::UseFd },
828        pathname: { *const sys::c_char },
829        statxbuf: { *mut types::Statx },
830        ;;
831        flags: sys::AtFlags = sys::AtFlags::empty(),
832        mask: sys::StatxFlags = sys::StatxFlags::empty()
833    }
834
835    pub const CODE = sys::IoringOp::Statx;
836
837    pub fn build(self) -> Entry {
838        let Statx {
839            dirfd, pathname, statxbuf,
840            flags, mask
841        } = self;
842
843        let mut sqe = sqe_zeroed();
844        sqe.opcode = Self::CODE;
845        sqe.fd = dirfd;
846        sqe.addr_or_splice_off_in.addr.ptr = pathname as _;
847        sqe.len.len = mask.bits();
848        sqe.off_or_addr2.addr2.ptr = statxbuf as _;
849        sqe.op_flags.statx_flags = flags;
850        Entry(sqe)
851    }
852}
853
854opcode! {
855    /// Issue the equivalent of a `pread(2)` or `pwrite(2)` system call
856    ///
857    /// * `fd` is the file descriptor to be operated on,
858    /// * `addr` contains the buffer in question,
859    /// * `len` contains the length of the IO operation,
860    ///
861    /// These are non-vectored versions of the `IoringOp::READV` and `IoringOp::WRITEV` opcodes.
862    /// See also `read(2)` and `write(2)` for the general description of the related system call.
863    ///
864    /// Available since 5.6.
865    pub struct Read {
866        fd: { impl sealed::UseFixed },
867        buf: { *mut u8 },
868        len: { u32 },
869        ;;
870        /// `offset` contains the read or write offset.
871        ///
872        /// If `fd` does not refer to a seekable file, `offset` must be set to zero.
873        /// If `offset` is set to `-1`, the offset will use (and advance) the file position,
874        /// like the `read(2)` and `write(2)` system calls.
875        offset: u64 = 0,
876        ioprio: u16 = 0,
877        rw_flags: types::RwFlags = types::RwFlags::empty(),
878        buf_group: u16 = 0
879    }
880
881    pub const CODE = sys::IoringOp::Read;
882
883    pub fn build(self) -> Entry {
884        let Read {
885            fd,
886            buf, len, offset,
887            ioprio, rw_flags,
888            buf_group
889        } = self;
890
891        let mut sqe = sqe_zeroed();
892        sqe.opcode = Self::CODE;
893        assign_fd!(sqe.fd = fd);
894        sqe.ioprio.ioprio = ioprio;
895        sqe.addr_or_splice_off_in.addr.ptr = buf as _;
896        sqe.len.len = len;
897        sqe.off_or_addr2.off = offset as _;
898        sqe.op_flags.rw_flags = rw_flags;
899        sqe.buf.buf_group = buf_group;
900        Entry(sqe)
901    }
902}
903
904opcode! {
905    /// Issue the equivalent of a `pread(2)` or `pwrite(2)` system call
906    ///
907    /// * `fd` is the file descriptor to be operated on,
908    /// * `addr` contains the buffer in question,
909    /// * `len` contains the length of the IO operation,
910    ///
911    /// These are non-vectored versions of the `IoringOp::READV` and `IoringOp::WRITEV` opcodes.
912    /// See also `read(2)` and `write(2)` for the general description of the related system call.
913    ///
914    /// Available since 5.6.
915    pub struct Write {
916        fd: { impl sealed::UseFixed },
917        buf: { *const u8 },
918        len: { u32 },
919        ;;
920        /// `offset` contains the read or write offset.
921        ///
922        /// If `fd` does not refer to a seekable file, `offset` must be set to zero.
923        /// If `offsett` is set to `-1`, the offset will use (and advance) the file position,
924        /// like the `read(2)` and `write(2)` system calls.
925        offset: u64 = 0,
926        ioprio: u16 = 0,
927        rw_flags: types::RwFlags = types::RwFlags::empty()
928    }
929
930    pub const CODE = sys::IoringOp::Write;
931
932    pub fn build(self) -> Entry {
933        let Write {
934            fd,
935            buf, len, offset,
936            ioprio, rw_flags
937        } = self;
938
939        let mut sqe = sqe_zeroed();
940        sqe.opcode = Self::CODE;
941        assign_fd!(sqe.fd = fd);
942        sqe.ioprio.ioprio = ioprio;
943        sqe.addr_or_splice_off_in.addr.ptr = buf as _;
944        sqe.len.len = len;
945        sqe.off_or_addr2.off = offset as _;
946        sqe.op_flags.rw_flags = rw_flags;
947        Entry(sqe)
948    }
949}
950
951opcode! {
952    /// Predeclare an access pattern for file data, equivalent to `posix_fadvise(2)`.
953    pub struct Fadvise {
954        fd: { impl sealed::UseFixed },
955        len: { u32 },
956        advice: { sys::Advice },
957        ;;
958        offset: u64 = 0,
959    }
960
961    pub const CODE = sys::IoringOp::Fadvise;
962
963    pub fn build(self) -> Entry {
964        let Fadvise { fd, len, advice, offset } = self;
965
966        let mut sqe = sqe_zeroed();
967        sqe.opcode = Self::CODE;
968        assign_fd!(sqe.fd = fd);
969        sqe.len.len = len;
970        sqe.off_or_addr2.off = offset as _;
971        sqe.op_flags.fadvise_advice = advice;
972        Entry(sqe)
973    }
974}
975
976opcode! {
977    /// Give advice about use of memory, equivalent to `madvise(2)`.
978    pub struct Madvise {
979        addr: { *const core::ffi::c_void },
980        len: { u32 },
981        advice: { sys::Advice },
982        ;;
983    }
984
985    pub const CODE = sys::IoringOp::Madvise;
986
987    pub fn build(self) -> Entry {
988        let Madvise { addr, len, advice } = self;
989
990        let mut sqe = sqe_zeroed();
991        sqe.opcode = Self::CODE;
992        sqe.fd = -1;
993        sqe.addr_or_splice_off_in.addr.ptr = addr as _;
994        sqe.len.len = len;
995        sqe.op_flags.fadvise_advice = advice as _;
996        Entry(sqe)
997    }
998}
999
1000opcode! {
1001    /// Send a message on a socket, equivalent to `send(2)`.
1002    pub struct Send {
1003        fd: { impl sealed::UseFixed },
1004        buf: { *const u8 },
1005        len: { u32 },
1006        ;;
1007        flags: sys::SendFlags = sys::SendFlags::empty(),
1008
1009        /// Set the destination address, for sending from an unconnected socket.
1010        ///
1011        /// When set, `dest_addr_len` must be set as well.
1012        /// See also `man 3 io_uring_prep_send_set_addr`.
1013        dest_addr: *const rustix::net::addr::SocketAddrStorage = core::ptr::null(),
1014        dest_addr_len: rustix::net::addr::SocketAddrLen = 0,
1015    }
1016
1017    pub const CODE = sys::IoringOp::Send;
1018
1019    pub fn build(self) -> Entry {
1020        let Send { fd, buf, len, flags, dest_addr, dest_addr_len } = self;
1021
1022        let mut sqe = sqe_zeroed();
1023        sqe.opcode = Self::CODE;
1024        assign_fd!(sqe.fd = fd);
1025        sqe.addr_or_splice_off_in.addr.ptr = buf as _;
1026        sqe.off_or_addr2.addr2.ptr = dest_addr as _;
1027        sqe.splice_fd_in_or_file_index_or_addr_len.addr_len.addr_len = dest_addr_len as _;
1028        sqe.len.len = len;
1029        sqe.op_flags.send_flags = flags;
1030        Entry(sqe)
1031    }
1032}
1033
1034opcode! {
1035    /// Receive a message from a socket, equivalent to `recv(2)`.
1036    pub struct Recv {
1037        fd: { impl sealed::UseFixed },
1038        buf: { *mut u8 },
1039        len: { u32 },
1040        ;;
1041        flags: sys::RecvFlags = sys::RecvFlags::empty(),
1042        buf_group: u16 = 0
1043    }
1044
1045    pub const CODE = sys::IoringOp::Recv;
1046
1047    pub fn build(self) -> Entry {
1048        let Recv { fd, buf, len, flags, buf_group } = self;
1049
1050        let mut sqe = sqe_zeroed();
1051        sqe.opcode = Self::CODE;
1052        assign_fd!(sqe.fd = fd);
1053        sqe.addr_or_splice_off_in.addr.ptr = buf as _;
1054        sqe.len.len = len;
1055        sqe.op_flags.recv_flags = flags;
1056        sqe.buf.buf_group = buf_group;
1057        Entry(sqe)
1058    }
1059}
1060
1061opcode! {
1062    /// Receive multiple messages from a socket, equivalent to `recv(2)`.
1063    ///
1064    /// Parameter:
1065    ///     buf_group: The id of the provided buffer pool to use for each received message.
1066    ///
1067    /// MSG_WAITALL should not be set in flags.
1068    ///
1069    /// The multishot version allows the application to issue a single receive request, which
1070    /// repeatedly posts a CQE when data is available. Each CQE will take a buffer out of a
1071    /// provided buffer pool for receiving. The application should check the flags of each CQE,
1072    /// regardless of its result. If a posted CQE does not have the IORING_CQE_F_MORE flag set then
1073    /// the multishot receive will be done and the application should issue a new request.
1074    ///
1075    /// Multishot variants are available since kernel 6.0.
1076
1077    pub struct RecvMulti {
1078        fd: { impl sealed::UseFixed },
1079        buf_group: { u16 },
1080        ;;
1081        flags: sys::RecvFlags = sys::RecvFlags::empty(),
1082    }
1083
1084    pub const CODE = sys::IoringOp::Recv;
1085
1086    pub fn build(self) -> Entry {
1087        let RecvMulti { fd, buf_group, flags } = self;
1088
1089        let mut sqe = sqe_zeroed();
1090        sqe.opcode = Self::CODE;
1091        assign_fd!(sqe.fd = fd);
1092        sqe.op_flags.recv_flags = flags;
1093        sqe.buf.buf_group = buf_group;
1094        sqe.flags |= sys::IoringSqeFlags::BUFFER_SELECT;
1095        sqe.ioprio.recv_flags = sys::IoringRecvFlags::MULTISHOT;
1096        Entry(sqe)
1097    }
1098}
1099
1100opcode! {
1101    /// Open a file, equivalent to `openat2(2)`.
1102    pub struct OpenAt2 {
1103        dirfd: { impl sealed::UseFd },
1104        pathname: { *const sys::c_char },
1105        how: { *const types::OpenHow }
1106        ;;
1107        file_index: Option<types::DestinationSlot> = None,
1108    }
1109
1110    pub const CODE = sys::IoringOp::Openat2;
1111
1112    pub fn build(self) -> Entry {
1113        let OpenAt2 { dirfd, pathname, how, file_index } = self;
1114
1115        let mut sqe = sqe_zeroed();
1116        sqe.opcode = Self::CODE;
1117        sqe.fd = dirfd;
1118        sqe.addr_or_splice_off_in.addr.ptr = pathname as _;
1119        sqe.len.len = mem::size_of::<sys::open_how>() as _;
1120        sqe.off_or_addr2.addr2.ptr = how as _;
1121        if let Some(dest) = file_index {
1122            sqe.splice_fd_in_or_file_index_or_addr_len.file_index = dest.kernel_index_arg();
1123        }
1124        Entry(sqe)
1125    }
1126}
1127
1128opcode! {
1129    /// Modify an epoll file descriptor, equivalent to `epoll_ctl(2)`.
1130    pub struct EpollCtl {
1131        epfd: { impl sealed::UseFixed },
1132        fd: { impl sealed::UseFd },
1133        op: { i32 },
1134        ev: { *const types::EpollEvent },
1135        ;;
1136    }
1137
1138    pub const CODE = sys::IoringOp::EpollCtl;
1139
1140    pub fn build(self) -> Entry {
1141        let EpollCtl { epfd, fd, op, ev } = self;
1142
1143        let mut sqe = sqe_zeroed();
1144        sqe.opcode = Self::CODE;
1145        assign_fd!(sqe.fd = epfd);
1146        sqe.addr_or_splice_off_in.addr.ptr = ev as _;
1147        sqe.len.len = op as _;
1148        sqe.off_or_addr2.off = fd as _;
1149        Entry(sqe)
1150    }
1151}
1152
1153// === 5.7 ===
1154
1155opcode! {
1156    /// Splice data to/from a pipe, equivalent to `splice(2)`.
1157    ///
1158    /// if `fd_in` refers to a pipe, `off_in` must be `-1`;
1159    /// The description of `off_in` also applied to `off_out`.
1160    pub struct Splice {
1161        fd_in: { impl sealed::UseFixed },
1162        off_in: { i64 },
1163        fd_out: { impl sealed::UseFixed },
1164        off_out: { i64 },
1165        len: { u32 },
1166        ;;
1167        /// see man `splice(2)` for description of flags.
1168        flags: sys::SpliceFlags = sys::SpliceFlags::empty()
1169    }
1170
1171    pub const CODE = sys::IoringOp::Splice;
1172
1173    pub fn build(self) -> Entry {
1174        let Splice { fd_in, off_in, fd_out, off_out, len, mut flags } = self;
1175
1176        let mut sqe = sqe_zeroed();
1177        sqe.opcode = Self::CODE;
1178        assign_fd!(sqe.fd = fd_out);
1179        sqe.len.len = len;
1180        sqe.off_or_addr2.off = off_out as _;
1181
1182        sqe.splice_fd_in_or_file_index_or_addr_len.splice_fd_in = match fd_in {
1183            sealed::Target::Fd(fd) => fd,
1184            sealed::Target::Fixed(idx) => {
1185                flags |= sys::SpliceFlags::FD_IN_FIXED;
1186                idx as _
1187            }
1188        };
1189
1190        sqe.addr_or_splice_off_in.splice_off_in = off_in as _;
1191        sqe.op_flags.splice_flags = flags;
1192        Entry(sqe)
1193    }
1194}
1195
1196opcode! {
1197    /// Register `nbufs` buffers that each have the length `len` with ids starting from `bid` in the
1198    /// group `bgid` that can be used for any request. See
1199    /// [`BUFFER_SELECT`](crate::squeue::Flags::BUFFER_SELECT) for more info.
1200    pub struct ProvideBuffers {
1201        addr: { *mut u8 },
1202        len: { i32 },
1203        nbufs: { u16 },
1204        bgid: { u16 },
1205        bid: { u16 }
1206        ;;
1207    }
1208
1209    pub const CODE = sys::IoringOp::ProvideBuffers;
1210
1211    pub fn build(self) -> Entry {
1212        let ProvideBuffers { addr, len, nbufs, bgid, bid } = self;
1213
1214        let mut sqe = sqe_zeroed();
1215        sqe.opcode = Self::CODE;
1216        sqe.fd = nbufs as _;
1217        sqe.addr_or_splice_off_in.addr.ptr = addr as _;
1218        sqe.len.len = len as _;
1219        sqe.off_or_addr2.off = bid as _;
1220        sqe.buf.buf_group = bgid;
1221        Entry(sqe)
1222    }
1223}
1224
1225opcode! {
1226    /// Remove some number of buffers from a buffer group. See
1227    /// [`BUFFER_SELECT`](crate::squeue::Flags::BUFFER_SELECT) for more info.
1228    pub struct RemoveBuffers {
1229        nbufs: { u16 },
1230        bgid: { u16 }
1231        ;;
1232    }
1233
1234    pub const CODE = sys::IoringOp::RemoveBuffers;
1235
1236    pub fn build(self) -> Entry {
1237        let RemoveBuffers { nbufs, bgid } = self;
1238
1239        let mut sqe = sqe_zeroed();
1240        sqe.opcode = Self::CODE;
1241        sqe.fd = nbufs as _;
1242        sqe.buf.buf_group = bgid;
1243        Entry(sqe)
1244    }
1245}
1246
1247// === 5.8 ===
1248
1249opcode! {
1250    /// Duplicate pipe content, equivalent to `tee(2)`.
1251    pub struct Tee {
1252        fd_in: { impl sealed::UseFixed },
1253        fd_out: { impl sealed::UseFixed },
1254        len: { u32 }
1255        ;;
1256        flags: sys::SpliceFlags = sys::SpliceFlags::empty()
1257    }
1258
1259    pub const CODE = sys::IoringOp::Tee;
1260
1261    pub fn build(self) -> Entry {
1262        let Tee { fd_in, fd_out, len, mut flags } = self;
1263
1264        let mut sqe = sqe_zeroed();
1265        sqe.opcode = Self::CODE;
1266
1267        assign_fd!(sqe.fd = fd_out);
1268        sqe.len.len = len;
1269
1270        sqe.splice_fd_in_or_file_index_or_addr_len.splice_fd_in = match fd_in {
1271            sealed::Target::Fd(fd) => fd,
1272            sealed::Target::Fixed(idx) => {
1273                flags |= sys::SpliceFlags::FD_IN_FIXED;
1274                idx as _
1275            }
1276        };
1277
1278        sqe.op_flags.splice_flags = flags;
1279
1280        Entry(sqe)
1281    }
1282}
1283
1284// === 5.11 ===
1285
1286opcode! {
1287    /// Shut down all or part of a full duplex connection on a socket, equivalent to `shutdown(2)`.
1288    /// Available since kernel 5.11.
1289    pub struct Shutdown {
1290        fd: { impl sealed::UseFixed },
1291        how: { i32 },
1292        ;;
1293    }
1294
1295    pub const CODE = sys::IoringOp::Shutdown;
1296
1297    pub fn build(self) -> Entry {
1298        let Shutdown { fd, how } = self;
1299
1300        let mut sqe = sqe_zeroed();
1301        sqe.opcode = Self::CODE;
1302        assign_fd!(sqe.fd = fd);
1303        sqe.len.len = how as _;
1304        Entry(sqe)
1305    }
1306}
1307
1308opcode! {
1309    // Change the name or location of a file, equivalent to `renameat2(2)`.
1310    // Available since kernel 5.11.
1311    pub struct RenameAt {
1312        olddirfd: { impl sealed::UseFd },
1313        oldpath: { *const sys::c_char },
1314        newdirfd: { impl sealed::UseFd },
1315        newpath: { *const sys::c_char },
1316        ;;
1317        flags: sys::RenameFlags = sys::RenameFlags::empty()
1318    }
1319
1320    pub const CODE = sys::IoringOp::Renameat;
1321
1322    pub fn build(self) -> Entry {
1323        let RenameAt {
1324            olddirfd, oldpath,
1325            newdirfd, newpath,
1326            flags
1327        } = self;
1328
1329        let mut sqe = sqe_zeroed();
1330        sqe.opcode = Self::CODE;
1331        sqe.fd = olddirfd;
1332        sqe.addr_or_splice_off_in.addr.ptr = oldpath as _;
1333        sqe.len.len = newdirfd as _;
1334        sqe.off_or_addr2.addr2.ptr = newpath as _;
1335        sqe.op_flags.rename_flags = flags;
1336        Entry(sqe)
1337    }
1338}
1339
1340opcode! {
1341    // Delete a name and possible the file it refers to, equivalent to `unlinkat(2)`.
1342    // Available since kernel 5.11.
1343    pub struct UnlinkAt {
1344        dirfd: { impl sealed::UseFd },
1345        pathname: { *const sys::c_char },
1346        ;;
1347        flags: sys::AtFlags = sys::AtFlags::empty()
1348    }
1349
1350    pub const CODE = sys::IoringOp::Unlinkat;
1351
1352    pub fn build(self) -> Entry {
1353        let UnlinkAt { dirfd, pathname, flags } = self;
1354
1355        let mut sqe = sqe_zeroed();
1356        sqe.opcode = Self::CODE;
1357        sqe.fd = dirfd;
1358        sqe.addr_or_splice_off_in.addr.ptr = pathname as _;
1359        sqe.op_flags.unlink_flags = flags;
1360        Entry(sqe)
1361    }
1362}
1363
1364// === 5.15 ===
1365
1366opcode! {
1367    /// Make a directory, equivalent to `mkdirat(2)`.
1368    pub struct MkDirAt {
1369        dirfd: { impl sealed::UseFd },
1370        pathname: { *const sys::c_char },
1371        ;;
1372        mode: sys::Mode = sys::Mode::empty()
1373    }
1374
1375    pub const CODE = sys::IoringOp::Mkdirat;
1376
1377    pub fn build(self) -> Entry {
1378        let MkDirAt { dirfd, pathname, mode } = self;
1379
1380        let mut sqe = sqe_zeroed();
1381        sqe.opcode = Self::CODE;
1382        sqe.fd = dirfd;
1383        sqe.addr_or_splice_off_in.addr.ptr = pathname as _;
1384        sqe.len.len = mode.into();
1385        Entry(sqe)
1386    }
1387}
1388
1389opcode! {
1390    /// Create a symlink, equivalent to `symlinkat(2)`.
1391    pub struct SymlinkAt {
1392        newdirfd: { impl sealed::UseFd },
1393        target: { *const sys::c_char },
1394        linkpath: { *const sys::c_char },
1395        ;;
1396    }
1397
1398    pub const CODE = sys::IoringOp::Symlinkat;
1399
1400    pub fn build(self) -> Entry {
1401        let SymlinkAt { newdirfd, target, linkpath } = self;
1402
1403        let mut sqe = sqe_zeroed();
1404        sqe.opcode = Self::CODE;
1405        sqe.fd = newdirfd;
1406        sqe.addr_or_splice_off_in.addr.ptr = target as _;
1407        sqe.off_or_addr2.addr2.ptr = linkpath as _;
1408        Entry(sqe)
1409    }
1410}
1411
1412opcode! {
1413    /// Create a hard link, equivalent to `linkat(2)`.
1414    pub struct LinkAt {
1415        olddirfd: { impl sealed::UseFd },
1416        oldpath: { *const sys::c_char },
1417        newdirfd: { impl sealed::UseFd },
1418        newpath: { *const sys::c_char },
1419        ;;
1420        flags: sys::AtFlags = sys::AtFlags::empty()
1421    }
1422
1423    pub const CODE = sys::IoringOp::Linkat;
1424
1425    pub fn build(self) -> Entry {
1426        let LinkAt { olddirfd, oldpath, newdirfd, newpath, flags } = self;
1427
1428        let mut sqe = sqe_zeroed();
1429        sqe.opcode = Self::CODE;
1430        sqe.fd = olddirfd as _;
1431        sqe.addr_or_splice_off_in.addr.ptr = oldpath as _;
1432        sqe.len.len = newdirfd as _;
1433        sqe.off_or_addr2.addr2.ptr = newpath as _;
1434        sqe.op_flags.hardlink_flags = flags;
1435        Entry(sqe)
1436    }
1437}
1438
1439// === 5.18 ===
1440
1441opcode! {
1442    /// Send a message (with data) to a target ring.
1443    pub struct MsgRingData {
1444        ring_fd: { impl sealed::UseFd },
1445        result: { i32 },
1446        user_data: { sys::io_uring_user_data },
1447        user_flags: { Option<u32> },
1448        ;;
1449        opcode_flags: sys::IoringMsgringFlags = sys::IoringMsgringFlags::empty()
1450    }
1451
1452    pub const CODE = sys::IoringOp::MsgRing;
1453
1454    pub fn build(self) -> Entry {
1455        let MsgRingData { ring_fd, result, user_data, user_flags, opcode_flags } = self;
1456
1457        let mut sqe = sqe_zeroed();
1458        sqe.opcode = Self::CODE;
1459        sqe.addr_or_splice_off_in.msgring_cmd = sys::IoringMsgringCmds::Data;
1460        sqe.fd = ring_fd;
1461        sqe.len.len = result as u32;
1462        sqe.off_or_addr2.user_data = user_data;
1463        sqe.op_flags.msg_ring_flags = opcode_flags;
1464        if let Some(flags) = user_flags {
1465            sqe.splice_fd_in_or_file_index_or_addr_len.file_index = flags;
1466            unsafe {sqe.op_flags.msg_ring_flags |= sys::IoringMsgringFlags::FLAGS_PASS};
1467        }
1468        Entry(sqe)
1469    }
1470}
1471
1472// === 5.19 ===
1473
1474opcode! {
1475    /// Attempt to cancel an already issued request, receiving a cancellation
1476    /// builder, which allows for the new cancel criterias introduced since
1477    /// 5.19.
1478    pub struct AsyncCancel2 {
1479        builder: { types::CancelBuilder }
1480        ;;
1481    }
1482
1483    pub const CODE = sys::IoringOp::AsyncCancel;
1484
1485    pub fn build(self) -> Entry {
1486        let AsyncCancel2 { builder } = self;
1487
1488        let mut sqe = sqe_zeroed();
1489        sqe.opcode = Self::CODE;
1490        sqe.fd = builder.to_fd();
1491        sqe.addr_or_splice_off_in.user_data = builder.user_data;
1492        sqe.op_flags.cancel_flags = sys::IoringAsyncCancelFlags::from_bits_retain(builder.flags.bits());
1493        Entry(sqe)
1494    }
1495}
1496
1497opcode! {
1498    /// A file/device-specific 16-byte command, akin (but not equivalent) to `ioctl(2)`.
1499    pub struct UringCmd16 {
1500        fd: { impl sealed::UseFixed },
1501        cmd_op: { u32 },
1502        ;;
1503        /// The `buf_index` is an index into an array of fixed buffers,
1504        /// and is only valid if fixed buffers were registered.
1505        buf_index: Option<u16> = None,
1506        /// Arbitrary command data.
1507        cmd: [u8; 16] = [0u8; 16]
1508    }
1509
1510    pub const CODE = sys::IoringOp::UringCmd;
1511
1512    pub fn build(self) -> Entry {
1513        let UringCmd16 { fd, cmd_op, cmd, buf_index } = self;
1514
1515        let mut sqe = sqe_zeroed();
1516        sqe.opcode = Self::CODE;
1517        assign_fd!(sqe.fd = fd);
1518        sqe.off_or_addr2.cmd_op.cmd_op = cmd_op;
1519        unsafe { *sqe.addr3_or_cmd.cmd.as_mut().as_mut_ptr().cast::<[u8; 16]>() = cmd };
1520        if let Some(buf_index) = buf_index {
1521            sqe.buf.buf_index = buf_index;
1522            unsafe {
1523                sqe.op_flags.uring_cmd_flags |= sys::IoringUringCmdFlags::FIXED;
1524            }
1525        }
1526        Entry(sqe)
1527    }
1528}
1529
1530opcode! {
1531    /// A file/device-specific 80-byte command, akin (but not equivalent) to `ioctl(2)`.
1532    pub struct UringCmd80 {
1533        fd: { impl sealed::UseFixed },
1534        cmd_op: { u32 },
1535        ;;
1536        /// The `buf_index` is an index into an array of fixed buffers,
1537        /// and is only valid if fixed buffers were registered.
1538        buf_index: Option<u16> = None,
1539        /// Arbitrary command data.
1540        cmd: [u8; 80] = [0u8; 80]
1541    }
1542
1543    pub const CODE = sys::IoringOp::UringCmd;
1544
1545    pub fn build(self) -> Entry128 {
1546        let UringCmd80 { fd, cmd_op, cmd, buf_index } = self;
1547
1548        let cmd1 = cmd[..16].try_into().unwrap();
1549        let cmd2 = cmd[16..].try_into().unwrap();
1550
1551        let mut sqe = sqe_zeroed();
1552        sqe.opcode = Self::CODE;
1553        assign_fd!(sqe.fd = fd);
1554        sqe.off_or_addr2.cmd_op.cmd_op = cmd_op;
1555        unsafe { *sqe.addr3_or_cmd.cmd.as_mut().as_mut_ptr().cast::<[u8; 16]>() = cmd1 };
1556        if let Some(buf_index) = buf_index {
1557            sqe.buf.buf_index = buf_index;
1558            unsafe {
1559                sqe.op_flags.uring_cmd_flags |= sys::IoringUringCmdFlags::FIXED;
1560            }
1561        }
1562        Entry128(Entry(sqe), cmd2)
1563    }
1564}
1565
1566opcode! {
1567    /// Create an endpoint for communication, equivalent to `socket(2)`.
1568    ///
1569    /// If the `file_index` argument is set, the resulting socket is
1570    /// directly mapped to the given fixed-file slot instead of being
1571    /// returned as a normal file descriptor. The application must first
1572    /// have registered a file table, and the target slot should fit into
1573    /// it.
1574    ///
1575    /// Available since 5.19.
1576    pub struct Socket {
1577        domain: { i32 },
1578        socket_type: { i32 },
1579        protocol: { i32 },
1580        ;;
1581        file_index: Option<types::DestinationSlot> = None,
1582        flags: types::RwFlags = types::RwFlags::empty(),
1583    }
1584
1585    pub const CODE = sys::IoringOp::Socket;
1586
1587    pub fn build(self) -> Entry {
1588        let Socket { domain, socket_type, protocol, file_index, flags } = self;
1589
1590        let mut sqe = sqe_zeroed();
1591        sqe.opcode = Self::CODE;
1592        sqe.fd = domain as _;
1593        sqe.off_or_addr2.off = socket_type as _;
1594        sqe.len.len = protocol as _;
1595        sqe.op_flags.rw_flags = flags;
1596        if let Some(dest) = file_index {
1597            sqe.splice_fd_in_or_file_index_or_addr_len.file_index = dest.kernel_index_arg();
1598        }
1599        Entry(sqe)
1600    }
1601}
1602
1603opcode! {
1604    /// Accept multiple new connections on a socket.
1605    ///
1606    /// Set the `allocate_file_index` property if fixed file table entries should be used.
1607    ///
1608    /// Available since 5.19.
1609    pub struct AcceptMulti {
1610        fd: { impl sealed::UseFixed },
1611        ;;
1612        allocate_file_index: bool = false,
1613        flags: sys::SocketFlags = sys::SocketFlags::empty()
1614    }
1615
1616    pub const CODE = sys::IoringOp::Accept;
1617
1618    pub fn build(self) -> Entry {
1619        let AcceptMulti { fd, allocate_file_index, flags } = self;
1620
1621        let mut sqe = sqe_zeroed();
1622        sqe.opcode = Self::CODE;
1623        assign_fd!(sqe.fd = fd);
1624        sqe.ioprio.accept_flags = sys::IoringAcceptFlags::MULTISHOT;
1625        // No out SockAddr is passed for the multishot accept case.
1626        // The user should perform a syscall to get any resulting connection's remote address.
1627        sqe.op_flags.accept_flags = flags;
1628        if allocate_file_index {
1629            sqe.splice_fd_in_or_file_index_or_addr_len.file_index = sys::IORING_FILE_INDEX_ALLOC as u32;
1630        }
1631        Entry(sqe)
1632    }
1633}
1634
1635// === 6.0 ===
1636
1637opcode! {
1638    /// Send a message (with fixed FD) to a target ring.
1639    pub struct MsgRingSendFd {
1640        ring_fd: { impl sealed::UseFd },
1641        fixed_slot_src: { types::Fixed },
1642        dest_slot_index: { types::DestinationSlot },
1643        user_data: { sys::io_uring_user_data }
1644        ;;
1645        opcode_flags: sys::IoringMsgringFlags = sys::IoringMsgringFlags::empty()
1646    }
1647
1648    pub const CODE = sys::IoringOp::MsgRing;
1649
1650    pub fn build(self) -> Entry {
1651        let MsgRingSendFd { ring_fd, fixed_slot_src, dest_slot_index, user_data, opcode_flags } = self;
1652
1653        let mut sqe = sqe_zeroed();
1654        sqe.opcode = Self::CODE;
1655        sqe.addr_or_splice_off_in.msgring_cmd = sys::IoringMsgringCmds::SendFd;
1656        sqe.fd = ring_fd;
1657        sqe.off_or_addr2.user_data = user_data;
1658        sqe.addr3_or_cmd.addr3.addr3 = fixed_slot_src.0 as u64;
1659        sqe.splice_fd_in_or_file_index_or_addr_len.file_index = dest_slot_index.kernel_index_arg();
1660        sqe.op_flags.msg_ring_flags = opcode_flags;
1661        Entry(sqe)
1662    }
1663}
1664
1665// === 6.0 ===
1666
1667opcode! {
1668    /// Send a zerocopy message on a socket, equivalent to `send(2)`.
1669    ///
1670    /// When `dest_addr` is non-zero it points to the address of the target with `dest_addr_len`
1671    /// specifying its size, turning the request into a `sendto(2)`
1672    ///
1673    /// A fixed (pre-mapped) buffer can optionally be used from pre-mapped buffers that have been
1674    /// previously registered with [`Submitter::register_buffers`](crate::Submitter::register_buffers).
1675    ///
1676    /// This operation might result in two completion queue entries.
1677    /// See the `IORING_OP_SEND_ZC` section at [io_uring_enter][] for the exact semantics.
1678    /// Notifications posted by this operation can be checked with [notif](crate::cqueue::notif).
1679    ///
1680    /// [io_uring_enter]: https://man7.org/linux/man-pages/man2/io_uring_enter.2.html
1681    pub struct SendZc {
1682        fd: { impl sealed::UseFixed },
1683        buf: { *const u8 },
1684        len: { u32 },
1685        ;;
1686        /// The `buf_index` is an index into an array of fixed buffers, and is only valid if fixed
1687        /// buffers were registered.
1688        ///
1689        /// The buf and len arguments must fall within a region specified by buf_index in the
1690        /// previously registered buffer. The buffer need not be aligned with the start of the
1691        /// registered buffer.
1692        buf_index: Option<u16> = None,
1693        dest_addr: *const sys::SocketAddrStorage = core::ptr::null(),
1694        dest_addr_len: sys::SocketAddrLen = 0,
1695        flags: sys::SendFlags = sys::SendFlags::empty(),
1696        zc_flags: sys::IoringSendFlags = sys::IoringSendFlags::empty(),
1697    }
1698
1699    pub const CODE = sys::IoringOp::SendZc;
1700
1701    pub fn build(self) -> Entry {
1702        let SendZc { fd, buf, len, buf_index, dest_addr, dest_addr_len, flags, zc_flags } = self;
1703
1704        let mut sqe = sqe_zeroed();
1705        sqe.opcode = Self::CODE;
1706        assign_fd!(sqe.fd = fd);
1707        sqe.addr_or_splice_off_in.addr.ptr = buf as _;
1708        sqe.len.len = len;
1709        sqe.op_flags.send_flags = flags;
1710        sqe.ioprio.send_flags = zc_flags;
1711        if let Some(buf_index) = buf_index {
1712            sqe.buf.buf_index = buf_index;
1713            unsafe { sqe.ioprio.send_flags |= sys::IoringSendFlags::FIXED_BUF; }
1714        }
1715        sqe.off_or_addr2.addr2.ptr = dest_addr as _;
1716        sqe.splice_fd_in_or_file_index_or_addr_len.addr_len.addr_len = dest_addr_len as _;
1717        Entry(sqe)
1718    }
1719}
1720
1721// === 6.1 ===
1722
1723opcode! {
1724    /// Send a zerocopy message on a socket, equivalent to `send(2)`.
1725    ///
1726    /// fd must be set to the socket file descriptor, addr must contains a pointer to the MsgHdr
1727    /// structure, and flags holds the flags associated with the system call.
1728    #[derive(Debug)]
1729    pub struct SendMsgZc {
1730        fd: { impl sealed::UseFixed },
1731        msg: { *const sys::MsgHdr },
1732        ;;
1733        ioprio: u16 = 0,
1734        flags: sys::SendFlags = sys::SendFlags::empty()
1735    }
1736
1737    pub const CODE = sys::IoringOp::SendmsgZc;
1738
1739    pub fn build(self) -> Entry {
1740        let SendMsgZc { fd, msg, ioprio, flags } = self;
1741
1742        let mut sqe = sqe_zeroed();
1743        sqe.opcode = Self::CODE;
1744        assign_fd!(sqe.fd = fd);
1745        sqe.ioprio.ioprio = ioprio;
1746        sqe.addr_or_splice_off_in.addr.ptr = msg as _;
1747        sqe.len.len = 1;
1748        sqe.op_flags.send_flags = flags;
1749        Entry(sqe)
1750    }
1751}
1752
1753// === 6.7 ===
1754
1755opcode! {
1756    /// Wait on a futex, like but not equivalant to `futex(2)`'s `FUTEX_WAIT_BITSET`.
1757    ///
1758    /// Wait on a futex at address `futex` and which still has the value `val` and with `futex2(2)`
1759    /// flags of `futex_flags`. `musk` can be set to a specific bitset mask, which will be matched
1760    /// by the waking side to decide who to wake up. To always get woken, an application may use
1761    /// `FUTEX_BITSET_MATCH_ANY` (truncated to futex bits). `futex_flags` follows the `futex2(2)`
1762    /// flags, not the `futex(2)` v1 interface flags. `flags` are currently unused and hence `0`
1763    /// must be passed.
1764    #[derive(Debug)]
1765    pub struct FutexWait {
1766        futex: { *const u32 },
1767        val: { u64 },
1768        mask: { u64 },
1769        futex_flags: { sys::FutexWaitFlags },
1770        ;;
1771        flags: sys::FutexWaitvFlags = sys::FutexWaitvFlags::empty()
1772    }
1773
1774    pub const CODE = sys::IoringOp::FutexWait;
1775
1776    pub fn build(self) -> Entry {
1777        let FutexWait { futex, val, mask, futex_flags, flags } = self;
1778
1779        let mut sqe = sqe_zeroed();
1780        sqe.opcode = Self::CODE;
1781        sqe.fd = futex_flags.bits() as _;
1782        sqe.addr_or_splice_off_in.addr.ptr = futex as _;
1783        sqe.off_or_addr2.off = val;
1784        sqe.addr3_or_cmd.addr3.addr3 = mask;
1785        sqe.op_flags.futex_flags = flags;
1786        Entry(sqe)
1787    }
1788}
1789
1790opcode! {
1791    /// Wake up waiters on a futex, like but not equivalant to `futex(2)`'s `FUTEX_WAKE_BITSET`.
1792    ///
1793    /// Wake any waiters on the futex indicated by `futex` and at most `val` futexes. `futex_flags`
1794    /// indicates the `futex2(2)` modifier flags. If a given bitset for who to wake is desired,
1795    /// then that must be set in `mask`. Use `FUTEX_BITSET_MATCH_ANY` (truncated to futex bits) to
1796    /// match any waiter on the given futex. `flags` are currently unused and hence `0` must be
1797    /// passed.
1798    #[derive(Debug)]
1799    pub struct FutexWake {
1800        futex: { *const u32 },
1801        val: { u64 },
1802        mask: { u64 },
1803        futex_flags: { sys::FutexWaitFlags },
1804        ;;
1805        flags: sys::FutexWaitvFlags = sys::FutexWaitvFlags::empty(),
1806    }
1807
1808    pub const CODE = sys::IoringOp::FutexWake;
1809
1810    pub fn build(self) -> Entry {
1811        let FutexWake { futex, val, mask, futex_flags, flags } = self;
1812
1813        let mut sqe = sqe_zeroed();
1814        sqe.opcode = Self::CODE;
1815        sqe.fd = futex_flags.bits() as _;
1816        sqe.addr_or_splice_off_in.addr.ptr = futex as _;
1817        sqe.off_or_addr2.off = val;
1818        sqe.addr3_or_cmd.addr3.addr3 = mask;
1819        sqe.op_flags.futex_flags = flags;
1820        Entry(sqe)
1821    }
1822}
1823
1824opcode! {
1825    /// Wait on multiple futexes.
1826    ///
1827    /// Wait on multiple futexes at the same time. Futexes are given by `futexv` and `nr_futex` is
1828    /// the number of futexes in that array. Unlike `FutexWait`, the desired bitset mask and values
1829    /// are passed in `futexv`. `flags` are currently unused and hence `0` must be passed.
1830    #[derive(Debug)]
1831    pub struct FutexWaitV {
1832        futexv: { *const types::FutexWaitV },
1833        nr_futex: { u32 },
1834        ;;
1835        flags: sys::FutexWaitvFlags = sys::FutexWaitvFlags::empty(),
1836    }
1837
1838    pub const CODE = sys::IoringOp::FutexWaitv;
1839
1840    pub fn build(self) -> Entry {
1841        let FutexWaitV { futexv, nr_futex, flags } = self;
1842
1843        let mut sqe = sqe_zeroed();
1844        sqe.opcode = Self::CODE;
1845        sqe.addr_or_splice_off_in.addr.ptr = futexv as _;
1846        sqe.len.len = nr_futex;
1847        sqe.op_flags.futex_flags = flags;
1848        Entry(sqe)
1849    }
1850}
1851
1852// === 6.8 ===
1853
1854opcode! {
1855    /// Install a fixed file descriptor
1856    ///
1857    /// Turns a direct descriptor into a regular file descriptor that can be later used by regular
1858    /// system calls that take a normal raw file descriptor
1859    #[derive(Debug)]
1860    pub struct FixedFdInstall {
1861        fd: { types::Fixed },
1862        file_flags: { sys::IoringFixedFdFlags },
1863        ;;
1864    }
1865
1866    pub const CODE = sys::IoringOp::FixedFdInstall;
1867
1868    pub fn build(self) -> Entry {
1869        let FixedFdInstall { fd, file_flags } = self;
1870
1871        let mut sqe = sqe_zeroed();
1872        sqe.opcode = Self::CODE;
1873        sqe.fd = fd.0 as _;
1874        sqe.flags = sys::IoringSqeFlags::FIXED_FILE;
1875        sqe.op_flags.install_fd_flags = file_flags;
1876        Entry(sqe)
1877    }
1878}
1879
1880// === 6.9 ===
1881
1882opcode! {
1883    /// Perform file truncation, equivalent to `ftruncate(2)`.
1884    #[derive(Debug)]
1885    pub struct Ftruncate {
1886        fd: { impl sealed::UseFixed },
1887        len: { u64 },
1888        ;;
1889    }
1890
1891    pub const CODE = sys::IoringOp::Ftruncate;
1892
1893    pub fn build(self) -> Entry {
1894        let Ftruncate { fd, len } = self;
1895
1896        let mut sqe = sqe_zeroed();
1897        sqe.opcode = Self::CODE;
1898        assign_fd!(sqe.fd = fd);
1899        sqe.off_or_addr2.off = len;
1900        Entry(sqe)
1901    }
1902}
1903
1904// === 6.10 ===
1905
1906opcode! {
1907    /// Send a bundle of messages on a socket in a single request.
1908    pub struct SendBundle {
1909        fd: { impl sealed::UseFixed },
1910        buf_group: { u16 },
1911        ;;
1912        flags: sys::SendFlags = sys::SendFlags::empty(),
1913        len: u32 = 0
1914    }
1915
1916    pub const CODE = sys::IoringOp::Send;
1917
1918    pub fn build(self) -> Entry {
1919        let SendBundle { fd, len, flags, buf_group } = self;
1920
1921        let mut sqe = sqe_zeroed();
1922        sqe.opcode = Self::CODE;
1923        assign_fd!(sqe.fd = fd);
1924        sqe.len.len = len;
1925        sqe.op_flags.send_flags = flags;
1926        sqe.ioprio.send_flags = sys::IoringSendFlags::BUNDLE;
1927        sqe.flags |= sys::IoringSqeFlags::BUFFER_SELECT;
1928        sqe.buf.buf_group = buf_group;
1929        Entry(sqe)
1930    }
1931}
1932
1933opcode! {
1934    /// Receive a bundle of buffers from a socket.
1935    ///
1936    /// Parameter
1937    ///     buf_group: The id of the provided buffer pool to use for the bundle.
1938    ///
1939    /// Note that as of kernel 6.10 first recv always gets a single buffer, while second
1940    /// obtains the bundle of remaining buffers. This behavior may change in the future.
1941    ///
1942    /// Bundle variant is available since kernel 6.10
1943    pub struct RecvBundle {
1944        fd: { impl sealed::UseFixed },
1945        buf_group: { u16 },
1946        ;;
1947        flags: sys::RecvFlags = sys::RecvFlags::empty(),
1948    }
1949
1950    pub const CODE = sys::IoringOp::Recv;
1951
1952    pub fn build(self) -> Entry {
1953        let RecvBundle { fd, buf_group, flags } = self;
1954
1955        let mut sqe = sqe_zeroed();
1956        sqe.opcode = Self::CODE;
1957        assign_fd!(sqe.fd = fd);
1958        sqe.op_flags.recv_flags = flags;
1959        sqe.buf.buf_group = buf_group;
1960        sqe.flags |= sys::IoringSqeFlags::BUFFER_SELECT;
1961        sqe.ioprio.recv_flags = sys::IoringRecvFlags::BUNDLE;
1962        Entry(sqe)
1963    }
1964}
1965
1966opcode! {
1967    /// Receive multiple messages from a socket as a bundle.
1968    ///
1969    /// Parameter:
1970    ///     buf_group: The id of the provided buffer pool to use for each received message.
1971    ///
1972    /// MSG_WAITALL should not be set in flags.
1973    ///
1974    /// The multishot version allows the application to issue a single receive request, which
1975    /// repeatedly posts a CQE when data is available. Each CQE will take a bundle of buffers
1976    /// out of a provided buffer pool for receiving. The application should check the flags of each CQE,
1977    /// regardless of its result. If a posted CQE does not have the IORING_CQE_F_MORE flag set then
1978    /// the multishot receive will be done and the application should issue a new request.
1979    ///
1980    /// Note that as of kernel 6.10 first CQE always gets a single buffer, while second
1981    /// obtains the bundle of remaining buffers. This behavior may change in the future.
1982    ///
1983    /// Multishot bundle variant is available since kernel 6.10.
1984    pub struct RecvMultiBundle {
1985        fd: { impl sealed::UseFixed },
1986        buf_group: { u16 },
1987        ;;
1988        flags: sys::RecvFlags = sys::RecvFlags::empty(),
1989    }
1990
1991    pub const CODE = sys::IoringOp::Recv;
1992
1993    pub fn build(self) -> Entry {
1994        let RecvMultiBundle { fd, buf_group, flags } = self;
1995
1996        let mut sqe = sqe_zeroed();
1997        sqe.opcode = Self::CODE;
1998        assign_fd!(sqe.fd = fd);
1999        sqe.op_flags.recv_flags = flags;
2000        sqe.buf.buf_group = buf_group;
2001        sqe.flags |= sys::IoringSqeFlags::BUFFER_SELECT;
2002        sqe.ioprio.recv_flags = sys::IoringRecvFlags::MULTISHOT | sys::IoringRecvFlags::BUNDLE;
2003        Entry(sqe)
2004    }
2005}