1use super::owned::OwnedOp;
27use super::raw::{self, FutexWaitvRaw, KernelTimespec};
28use super::{CancelTarget, IoUring, SubmissionToken, opcode_number};
29use crate::mem::MmapRegion;
30use air_sys_types::Errno;
31use air_sys_types::fd::{AsRawFd, BorrowedFd};
32use air_sys_types::io_uring::{
33 CancelFlags, EpollEvent, EpollOp, FutexFlags, MessageRingFlags, PollEvents, TimeoutFlags,
34 TimeoutSpec,
35};
36use air_sys_types::process::{WaitOptions, WaitTarget};
37use air_sys_types::signal::SignalInfo;
38use alloc::boxed::Box;
39use alloc::vec::Vec;
40use core::time::Duration;
41
42pub(crate) fn timespec_of(spec: TimeoutSpec) -> KernelTimespec {
45 let d = spec.duration.unwrap_or(Duration::ZERO);
46 KernelTimespec {
47 tv_sec: i64::try_from(d.as_secs()).unwrap_or(i64::MAX),
48 tv_nsec: i64::from(d.subsec_nanos()),
49 }
50}
51
52fn waitid_target(target: WaitTarget<'_>) -> (i32, i32) {
54 match target {
55 WaitTarget::AnyChild => (raw::P_ALL, 0),
56 WaitTarget::Pid(p) => (raw::P_PID, p.as_raw()),
57 WaitTarget::ProcessGroup(p) => (raw::P_PGID, p.as_raw()),
58 WaitTarget::AnyProcessGroup => (raw::P_PGID, 0),
59 WaitTarget::PidFd(fd) => (raw::P_PIDFD, fd.as_raw_fd()),
60 }
61}
62
63impl IoUring {
64 pub fn submit_nop_with_result(&mut self, injected: i32) -> Result<SubmissionToken, Errno> {
75 let len = injected.cast_unsigned();
76 self.submit_op(None, |sqe| {
77 sqe.opcode = raw::IORING_OP_NOP;
78 sqe.len = len;
79 sqe.op_flags = raw::IORING_NOP_INJECT_RESULT;
80 })
81 }
82
83 pub fn submit_timeout(
94 &mut self,
95 spec: TimeoutSpec,
96 flags: TimeoutFlags,
97 ) -> Result<SubmissionToken, Errno> {
98 let ts = Box::new(timespec_of(spec));
99 let ts_ptr = core::ptr::from_ref(&*ts) as u64;
100 let count = u64::from(spec.count);
101 let op_flags = flags.bits();
102 self.submit_op(Some(OwnedOp::Timeout(ts)), |sqe| {
103 sqe.opcode = raw::IORING_OP_TIMEOUT;
104 sqe.fd = -1;
105 sqe.addr_or_splice_off_in = ts_ptr;
106 sqe.len = 1;
107 sqe.off_or_addr2 = count;
108 sqe.op_flags = op_flags;
109 })
110 }
111
112 pub fn submit_timeout_remove(
120 &mut self,
121 target: SubmissionToken,
122 ) -> Result<SubmissionToken, Errno> {
123 let addr = target.to_user_data();
124 self.submit_op(None, |sqe| {
125 sqe.opcode = raw::IORING_OP_TIMEOUT_REMOVE;
126 sqe.fd = -1;
127 sqe.addr_or_splice_off_in = addr;
128 })
129 }
130
131 pub fn submit_timeout_update(
139 &mut self,
140 target: SubmissionToken,
141 spec: TimeoutSpec,
142 flags: TimeoutFlags,
143 ) -> Result<SubmissionToken, Errno> {
144 let ts = Box::new(timespec_of(spec));
145 let ts_ptr = core::ptr::from_ref(&*ts) as u64;
146 let addr = target.to_user_data();
147 let op_flags = flags.bits() | raw::IORING_TIMEOUT_UPDATE;
148 self.submit_op(Some(OwnedOp::Timeout(ts)), |sqe| {
149 sqe.opcode = raw::IORING_OP_TIMEOUT_REMOVE;
150 sqe.fd = -1;
151 sqe.addr_or_splice_off_in = addr;
152 sqe.off_or_addr2 = ts_ptr;
153 sqe.op_flags = op_flags;
154 })
155 }
156
157 pub fn submit_cancel(
169 &mut self,
170 target: CancelTarget<'_>,
171 flags: CancelFlags,
172 ) -> Result<SubmissionToken, Errno> {
173 let mut fd = -1;
174 let mut addr = 0u64;
175 let mut len = 0u32;
176 let mut cancel_flags = flags.bits();
177 match target {
178 CancelTarget::Token(token) => addr = token.to_user_data(),
179 CancelTarget::Fd(target_fd) => {
180 fd = target_fd.as_raw_fd();
181 cancel_flags |= raw::IORING_ASYNC_CANCEL_FD;
182 }
183 CancelTarget::Op(op) => {
184 len = u32::from(opcode_number(op));
185 cancel_flags |= raw::IORING_ASYNC_CANCEL_OP;
186 }
187 CancelTarget::Any => cancel_flags |= raw::IORING_ASYNC_CANCEL_ANY,
188 }
189 self.submit_op(None, |sqe| {
190 sqe.opcode = raw::IORING_OP_ASYNC_CANCEL;
191 sqe.fd = fd;
192 sqe.addr_or_splice_off_in = addr;
193 sqe.len = len;
194 sqe.op_flags = cancel_flags;
195 })
196 }
197
198 pub fn submit_poll_add(
208 &mut self,
209 fd: BorrowedFd<'_>,
210 events: PollEvents,
211 ) -> Result<SubmissionToken, Errno> {
212 let fd = fd.as_raw_fd();
213 let events = events.bits();
214 self.submit_op(None, |sqe| {
215 sqe.opcode = raw::IORING_OP_POLL_ADD;
216 sqe.fd = fd;
217 sqe.op_flags = events;
218 })
219 }
220
221 pub fn submit_poll_remove(
228 &mut self,
229 target: SubmissionToken,
230 ) -> Result<SubmissionToken, Errno> {
231 let addr = target.to_user_data();
232 self.submit_op(None, |sqe| {
233 sqe.opcode = raw::IORING_OP_POLL_REMOVE;
234 sqe.fd = -1;
235 sqe.addr_or_splice_off_in = addr;
236 })
237 }
238
239 pub fn submit_poll_update(
247 &mut self,
248 target: SubmissionToken,
249 events: PollEvents,
250 ) -> Result<SubmissionToken, Errno> {
251 let ud = target.to_user_data();
252 let events = events.bits();
253 self.submit_op(None, |sqe| {
254 sqe.opcode = raw::IORING_OP_POLL_REMOVE;
255 sqe.fd = -1;
256 sqe.addr_or_splice_off_in = ud;
257 sqe.off_or_addr2 = ud;
258 sqe.len = raw::IORING_POLL_UPDATE_EVENTS;
259 sqe.op_flags = events;
260 })
261 }
262
263 pub fn submit_epoll_ctl(
271 &mut self,
272 epfd: BorrowedFd<'_>,
273 op: EpollOp,
274 fd: BorrowedFd<'_>,
275 event: EpollEvent,
276 ) -> Result<SubmissionToken, Errno> {
277 let epfd = epfd.as_raw_fd();
278 let target_fd = u64::from(fd.as_raw_fd().cast_unsigned());
279 let op = op.as_raw().cast_unsigned();
280 let event = Box::new(event);
281 let event_ptr = core::ptr::from_ref(&*event) as u64;
282 self.submit_op(Some(OwnedOp::Epoll(event)), |sqe| {
283 sqe.opcode = raw::IORING_OP_EPOLL_CTL;
284 sqe.fd = epfd;
285 sqe.addr_or_splice_off_in = event_ptr;
286 sqe.len = op;
287 sqe.off_or_addr2 = target_fd;
288 })
289 }
290
291 pub fn submit_waitid(
302 &mut self,
303 target: WaitTarget<'_>,
304 options: WaitOptions,
305 info: Box<SignalInfo>,
306 ) -> Result<SubmissionToken, Errno> {
307 let (idtype, id) = waitid_target(target);
308 let info_ptr = info.as_bytes().as_ptr() as u64;
309 let options = options.bits().cast_unsigned();
310 self.submit_op(Some(OwnedOp::Waitid(info)), |sqe| {
311 sqe.opcode = raw::IORING_OP_WAITID;
312 sqe.fd = id;
313 sqe.len = idtype.cast_unsigned();
314 sqe.off_or_addr2 = info_ptr;
315 sqe.splice_fd_in_or_file_index = options;
316 })
317 }
318
319 pub fn submit_message_ring_data(
330 &mut self,
331 target: &IoUring,
332 res: i32,
333 user_data: u64,
334 flags: MessageRingFlags,
335 ) -> Result<SubmissionToken, Errno> {
336 let target_fd = target.fd_raw();
337 let len = res.cast_unsigned();
338 let op_flags = flags.bits();
339 self.submit_op(None, |sqe| {
340 sqe.opcode = raw::IORING_OP_MSG_RING;
341 sqe.fd = target_fd;
342 sqe.addr_or_splice_off_in = raw::IORING_MSG_DATA;
343 sqe.len = len;
344 sqe.off_or_addr2 = user_data;
345 sqe.op_flags = op_flags;
346 })
347 }
348
349 fn futex2_fd(flags: FutexFlags) -> Result<i32, Errno> {
361 i32::try_from(raw::FUTEX2_SIZE_U32 | flags.bits()).map_err(|_| Errno::EINVAL)
362 }
363
364 pub fn submit_futex_wait(
376 &mut self,
377 region: &MmapRegion,
378 offset: usize,
379 expected: u64,
380 mask: u64,
381 flags: FutexFlags,
382 ) -> Result<SubmissionToken, Errno> {
383 let uaddr = core::ptr::from_ref(region.futex_word(offset)?) as u64;
384 let futex2 = Self::futex2_fd(flags)?;
385 let guard = region.liveness_handle();
386 self.submit_op(Some(OwnedOp::MemLiveness(guard)), |sqe| {
387 sqe.opcode = raw::IORING_OP_FUTEX_WAIT;
388 sqe.fd = futex2;
389 sqe.addr_or_splice_off_in = uaddr;
390 sqe.off_or_addr2 = expected;
391 sqe.addr3 = mask;
392 })
393 }
394
395 pub fn submit_futex_wake(
405 &mut self,
406 region: &MmapRegion,
407 offset: usize,
408 nr: u64,
409 mask: u64,
410 flags: FutexFlags,
411 ) -> Result<SubmissionToken, Errno> {
412 let uaddr = core::ptr::from_ref(region.futex_word(offset)?) as u64;
413 let futex2 = Self::futex2_fd(flags)?;
414 let guard = region.liveness_handle();
415 self.submit_op(Some(OwnedOp::MemLiveness(guard)), |sqe| {
416 sqe.opcode = raw::IORING_OP_FUTEX_WAKE;
417 sqe.fd = futex2;
418 sqe.addr_or_splice_off_in = uaddr;
419 sqe.off_or_addr2 = nr;
420 sqe.addr3 = mask;
421 })
422 }
423
424 pub fn submit_futex_waitv(
442 &mut self,
443 waiters: Vec<FutexWaiter>,
444 flags: FutexFlags,
445 ) -> Result<SubmissionToken, Errno> {
446 if waiters.is_empty() || waiters.len() > raw::FUTEX_WAITV_MAX {
448 return Err(Errno::EINVAL);
449 }
450 let futex2 = raw::FUTEX2_SIZE_U32 | flags.bits();
451 let mut raws: Vec<FutexWaitvRaw> = Vec::with_capacity(waiters.len());
452 let mut guards = Vec::with_capacity(waiters.len());
453 for waiter in &waiters {
454 let uaddr = core::ptr::from_ref(waiter.region.futex_word(waiter.offset)?) as u64;
456 raws.push(FutexWaitvRaw {
457 val: waiter.expected,
458 uaddr,
459 flags: futex2,
460 reserved: 0,
461 });
462 guards.push(waiter.region.liveness_handle());
463 }
464 let raws = raws.into_boxed_slice();
465 let arr_ptr = raws.as_ptr() as u64;
466 let nr = u32::try_from(raws.len()).map_err(|_| Errno::EINVAL)?;
468 self.submit_op(
469 Some(OwnedOp::FutexWaitv {
470 waiters: raws,
471 guards,
472 }),
473 |sqe| {
474 sqe.opcode = raw::IORING_OP_FUTEX_WAITV;
475 sqe.fd = 0;
476 sqe.addr_or_splice_off_in = arr_ptr;
477 sqe.len = nr;
478 },
479 )
480 }
481}
482
483#[derive(Debug, Clone)]
490pub struct FutexWaiter {
491 pub region: MmapRegion,
493 pub offset: usize,
495 pub expected: u64,
497}
498
499#[cfg(test)]
507mod tests {
508 use super::*;
509 use crate::io_uring::{Completion, IoUring, IoUringBuilder, SubmissionToken};
510 use crate::test_support::sfd;
511 use air_sys_types::fd::{AsFd, FromRawFd, OwnedFd};
512 use air_sys_types::process::Pid;
513 use core::num::NonZeroU32;
514 use std::io::Write;
515 use std::process::Command;
516
517 fn ring8() -> IoUring {
518 IoUring::new(NonZeroU32::new(16).expect("16 ≠ 0")).expect("io_uring_setup 6.12")
519 }
520
521 fn complete_one(ring: &mut IoUring, expected: SubmissionToken) -> Completion {
522 ring.submit_and_wait(1).expect("submit_and_wait");
523 let c = ring.wait_completion().expect("wait_completion");
524 assert_eq!(c.token(), expected, "user_data round-trip");
525 c
526 }
527
528 fn epoll_create1_cloexec() -> OwnedFd {
531 const EPOLL_CLOEXEC: i32 = 0x0008_0000;
532 let ret: i64;
533 #[cfg(target_arch = "x86_64")]
534 unsafe {
537 core::arch::asm!(
538 "syscall",
539 in("rax") 291i64,
540 in("rdi") EPOLL_CLOEXEC,
541 lateout("rax") ret,
542 lateout("rcx") _,
543 lateout("r11") _,
544 options(nostack, preserves_flags),
545 );
546 }
547 #[cfg(target_arch = "aarch64")]
548 unsafe {
550 core::arch::asm!(
551 "svc #0",
552 in("x8") 20i64,
553 in("x0") EPOLL_CLOEXEC,
554 lateout("x0") ret,
555 options(nostack, preserves_flags),
556 );
557 }
558 let raw = i32::try_from(ret).expect("fd epoll dans i32");
559 assert!(raw >= 0, "epoll_create1 a échoué : {ret}");
560 unsafe { OwnedFd::from_raw_fd(raw) }
562 }
563
564 #[test]
567 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
568 fn nop_with_result_injects_res() {
569 let mut ring = ring8();
570 let tok = ring.submit_nop_with_result(42).expect("nop inject");
572 assert_eq!(complete_one(&mut ring, tok).into_result().expect("ok"), 42);
573 let tok = ring.submit_nop_with_result(-22).expect("nop inject err");
575 assert_eq!(
576 complete_one(&mut ring, tok).into_result().err(),
577 Some(Errno::EINVAL)
578 );
579 }
580
581 #[test]
584 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
585 fn timeout_expires_etime() {
586 let mut ring = ring8();
587 let tok = ring
589 .submit_timeout(
590 TimeoutSpec::after(Duration::from_millis(5)),
591 TimeoutFlags::empty(),
592 )
593 .expect("timeout");
594 assert_eq!(
595 complete_one(&mut ring, tok).completed().err(),
596 Some(Errno::ETIME)
597 );
598 let tok = ring
602 .submit_timeout(
603 TimeoutSpec::after(Duration::from_millis(5)),
604 TimeoutFlags::ETIME_SUCCESS,
605 )
606 .expect("timeout etime_success");
607 assert_eq!(
608 complete_one(&mut ring, tok).completed().err(),
609 Some(Errno::ETIME)
610 );
611 }
612
613 #[test]
614 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
615 fn timeout_after_completions_count() {
616 let mut ring = ring8();
617 let timeout_tok = ring
619 .submit_timeout(TimeoutSpec::after_completions(1), TimeoutFlags::empty())
620 .expect("timeout count");
621 let nop_tok = ring.submit_nop_with_result(7).expect("nop");
623 ring.submit_and_wait(2).expect("submit");
624 let mut seen_nop = false;
625 let mut seen_timeout = false;
626 while ring.in_flight() > 0 {
627 let c = ring.wait_completion().expect("wait");
628 if c.token() == nop_tok {
629 seen_nop = true;
630 } else if c.token() == timeout_tok {
631 seen_timeout = true;
632 }
633 }
634 assert!(seen_nop && seen_timeout);
635 }
636
637 #[test]
638 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
639 fn timeout_remove_and_update() {
640 let mut ring = ring8();
641 let t = ring
643 .submit_timeout(
644 TimeoutSpec::after(Duration::from_secs(60)),
645 TimeoutFlags::empty(),
646 )
647 .expect("timeout long");
648 let rm = ring.submit_timeout_remove(t).expect("timeout_remove");
649 ring.submit_and_wait(2).expect("submit");
650 while ring.in_flight() > 0 {
651 let _ = ring.wait_completion().expect("wait");
652 }
653 let _ = (t, rm);
654
655 let t2 = ring
657 .submit_timeout(
658 TimeoutSpec::after(Duration::from_secs(60)),
659 TimeoutFlags::empty(),
660 )
661 .expect("timeout long 2");
662 let up = ring
663 .submit_timeout_update(
664 t2,
665 TimeoutSpec::after(Duration::from_millis(5)),
666 TimeoutFlags::empty(),
667 )
668 .expect("timeout_update");
669 ring.submit_and_wait(2).expect("submit");
670 while ring.in_flight() > 0 {
671 let _ = ring.wait_completion().expect("wait");
672 }
673 let _ = (t2, up);
674 assert_eq!(ring.in_flight(), 0);
675 }
676
677 #[test]
680 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
681 fn cancel_inflight_poll_yields_ecanceled_and_restitutes_slot() {
682 let (reader, _writer) = std::io::pipe().expect("pipe");
683 let mut ring = ring8();
684 let poll = ring
686 .submit_poll_add(sfd(&reader), PollEvents::IN)
687 .expect("poll_add");
688 ring.submit().expect("arm poll");
692 let cancel = ring
695 .submit_cancel(CancelTarget::Token(poll), CancelFlags::ALL)
696 .expect("cancel");
697 ring.submit_and_wait(2).expect("submit");
698 let mut cancel_count = None;
699 let mut poll_err = None;
700 while ring.in_flight() > 0 {
701 let c = ring.wait_completion().expect("wait");
702 if c.token() == cancel {
703 cancel_count = Some(c.into_result().expect("cancel ok"));
704 } else if c.token() == poll {
705 poll_err = Some(c.into_poll_result().err());
706 }
707 }
708 assert_eq!(cancel_count, Some(1), "une op annulée");
709 assert_eq!(poll_err, Some(Some(Errno::ECANCELED)), "poll en -ECANCELED");
710 assert_eq!(ring.in_flight(), 0, "slot restitué");
711 }
712
713 #[test]
716 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
717 fn poll_add_fires_on_write() {
718 let (reader, mut writer) = std::io::pipe().expect("pipe");
719 let mut ring = ring8();
720 let tok = ring
721 .submit_poll_add(sfd(&reader), PollEvents::IN)
722 .expect("poll_add");
723 ring.submit().expect("submit");
724 writer.write_all(b"x").expect("write");
725 let events = complete_one(&mut ring, tok)
726 .into_poll_result()
727 .expect("poll ok");
728 assert!(events.contains(PollEvents::IN), "POLLIN signalé");
729 }
730
731 #[test]
732 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
733 fn poll_update_then_remove() {
734 let (reader, _writer) = std::io::pipe().expect("pipe");
735 let mut ring = ring8();
736 let poll = ring
737 .submit_poll_add(sfd(&reader), PollEvents::IN)
738 .expect("poll_add");
739 let up = ring
740 .submit_poll_update(poll, PollEvents::IN | PollEvents::PRI)
741 .expect("poll_update");
742 complete_one(&mut ring, up).completed().expect("update ok");
743 let rm = ring.submit_poll_remove(poll).expect("poll_remove");
744 ring.submit_and_wait(2).expect("submit");
745 while ring.in_flight() > 0 {
746 let _ = ring.wait_completion().expect("wait");
747 }
748 let _ = rm;
749 assert_eq!(ring.in_flight(), 0);
750 }
751
752 #[test]
755 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
756 fn epoll_ctl_add_mod_del() {
757 let epfd = epoll_create1_cloexec();
758 let (reader, _writer) = std::io::pipe().expect("pipe");
759 let mut ring = ring8();
760
761 let tok = ring
762 .submit_epoll_ctl(
763 epfd.as_fd(),
764 EpollOp::Add,
765 sfd(&reader),
766 EpollEvent::new(PollEvents::IN, 0xA),
767 )
768 .expect("epoll add");
769 complete_one(&mut ring, tok).completed().expect("add ok");
770
771 let tok = ring
772 .submit_epoll_ctl(
773 epfd.as_fd(),
774 EpollOp::Modify,
775 sfd(&reader),
776 EpollEvent::new(PollEvents::IN | PollEvents::OUT, 0xB),
777 )
778 .expect("epoll mod");
779 complete_one(&mut ring, tok).completed().expect("mod ok");
780
781 let tok = ring
782 .submit_epoll_ctl(
783 epfd.as_fd(),
784 EpollOp::Delete,
785 sfd(&reader),
786 EpollEvent::new(PollEvents::empty(), 0),
787 )
788 .expect("epoll del");
789 complete_one(&mut ring, tok).completed().expect("del ok");
790 }
791
792 #[test]
795 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
796 fn waitid_reaps_child() {
797 let child = Command::new("true").spawn().expect("spawn true");
799 let pid_raw = i32::try_from(child.id()).expect("pid i32");
800 std::mem::forget(child);
802 let pid = Pid::try_from_raw(pid_raw).expect("pid > 0");
803
804 let mut ring = ring8();
805 let tok = ring
806 .submit_waitid(
807 WaitTarget::Pid(pid),
808 WaitOptions::EXITED,
809 Box::new(SignalInfo::zeroed()),
810 )
811 .expect("submit_waitid");
812 let info = complete_one(&mut ring, tok)
813 .into_waitid_result()
814 .expect("waitid ok");
815 let bytes = info.as_bytes();
816 let si_signo = i32::from_ne_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
817 let si_pid = i32::from_ne_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]);
818 assert_eq!(si_signo, 17, "SIGCHLD");
819 assert_eq!(si_pid, pid_raw, "PID de l'enfant récolté");
820 }
821
822 #[test]
825 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
826 fn message_ring_data_posts_cqe_to_target() {
827 let mut sender = ring8();
828 let mut target = ring8();
829 assert_eq!(target.cq_ready(), 0);
830 let tok = sender
831 .submit_message_ring_data(&target, 99, 0xABCD, MessageRingFlags::empty())
832 .expect("msg_ring data");
833 complete_one(&mut sender, tok)
834 .completed()
835 .expect("émetteur ok");
836 assert_eq!(target.cq_ready(), 1, "CQE posté côté cible");
839 assert!(target.try_completion().is_none(), "CQE périmé filtré");
841 assert_eq!(target.cq_ready(), 0);
842 }
843
844 #[test]
847 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
848 fn slab_full_returns_ebusy() {
849 let mut ring = IoUringBuilder::new(NonZeroU32::new(8).expect("8"))
850 .max_inflight(NonZeroU32::new(1).expect("1"))
851 .build()
852 .expect("ring slab=1");
853 let _t = ring.submit_nop_with_result(0).expect("1ʳᵉ op");
854 assert_eq!(ring.submit_nop_with_result(0), Err(Errno::EBUSY));
855 ring.submit_and_wait(1).expect("submit");
856 let _ = ring.wait_completion().expect("complétion");
857 }
858
859 fn cqe(res: i32, payload: Option<OwnedOp>) -> Completion {
862 Completion::for_test(SubmissionToken::new(0, 0), res, 0, payload)
863 }
864
865 #[test]
866 fn poll_result_accessor_paths() {
867 let ok = cqe(0x005, None).into_poll_result().expect("ok");
869 assert_eq!(ok, PollEvents::IN | PollEvents::OUT);
870 assert_eq!(
872 cqe(-125, None).into_poll_result().err(),
873 Some(Errno::ECANCELED)
874 );
875 }
876
877 #[test]
878 fn waitid_result_accessor_paths() {
879 assert_eq!(
881 cqe(-10, None).into_waitid_result().err(),
882 Some(Errno::ECHILD)
883 );
884 let mut info = Box::new(SignalInfo::zeroed());
886 let _ = &mut info;
888 let restituted = cqe(0, Some(OwnedOp::Waitid(info)))
889 .into_waitid_result()
890 .expect("ok");
891 assert_eq!(restituted.as_bytes()[0], 0);
892 let default = cqe(0, None).into_waitid_result().expect("ok");
894 assert_eq!(default.as_bytes(), &[0u8; 128]);
895 }
896
897 #[test]
898 fn waitid_target_maps_all_variants() {
899 assert_eq!(super::waitid_target(WaitTarget::AnyChild), (0, 0));
901 let pid = Pid::try_from_raw(123).expect("pid");
902 assert_eq!(super::waitid_target(WaitTarget::Pid(pid)), (1, 123));
903 assert_eq!(
904 super::waitid_target(WaitTarget::ProcessGroup(pid)),
905 (2, 123)
906 );
907 assert_eq!(super::waitid_target(WaitTarget::AnyProcessGroup), (2, 0));
908 let borrowed = unsafe { BorrowedFd::borrow_raw(0) };
911 assert_eq!(super::waitid_target(WaitTarget::PidFd(borrowed)), (3, 0));
912 }
913
914 #[test]
915 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
916 fn cancel_target_variants_are_submittable() {
917 let mut ring = ring8();
921 let stdin = unsafe { BorrowedFd::borrow_raw(0) };
924 let tok = ring
925 .submit_cancel(CancelTarget::Fd(stdin), CancelFlags::ALL)
926 .expect("cancel fd");
927 let _ = complete_one(&mut ring, tok).into_result();
928 let tok = ring
929 .submit_cancel(
930 CancelTarget::Op(crate::io_uring::IoUringOpcode::Read),
931 CancelFlags::ALL,
932 )
933 .expect("cancel op");
934 let _ = complete_one(&mut ring, tok).into_result();
935 let tok = ring
936 .submit_cancel(CancelTarget::Any, CancelFlags::ALL)
937 .expect("cancel any");
938 let _ = complete_one(&mut ring, tok).into_result();
939 assert_eq!(ring.in_flight(), 0);
940 }
941
942 #[test]
945 fn ownership_2c_payloads_round_trip_via_slab() {
946 use super::super::slab::{InflightSlab, SlotOutcome};
947 let mut slab = InflightSlab::with_capacity(NonZeroU32::new(4).expect("4"));
948 let info = Box::new(SignalInfo::zeroed());
950 let token = slab.reserve(Some(OwnedOp::Waitid(info))).expect("réserve");
951 let restituted = matches!(
952 slab.complete(token.to_user_data(), false),
953 SlotOutcome::Final {
954 payload: Some(OwnedOp::Waitid(_))
955 }
956 );
957 assert!(restituted);
958 let ts = slab
960 .reserve(Some(OwnedOp::Timeout(Box::new(KernelTimespec {
961 tv_sec: 1,
962 tv_nsec: 2,
963 }))))
964 .expect("réserve ts");
965 assert!(matches!(
966 slab.complete(ts.to_user_data(), false),
967 SlotOutcome::Final { .. }
968 ));
969 let ev = slab
970 .reserve(Some(OwnedOp::Epoll(Box::new(EpollEvent::new(
971 PollEvents::IN,
972 7,
973 )))))
974 .expect("réserve ev");
975 assert!(matches!(
976 slab.complete(ev.to_user_data(), false),
977 SlotOutcome::Final { .. }
978 ));
979 assert_eq!(slab.in_flight(), 0);
980 }
981
982 use core::sync::atomic::Ordering;
985
986 fn rw_region(len: usize) -> MmapRegion {
987 MmapRegion::new_anonymous(
988 len,
989 air_sys_types::mem::ProtectionFlags::READ | air_sys_types::mem::ProtectionFlags::WRITE,
990 air_sys_types::mem::MapFlags::PRIVATE,
991 )
992 .expect("new_anonymous RW")
993 }
994
995 #[cfg(not(loom))]
1001 #[test]
1002 fn mem_liveness_payloads_debug_and_drop() {
1003 use std::sync::Arc;
1004 use std::sync::atomic::AtomicUsize;
1005 let counter = Arc::new(AtomicUsize::new(0));
1006 let region = MmapRegion::new_for_test(64, Arc::clone(&counter));
1007 let p1 = OwnedOp::MemLiveness(region.liveness_handle());
1008 let raws = Vec::<FutexWaitvRaw>::new().into_boxed_slice();
1009 let p2 = OwnedOp::FutexWaitv {
1010 waiters: raws,
1011 guards: vec![region.liveness_handle()],
1012 };
1013 let _ = format!("{p1:?} {p2:?}");
1014 drop(p1);
1015 drop(p2);
1016 assert_eq!(counter.load(Ordering::SeqCst), 0, "region vit encore");
1017 drop(region);
1018 assert_eq!(counter.load(Ordering::SeqCst), 1, "munmap au dernier drop");
1019 }
1020
1021 #[test]
1023 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1024 fn futex_wake_without_waiters_returns_zero() {
1025 let region = rw_region(4096);
1026 region
1027 .futex_word(0)
1028 .expect("word")
1029 .store(0, Ordering::SeqCst);
1030 let mut ring = ring8();
1031 let tok = ring
1032 .submit_futex_wake(®ion, 0, 1, u64::from(u32::MAX), FutexFlags::PRIVATE)
1033 .expect("submit_futex_wake");
1034 let woken = complete_one(&mut ring, tok).into_result().expect("wake ok");
1035 assert_eq!(woken, 0);
1036 }
1037
1038 #[test]
1041 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1042 fn futex_wait_value_mismatch_is_eagain() {
1043 let region = rw_region(4096);
1044 region
1045 .futex_word(0)
1046 .expect("word")
1047 .store(1, Ordering::SeqCst);
1048 let mut ring = ring8();
1049 let tok = ring
1050 .submit_futex_wait(®ion, 0, 999, u64::from(u32::MAX), FutexFlags::PRIVATE)
1051 .expect("submit_futex_wait");
1052 let err = complete_one(&mut ring, tok).completed().unwrap_err();
1053 assert_eq!(err, Errno::EAGAIN);
1054 }
1055
1056 #[test]
1059 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1060 fn futex_wait_woken_by_futex_wake() {
1061 let region = rw_region(4096);
1062 region
1063 .futex_word(0)
1064 .expect("word")
1065 .store(0, Ordering::SeqCst);
1066 let mut ring = ring8();
1067 let mask = u64::from(u32::MAX);
1068 let wait_tok = ring
1071 .submit_futex_wait(®ion, 0, 0, mask, FutexFlags::PRIVATE)
1072 .expect("submit_futex_wait");
1073 assert_eq!(ring.submit().expect("submit (arm)"), 1);
1074 let wake_tok = ring
1075 .submit_futex_wake(®ion, 0, 1, mask, FutexFlags::PRIVATE)
1076 .expect("submit_futex_wake");
1077 ring.submit_and_wait(2).expect("submit_and_wait(2)");
1078 let (mut seen_wait, mut seen_wake) = (false, false);
1079 for _ in 0..2 {
1080 let c = ring.wait_completion().expect("wait_completion");
1081 if c.token() == wait_tok {
1084 c.completed().expect("wait réveillé");
1085 seen_wait = true;
1086 } else {
1087 assert_eq!(c.token(), wake_tok, "jeton wake attendu");
1088 assert_eq!(c.into_result().expect("wake ok"), 1, "un waiter réveillé");
1089 seen_wake = true;
1090 }
1091 }
1092 assert!(seen_wait && seen_wake, "les deux ops complètent");
1093 }
1094
1095 #[test]
1097 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1098 fn futex_waitv_woken_by_wake_on_one_word() {
1099 let region = rw_region(4096);
1100 region.futex_word(0).expect("w0").store(0, Ordering::SeqCst);
1101 region
1102 .futex_word(64)
1103 .expect("w64")
1104 .store(0, Ordering::SeqCst);
1105 let mut ring = ring8();
1106 let waiters = vec![
1107 FutexWaiter {
1108 region: region.clone(),
1109 offset: 0,
1110 expected: 0,
1111 },
1112 FutexWaiter {
1113 region: region.clone(),
1114 offset: 64,
1115 expected: 0,
1116 },
1117 ];
1118 let wait_tok = ring
1119 .submit_futex_waitv(waiters, FutexFlags::PRIVATE)
1120 .expect("submit_futex_waitv");
1121 assert_eq!(ring.submit().expect("submit (arm)"), 1);
1122 let wake_tok = ring
1123 .submit_futex_wake(®ion, 64, 1, u64::from(u32::MAX), FutexFlags::PRIVATE)
1124 .expect("submit_futex_wake");
1125 ring.submit_and_wait(2).expect("submit_and_wait(2)");
1126 let mut seen = 0;
1127 for _ in 0..2 {
1128 let c = ring.wait_completion().expect("wait_completion");
1129 if c.token() == wait_tok {
1130 c.completed().expect("waitv réveillé");
1131 seen += 1;
1132 } else if c.token() == wake_tok {
1133 let _ = c.into_result();
1134 seen += 1;
1135 }
1136 }
1137 assert_eq!(seen, 2);
1138 }
1139
1140 #[test]
1143 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1144 fn futex_waitv_validation_bounds() {
1145 let region = rw_region(4096);
1146 let mut ring = ring8();
1147 assert_eq!(
1148 ring.submit_futex_waitv(vec![], FutexFlags::empty())
1149 .unwrap_err(),
1150 Errno::EINVAL
1151 );
1152 let too_many: Vec<FutexWaiter> = (0..=raw::FUTEX_WAITV_MAX)
1153 .map(|_| FutexWaiter {
1154 region: region.clone(),
1155 offset: 0,
1156 expected: 0,
1157 })
1158 .collect();
1159 assert_eq!(
1160 ring.submit_futex_waitv(too_many, FutexFlags::empty())
1161 .unwrap_err(),
1162 Errno::EINVAL
1163 );
1164 let bad_offset = vec![FutexWaiter {
1165 region: region.clone(),
1166 offset: 1,
1167 expected: 0,
1168 }];
1169 assert_eq!(
1170 ring.submit_futex_waitv(bad_offset, FutexFlags::empty())
1171 .unwrap_err(),
1172 Errno::EINVAL
1173 );
1174 }
1175
1176 #[test]
1179 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1180 fn futex_wait_wake_offset_validation() {
1181 let region = rw_region(4096);
1182 let mut ring = ring8();
1183 assert_eq!(
1185 ring.submit_futex_wait(®ion, 1, 0, 0, FutexFlags::PRIVATE)
1186 .unwrap_err(),
1187 Errno::EINVAL
1188 );
1189 assert_eq!(
1191 ring.submit_futex_wake(®ion, 4096, 1, 0, FutexFlags::PRIVATE)
1192 .unwrap_err(),
1193 Errno::EINVAL
1194 );
1195 }
1196
1197 proptest::proptest! {
1198 #[test]
1201 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1202 fn cancel_unknown_token_is_enoent(slot in 100u32..1000, generation in 0u32..1000) {
1203 let mut ring = ring8();
1204 let bogus = SubmissionToken::new(slot, generation);
1205 let tok = ring
1206 .submit_cancel(CancelTarget::Token(bogus), CancelFlags::empty())
1207 .expect("cancel");
1208 let err = complete_one(&mut ring, tok).into_result().err();
1209 proptest::prop_assert_eq!(err, Some(Errno::ENOENT));
1210 proptest::prop_assert_eq!(ring.in_flight(), 0);
1211 }
1212 }
1213}