#![cfg_attr(feature = "nightly", feature(async_iterator, cfg_sanitize))]
#[cfg(not(any(
target_os = "android",
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "linux",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "tvos",
target_os = "visionos",
target_os = "watchos",
)))]
compile_error!("OS not supported");
use std::fmt;
use std::sync::atomic::{AtomicU8, Ordering};
use std::time::Duration;
pub mod fd;
mod asan;
mod config;
mod msan;
mod op;
#[cfg(unix)]
mod unix;
pub mod extract;
pub mod fs;
pub mod io;
pub mod mem;
pub mod net;
pub mod pipe;
pub mod poll;
pub mod process;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod io_uring;
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "tvos",
target_os = "visionos",
target_os = "watchos",
))]
mod kqueue;
#[cfg(any(target_os = "android", target_os = "linux"))]
mod inotify;
mod sys {
#[cfg(any(target_os = "android", target_os = "linux"))]
pub(crate) use crate::io_uring::*;
#[cfg(any(
target_os = "dragonfly",
target_os = "freebsd",
target_os = "ios",
target_os = "macos",
target_os = "netbsd",
target_os = "openbsd",
target_os = "tvos",
target_os = "visionos",
target_os = "watchos",
))]
pub(crate) use crate::kqueue::*;
#[cfg(any(target_os = "android", target_os = "linux"))]
pub(crate) use crate::inotify as fs_notify;
}
#[doc(inline)]
pub use config::Config;
#[doc(no_inline)]
pub use extract::Extract;
#[doc(no_inline)]
pub use fd::AsyncFd;
#[derive(Debug)]
pub struct Ring {
cq: sys::Completions,
sq: sys::Submissions,
}
impl Ring {
pub const fn config<'r>() -> Config<'r> {
Config {
sys: crate::sys::Config::new(),
}
}
#[doc(alias = "io_uring_setup")]
#[doc(alias = "kqueue")]
pub fn new() -> io::Result<Ring> {
Ring::config().build()
}
pub fn sq(&self) -> SubmissionQueue {
SubmissionQueue(self.sq.clone())
}
#[doc(alias = "io_uring_enter")]
#[doc(alias = "kevent")]
pub fn poll(&mut self, timeout: Option<Duration>) -> io::Result<()> {
self.cq.poll(self.sq.shared(), timeout)
}
pub fn pollable(&self, sq: SubmissionQueue) -> poll::Pollable {
assert!(
self.sq != sq.0,
"can't wait on pollable with sq of the same ring"
);
let state = poll::PollableState::new(self.sq());
poll::Pollable::new(sq, state, ())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[repr(transparent)]
pub struct SubmissionQueue(sys::Submissions);
impl SubmissionQueue {
pub fn wake(&self) {
if let Err(err) = self.0.wake() {
log::warn!("failed to wake a10::Ring: {err}");
}
}
pub(crate) fn submissions(&self) -> &sys::Submissions {
&self.0
}
#[cfg(any(target_os = "android", target_os = "linux"))]
pub(crate) fn from_ref<'a>(submissions: &'a sys::Submissions) -> &'a SubmissionQueue {
unsafe { &*std::ptr::from_ref(submissions).cast::<SubmissionQueue>() }
}
pub(crate) const fn sq(&self) -> &SubmissionQueue {
self
}
}
macro_rules! syscall {
($fn: ident ( $($arg: expr),* $(,)? ) ) => {{
#[allow(unused_unsafe)]
let res = unsafe { libc::$fn($( $arg, )*) };
if res == -1 {
::std::result::Result::Err(::std::io::Error::last_os_error())
} else {
::std::result::Result::Ok(res)
}
}};
}
#[rustfmt::skip]
macro_rules! man_link {
($syscall: tt ( $section: tt ) ) => {
concat!(
"\n\nAdditional documentation can be found in the ",
"[`", stringify!($syscall), "(", stringify!($section), ")`]",
"(https://man7.org/linux/man-pages/man", stringify!($section), "/", stringify!($syscall), ".", stringify!($section), ".html)",
" manual.\n"
)
};
}
macro_rules! new_flag {
(
$(
$(#[$type_meta:meta])*
$type_vis: vis struct $type_name: ident ( $type_repr: ty ) $( impl BitOr $( $type_or: ty )* )? {
$(
$(#[$value_meta:meta])*
$value_name: ident = $libc: ident :: $value_type: ident,
)*
}
)+
) => {
$(
$(#[$type_meta])*
#[derive(Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
$type_vis struct $type_name(pub(crate) $type_repr);
impl $type_name {
$(
$(#[$value_meta])*
#[allow(trivial_numeric_casts, clippy::cast_sign_loss)]
$type_vis const $value_name: $type_name = $type_name($libc::$value_type as $type_repr);
)*
#[allow(unused_doc_comments, dead_code)]
pub(crate) const ALL_VALUES: &[$type_name] = &[
$(
$(#[$value_meta])*
$type_name::$value_name,
)*
];
}
$crate::debug_detail!(impl match for $type_name($type_repr), $( $(#[$value_meta])* $libc::$value_type ),*);
$(
impl std::ops::BitOr for $type_name {
type Output = Self;
fn bitor(self, rhs: Self) -> Self::Output {
$type_name(self.0 | rhs.0)
}
}
$(
impl std::ops::BitOr<$type_or> for $type_name {
type Output = Self;
#[allow(clippy::cast_sign_loss)]
fn bitor(self, rhs: $type_or) -> Self::Output {
$type_name(self.0 | rhs as $type_repr)
}
}
)*
)?
)+
};
}
macro_rules! debug_detail {
(
// Match a value exactly.
match $type: ident ($event_type: ty),
$( $( #[$meta: meta] )* $libc: ident :: $flag: ident ),+ $(,)?
) => {
#[repr(transparent)]
struct $type($event_type);
impl $type {
#[allow(dead_code)]
fn from_ref(value: &$event_type) -> &$type {
unsafe { &*::std::ptr::from_ref(value).cast::<$type>() }
}
}
$crate::debug_detail!(impl match for $type($event_type), $( $(#[$meta])* $libc::$flag ),*);
};
(
impl match for $type: ident ($type_repr: ty),
$( $( #[$meta: meta] )* $libc: ident :: $flag: ident ),* $(,)?
) => {
impl ::std::fmt::Debug for $type {
#[allow(trivial_numeric_casts, unreachable_patterns, unreachable_code, unused_doc_comments, clippy::bad_bit_mask)]
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
$(
$(#[$meta])*
pub(super) const $flag: $type_repr = $libc :: $flag as $type_repr;
)*
f.write_str(match self.0 {
$(
$(#[$meta])*
$flag => stringify!($flag),
)*
value => return value.fmt(f),
})
}
}
};
(
bitset $type: ident ($event_type: ty),
$( $( #[$meta: meta] )* $libc: ident :: $flag: ident ),+ $(,)?
) => {
#[repr(transparent)]
struct $type($event_type);
impl $type {
#[allow(dead_code)]
fn from_ref(value: &$event_type) -> &$type {
unsafe { &*::std::ptr::from_ref(value).cast::<$type>() }
}
}
$crate::debug_detail!(impl bitset for $type($event_type), $( $(#[$meta])* $libc::$flag ),*);
};
(
impl bitset for $type: ident ($event_type: ty),
$( $( #[$meta: meta] )* $libc: ident :: $flag: ident ),+ $(,)?
) => {
impl ::std::fmt::Debug for $type {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
#[allow(unused_mut)]
let mut written_one = false;
$(
$(#[$meta])*
#[allow(clippy::bad_bit_mask)] {
if self.0 & $libc :: $flag != 0 {
if !written_one {
write!(f, "{}", stringify!($flag))?;
written_one = true;
} else {
write!(f, "|{}", stringify!($flag))?;
}
}
}
)+
if !written_one {
write!(f, "(empty)")
} else {
Ok(())
}
}
}
};
}
use {debug_detail, man_link, new_flag, syscall};
fn lock<'a, T>(mutex: &'a std::sync::Mutex<T>) -> std::sync::MutexGuard<'a, T> {
match mutex.lock() {
Ok(guard) => guard,
Err(err) => {
mutex.clear_poison();
err.into_inner()
}
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn try_lock<'a, T>(mutex: &'a std::sync::Mutex<T>) -> Option<std::sync::MutexGuard<'a, T>> {
match mutex.try_lock() {
Ok(guard) => Some(guard),
Err(std::sync::TryLockError::Poisoned(err)) => {
mutex.clear_poison();
Some(err.into_inner())
}
Err(std::sync::TryLockError::WouldBlock) => None,
}
}
#[cfg(any(target_os = "android", target_os = "linux"))]
fn get_mut<'a, T>(mutex: &'a mut std::sync::Mutex<T>) -> &'a mut T {
match mutex.get_mut() {
Ok(guard) => guard,
Err(err) => err.into_inner(),
}
}
#[allow(unused)]
trait OpPollResult<T> {
fn from_ok(ok: T) -> Self;
fn from_err(err: io::Error) -> Self;
fn from_res(res: io::Result<T>) -> Self;
fn done() -> Self;
}
impl<T> OpPollResult<T> for io::Result<T> {
fn from_ok(ok: T) -> Self {
Ok(ok)
}
fn from_err(err: io::Error) -> Self {
Err(err)
}
fn from_res(res: io::Result<T>) -> Self {
res
}
fn done() -> Self {
unreachable!()
}
}
impl<T> OpPollResult<T> for Option<io::Result<T>> {
fn from_ok(ok: T) -> Self {
Some(Ok(ok))
}
fn from_err(err: io::Error) -> Self {
Some(Err(err))
}
fn from_res(res: io::Result<T>) -> Self {
Some(res)
}
fn done() -> Self {
None
}
}
pub(crate) struct PollingState(AtomicU8);
const IS_POLLING: u8 = 0b01;
#[allow(unused)] const NOT_POLLING: u8 = 0b00;
const IS_AWOKEN: u8 = 0b10;
const NOT_AWOKEN: u8 = 0b00;
impl PollingState {
pub(crate) const fn new() -> PollingState {
PollingState(AtomicU8::new(0))
}
#[allow(clippy::cast_lossless)]
pub(crate) fn set_polling(&self, is_polling: bool) -> bool {
const _BOOL_CAST_CHECK_TRUE: () = assert!(true as u8 == IS_POLLING);
const _BOOL_CAST_CHECK_FALSE: () = assert!(false as u8 == NOT_POLLING);
let state = self.0.swap(is_polling as u8 | NOT_AWOKEN, Ordering::AcqRel);
(state & IS_AWOKEN) != 0
}
pub(crate) fn wake(&self) -> bool {
let state = self.0.fetch_or(IS_AWOKEN, Ordering::AcqRel);
state == (IS_POLLING | NOT_AWOKEN)
}
}
impl fmt::Debug for PollingState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = self.0.load(Ordering::Relaxed);
f.debug_struct("PollingState")
.field("polling", &((state & IS_POLLING) != 0))
.field("awoken", &((state & IS_AWOKEN) != 0))
.finish()
}
}