1use super::owned::OwnedOp;
20use super::raw::{self, IoUringSqe, Iovec, OpenHowRaw};
21use super::{IoUring, SubmissionToken, SubmitOptions};
22use crate::mem::MmapRegion;
23use air_sys_types::Errno;
24use air_sys_types::fd::{AsRawFd, BorrowedFd, IntoRawFd, OwnedFd};
25use air_sys_types::fs::{
26 DirFd, FadviseAdvice, FallocateMode, FsyncFlags, LinkFlags, Mode, OpenHow, RenameFlags, Statx,
27 StatxFlags, StatxMask, SyncFileRangeFlags, UnlinkFlags, XattrFlags,
28};
29use air_sys_types::ipc::SpliceFlags;
30use air_sys_types::mem::MadviseAdvice;
31use alloc::boxed::Box;
32use alloc::ffi::CString;
33use alloc::vec::Vec;
34use core::ops::Range;
35
36fn dirfd_to_raw(dirfd: DirFd<'_>) -> i32 {
39 match dirfd {
40 DirFd::Cwd => raw::AT_FDCWD,
41 DirFd::Fd(fd) => fd.as_raw_fd(),
42 }
43}
44
45fn offset_to_raw(offset: Option<u64>) -> u64 {
49 offset.unwrap_or(u64::MAX)
51}
52
53fn len_u32(n: usize) -> Result<u32, Errno> {
56 u32::try_from(n).map_err(|_| Errno::EINVAL)
57}
58
59impl IoUring {
60 pub(crate) fn submit_op(
69 &mut self,
70 payload: Option<OwnedOp>,
71 fill: impl FnOnce(&mut IoUringSqe),
72 ) -> Result<SubmissionToken, Errno> {
73 self.submit_filled(payload, |sqe_ptr| {
74 let sqe = unsafe { &mut *sqe_ptr };
78 fill(sqe);
79 })
80 }
81
82 pub(crate) fn submit_filled(
89 &mut self,
90 payload: Option<OwnedOp>,
91 fill: impl FnOnce(*mut IoUringSqe),
92 ) -> Result<SubmissionToken, Errno> {
93 if self.sq.space_left() == 0 {
94 return Err(Errno::EBUSY);
95 }
96 let has_payload = payload.is_some();
97 let token = self.slab.reserve(payload).map_err(|_| Errno::EBUSY)?;
98 let opts = self.pending_options;
99 self.pending_options = SubmitOptions::default();
100 let personality = self.pending_personality;
103 self.pending_personality = 0;
104 let sqe_ptr = unsafe { self.sq.prepare() }.expect("place SQ vérifiée");
108 fill(sqe_ptr);
109 let sqe = unsafe { &mut *sqe_ptr };
114 sqe.user_data = token.to_user_data();
115 sqe.personality = personality;
116 let mut flags = opts.iosqe_flags();
117 if has_payload {
118 flags &= !raw::IOSQE_CQE_SKIP_SUCCESS;
123 }
124 sqe.flags = flags;
125 if !has_payload && opts.skips_cqe_on_success() {
126 self.slab.release(token);
129 }
130 Ok(token)
131 }
132
133 pub fn submit_read(
145 &mut self,
146 fd: BorrowedFd<'_>,
147 mut buffer: Vec<u8>,
148 offset: Option<u64>,
149 ) -> Result<SubmissionToken, Errno> {
150 let fd = fd.as_raw_fd();
151 let len = len_u32(buffer.len())?;
152 let addr = buffer.as_mut_ptr() as u64;
153 let off = offset_to_raw(offset);
154 self.submit_op(Some(OwnedOp::Bytes(buffer)), |sqe| {
155 sqe.opcode = raw::IORING_OP_READ;
156 sqe.fd = fd;
157 sqe.addr_or_splice_off_in = addr;
158 sqe.len = len;
159 sqe.off_or_addr2 = off;
160 })
161 }
162
163 pub fn submit_write(
171 &mut self,
172 fd: BorrowedFd<'_>,
173 mut buffer: Vec<u8>,
174 offset: Option<u64>,
175 ) -> Result<SubmissionToken, Errno> {
176 let fd = fd.as_raw_fd();
177 let len = len_u32(buffer.len())?;
178 let addr = buffer.as_mut_ptr() as u64;
179 let off = offset_to_raw(offset);
180 self.submit_op(Some(OwnedOp::Bytes(buffer)), |sqe| {
181 sqe.opcode = raw::IORING_OP_WRITE;
182 sqe.fd = fd;
183 sqe.addr_or_splice_off_in = addr;
184 sqe.len = len;
185 sqe.off_or_addr2 = off;
186 })
187 }
188
189 pub fn submit_readv(
198 &mut self,
199 fd: BorrowedFd<'_>,
200 buffers: Vec<Vec<u8>>,
201 offset: Option<u64>,
202 ) -> Result<SubmissionToken, Errno> {
203 self.submit_vectored(raw::IORING_OP_READV, fd, buffers, offset)
204 }
205
206 pub fn submit_writev(
213 &mut self,
214 fd: BorrowedFd<'_>,
215 buffers: Vec<Vec<u8>>,
216 offset: Option<u64>,
217 ) -> Result<SubmissionToken, Errno> {
218 self.submit_vectored(raw::IORING_OP_WRITEV, fd, buffers, offset)
219 }
220
221 fn submit_vectored(
224 &mut self,
225 opcode: u8,
226 fd: BorrowedFd<'_>,
227 mut buffers: Vec<Vec<u8>>,
228 offset: Option<u64>,
229 ) -> Result<SubmissionToken, Errno> {
230 if buffers.len() > raw::IOV_MAX {
231 return Err(Errno::EINVAL);
232 }
233 let fd = fd.as_raw_fd();
234 let off = offset_to_raw(offset);
235 let mut iovecs: Vec<Iovec> = Vec::with_capacity(buffers.len());
237 for buf in &mut buffers {
238 iovecs.push(Iovec {
239 iov_base: buf.as_mut_ptr(),
240 iov_len: buf.len(),
241 });
242 }
243 let iovecs = iovecs.into_boxed_slice();
244 let count = u32::try_from(iovecs.len()).unwrap_or(u32::MAX);
248 let addr = iovecs.as_ptr() as u64;
249 self.submit_op(Some(OwnedOp::Vectored { buffers, iovecs }), |sqe| {
250 sqe.opcode = opcode;
251 sqe.fd = fd;
252 sqe.addr_or_splice_off_in = addr;
253 sqe.len = count;
254 sqe.off_or_addr2 = off;
255 })
256 }
257
258 pub fn submit_fsync(
267 &mut self,
268 fd: BorrowedFd<'_>,
269 flags: FsyncFlags,
270 ) -> Result<SubmissionToken, Errno> {
271 let fd = fd.as_raw_fd();
272 let fsync_flags = if flags.contains(FsyncFlags::DATASYNC) {
273 raw::IORING_FSYNC_DATASYNC
274 } else {
275 0
276 };
277 self.submit_op(None, |sqe| {
278 sqe.opcode = raw::IORING_OP_FSYNC;
279 sqe.fd = fd;
280 sqe.op_flags = fsync_flags;
281 })
282 }
283
284 pub fn submit_sync_file_range(
291 &mut self,
292 fd: BorrowedFd<'_>,
293 offset: u64,
294 nbytes: u64,
295 flags: SyncFileRangeFlags,
296 ) -> Result<SubmissionToken, Errno> {
297 let fd = fd.as_raw_fd();
298 let len = u32::try_from(nbytes).map_err(|_| Errno::EINVAL)?;
299 let op_flags = flags.bits();
300 self.submit_op(None, |sqe| {
301 sqe.opcode = raw::IORING_OP_SYNC_FILE_RANGE;
302 sqe.fd = fd;
303 sqe.off_or_addr2 = offset;
304 sqe.len = len;
305 sqe.op_flags = op_flags;
306 })
307 }
308
309 pub fn submit_fallocate(
318 &mut self,
319 fd: BorrowedFd<'_>,
320 mode: FallocateMode,
321 offset: i64,
322 length: i64,
323 ) -> Result<SubmissionToken, Errno> {
324 if length <= 0 || offset < 0 || offset.checked_add(length).is_none() {
325 return Err(Errno::EINVAL);
326 }
327 let fd = fd.as_raw_fd();
328 let off = offset.cast_unsigned();
330 let addr = length.cast_unsigned();
331 let mode_bits = mode.bits().cast_unsigned();
332 self.submit_op(None, |sqe| {
333 sqe.opcode = raw::IORING_OP_FALLOCATE;
334 sqe.fd = fd;
335 sqe.off_or_addr2 = off;
336 sqe.addr_or_splice_off_in = addr;
337 sqe.len = mode_bits;
338 })
339 }
340
341 pub fn submit_ftruncate(
348 &mut self,
349 fd: BorrowedFd<'_>,
350 length: u64,
351 ) -> Result<SubmissionToken, Errno> {
352 let fd = fd.as_raw_fd();
353 self.submit_op(None, |sqe| {
354 sqe.opcode = raw::IORING_OP_FTRUNCATE;
355 sqe.fd = fd;
356 sqe.off_or_addr2 = length;
357 })
358 }
359
360 pub fn submit_openat2(
371 &mut self,
372 dirfd: DirFd<'_>,
373 path: CString,
374 how: OpenHow,
375 ) -> Result<SubmissionToken, Errno> {
376 let dirfd = dirfd_to_raw(dirfd);
377 let how = Box::new(OpenHowRaw {
378 flags: how.flags.bits() | raw::O_CLOEXEC,
379 mode: u64::from(how.mode),
380 resolve: how.resolve.bits(),
381 });
382 let how_ptr = core::ptr::from_ref(&*how) as u64;
383 let path_ptr = path.as_ptr() as u64;
384 let how_len: u32 = 24;
386 self.submit_op(Some(OwnedOp::Open { path, how }), |sqe| {
387 sqe.opcode = raw::IORING_OP_OPENAT2;
388 sqe.fd = dirfd;
389 sqe.addr_or_splice_off_in = path_ptr;
390 sqe.len = how_len;
391 sqe.off_or_addr2 = how_ptr;
392 })
393 }
394
395 pub fn submit_close(&mut self, fd: OwnedFd) -> Result<SubmissionToken, Errno> {
404 let raw_fd = fd.into_raw_fd();
410 self.submit_op(None, |sqe| {
411 sqe.opcode = raw::IORING_OP_CLOSE;
412 sqe.fd = raw_fd;
413 })
414 }
415
416 pub fn submit_statx(
426 &mut self,
427 dirfd: DirFd<'_>,
428 path: CString,
429 flags: StatxFlags,
430 mask: StatxMask,
431 mut out: Box<Statx>,
432 ) -> Result<SubmissionToken, Errno> {
433 let dirfd = dirfd_to_raw(dirfd);
434 let path_ptr = path.as_ptr() as u64;
435 let out_ptr = core::ptr::from_mut(&mut *out) as u64;
436 let mask_bits = mask.bits();
437 let flags_bits = flags.bits();
438 self.submit_op(Some(OwnedOp::Statx { out, path }), |sqe| {
439 sqe.opcode = raw::IORING_OP_STATX;
440 sqe.fd = dirfd;
441 sqe.addr_or_splice_off_in = path_ptr;
442 sqe.len = mask_bits;
443 sqe.off_or_addr2 = out_ptr;
444 sqe.op_flags = flags_bits;
445 })
446 }
447
448 pub fn submit_fadvise(
458 &mut self,
459 fd: BorrowedFd<'_>,
460 offset: u64,
461 length: u64,
462 advice: FadviseAdvice,
463 ) -> Result<SubmissionToken, Errno> {
464 let fd = fd.as_raw_fd();
465 let len = u32::try_from(length).map_err(|_| Errno::EINVAL)?;
466 let advice = advice.as_raw().cast_unsigned();
467 self.submit_op(None, |sqe| {
468 sqe.opcode = raw::IORING_OP_FADVISE;
469 sqe.fd = fd;
470 sqe.off_or_addr2 = offset;
471 sqe.len = len;
472 sqe.op_flags = advice;
473 })
474 }
475
476 pub fn submit_madvise(
493 &mut self,
494 region: &MmapRegion,
495 range: Range<usize>,
496 advice: MadviseAdvice,
497 ) -> Result<SubmissionToken, Errno> {
498 if range.start > range.end || range.end > region.len() {
500 return Err(Errno::EINVAL);
501 }
502 let span = range.end.checked_sub(range.start).ok_or(Errno::EINVAL)?;
503 let len = len_u32(span)?;
504 let start = u64::try_from(range.start).map_err(|_| Errno::EINVAL)?;
505 let addr = (region.as_ptr() as u64)
506 .checked_add(start)
507 .ok_or(Errno::EINVAL)?;
508 #[allow(clippy::as_conversions)]
511 let advice_flags = (advice as i32).cast_unsigned();
512 let guard = region.liveness_handle();
513 self.submit_op(Some(OwnedOp::MemLiveness(guard)), |sqe| {
514 sqe.opcode = raw::IORING_OP_MADVISE;
515 sqe.fd = -1;
516 sqe.addr_or_splice_off_in = addr;
517 sqe.len = len;
518 sqe.op_flags = advice_flags;
519 })
520 }
521
522 pub fn submit_splice(
533 &mut self,
534 fd_in: BorrowedFd<'_>,
535 offset_in: Option<u64>,
536 fd_out: BorrowedFd<'_>,
537 offset_out: Option<u64>,
538 length: u32,
539 flags: SpliceFlags,
540 ) -> Result<SubmissionToken, Errno> {
541 let fd_in = fd_in.as_raw_fd();
542 let fd_out = fd_out.as_raw_fd();
543 let off_in = offset_to_raw(offset_in);
544 let off_out = offset_to_raw(offset_out);
545 let op_flags = flags.bits();
546 self.submit_op(None, |sqe| {
547 sqe.opcode = raw::IORING_OP_SPLICE;
548 sqe.fd = fd_out;
549 sqe.off_or_addr2 = off_out;
550 sqe.addr_or_splice_off_in = off_in;
551 sqe.len = length;
552 sqe.op_flags = op_flags;
553 sqe.splice_fd_in_or_file_index = fd_in.cast_unsigned();
554 })
555 }
556
557 pub fn submit_tee(
564 &mut self,
565 fd_in: BorrowedFd<'_>,
566 fd_out: BorrowedFd<'_>,
567 length: u32,
568 flags: SpliceFlags,
569 ) -> Result<SubmissionToken, Errno> {
570 let fd_in = fd_in.as_raw_fd();
571 let fd_out = fd_out.as_raw_fd();
572 let op_flags = flags.bits();
573 self.submit_op(None, |sqe| {
574 sqe.opcode = raw::IORING_OP_TEE;
575 sqe.fd = fd_out;
576 sqe.len = length;
577 sqe.op_flags = op_flags;
578 sqe.splice_fd_in_or_file_index = fd_in.cast_unsigned();
579 })
580 }
581
582 pub fn submit_mkdirat(
590 &mut self,
591 dirfd: DirFd<'_>,
592 path: CString,
593 mode: Mode,
594 ) -> Result<SubmissionToken, Errno> {
595 let dirfd = dirfd_to_raw(dirfd);
596 let path_ptr = path.as_ptr() as u64;
597 self.submit_op(Some(OwnedOp::Path(path)), |sqe| {
598 sqe.opcode = raw::IORING_OP_MKDIRAT;
599 sqe.fd = dirfd;
600 sqe.addr_or_splice_off_in = path_ptr;
601 sqe.len = mode;
602 })
603 }
604
605 pub fn submit_unlinkat(
612 &mut self,
613 dirfd: DirFd<'_>,
614 path: CString,
615 flags: UnlinkFlags,
616 ) -> Result<SubmissionToken, Errno> {
617 let dirfd = dirfd_to_raw(dirfd);
618 let path_ptr = path.as_ptr() as u64;
619 let op_flags = flags.bits().cast_unsigned();
620 self.submit_op(Some(OwnedOp::Path(path)), |sqe| {
621 sqe.opcode = raw::IORING_OP_UNLINKAT;
622 sqe.fd = dirfd;
623 sqe.addr_or_splice_off_in = path_ptr;
624 sqe.op_flags = op_flags;
625 })
626 }
627
628 pub fn submit_renameat(
635 &mut self,
636 old_dirfd: DirFd<'_>,
637 old_path: CString,
638 new_dirfd: DirFd<'_>,
639 new_path: CString,
640 flags: RenameFlags,
641 ) -> Result<SubmissionToken, Errno> {
642 let old_dirfd = dirfd_to_raw(old_dirfd);
643 let new_dirfd = dirfd_to_raw(new_dirfd);
644 let old_ptr = old_path.as_ptr() as u64;
645 let new_ptr = new_path.as_ptr() as u64;
646 let op_flags = flags.bits();
647 self.submit_op(Some(OwnedOp::TwoPaths(old_path, new_path)), |sqe| {
648 sqe.opcode = raw::IORING_OP_RENAMEAT;
649 sqe.fd = old_dirfd;
650 sqe.addr_or_splice_off_in = old_ptr;
651 sqe.len = new_dirfd.cast_unsigned();
652 sqe.off_or_addr2 = new_ptr;
653 sqe.op_flags = op_flags;
654 })
655 }
656
657 pub fn submit_linkat(
663 &mut self,
664 old_dirfd: DirFd<'_>,
665 old_path: CString,
666 new_dirfd: DirFd<'_>,
667 new_path: CString,
668 flags: LinkFlags,
669 ) -> Result<SubmissionToken, Errno> {
670 let old_dirfd = dirfd_to_raw(old_dirfd);
671 let new_dirfd = dirfd_to_raw(new_dirfd);
672 let old_ptr = old_path.as_ptr() as u64;
673 let new_ptr = new_path.as_ptr() as u64;
674 let op_flags = flags.bits().cast_unsigned();
675 self.submit_op(Some(OwnedOp::TwoPaths(old_path, new_path)), |sqe| {
676 sqe.opcode = raw::IORING_OP_LINKAT;
677 sqe.fd = old_dirfd;
678 sqe.addr_or_splice_off_in = old_ptr;
679 sqe.len = new_dirfd.cast_unsigned();
680 sqe.off_or_addr2 = new_ptr;
681 sqe.op_flags = op_flags;
682 })
683 }
684
685 pub fn submit_symlinkat(
692 &mut self,
693 target: CString,
694 new_dirfd: DirFd<'_>,
695 link_path: CString,
696 ) -> Result<SubmissionToken, Errno> {
697 let new_dirfd = dirfd_to_raw(new_dirfd);
698 let target_ptr = target.as_ptr() as u64;
699 let link_ptr = link_path.as_ptr() as u64;
700 self.submit_op(Some(OwnedOp::TwoPaths(target, link_path)), |sqe| {
701 sqe.opcode = raw::IORING_OP_SYMLINKAT;
702 sqe.fd = new_dirfd;
703 sqe.addr_or_splice_off_in = target_ptr;
704 sqe.off_or_addr2 = link_ptr;
705 })
706 }
707
708 pub fn submit_setxattr(
718 &mut self,
719 path: CString,
720 name: CString,
721 value: Vec<u8>,
722 flags: XattrFlags,
723 ) -> Result<SubmissionToken, Errno> {
724 let name_ptr = name.as_ptr() as u64;
725 let path_ptr = path.as_ptr() as u64;
726 let value_ptr = value.as_ptr() as u64;
727 let len = len_u32(value.len())?;
728 let op_flags = flags.bits().cast_unsigned();
729 self.submit_op(
730 Some(OwnedOp::Xattr {
731 value,
732 name,
733 path: Some(path),
734 }),
735 |sqe| {
736 sqe.opcode = raw::IORING_OP_SETXATTR;
737 sqe.addr_or_splice_off_in = name_ptr;
738 sqe.off_or_addr2 = value_ptr;
739 sqe.len = len;
740 sqe.addr3 = path_ptr;
741 sqe.op_flags = op_flags;
742 },
743 )
744 }
745
746 pub fn submit_getxattr(
755 &mut self,
756 path: CString,
757 name: CString,
758 mut value: Vec<u8>,
759 ) -> Result<SubmissionToken, Errno> {
760 let name_ptr = name.as_ptr() as u64;
761 let path_ptr = path.as_ptr() as u64;
762 let value_ptr = value.as_mut_ptr() as u64;
763 let len = len_u32(value.len())?;
764 self.submit_op(
765 Some(OwnedOp::Xattr {
766 value,
767 name,
768 path: Some(path),
769 }),
770 |sqe| {
771 sqe.opcode = raw::IORING_OP_GETXATTR;
772 sqe.addr_or_splice_off_in = name_ptr;
773 sqe.off_or_addr2 = value_ptr;
774 sqe.len = len;
775 sqe.addr3 = path_ptr;
776 },
777 )
778 }
779
780 pub fn submit_fsetxattr(
788 &mut self,
789 fd: BorrowedFd<'_>,
790 name: CString,
791 value: Vec<u8>,
792 flags: XattrFlags,
793 ) -> Result<SubmissionToken, Errno> {
794 let fd = fd.as_raw_fd();
795 let name_ptr = name.as_ptr() as u64;
796 let value_ptr = value.as_ptr() as u64;
797 let len = len_u32(value.len())?;
798 let op_flags = flags.bits().cast_unsigned();
799 self.submit_op(
800 Some(OwnedOp::Xattr {
801 value,
802 name,
803 path: None,
804 }),
805 |sqe| {
806 sqe.opcode = raw::IORING_OP_FSETXATTR;
807 sqe.fd = fd;
808 sqe.addr_or_splice_off_in = name_ptr;
809 sqe.off_or_addr2 = value_ptr;
810 sqe.len = len;
811 sqe.op_flags = op_flags;
812 },
813 )
814 }
815
816 pub fn submit_fgetxattr(
824 &mut self,
825 fd: BorrowedFd<'_>,
826 name: CString,
827 mut value: Vec<u8>,
828 ) -> Result<SubmissionToken, Errno> {
829 let fd = fd.as_raw_fd();
830 let name_ptr = name.as_ptr() as u64;
831 let value_ptr = value.as_mut_ptr() as u64;
832 let len = len_u32(value.len())?;
833 self.submit_op(
834 Some(OwnedOp::Xattr {
835 value,
836 name,
837 path: None,
838 }),
839 |sqe| {
840 sqe.opcode = raw::IORING_OP_FGETXATTR;
841 sqe.fd = fd;
842 sqe.addr_or_splice_off_in = name_ptr;
843 sqe.off_or_addr2 = value_ptr;
844 sqe.len = len;
845 },
846 )
847 }
848}
849
850#[cfg(test)]
862mod tests {
863 use super::*;
864 use crate::io_uring::{Completion, IoUring, SubmissionToken};
865 use crate::test_support::sfd;
866 use air_sys_types::fd::AsFd;
867 use air_sys_types::fs::OpenHow;
868 use core::num::NonZeroU32;
869 use std::ffi::CString;
870 use std::io::{Read, Write};
871 use std::os::unix::ffi::OsStrExt;
872 use std::path::{Path, PathBuf};
873 use std::sync::atomic::{AtomicU32, Ordering};
874
875 static COUNTER: AtomicU32 = AtomicU32::new(0);
876
877 fn ring8() -> IoUring {
878 IoUring::new(NonZeroU32::new(8).expect("8 ≠ 0")).expect("io_uring_setup 6.12")
879 }
880
881 fn temp_path(tag: &str) -> PathBuf {
882 let n = COUNTER.fetch_add(1, Ordering::Relaxed);
883 std::env::temp_dir().join(format!("air-iou2a-{}-{n}-{tag}", std::process::id()))
884 }
885
886 fn cpath(p: &Path) -> CString {
887 CString::new(p.as_os_str().as_bytes()).expect("chemin sans NUL")
888 }
889
890 fn rw_file(path: &Path) -> std::fs::File {
892 std::fs::OpenOptions::new()
893 .read(true)
894 .write(true)
895 .create(true)
896 .truncate(true)
897 .open(path)
898 .expect("ouverture fichier de test")
899 }
900
901 fn complete_one(ring: &mut IoUring, expected: SubmissionToken) -> Completion {
903 assert_eq!(ring.submit_and_wait(1).expect("submit_and_wait"), 1);
904 let completion = ring.wait_completion().expect("wait_completion");
905 assert_eq!(completion.token(), expected, "user_data round-trip");
906 completion
907 }
908
909 #[test]
912 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
913 fn write_then_read_round_trips_with_some_and_none_offset() {
914 let path = temp_path("rw");
915 let file = rw_file(&path);
916 let mut ring = ring8();
917
918 let tok = ring
920 .submit_write(sfd(&file), b"hello".to_vec(), Some(0))
921 .expect("submit_write");
922 let (buf, n) = complete_one(&mut ring, tok)
923 .into_buffer_result()
924 .expect("write ok");
925 assert_eq!(n, 5);
926 assert_eq!(buf, b"hello");
927
928 let tok = ring
930 .submit_read(sfd(&file), vec![0u8; 5], Some(0))
931 .expect("submit_read");
932 let (buf, n) = complete_one(&mut ring, tok)
933 .into_buffer_result()
934 .expect("read ok");
935 assert_eq!(n, 5);
936 assert_eq!(&buf, b"hello");
937
938 let tok = ring
940 .submit_write(sfd(&file), b"WORLD".to_vec(), None)
941 .expect("submit_write None");
942 let (_b, n) = complete_one(&mut ring, tok)
943 .into_buffer_result()
944 .expect("write None ok");
945 assert_eq!(n, 5);
946 let tok = ring
947 .submit_read(sfd(&file), vec![0u8; 5], None)
948 .expect("submit_read None");
949 let (_b, n) = complete_one(&mut ring, tok)
950 .into_buffer_result()
951 .expect("read None ok");
952 assert_eq!(n, 0, "position avancée par l'écriture None ⇒ EOF");
953
954 let _ = std::fs::remove_file(&path);
955 }
956
957 #[test]
958 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
959 fn readv_writev_round_trip_multi_buffers() {
960 let path = temp_path("rwv");
961 let file = rw_file(&path);
962 let mut ring = ring8();
963
964 let tok = ring
965 .submit_writev(sfd(&file), vec![b"foo".to_vec(), b"bar".to_vec()], Some(0))
966 .expect("submit_writev");
967 let (_bufs, n) = complete_one(&mut ring, tok)
968 .into_vectored_result()
969 .expect("writev ok");
970 assert_eq!(n, 6);
971
972 let tok = ring
973 .submit_readv(sfd(&file), vec![vec![0u8; 3], vec![0u8; 3]], Some(0))
974 .expect("submit_readv");
975 let (bufs, n) = complete_one(&mut ring, tok)
976 .into_vectored_result()
977 .expect("readv ok");
978 assert_eq!(n, 6);
979 assert_eq!(bufs, vec![b"foo".to_vec(), b"bar".to_vec()]);
980
981 let _ = std::fs::remove_file(&path);
982 }
983
984 #[test]
985 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
986 fn openat2_then_read_then_close_chain() {
987 let path = temp_path("open");
988 std::fs::write(&path, b"hello world").expect("fixture");
989 let mut ring = ring8();
990
991 let tok = ring
992 .submit_openat2(DirFd::Cwd, cpath(&path), OpenHow::default())
993 .expect("submit_openat2");
994 let opened = complete_one(&mut ring, tok)
995 .opened_fd()
996 .expect("openat2 ok");
997
998 let tok = ring
999 .submit_read(opened.as_fd(), vec![0u8; 11], Some(0))
1000 .expect("submit_read");
1001 let (buf, n) = complete_one(&mut ring, tok)
1002 .into_buffer_result()
1003 .expect("read ok");
1004 assert_eq!(&buf[..n], b"hello world");
1005
1006 let tok = ring.submit_close(opened).expect("submit_close");
1007 complete_one(&mut ring, tok).completed().expect("close ok");
1008
1009 let _ = std::fs::remove_file(&path);
1010 }
1011
1012 #[test]
1013 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1014 fn statx_reports_size() {
1015 let path = temp_path("statx");
1016 std::fs::write(&path, b"0123456789").expect("fixture (10 octets)");
1017 let mut ring = ring8();
1018
1019 let tok = ring
1020 .submit_statx(
1021 DirFd::Cwd,
1022 cpath(&path),
1023 StatxFlags::empty(),
1024 StatxMask::SIZE,
1025 Box::new(Statx::default()),
1026 )
1027 .expect("submit_statx");
1028 let statx = complete_one(&mut ring, tok).into_statx().expect("statx ok");
1029 assert!(statx.has(StatxMask::SIZE));
1030 assert_eq!(statx.size, 10);
1031
1032 let _ = std::fs::remove_file(&path);
1033 }
1034
1035 #[test]
1036 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1037 fn directory_and_link_ops() {
1038 let mut ring = ring8();
1039
1040 let dir = temp_path("dir");
1042 let tok = ring
1043 .submit_mkdirat(DirFd::Cwd, cpath(&dir), 0o755)
1044 .expect("submit_mkdirat");
1045 complete_one(&mut ring, tok).completed().expect("mkdir ok");
1046 assert!(dir.is_dir());
1047 let tok = ring
1048 .submit_unlinkat(DirFd::Cwd, cpath(&dir), UnlinkFlags::REMOVEDIR)
1049 .expect("submit_unlinkat");
1050 complete_one(&mut ring, tok).completed().expect("rmdir ok");
1051 assert!(!dir.exists());
1052
1053 let src = temp_path("ren-a");
1055 let dst = temp_path("ren-b");
1056 std::fs::write(&src, b"x").expect("fixture");
1057 let tok = ring
1058 .submit_renameat(
1059 DirFd::Cwd,
1060 cpath(&src),
1061 DirFd::Cwd,
1062 cpath(&dst),
1063 RenameFlags::empty(),
1064 )
1065 .expect("submit_renameat");
1066 complete_one(&mut ring, tok).completed().expect("rename ok");
1067 assert!(!src.exists() && dst.exists());
1068
1069 let link = temp_path("link");
1071 let tok = ring
1072 .submit_linkat(
1073 DirFd::Cwd,
1074 cpath(&dst),
1075 DirFd::Cwd,
1076 cpath(&link),
1077 LinkFlags::empty(),
1078 )
1079 .expect("submit_linkat");
1080 complete_one(&mut ring, tok).completed().expect("link ok");
1081 assert!(link.exists());
1082
1083 let sym = temp_path("sym");
1085 let tok = ring
1086 .submit_symlinkat(cpath(&dst), DirFd::Cwd, cpath(&sym))
1087 .expect("submit_symlinkat");
1088 complete_one(&mut ring, tok)
1089 .completed()
1090 .expect("symlink ok");
1091 assert!(
1092 std::fs::symlink_metadata(&sym)
1093 .expect("lstat")
1094 .file_type()
1095 .is_symlink()
1096 );
1097
1098 for p in [&dst, &link, &sym] {
1099 let _ = std::fs::remove_file(p);
1100 }
1101 }
1102
1103 #[test]
1104 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1105 fn xattr_set_get_round_trip() {
1106 let path = temp_path("xattr");
1107 std::fs::write(&path, b"data").expect("fixture");
1108 let mut ring = ring8();
1109 let name = CString::new("user.air_test").expect("nom sans NUL");
1110
1111 let tok = ring
1113 .submit_setxattr(
1114 cpath(&path),
1115 name.clone(),
1116 b"value42".to_vec(),
1117 XattrFlags::empty(),
1118 )
1119 .expect("submit_setxattr");
1120 complete_one(&mut ring, tok)
1121 .completed()
1122 .expect("setxattr ok (ext4 supporte user.*)");
1123
1124 let tok = ring
1126 .submit_getxattr(cpath(&path), name.clone(), vec![0u8; 64])
1127 .expect("submit_getxattr");
1128 let (value, n) = complete_one(&mut ring, tok)
1129 .into_xattr_result()
1130 .expect("getxattr ok");
1131 assert_eq!(&value[..n], b"value42");
1132
1133 let file = std::fs::OpenOptions::new()
1135 .read(true)
1136 .write(true)
1137 .open(&path)
1138 .expect("réouverture");
1139 let fname = CString::new("user.air_fd").expect("nom sans NUL");
1140 let tok = ring
1141 .submit_fsetxattr(
1142 sfd(&file),
1143 fname.clone(),
1144 b"fdval".to_vec(),
1145 XattrFlags::empty(),
1146 )
1147 .expect("submit_fsetxattr");
1148 complete_one(&mut ring, tok)
1149 .completed()
1150 .expect("fsetxattr ok");
1151 let tok = ring
1152 .submit_fgetxattr(sfd(&file), fname, vec![0u8; 64])
1153 .expect("submit_fgetxattr");
1154 let (value, n) = complete_one(&mut ring, tok)
1155 .into_xattr_result()
1156 .expect("fgetxattr ok");
1157 assert_eq!(&value[..n], b"fdval");
1158
1159 let _ = std::fs::remove_file(&path);
1160 }
1161
1162 #[test]
1163 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1164 fn sync_alloc_trunc_advise_ops() {
1165 let path = temp_path("sync");
1166 let file = rw_file(&path);
1167 let mut ring = ring8();
1168
1169 let tok = ring
1171 .submit_write(sfd(&file), vec![7u8; 256], Some(0))
1172 .expect("submit_write");
1173 let _ = complete_one(&mut ring, tok)
1174 .into_buffer_result()
1175 .expect("ok");
1176
1177 let tok = ring
1179 .submit_fsync(sfd(&file), FsyncFlags::DATASYNC)
1180 .expect("submit_fsync");
1181 complete_one(&mut ring, tok)
1182 .completed()
1183 .expect("fdatasync ok");
1184 let tok = ring
1185 .submit_fsync(sfd(&file), FsyncFlags::empty())
1186 .expect("submit_fsync");
1187 complete_one(&mut ring, tok).completed().expect("fsync ok");
1188
1189 let tok = ring
1191 .submit_sync_file_range(sfd(&file), 0, 256, SyncFileRangeFlags::WRITE)
1192 .expect("submit_sync_file_range");
1193 complete_one(&mut ring, tok)
1194 .completed()
1195 .expect("sync_file_range ok");
1196
1197 let tok = ring
1199 .submit_fallocate(sfd(&file), FallocateMode::empty(), 0, 4096)
1200 .expect("submit_fallocate");
1201 complete_one(&mut ring, tok)
1202 .completed()
1203 .expect("fallocate ok");
1204 assert!(std::fs::metadata(&path).expect("stat").len() >= 4096);
1205
1206 let tok = ring
1208 .submit_ftruncate(sfd(&file), 100)
1209 .expect("submit_ftruncate");
1210 complete_one(&mut ring, tok)
1211 .completed()
1212 .expect("ftruncate ok");
1213 assert_eq!(std::fs::metadata(&path).expect("stat").len(), 100);
1214
1215 let tok = ring
1217 .submit_fadvise(sfd(&file), 0, 0, FadviseAdvice::WillNeed)
1218 .expect("submit_fadvise");
1219 complete_one(&mut ring, tok)
1220 .completed()
1221 .expect("fadvise ok");
1222
1223 let _ = std::fs::remove_file(&path);
1224 }
1225
1226 #[test]
1227 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1228 fn splice_file_to_pipe_and_tee_pipe_to_pipe() {
1229 let path = temp_path("splice");
1230 std::fs::write(&path, b"splice me").expect("fixture (9 octets)");
1231 let file = std::fs::File::open(&path).expect("open ro");
1232 let mut ring = ring8();
1233
1234 let (mut reader, writer) = std::io::pipe().expect("pipe");
1236 let tok = ring
1237 .submit_splice(
1238 sfd(&file),
1239 Some(0),
1240 sfd(&writer),
1241 None,
1242 9,
1243 SpliceFlags::empty(),
1244 )
1245 .expect("submit_splice");
1246 let n = complete_one(&mut ring, tok)
1247 .into_result()
1248 .expect("splice ok");
1249 assert_eq!(n, 9);
1250 let mut got = [0u8; 9];
1251 reader.read_exact(&mut got).expect("lecture pipe");
1252 assert_eq!(&got, b"splice me");
1253
1254 let (reader1, mut writer1) = std::io::pipe().expect("pipe1");
1256 let (mut reader2, writer2) = std::io::pipe().expect("pipe2");
1257 writer1.write_all(b"tee").expect("alim pipe1");
1258 let tok = ring
1259 .submit_tee(sfd(&reader1), sfd(&writer2), 3, SpliceFlags::empty())
1260 .expect("submit_tee");
1261 let n = complete_one(&mut ring, tok).into_result().expect("tee ok");
1262 assert_eq!(n, 3);
1263 let mut got = [0u8; 3];
1264 reader2.read_exact(&mut got).expect("lecture pipe2");
1265 assert_eq!(&got, b"tee");
1266
1267 let _ = std::fs::remove_file(&path);
1268 }
1269
1270 #[test]
1271 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1272 fn relative_ops_resolve_against_a_directory_fd() {
1273 let dir = temp_path("dirfd");
1277 std::fs::create_dir(&dir).expect("mkdir fixture");
1278 std::fs::write(dir.join("inside.txt"), b"abcd").expect("fichier interne");
1279 let dir_fd = std::fs::File::open(&dir).expect("open dir fd");
1280 let mut ring = ring8();
1281
1282 let rel = CString::new("inside.txt").expect("nom sans NUL");
1283 let tok = ring
1284 .submit_statx(
1285 DirFd::Fd(sfd(&dir_fd)),
1286 rel,
1287 StatxFlags::empty(),
1288 StatxMask::SIZE,
1289 Box::new(Statx::default()),
1290 )
1291 .expect("submit_statx relatif");
1292 let statx = complete_one(&mut ring, tok).into_statx().expect("statx ok");
1293 assert_eq!(statx.size, 4, "taille du fichier résolu via le dir fd");
1294
1295 let _ = std::fs::remove_file(dir.join("inside.txt"));
1296 let _ = std::fs::remove_dir(&dir);
1297 }
1298
1299 #[test]
1302 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1303 fn fallocate_rejects_invalid_geometry_before_submission() {
1304 let path = temp_path("falloc-inval");
1305 let file = rw_file(&path);
1306 let mut ring = ring8();
1307 assert_eq!(
1309 ring.submit_fallocate(sfd(&file), FallocateMode::empty(), 0, 0),
1310 Err(Errno::EINVAL)
1311 );
1312 assert_eq!(
1314 ring.submit_fallocate(sfd(&file), FallocateMode::empty(), -1, 10),
1315 Err(Errno::EINVAL)
1316 );
1317 assert_eq!(
1319 ring.submit_fallocate(sfd(&file), FallocateMode::empty(), i64::MAX, 1),
1320 Err(Errno::EINVAL)
1321 );
1322 assert_eq!(ring.in_flight(), 0, "aucun slot consommé sur rejet amont");
1323 let _ = std::fs::remove_file(&path);
1324 }
1325
1326 #[test]
1327 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1328 fn length_overflow_validations_reject_before_submission() {
1329 let path = temp_path("len-inval");
1330 let file = rw_file(&path);
1331 let mut ring = ring8();
1332 let over_u32 = u64::from(u32::MAX) + 1;
1333 assert_eq!(
1334 ring.submit_sync_file_range(sfd(&file), 0, over_u32, SyncFileRangeFlags::WRITE),
1335 Err(Errno::EINVAL)
1336 );
1337 assert_eq!(
1338 ring.submit_fadvise(sfd(&file), 0, over_u32, FadviseAdvice::Normal),
1339 Err(Errno::EINVAL)
1340 );
1341 let too_many = vec![Vec::<u8>::new(); raw::IOV_MAX + 1];
1343 assert_eq!(
1344 ring.submit_readv(sfd(&file), too_many, Some(0)),
1345 Err(Errno::EINVAL)
1346 );
1347 assert_eq!(ring.in_flight(), 0);
1348 let _ = std::fs::remove_file(&path);
1349 }
1350
1351 #[test]
1354 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1355 fn slab_full_returns_ebusy() {
1356 use crate::io_uring::IoUringBuilder;
1357 let path = temp_path("ebusy-slab");
1358 let file = rw_file(&path);
1359 let mut ring = IoUringBuilder::new(NonZeroU32::new(8).expect("8"))
1361 .max_inflight(NonZeroU32::new(1).expect("1"))
1362 .build()
1363 .expect("ring slab=1");
1364 let _t = ring
1365 .submit_read(sfd(&file), vec![0u8; 4], Some(0))
1366 .expect("1ʳᵉ op");
1367 assert_eq!(
1368 ring.submit_read(sfd(&file), vec![0u8; 4], Some(0)),
1369 Err(Errno::EBUSY),
1370 "slab plein ⇒ EBUSY (reserve)"
1371 );
1372 ring.submit_and_wait(1).expect("submit");
1374 let _ = ring.wait_completion().expect("complétion");
1375 let _ = std::fs::remove_file(&path);
1376 }
1377
1378 #[test]
1379 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1380 fn full_submission_queue_returns_ebusy() {
1381 use crate::io_uring::IoUringBuilder;
1382 let path = temp_path("ebusy-sq");
1383 let file = rw_file(&path);
1384 let mut ring = IoUringBuilder::new(NonZeroU32::new(4).expect("4"))
1386 .max_inflight(NonZeroU32::new(8).expect("8"))
1387 .build()
1388 .expect("ring SQ=4");
1389 for _ in 0..4 {
1390 ring.submit_fsync(sfd(&file), FsyncFlags::empty())
1391 .expect("remplissage SQ");
1392 }
1393 assert_eq!(
1394 ring.submit_fsync(sfd(&file), FsyncFlags::empty()),
1395 Err(Errno::EBUSY),
1396 "SQ pleine ⇒ EBUSY (space_left == 0)"
1397 );
1398 ring.submit_and_wait(4).expect("submit");
1400 let mut n = 0;
1401 while n < 4 {
1402 for _ in ring.completions() {
1403 n += 1;
1404 }
1405 }
1406 let _ = std::fs::remove_file(&path);
1407 }
1408
1409 #[test]
1412 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1413 fn skip_cqe_applies_to_no_payload_ops_only() {
1414 use crate::io_uring::SubmitOptions;
1415 let path = temp_path("skip");
1416 let file = rw_file(&path);
1417 let mut ring = ring8();
1418
1419 ring.with(SubmitOptions::default().skip_cqe_on_success());
1421 ring.submit_fsync(sfd(&file), FsyncFlags::empty())
1422 .expect("fsync skip");
1423 assert_eq!(ring.in_flight(), 0, "no-payload + skip ⇒ slot libéré");
1424 assert_eq!(ring.submit().expect("submit"), 1);
1425
1426 ring.with(SubmitOptions::default().skip_cqe_on_success());
1428 let tok = ring
1429 .submit_read(sfd(&file), vec![0u8; 4], Some(0))
1430 .expect("read skip");
1431 assert_eq!(ring.in_flight(), 1, "payload + skip ⇒ slot conservé");
1432 let (_b, _n) = complete_one(&mut ring, tok)
1433 .into_buffer_result()
1434 .expect("read complète bien (CQE non sauté)");
1435 assert_eq!(ring.in_flight(), 0);
1436 let _ = std::fs::remove_file(&path);
1437 }
1438
1439 proptest::proptest! {
1442 #[test]
1446 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1447 fn non_utf8_paths_never_panic(bytes in proptest::collection::vec(1u8..=255, 1..40)) {
1448 let cpath = CString::new(bytes).expect("aucun NUL (octets 1..=255)");
1449 let mut ring = ring8();
1450 let tok = ring
1451 .submit_statx(
1452 DirFd::Cwd,
1453 cpath,
1454 StatxFlags::empty(),
1455 StatxMask::SIZE,
1456 Box::new(Statx::default()),
1457 )
1458 .expect("submission jamais en panique");
1459 assert_eq!(ring.submit_and_wait(1).expect("submit"), 1);
1460 let completion = ring.wait_completion().expect("complétion");
1461 proptest::prop_assert_eq!(completion.token(), tok);
1462 let _ = completion.into_statx();
1464 }
1465
1466 #[test]
1469 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1470 fn write_read_transfers_bounded_and_faithful(
1471 data in proptest::collection::vec(proptest::prelude::any::<u8>(), 0..512)
1472 ) {
1473 let path = temp_path("prop-rw");
1474 let file = rw_file(&path);
1475 let mut ring = ring8();
1476 let len = data.len();
1477
1478 let tok = ring
1479 .submit_write(sfd(&file), data.clone(), Some(0))
1480 .expect("submit_write");
1481 assert_eq!(ring.submit_and_wait(1).expect("submit"), 1);
1482 let (_b, written) = ring
1483 .wait_completion()
1484 .expect("complétion")
1485 .into_buffer_result()
1486 .expect("write ok");
1487 let _ = tok;
1488 proptest::prop_assert!(written <= len);
1489 proptest::prop_assert_eq!(written, len);
1490
1491 let tok = ring
1492 .submit_read(sfd(&file), vec![0u8; len], Some(0))
1493 .expect("submit_read");
1494 assert_eq!(ring.submit_and_wait(1).expect("submit"), 1);
1495 let (buf, read) = ring
1496 .wait_completion()
1497 .expect("complétion")
1498 .into_buffer_result()
1499 .expect("read ok");
1500 let _ = tok;
1501 proptest::prop_assert!(read <= len);
1502 proptest::prop_assert_eq!(&buf[..read], &data[..read]);
1503 let _ = std::fs::remove_file(&path);
1504 }
1505 }
1506
1507 fn cqe(res: i32, payload: Option<OwnedOp>) -> Completion {
1514 Completion {
1515 token: SubmissionToken::new(0, 0),
1516 res,
1517 raw_flags: 0,
1518 payload,
1519 multishot: false,
1520 }
1521 }
1522
1523 #[test]
1524 fn accessors_on_wrong_payload_yield_empty_defaults() {
1525 let (b, n) = cqe(0, Some(OwnedOp::Path(CString::new("x").unwrap())))
1527 .into_buffer_result()
1528 .expect("ok");
1529 assert!(b.is_empty() && n == 0);
1530 let (v, n) = cqe(0, Some(OwnedOp::Bytes(vec![1, 2])))
1532 .into_vectored_result()
1533 .expect("ok");
1534 assert!(v.is_empty() && n == 0);
1535 let st = cqe(0, None).into_statx().expect("ok");
1537 assert_eq!(st.mask, 0);
1538 let (val, n) = cqe(0, None).into_xattr_result().expect("ok");
1540 assert!(val.is_empty() && n == 0);
1541 }
1542
1543 #[test]
1544 fn accessors_propagate_kernel_errors() {
1545 assert_eq!(
1547 cqe(-2, Some(OwnedOp::Bytes(vec![1]))).into_buffer_result(),
1548 Err(Errno::ENOENT)
1549 );
1550 assert_eq!(cqe(-2, None).into_vectored_result(), Err(Errno::ENOENT));
1551 assert_eq!(cqe(-13, None).into_statx().err(), Some(Errno::EACCES));
1552 assert_eq!(cqe(-9, None).into_xattr_result(), Err(Errno::EBADF));
1553 assert_eq!(cqe(-2, None).opened_fd().err(), Some(Errno::ENOENT));
1555 }
1556
1557 #[test]
1560 fn ownership_bytes_round_trips_without_uaf() {
1561 let data = vec![3u8; 32];
1562 let (buf, n) = cqe(32, Some(OwnedOp::Bytes(data.clone())))
1563 .into_buffer_result()
1564 .expect("ok");
1565 assert_eq!(n, 32);
1566 assert_eq!(buf, data);
1567 }
1568
1569 #[test]
1570 fn ownership_vectored_round_trips_without_uaf() {
1571 let original = vec![vec![1u8, 2, 3], vec![4, 5]];
1572 let mut buffers = original.clone();
1573 let iovecs: Box<[Iovec]> = buffers
1574 .iter_mut()
1575 .map(|b| Iovec {
1576 iov_base: b.as_mut_ptr(),
1577 iov_len: b.len(),
1578 })
1579 .collect();
1580 let payload = OwnedOp::Vectored { buffers, iovecs };
1581 let (restituted, n) = cqe(5, Some(payload)).into_vectored_result().expect("ok");
1582 assert_eq!(n, 5);
1583 assert_eq!(restituted, original);
1584 }
1585
1586 #[test]
1587 fn ownership_statx_and_xattr_round_trip_without_uaf() {
1588 let mut out = Box::new(Statx::default());
1589 out.size = 4242;
1590 let payload = OwnedOp::Statx {
1591 out,
1592 path: CString::new("/some/path").unwrap(),
1593 };
1594 let st = cqe(0, Some(payload)).into_statx().expect("ok");
1595 assert_eq!(st.size, 4242);
1596
1597 let payload = OwnedOp::Xattr {
1598 value: b"abc".to_vec(),
1599 name: CString::new("user.k").unwrap(),
1600 path: Some(CString::new("/p").unwrap()),
1601 };
1602 let (value, n) = cqe(3, Some(payload)).into_xattr_result().expect("ok");
1603 assert_eq!(&value[..n], b"abc");
1604 }
1605
1606 #[test]
1607 fn ownership_payloads_drop_without_extraction() {
1608 let mut buffers = vec![vec![9u8; 8]];
1611 let iovecs: Box<[Iovec]> = buffers
1612 .iter_mut()
1613 .map(|b| Iovec {
1614 iov_base: b.as_mut_ptr(),
1615 iov_len: b.len(),
1616 })
1617 .collect();
1618 let payloads = [
1619 OwnedOp::Bytes(vec![1, 2, 3]),
1620 OwnedOp::Vectored { buffers, iovecs },
1621 OwnedOp::Path(CString::new("p").unwrap()),
1622 OwnedOp::TwoPaths(CString::new("a").unwrap(), CString::new("b").unwrap()),
1623 OwnedOp::Xattr {
1624 value: vec![0u8; 4],
1625 name: CString::new("n").unwrap(),
1626 path: None,
1627 },
1628 ];
1629 for p in payloads {
1630 drop(cqe(0, Some(p)));
1631 }
1632 }
1633
1634 #[test]
1639 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1640 fn madvise_on_mmap_region_completes_and_validates() {
1641 use air_sys_types::mem::{MapFlags, ProtectionFlags};
1642 let region = MmapRegion::new_anonymous(
1643 4096,
1644 ProtectionFlags::READ | ProtectionFlags::WRITE,
1645 MapFlags::PRIVATE,
1646 )
1647 .expect("new_anonymous");
1648 let mut ring = ring8();
1649
1650 let tok = ring
1651 .submit_madvise(®ion, 0..4096, MadviseAdvice::WillNeed)
1652 .expect("submit_madvise");
1653 complete_one(&mut ring, tok)
1654 .completed()
1655 .expect("madvise WILLNEED ok");
1656
1657 let tok = ring
1659 .submit_madvise(®ion, 0..2048, MadviseAdvice::DontNeed)
1660 .expect("submit_madvise demi");
1661 complete_one(&mut ring, tok)
1662 .completed()
1663 .expect("madvise DONTNEED ok");
1664
1665 assert_eq!(
1667 ring.submit_madvise(®ion, 0..8192, MadviseAdvice::DontNeed)
1668 .unwrap_err(),
1669 Errno::EINVAL
1670 );
1671 let malformed = Range {
1674 start: 100,
1675 end: 50,
1676 };
1677 assert_eq!(
1678 ring.submit_madvise(®ion, malformed, MadviseAdvice::Normal)
1679 .unwrap_err(),
1680 Errno::EINVAL
1681 );
1682 }
1683
1684 #[test]
1688 #[cfg_attr(miri, ignore = "syscall io_uring non supporté par Miri")]
1689 fn madvise_keeps_region_alive_while_in_flight() {
1690 use air_sys_types::mem::{MapFlags, ProtectionFlags};
1691 let region = MmapRegion::new_anonymous(
1692 4096,
1693 ProtectionFlags::READ | ProtectionFlags::WRITE,
1694 MapFlags::PRIVATE,
1695 )
1696 .expect("new_anonymous");
1697 let mut ring = ring8();
1698 let tok = ring
1699 .submit_madvise(®ion, 0..4096, MadviseAdvice::WillNeed)
1700 .expect("submit_madvise");
1701 drop(region);
1703 complete_one(&mut ring, tok)
1705 .completed()
1706 .expect("madvise en vol ok malgré drop côté utilisateur");
1707 }
1708}