From 3b09e68bf3616721f609402027bffa0a6bc1a141 Mon Sep 17 00:00:00 2001 From: Fathy Boundjadj Date: Sat, 18 Mar 2023 15:22:50 +0100 Subject: [PATCH] add tests and docs --- io-uring-test/src/main.rs | 3 +- io-uring-test/src/tests/mod.rs | 1 + io-uring-test/src/tests/register.rs | 91 ------- io-uring-test/src/tests/register_buffers.rs | 248 ++++++++++++++++++++ src/submit.rs | 10 + 5 files changed, 261 insertions(+), 92 deletions(-) create mode 100644 io-uring-test/src/tests/register_buffers.rs diff --git a/io-uring-test/src/main.rs b/io-uring-test/src/main.rs index 70d4d0b6..e9563caf 100644 --- a/io-uring-test/src/main.rs +++ b/io-uring-test/src/main.rs @@ -76,7 +76,8 @@ fn test( // register tests::register::test_register_files_sparse(&mut ring, &test)?; - tests::register::test_register_buffers(&mut ring, &test)?; + tests::register_buffers::test_register_buffers(&mut ring, &test)?; + tests::register_buffers::test_register_buffers_update_tag(&mut ring, &test)?; tests::register_buf_ring::test_register_buf_ring(&mut ring, &test)?; // fs diff --git a/io-uring-test/src/tests/mod.rs b/io-uring-test/src/tests/mod.rs index f2a9087f..a7cf3ae4 100644 --- a/io-uring-test/src/tests/mod.rs +++ b/io-uring-test/src/tests/mod.rs @@ -4,5 +4,6 @@ pub mod poll; pub mod queue; pub mod register; pub mod register_buf_ring; +pub mod register_buffers; pub mod regression; pub mod timeout; diff --git a/io-uring-test/src/tests/register.rs b/io-uring-test/src/tests/register.rs index d450a63e..f4d3efd5 100644 --- a/io-uring-test/src/tests/register.rs +++ b/io-uring-test/src/tests/register.rs @@ -1,5 +1,3 @@ -use std::io; - use crate::Test; use io_uring::{cqueue, opcode, squeue, IoUring}; @@ -70,92 +68,3 @@ pub fn test_register_files_sparse( - ring: &mut IoUring, - test: &Test, -) -> anyhow::Result<()> { - test_register("register_buffers_sparse", ring, test, |ring| { - ring.submitter().register_buffers_sparse(4) - })?; - test_register("register_buffers", ring, test, |ring| { - let buf = [0xde, 0xed, 0xbe, 0xef]; - - unsafe { - ring.submitter().register_buffers(&[libc::iovec { - iov_base: buf.as_ptr() as _, - iov_len: buf.len(), - }]) - } - })?; - test_register("register_buffers_tags", ring, test, |ring| { - let buf = [0xde, 0xed, 0xbe, 0xef]; - - unsafe { - ring.submitter().register_buffers_tags( - &[libc::iovec { - iov_base: buf.as_ptr() as _, - iov_len: buf.len(), - }], - &[0], - ) - } - })?; - - return Ok(()); - - fn test_register< - S: squeue::EntryMarker, - C: cqueue::EntryMarker, - F: FnMut(&mut IoUring) -> io::Result<()>, - >( - name: &str, - ring: &mut IoUring, - test: &Test, - mut register: F, - ) -> anyhow::Result<()> { - // register_files_sparse was introduced in kernel 5.19, as was the opcode for UringCmd16. - // So require the UringCmd16 to avoid running this test on earlier kernels. - require!( - test; - test.probe.is_supported(opcode::UringCmd16::CODE); - ); - - println!("test {name}"); - - if let Ok(()) = ring.submitter().unregister_buffers() { - return Err(anyhow::anyhow!( - "unregister_buffers should fail if not buffer table has been setup" - )); - } - - if let Err(e) = register(ring) { - return Err(anyhow::anyhow!("{name} failed: {}", e)); - } - - // See that same call again, with any value, will fail because a direct table cannot be built - // over an existing one. - - if let Ok(()) = register(ring) { - return Err(anyhow::anyhow!( - "{name} should not have succeeded twice in a row" - )); - } - - // See that the direct table can be removed. - - if let Err(e) = ring.submitter().unregister_buffers() { - return Err(anyhow::anyhow!("unregister_buffers failed: {}", e)); - } - - // See that a second attempt to remove the direct table would fail. - - if let Ok(()) = ring.submitter().unregister_buffers() { - return Err(anyhow::anyhow!( - "unregister_buffers should not have succeeded twice in a row" - )); - } - - Ok(()) - } -} diff --git a/io-uring-test/src/tests/register_buffers.rs b/io-uring-test/src/tests/register_buffers.rs new file mode 100644 index 00000000..06a76a04 --- /dev/null +++ b/io-uring-test/src/tests/register_buffers.rs @@ -0,0 +1,248 @@ +use std::{ + fs::File, + io::{self, Error, Write}, + os::fd::{AsRawFd, FromRawFd}, +}; + +use crate::Test; +use io_uring::{cqueue, opcode, squeue, types, IoUring}; +use libc::EFAULT; + +pub fn test_register_buffers( + ring: &mut IoUring, + test: &Test, +) -> anyhow::Result<()> { + let mut buf = [0xde, 0xed, 0xbe, 0xef]; + let tags = [0]; + let iovecs = [libc::iovec { + iov_len: buf.len(), + iov_base: buf.as_mut_ptr() as _, + }]; + + test_register(ring, test, "register_buffers", None, |ring| { + ring.submitter().register_buffers(&iovecs) + })?; + test_register( + ring, + test, + "register_buffers_tags", + ring.params().is_feature_resource_tagging(), + |ring| unsafe { ring.submitter().register_buffers_tags(&iovecs, &tags) }, + )?; + test_register( + ring, + test, + "register_buffers_sparse", + ring.params().is_feature_resource_tagging(), + |ring| ring.submitter().register_buffers_sparse(4), + )?; + + return Ok(()); + + fn test_register< + S: squeue::EntryMarker, + C: cqueue::EntryMarker, + F: FnMut(&mut IoUring) -> io::Result<()>, + P: Into>, + >( + ring: &mut IoUring, + test: &Test, + name: &str, + probe: P, + mut register: F, + ) -> anyhow::Result<()> { + require!( + test; + test.probe.is_supported(opcode::ReadFixed::CODE); + probe.into().unwrap_or(true); + ); + + println!("test {name}"); + + ring.submitter().unregister_buffers().err().ok_or_else(|| { + anyhow::anyhow!("unregister_buffers should fail if not buffer table has been setup") + })?; + + register(ring).map_err(|e| anyhow::anyhow!("{name} failed: {e}"))?; + + // See that same call again, with any value, will fail because a direct table cannot be built + // over an existing one. + register(ring) + .err() + .ok_or_else(|| anyhow::anyhow!("{name} should not have succeeded twice in a row"))?; + + // See that the direct table can be removed. + ring.submitter() + .unregister_buffers() + .map_err(|e| anyhow::anyhow!("unregister_buffers failed: {e}"))?; + + // See that a second attempt to remove the direct table would fail. + ring.submitter().unregister_buffers().err().ok_or_else(|| { + anyhow::anyhow!("unregister_buffers should not have succeeded twice in a row") + })?; + + Ok(()) + } +} + +const BUFFER_TAG: u64 = 0xbadcafe; +const TIMEOUT_TAG: u64 = 0xbadf00d; + +pub fn test_register_buffers_update_tag( + ring: &mut IoUring, + test: &Test, +) -> anyhow::Result<()> { + require!( + test; + test.probe.is_supported(opcode::ReadFixed::CODE); + ring.params().is_feature_resource_tagging(); + ); + + println!("test register_buffers_update_tag"); + + let (read, mut write) = create_pipe()?; + let mut buf = [0xde, 0xed, 0xbe, 0xef]; + let mut tags = [BUFFER_TAG]; + let iovecs = [libc::iovec { + iov_len: buf.len(), + iov_base: buf.as_mut_ptr() as _, + }]; + let timeout = types::Timespec::new().nsec(50 * 1_000_000); + let timeout = opcode::Timeout::new(&timeout as _) + .build() + .user_data(TIMEOUT_TAG) + .into(); + let read_sqe = opcode::ReadFixed::new( + types::Fd(read.as_raw_fd()), + buf.as_mut_ptr(), + buf.len() as _, + 5, + ) + .build() + .user_data(42) + .into(); + + // Register a buffer table and then immediately unregister it + ring.submitter().register_buffers_sparse(1)?; + ring.submitter().unregister_buffers()?; + + // Push a timeout of 50ms + unsafe { ring.submission().push(&timeout).unwrap() }; + + // We should not receive any other entries than the timeout + check_only_timeout(ring)?; + + // Register a sparse buffer table of 10 elements + ring.submitter().register_buffers_sparse(10)?; + + // Try read the pipe using a sparse buffer + let cqe = { + write.write("yo".as_bytes())?; + + unsafe { ring.submission().push(&read_sqe).unwrap() }; + + ring.submit_and_wait(1)?; + + ring.completion().next().unwrap().into() + }; + + // We should get the correct user_data + if cqe.user_data() != 42 { + return Err(anyhow::anyhow!("unexpected completion queue entry")); + } + + // EFAULT is to be expected with incorrect fixed buffers + if cqe.result() != -EFAULT { + return Err(anyhow::anyhow!("unexpected read result: {}", cqe.result())); + } + + // Register a buffer at the index 5 + unsafe { + ring.submitter() + .register_buffers_update_tag(5, &iovecs, &tags)?; + + // Push a timeout of 50ms + ring.submission().push(&timeout).unwrap(); + } + + // We should not receive any other entries than the timeout + check_only_timeout(ring)?; + + // Register a buffer at the same index 5, but this time with an empty tag. + let cqe = { + tags[0] = 0; + + unsafe { + ring.submitter() + .register_buffers_update_tag(5, &iovecs, &tags)?; + } + ring.submit_and_wait(1)?; + + ring.completion().next().unwrap().into() + }; + + // We should get a cqe with the first tag because we registered a + // new buffer at an index where a buffer was already registered. + if cqe.user_data() != BUFFER_TAG { + return Err(anyhow::anyhow!( + "expected completion queue to contain a buffer unregistered event" + )); + } + + // Try reading now that the buffer is registered at index 5 + let cqe = { + unsafe { + ring.submission().push(&read_sqe).unwrap(); + } + + ring.submit_and_wait(1)?; + + ring.completion().next().unwrap().into() + }; + + // We should get the correct user_data + if cqe.user_data() != 42 { + return Err(anyhow::anyhow!("unexpected completion queue entry")); + } + + // We should read exactly two bytes + if cqe.result() != 2 { + return Err(anyhow::anyhow!("unexpected read result: {}", cqe.result())); + } + + // The first two bytes of `buf` should be "yo" + if &buf[0..2] != "yo".as_bytes() { + return Err(anyhow::anyhow!("unexpected read buffer data: {:x?}", &buf)); + } + + return Ok(()); +} + +/// Create a pipe and return both ends as RAII `File` handles +fn create_pipe() -> io::Result<(File, File)> { + let mut fds = [-1, -1]; + + unsafe { + if libc::pipe(fds.as_mut_ptr()) == -1 { + Err(Error::last_os_error()) + } else { + Ok((File::from_raw_fd(fds[0]), File::from_raw_fd(fds[1]))) + } + } +} + +/// Submit sqes and asserts the only cqe is a timeout entry +fn check_only_timeout( + ring: &mut IoUring, +) -> Result<(), anyhow::Error> { + ring.submit_and_wait(1)?; + + if Into::::into(ring.completion().next().unwrap()).user_data() == TIMEOUT_TAG { + // There should not be any more entries in the queue + if ring.completion().next().is_none() { + return Ok(()); + } + } + + Err(anyhow::anyhow!("unexpected completion queue entry")) +} diff --git a/src/submit.rs b/src/submit.rs index 34d05e42..07b13e4f 100644 --- a/src/submit.rs +++ b/src/submit.rs @@ -169,6 +169,8 @@ impl<'a> Submitter<'a> { /// Register in-memory user buffers for I/O with the kernel. You can use these buffers with the /// [`ReadFixed`](crate::opcode::ReadFixed) and [`WriteFixed`](crate::opcode::WriteFixed) /// operations. + /// + /// Available since Linux 5.1. pub fn register_buffers(&self, bufs: &[libc::iovec]) -> io::Result<()> { execute( self.fd.as_raw_fd(), @@ -188,6 +190,8 @@ impl<'a> Submitter<'a> { /// See [`register_buffers_tags`](Self::register_buffers_tags) /// for more information about resource tagging. /// + /// Available since Linux 5.13. + /// /// # Safety /// /// This function is unsafe because improper use may lead to memory problems. @@ -229,6 +233,8 @@ impl<'a> Submitter<'a> { /// a CQE will be posted with `user_data` set to the specified /// tag and all other fields zeroed. /// + /// Available since Linux 5.13. + /// /// # Safety /// /// This function is unsafe because improper use may lead to memory problems. @@ -263,6 +269,8 @@ impl<'a> Submitter<'a> { /// /// See [`register_buffers`](Self::register_buffers) /// for more information about fixed buffers. + /// + /// Available since Linux 5.13. pub fn register_buffers_sparse(&self, nr: u32) -> io::Result<()> { let rr = sys::io_uring_rsrc_register { nr, @@ -420,6 +428,8 @@ impl<'a> Submitter<'a> { /// /// You do not need to explicitly call this before dropping the [`IoUring`](crate::IoUring), as /// it will be cleaned up by the kernel automatically. + /// + /// Available since Linux 5.1. pub fn unregister_buffers(&self) -> io::Result<()> { execute( self.fd.as_raw_fd(),