#![deny(unsafe_code)]
#![forbid(clippy::arithmetic_side_effects)]
#![forbid(clippy::cast_possible_truncation)]
#![forbid(clippy::cast_possible_wrap)]
#![forbid(clippy::cast_sign_loss)]
use std::str::FromStr;
use bitflags::{bitflags, Flags};
use libc::{syscall, SYS_io_uring_register};
use nix::errno::Errno;
use crate::compat::set_no_new_privs;
pub const URING_OPS: &[&str] = &[
"nop",
"readv",
"writev",
"fsync",
"read_fixed",
"write_fixed",
"poll_add",
"poll_remove",
"sync_file_range",
"sendmsg",
"recvmsg",
"timeout",
"timeout_remove",
"accept",
"async_cancel",
"link_timeout",
"connect",
"fallocate",
"openat",
"close",
"files_update",
"statx",
"read",
"write",
"fadvise",
"madvise",
"send",
"recv",
"openat2",
"epoll_ctl",
"splice",
"provide_buffers",
"remove_buffers",
"tee",
"shutdown",
"renameat",
"unlinkat",
"mkdirat",
"symlinkat",
"linkat",
"msg_ring",
"fsetxattr",
"setxattr",
"fgetxattr",
"getxattr",
"socket",
"uring_cmd",
"send_zc",
"sendmsg_zc",
"read_multishot",
"waitid",
"futex_wait",
"futex_wake",
"futex_waitv",
"fixed_fd_install",
"ftruncate",
"bind",
"listen",
"recv_zc",
"epoll_wait",
"readv_fixed",
"writev_fixed",
"pipe",
"nop128",
"uring_cmd128",
];
pub const URING_FLAGS: &[&str] = &[
"fixed_file",
"io_drain",
"io_link",
"io_hardlink",
"async",
"buffer_select",
"cqe_skip_success",
];
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct UringOp: u128 {
const NOP = 1 << 0;
const READV = 1 << 1;
const WRITEV = 1 << 2;
const FSYNC = 1 << 3;
const READ_FIXED = 1 << 4;
const WRITE_FIXED = 1 << 5;
const POLL_ADD = 1 << 6;
const POLL_REMOVE = 1 << 7;
const SYNC_FILE_RANGE = 1 << 8;
const SENDMSG = 1 << 9;
const RECVMSG = 1 << 10;
const TIMEOUT = 1 << 11;
const TIMEOUT_REMOVE = 1 << 12;
const ACCEPT = 1 << 13;
const ASYNC_CANCEL = 1 << 14;
const LINK_TIMEOUT = 1 << 15;
const CONNECT = 1 << 16;
const FALLOCATE = 1 << 17;
const OPENAT = 1 << 18;
const CLOSE = 1 << 19;
const FILES_UPDATE = 1 << 20;
const STATX = 1 << 21;
const READ = 1 << 22;
const WRITE = 1 << 23;
const FADVISE = 1 << 24;
const MADVISE = 1 << 25;
const SEND = 1 << 26;
const RECV = 1 << 27;
const OPENAT2 = 1 << 28;
const EPOLL_CTL = 1 << 29;
const SPLICE = 1 << 30;
const PROVIDE_BUFFERS = 1 << 31;
const REMOVE_BUFFERS = 1 << 32;
const TEE = 1 << 33;
const SHUTDOWN = 1 << 34;
const RENAMEAT = 1 << 35;
const UNLINKAT = 1 << 36;
const MKDIRAT = 1 << 37;
const SYMLINKAT = 1 << 38;
const LINKAT = 1 << 39;
const MSG_RING = 1 << 40;
const FSETXATTR = 1 << 41;
const SETXATTR = 1 << 42;
const FGETXATTR = 1 << 43;
const GETXATTR = 1 << 44;
const SOCKET = 1 << 45;
const URING_CMD = 1 << 46;
const SEND_ZC = 1 << 47;
const SENDMSG_ZC = 1 << 48;
const READ_MULTISHOT = 1 << 49;
const WAITID = 1 << 50;
const FUTEX_WAIT = 1 << 51;
const FUTEX_WAKE = 1 << 52;
const FUTEX_WAITV = 1 << 53;
const FIXED_FD_INSTALL = 1 << 54;
const FTRUNCATE = 1 << 55;
const BIND = 1 << 56;
const LISTEN = 1 << 57;
const RECV_ZC = 1 << 58;
const EPOLL_WAIT = 1 << 59;
const READV_FIXED = 1 << 60;
const WRITEV_FIXED = 1 << 61;
const PIPE = 1 << 62;
const NOP128 = 1 << 63;
const URING_CMD128 = 1 << 64;
}
}
impl Default for UringOp {
fn default() -> Self {
Self::EPOLL_CTL | Self::READ | Self::READV | Self::WRITE | Self::WRITEV
}
}
impl FromStr for UringOp {
type Err = Errno;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut ops = UringOp::empty();
for op in value.split(',') {
ops |= match op.to_ascii_lowercase().as_str() {
"all" => Self::all(),
"nop" => Self::NOP,
"readv" => Self::READV,
"writev" => Self::WRITEV,
"fsync" => Self::FSYNC,
"read_fixed" => Self::READ_FIXED,
"write_fixed" => Self::WRITE_FIXED,
"poll_add" => Self::POLL_ADD,
"poll_remove" => Self::POLL_REMOVE,
"sync_file_range" => Self::SYNC_FILE_RANGE,
"sendmsg" => Self::SENDMSG,
"recvmsg" => Self::RECVMSG,
"timeout" => Self::TIMEOUT,
"timeout_remove" => Self::TIMEOUT_REMOVE,
"accept" => Self::ACCEPT,
"async_cancel" => Self::ASYNC_CANCEL,
"link_timeout" => Self::LINK_TIMEOUT,
"connect" => Self::CONNECT,
"fallocate" => Self::FALLOCATE,
"openat" => Self::OPENAT,
"close" => Self::CLOSE,
"files_update" => Self::FILES_UPDATE,
"statx" => Self::STATX,
"read" => Self::READ,
"write" => Self::WRITE,
"fadvise" => Self::FADVISE,
"madvise" => Self::MADVISE,
"send" => Self::SEND,
"recv" => Self::RECV,
"openat2" => Self::OPENAT2,
"epoll_ctl" => Self::EPOLL_CTL,
"splice" => Self::SPLICE,
"provide_buffers" => Self::PROVIDE_BUFFERS,
"remove_buffers" => Self::REMOVE_BUFFERS,
"tee" => Self::TEE,
"shutdown" => Self::SHUTDOWN,
"renameat" => Self::RENAMEAT,
"unlinkat" => Self::UNLINKAT,
"mkdirat" => Self::MKDIRAT,
"symlinkat" => Self::SYMLINKAT,
"linkat" => Self::LINKAT,
"msg_ring" => Self::MSG_RING,
"fsetxattr" => Self::FSETXATTR,
"setxattr" => Self::SETXATTR,
"fgetxattr" => Self::FGETXATTR,
"getxattr" => Self::GETXATTR,
"socket" => Self::SOCKET,
"uring_cmd" => Self::URING_CMD,
"send_zc" => Self::SEND_ZC,
"sendmsg_zc" => Self::SENDMSG_ZC,
"read_multishot" => Self::READ_MULTISHOT,
"waitid" => Self::WAITID,
"futex_wait" => Self::FUTEX_WAIT,
"futex_wake" => Self::FUTEX_WAKE,
"futex_waitv" => Self::FUTEX_WAITV,
"fixed_fd_install" => Self::FIXED_FD_INSTALL,
"ftruncate" => Self::FTRUNCATE,
"bind" => Self::BIND,
"listen" => Self::LISTEN,
"recv_zc" => Self::RECV_ZC,
"epoll_wait" => Self::EPOLL_WAIT,
"readv_fixed" => Self::READV_FIXED,
"writev_fixed" => Self::WRITEV_FIXED,
"pipe" => Self::PIPE,
"nop128" => Self::NOP128,
"uring_cmd128" => Self::URING_CMD128,
_ => return Err(Errno::EINVAL),
};
}
if !ops.is_empty() {
Ok(ops)
} else {
Err(Errno::ENOENT)
}
}
}
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub(crate) struct UringSqeFlag: u8 {
const FIXED_FILE = 1 << 0;
const IO_DRAIN = 1 << 1;
const IO_LINK = 1 << 2;
const IO_HARDLINK = 1 << 3;
const ASYNC = 1 << 4;
const BUFFER_SELECT = 1 << 5;
const CQE_SKIP_SUCCESS = 1 << 6;
}
}
impl Default for UringSqeFlag {
fn default() -> Self {
Self::ASYNC
}
}
impl FromStr for UringSqeFlag {
type Err = Errno;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let mut flags = UringSqeFlag::empty();
for flag in value.split(',') {
flags |= match flag.to_ascii_lowercase().as_str() {
"all" => Self::all(),
"fixed_file" => Self::FIXED_FILE,
"io_drain" => Self::IO_DRAIN,
"io_link" => Self::IO_LINK,
"io_hardlink" => Self::IO_HARDLINK,
"async" => Self::ASYNC,
"buffer_select" => Self::BUFFER_SELECT,
"cqe_skip_success" => Self::CQE_SKIP_SUCCESS,
_ => return Err(Errno::EINVAL),
};
}
if !flags.is_empty() {
Ok(flags)
} else {
Err(Errno::ENOENT)
}
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct UringFilter {
ops: UringOp,
flags: UringSqeFlag,
}
impl UringFilter {
pub fn empty() -> Self {
Self {
ops: UringOp::empty(),
flags: UringSqeFlag::empty(),
}
}
pub(crate) fn insert_ops(&mut self, ops: UringOp) {
self.ops.insert(ops)
}
pub(crate) fn remove_ops(&mut self, ops: UringOp) {
self.ops.remove(ops)
}
pub(crate) fn clear_ops(&mut self) {
self.ops.clear()
}
pub(crate) fn insert_flags(&mut self, flags: UringSqeFlag) {
self.flags.insert(flags)
}
pub(crate) fn remove_flags(&mut self, flags: UringSqeFlag) {
self.flags.remove(flags)
}
pub(crate) fn clear_flags(&mut self) {
self.flags.clear()
}
pub fn is_empty(&self) -> bool {
self.ops.is_empty()
}
pub fn allow_ops(&mut self, ops: &str) -> Result<(), Errno> {
self.insert_ops(UringOp::from_str(ops)?);
Ok(())
}
pub fn allow_flags(&mut self, flags: &str) -> Result<(), Errno> {
self.insert_flags(UringSqeFlag::from_str(flags)?);
Ok(())
}
fn encode(&self) -> Result<IoUringTaskRestriction, Errno> {
let mut tres = IoUringTaskRestriction {
flags: 0,
nr_res: 0,
_resv: [0; 3],
restrictions: [IoUringRestriction::op(0, 0); MAX_RESTRICTIONS],
};
let mut nr_res: usize = 0;
for op in self.ops.iter() {
let sqe_op = u8::try_from(op.bits().trailing_zeros()).or(Err(Errno::EOVERFLOW))?;
let entry = tres.restrictions.get_mut(nr_res).ok_or(Errno::EFAULT)?;
*entry = IoUringRestriction::op(IORING_RESTRICTION_SQE_OP, sqe_op);
nr_res = nr_res.checked_add(1).ok_or(Errno::EOVERFLOW)?;
}
let entry = tres.restrictions.get_mut(nr_res).ok_or(Errno::EFAULT)?;
*entry = IoUringRestriction::flags(self.flags.bits());
nr_res = nr_res.checked_add(1).ok_or(Errno::EOVERFLOW)?;
tres.nr_res = u16::try_from(nr_res).or(Err(Errno::EOVERFLOW))?;
Ok(tres)
}
pub fn register(&self) -> Result<(), Errno> {
if self.ops.is_empty() {
return Ok(());
}
set_no_new_privs()?;
let tres = self.encode()?;
#[expect(unsafe_code)]
Errno::result(unsafe {
syscall(
SYS_io_uring_register,
-1i32,
IORING_REGISTER_RESTRICTIONS,
std::ptr::addr_of!(tres).cast::<libc::c_void>(),
1u32,
)
})
.map(drop)
}
}
const IORING_REGISTER_RESTRICTIONS: libc::c_ulong = 11;
const IORING_RESTRICTION_SQE_OP: u16 = 1;
const IORING_RESTRICTION_SQE_FLAGS_ALLOWED: u16 = 2;
const MAX_RESTRICTIONS: usize = 128 + 1;
#[derive(Clone, Copy)]
#[repr(C)]
union UringRestrictionArg {
sqe_op: u8,
sqe_flags: u8,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct IoUringRestriction {
opcode: u16,
arg: UringRestrictionArg,
_resv: u8,
_resv2: [u32; 3],
}
#[repr(C)]
struct IoUringTaskRestriction {
flags: u16,
nr_res: u16,
_resv: [u32; 3],
restrictions: [IoUringRestriction; MAX_RESTRICTIONS],
}
const _: () = assert!(std::mem::size_of::<IoUringRestriction>() == 16);
impl IoUringRestriction {
const fn op(opcode: u16, sqe_op: u8) -> Self {
Self {
opcode,
arg: UringRestrictionArg { sqe_op },
_resv: 0,
_resv2: [0; 3],
}
}
const fn flags(sqe_flags: u8) -> Self {
Self {
opcode: IORING_RESTRICTION_SQE_FLAGS_ALLOWED,
arg: UringRestrictionArg { sqe_flags },
_resv: 0,
_resv2: [0; 3],
}
}
#[cfg(test)]
fn arg(&self) -> u8 {
#[expect(unsafe_code)]
unsafe {
self.arg.sqe_op
}
}
}
#[cfg(test)]
mod tests {
use nix::{
sys::wait::{waitpid, WaitStatus},
unistd::{fork, ForkResult},
};
use super::*;
use crate::{
compat::set_no_new_privs,
t::uring::{flag, op, IoUring, Sqe},
};
fn op_filter(ops: UringOp) -> UringFilter {
let mut filter = UringFilter::default();
filter.clear_ops();
filter.insert_ops(ops);
filter
}
const SKIP: i32 = 42;
fn _exit(code: i32) -> ! {
#[expect(unsafe_code)]
unsafe {
libc::_exit(code)
}
}
fn set_up_ring(filter: &UringFilter) -> Option<IoUring> {
set_no_new_privs().ok()?;
match filter.register() {
Ok(()) => {}
Err(Errno::ENOSYS | Errno::EINVAL | Errno::EBADF) => return None,
Err(errno) => panic!("filter install failed: {errno}"),
}
IoUring::new(8).ok()
}
fn submit(mut ring: IoUring, entry: Sqe) -> i32 {
ring.push(&entry).expect("push");
ring.submit_and_wait(1).expect("submit");
ring.completion().expect("cqe")
}
fn uring_available() -> bool {
#[expect(unsafe_code)]
let forked = unsafe { fork() };
match forked {
Ok(ForkResult::Parent { child }) => {
matches!(waitpid(child, None).unwrap(), WaitStatus::Exited(_, 0))
}
Ok(ForkResult::Child) => {
_exit(i32::from(set_up_ring(&UringFilter::default()).is_none()));
}
Err(errno) => panic!("fork failed: {errno}"),
}
}
fn probe(filter: &UringFilter, entry: Sqe, want: i32) {
#[expect(unsafe_code)]
let forked = unsafe { fork() };
match forked {
Ok(ForkResult::Parent { child }) => match waitpid(child, None).unwrap() {
WaitStatus::Exited(_, 0) => {}
other => panic!("waitpid failed: {other:?}"),
},
Ok(ForkResult::Child) => {
let ring = match set_up_ring(filter) {
Some(ring) => ring,
None => _exit(SKIP),
};
_exit(i32::from(submit(ring, entry) != want));
}
Err(errno) => panic!("fork failed: {errno}"),
}
}
#[test]
fn test_uring_op_1() {
for (name, flag) in UringOp::all().iter_names() {
let lower = name.to_ascii_lowercase();
assert_eq!(UringOp::from_str(name), Ok(flag), "upper {name}");
assert_eq!(UringOp::from_str(&lower), Ok(flag), "lower {name}");
}
}
#[test]
fn test_uring_op_2() {
assert_eq!(UringOp::from_str("all"), Ok(UringOp::all()));
assert_eq!(UringOp::from_str("ALL"), Ok(UringOp::all()));
assert_eq!(UringOp::from_str("aLl"), Ok(UringOp::all()));
}
#[test]
fn test_uring_op_3() {
assert_eq!(
UringOp::from_str("read,write,fsync"),
Ok(UringOp::READ | UringOp::WRITE | UringOp::FSYNC)
);
assert_eq!(
UringOp::from_str("write,read,read"),
Ok(UringOp::READ | UringOp::WRITE)
);
assert_eq!(
UringOp::from_str("nop,uring_cmd128"),
Ok(UringOp::NOP | UringOp::URING_CMD128)
);
assert_eq!(UringOp::from_str("read,all"), Ok(UringOp::all()));
}
#[test]
fn test_uring_op_4() {
assert_eq!(UringOp::from_str("bogus"), Err(Errno::EINVAL));
assert_eq!(UringOp::from_str("read,bogus"), Err(Errno::EINVAL));
assert_eq!(UringOp::from_str(""), Err(Errno::EINVAL));
assert_eq!(UringOp::from_str("read,,write"), Err(Errno::EINVAL));
assert_eq!(UringOp::from_str(" read"), Err(Errno::EINVAL));
assert_eq!(UringOp::from_str("read "), Err(Errno::EINVAL));
assert_eq!(UringOp::from_str("read_"), Err(Errno::EINVAL));
}
#[test]
fn test_uring_op_5() {
assert_eq!(
UringOp::default(),
UringOp::EPOLL_CTL | UringOp::READ | UringOp::READV | UringOp::WRITE | UringOp::WRITEV
);
}
#[test]
fn test_uring_op_6() {
assert_eq!(UringOp::NOP.bits().trailing_zeros(), 0);
assert_eq!(UringOp::READ.bits().trailing_zeros(), 22);
assert_eq!(UringOp::WRITE.bits().trailing_zeros(), 23);
assert_eq!(UringOp::URING_CMD128.bits().trailing_zeros(), 64);
assert_eq!(UringOp::all().iter().count(), 65);
}
#[test]
fn test_uring_op_7() {
macro_rules! check {
($flag:ident, $code:expr) => {
assert_eq!(
UringOp::$flag.bits().trailing_zeros(),
u32::from($code),
concat!(stringify!($flag), " opcode"),
);
};
}
check!(NOP, op::NOP);
check!(READV, op::READV);
check!(WRITEV, op::WRITEV);
check!(FSYNC, op::FSYNC);
check!(READ_FIXED, op::READ_FIXED);
check!(WRITE_FIXED, op::WRITE_FIXED);
check!(POLL_ADD, op::POLL_ADD);
check!(POLL_REMOVE, op::POLL_REMOVE);
check!(SYNC_FILE_RANGE, op::SYNC_FILE_RANGE);
check!(TIMEOUT, op::TIMEOUT);
check!(ACCEPT, op::ACCEPT);
check!(CONNECT, op::CONNECT);
check!(FALLOCATE, op::FALLOCATE);
check!(OPENAT, op::OPENAT);
check!(CLOSE, op::CLOSE);
check!(STATX, op::STATX);
check!(READ, op::READ);
check!(WRITE, op::WRITE);
check!(FADVISE, op::FADVISE);
check!(MADVISE, op::MADVISE);
check!(SEND, op::SEND);
check!(RECV, op::RECV);
check!(OPENAT2, op::OPENAT2);
check!(EPOLL_CTL, op::EPOLL_CTL);
check!(SPLICE, op::SPLICE);
check!(TEE, op::TEE);
check!(SHUTDOWN, op::SHUTDOWN);
check!(RENAMEAT, op::RENAMEAT);
check!(UNLINKAT, op::UNLINKAT);
check!(MKDIRAT, op::MKDIRAT);
check!(SYMLINKAT, op::SYMLINKAT);
check!(LINKAT, op::LINKAT);
check!(SOCKET, op::SOCKET);
}
#[test]
fn test_uring_filter_1() {
let filter = UringFilter::default();
assert!(!filter.is_empty());
assert_eq!(filter.ops, UringOp::default());
assert_eq!(filter.flags, UringSqeFlag::default());
}
#[test]
fn test_uring_filter_2() {
let mut filter = UringFilter::default();
filter.insert_ops(UringOp::FSYNC);
assert_eq!(filter.ops, UringOp::default() | UringOp::FSYNC);
filter.insert_ops(UringOp::READ);
assert_eq!(filter.ops, UringOp::default() | UringOp::FSYNC);
}
#[test]
fn test_uring_filter_3() {
let mut filter = UringFilter::default();
filter.remove_ops(UringOp::READ);
assert!(!filter.ops.contains(UringOp::READ));
assert!(filter.ops.contains(UringOp::WRITE));
filter.remove_ops(UringOp::SOCKET);
assert!(filter.ops.contains(UringOp::WRITE));
}
#[test]
fn test_uring_filter_4() {
let mut filter = UringFilter::default();
assert!(!filter.is_empty());
filter.clear_ops();
assert!(filter.is_empty());
assert_eq!(filter.ops, UringOp::empty());
}
#[test]
fn test_uring_filter_5() {
let filter = UringFilter {
ops: UringOp::empty(),
..Default::default()
};
let tres = filter.encode().expect("encode");
assert_eq!(tres.flags, 0);
assert_eq!(tres.nr_res, 1);
assert_eq!(
tres.restrictions[0].opcode,
IORING_RESTRICTION_SQE_FLAGS_ALLOWED
);
assert_eq!(tres.restrictions[0].arg(), UringSqeFlag::default().bits());
}
#[test]
fn test_uring_filter_6() {
let filter = UringFilter {
ops: UringOp::NOP,
..Default::default()
};
let tres = filter.encode().expect("encode");
assert_eq!(tres.nr_res, 2);
assert_eq!(tres.restrictions[0].opcode, IORING_RESTRICTION_SQE_OP);
assert_eq!(tres.restrictions[0].arg(), 0);
assert_eq!(
tres.restrictions[1].opcode,
IORING_RESTRICTION_SQE_FLAGS_ALLOWED
);
}
#[test]
fn test_uring_filter_7() {
let tres = UringFilter::default().encode().expect("encode");
assert_eq!(tres.flags, 0);
assert_eq!(tres.nr_res, 6);
let sqe_ops: Vec<u8> = tres.restrictions[..5].iter().map(|r| r.arg()).collect();
assert_eq!(sqe_ops, vec![1, 2, 22, 23, 29]);
for r in &tres.restrictions[..5] {
assert_eq!(r.opcode, IORING_RESTRICTION_SQE_OP);
}
assert_eq!(
tres.restrictions[5].opcode,
IORING_RESTRICTION_SQE_FLAGS_ALLOWED
);
assert_eq!(tres.restrictions[5].arg(), UringSqeFlag::default().bits());
}
#[test]
fn test_uring_filter_8() {
let filter = UringFilter {
ops: UringOp::all(),
..Default::default()
};
let tres = filter.encode().expect("encode");
assert_eq!(tres.nr_res, 66);
let sqe_ops: Vec<u8> = tres.restrictions[..65].iter().map(|r| r.arg()).collect();
let expected: Vec<u8> = (0u8..65).collect();
assert_eq!(sqe_ops, expected);
}
#[test]
fn test_uring_filter_9() {
let filter = UringFilter {
ops: UringOp::URING_CMD128,
..Default::default()
};
let tres = filter.encode().expect("encode");
assert_eq!(tres.restrictions[0].arg(), 64);
}
#[test]
fn test_uring_filter_10() {
let filter = UringFilter {
ops: UringOp::empty(),
..Default::default()
};
assert_eq!(filter.register(), Ok(()));
}
#[test]
fn test_uring_filter_11() {
if !uring_available() {
eprintln!("skipping: io_uring task restrictions unavailable");
return;
}
let filter = op_filter(UringOp::default());
let mut data = [0u8; 1];
let read = Sqe::read(-1, data.as_mut_ptr(), 1);
probe(&filter, read, -libc::EBADF);
let write = Sqe::write(-1, data.as_ptr(), 1);
probe(&filter, write, -libc::EBADF);
let nop = Sqe::nop();
probe(&filter, nop, -libc::EACCES);
let fsync = Sqe::fsync(-1);
probe(&filter, fsync, -libc::EACCES);
let openat = Sqe::openat(libc::AT_FDCWD, std::ptr::null());
probe(&filter, openat, -libc::EACCES);
}
#[test]
fn test_uring_filter_12() {
if !uring_available() {
eprintln!("skipping: io_uring task restrictions unavailable");
return;
}
let filter = op_filter(UringOp::NOP);
let mut data = [0u8; 1];
probe(&filter, Sqe::nop(), 0);
let read = Sqe::read(-1, data.as_mut_ptr(), 1);
probe(&filter, read, -libc::EACCES);
}
#[test]
fn test_uring_filter_13() {
if !uring_available() {
eprintln!("skipping: io_uring task restrictions unavailable");
return;
}
let filter = op_filter(UringOp::all());
let mut data = [0u8; 1];
probe(&filter, Sqe::nop(), 0);
let fsync = Sqe::fsync(-1);
probe(&filter, fsync, -libc::EBADF);
let read = Sqe::read(-1, data.as_mut_ptr(), 1);
probe(&filter, read, -libc::EBADF);
}
#[test]
fn test_uring_filter_14() {
if !uring_available() {
eprintln!("skipping: io_uring task restrictions unavailable");
return;
}
let filter = op_filter(UringOp::default());
let mut data = [0u8; 1];
let read = Sqe::read(-1, data.as_mut_ptr(), 1).flags(flag::ASYNC);
probe(&filter, read, -libc::EBADF);
let nop = Sqe::nop().flags(flag::ASYNC);
probe(&filter, nop, -libc::EACCES);
}
#[test]
fn test_uring_sqe_flag_1() {
for (name, flag) in UringSqeFlag::all().iter_names() {
assert_eq!(UringSqeFlag::from_str(name), Ok(flag), "upper {name}");
assert_eq!(
UringSqeFlag::from_str(&name.to_ascii_lowercase()),
Ok(flag),
"lower {name}"
);
}
assert_eq!(
UringSqeFlag::from_str("fixed_file,async"),
Ok(UringSqeFlag::FIXED_FILE | UringSqeFlag::ASYNC)
);
assert_eq!(UringSqeFlag::from_str("all"), Ok(UringSqeFlag::all()));
assert_eq!(UringSqeFlag::from_str("async,bogus"), Err(Errno::EINVAL));
assert_eq!(UringSqeFlag::from_str("bogus"), Err(Errno::EINVAL));
assert_eq!(UringSqeFlag::from_str(""), Err(Errno::EINVAL));
}
#[test]
fn test_uring_sqe_flag_2() {
assert_eq!(UringSqeFlag::default(), UringSqeFlag::ASYNC);
assert_eq!(UringSqeFlag::ASYNC.bits(), 0x10);
}
#[test]
fn test_uring_filter_15() {
let mut filter = UringFilter::default();
assert_eq!(filter.flags, UringSqeFlag::ASYNC);
filter.insert_flags(UringSqeFlag::FIXED_FILE);
assert_eq!(filter.flags, UringSqeFlag::ASYNC | UringSqeFlag::FIXED_FILE);
filter.remove_flags(UringSqeFlag::ASYNC);
assert_eq!(filter.flags, UringSqeFlag::FIXED_FILE);
filter.clear_flags();
assert!(filter.flags.is_empty());
}
#[test]
fn test_uring_filter_16() {
let mut filter = UringFilter::default();
filter.insert_flags(UringSqeFlag::FIXED_FILE);
let tres = filter.encode().expect("encode");
assert_eq!(
tres.restrictions[5].opcode,
IORING_RESTRICTION_SQE_FLAGS_ALLOWED
);
assert_eq!(
tres.restrictions[5].arg(),
(UringSqeFlag::ASYNC | UringSqeFlag::FIXED_FILE).bits()
);
}
#[test]
fn test_uring_filter_17() {
let mut filter = UringFilter::default();
filter.clear_flags();
let tres = filter.encode().expect("encode");
assert_eq!(
tres.restrictions[5].opcode,
IORING_RESTRICTION_SQE_FLAGS_ALLOWED
);
assert_eq!(tres.restrictions[5].arg(), 0);
}
#[test]
fn test_uring_filter_18() {
if !uring_available() {
eprintln!("skipping: io_uring task restrictions unavailable");
return;
}
let filter = UringFilter::default();
let mut data = [0u8; 1];
let read_async = Sqe::read(-1, data.as_mut_ptr(), 1).flags(flag::ASYNC);
probe(&filter, read_async, -libc::EBADF);
let read_fixed = Sqe::read(-1, data.as_mut_ptr(), 1).flags(flag::FIXED_FILE);
probe(&filter, read_fixed, -libc::EACCES);
}
#[test]
fn test_uring_filter_19() {
if !uring_available() {
eprintln!("skipping: io_uring task restrictions unavailable");
return;
}
let mut filter = UringFilter::default();
filter.clear_flags();
let mut data = [0u8; 1];
let read = Sqe::read(-1, data.as_mut_ptr(), 1);
probe(&filter, read, -libc::EBADF);
let read_async = Sqe::read(-1, data.as_mut_ptr(), 1).flags(flag::ASYNC);
probe(&filter, read_async, -libc::EACCES);
}
}