use core::{
num::NonZero,
ptr::NonNull,
slice,
sync::atomic::{AtomicUsize, Ordering},
};
use std::{
io,
os::fd::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
};
use fack::prelude::Error;
use crate::ffi::binding;
pub mod action;
pub mod backend;
#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
pub enum InvalidSlabSize {
#[error("exception slab size cannot be zero")]
Zero,
#[error("exception slab size is not aligned")]
Misaligned,
#[error("exception slab size exceeds the kernel limit")]
TooLarge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SlabSize(
NonZero<usize>,
);
impl SlabSize {
pub const DEFAULT: Self = Self(
NonZero::new(binding::MIRILLA_EXCEPT_DEFAULT_SLAB_SIZE as usize)
.expect("the default slab size is nonzero"),
);
#[inline]
pub const fn new(value: usize) -> Result<Self, InvalidSlabSize> {
match NonZero::new(value) {
None => Err(InvalidSlabSize::Zero),
Some(target_size) => {
let size_value = target_size.get();
let page_aligned = size_value.is_multiple_of(4096);
let record_aligned = size_value
.is_multiple_of(core::mem::size_of::<binding::mirilla_except_record>());
let within_limit = size_value <= binding::MIRILLA_EXCEPT_SLAB_SIZE_LIMIT as usize;
match (page_aligned, record_aligned, within_limit) {
(true, true, true) => Ok(Self(target_size)),
(_, _, false) => Err(InvalidSlabSize::TooLarge),
_ => Err(InvalidSlabSize::Misaligned),
}
}
}
}
#[inline]
pub const fn get(self) -> usize {
let Self(value) = self;
value.get()
}
#[inline]
pub const fn record_capacity(self) -> usize {
let Self(target_size) = self;
target_size.get() / core::mem::size_of::<binding::mirilla_except_record>()
}
}
#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
pub enum InvalidSoftSlabLimit {
#[error("exception slab limit cannot be zero")]
Zero,
#[error("exception slab limit exceeds the kernel limit")]
AboveKernelLimit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SoftSlabLimit(
NonZero<usize>,
);
impl SoftSlabLimit {
pub const DEFAULT: Self = Self(NonZero::<usize>::MIN);
#[inline]
pub const fn new(value: usize) -> Result<Self, InvalidSoftSlabLimit> {
match NonZero::new(value) {
None => Err(InvalidSoftSlabLimit::Zero),
Some(value) => match value.get() <= binding::MIRILLA_EXCEPT_SLAB_LIMIT as usize {
true => Ok(Self(value)),
false => Err(InvalidSoftSlabLimit::AboveKernelLimit),
},
}
}
#[inline]
pub const fn get(self) -> usize {
let Self(value) = self;
value.get()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExceptionId(
NonZero<binding::mirilla_except_id_t>,
);
impl ExceptionId {
#[inline]
const fn from_raw(target_id: binding::mirilla_except_id_t) -> Option<Self> {
match NonZero::new(target_id) {
Some(target_id) => Some(Self(target_id)),
None => None,
}
}
#[inline]
pub const fn get(self) -> binding::mirilla_except_id_t {
let Self(value) = self;
value.get()
}
}
#[derive(Debug, Error)]
pub enum SlabAllocationError {
#[error("exception slab allocation limit reached")]
SoftLimitReached,
#[error("exception slab mapping failed with {0}")]
#[error(source(0))]
System(
io::Error,
),
}
#[derive(Debug)]
pub struct Context(
OwnedFd,
ExceptionId,
SlabSize,
SoftSlabLimit,
AtomicUsize,
);
impl Context {
#[inline]
pub unsafe fn create(
device: BorrowedFd<'_>,
slab_size: SlabSize,
soft_limit: SoftSlabLimit,
) -> io::Result<Self> {
let mut target_id = 0 as binding::mirilla_except_id_t;
let mut target_fd = -1 as RawFd;
let target_status = unsafe {
binding::catalejo_mirilla_except_create(
device.as_raw_fd(),
slab_size.get() as binding::virtual_size_t,
&raw mut target_id,
&raw mut target_fd,
)
};
status(target_status)?;
let target_id = ExceptionId::from_raw(target_id);
let target_fd = match target_fd {
0.. => {
Some(unsafe { OwnedFd::from_raw_fd(target_fd) })
}
_ => None,
};
match (target_id, target_fd) {
(Some(target_id), Some(target_fd)) => {
let allocated_count = AtomicUsize::new(0);
Ok(Self(
target_fd,
target_id,
slab_size,
soft_limit,
allocated_count,
))
}
(_, Some(target_fd)) => {
drop(target_fd);
Err(io::Error::from(io::ErrorKind::InvalidData))
}
_ => Err(io::Error::from(io::ErrorKind::InvalidData)),
}
}
#[inline]
pub const fn id(&self) -> ExceptionId {
let &Self(_, target_id, ..) = self;
target_id
}
#[inline]
pub const fn slab_size(&self) -> SlabSize {
let &Self(_, _, slab_size, ..) = self;
slab_size
}
#[inline]
pub const fn soft_limit(&self) -> SoftSlabLimit {
let &Self(_, _, _, soft_limit, ..) = self;
soft_limit
}
#[inline]
pub fn allocated(&self) -> usize {
let Self(_, _, _, _, allocated_count) = self;
allocated_count.load(Ordering::Acquire)
}
#[inline]
pub fn map(&self) -> Result<Slab<'_>, SlabAllocationError> {
Self::reserve_slab(self)?;
let Self(target_fd, _, slab_size, ..) = self;
let mut record_list = core::ptr::null_mut();
let target_status = unsafe {
binding::catalejo_except_slab_map(
target_fd.as_raw_fd(),
slab_size.get() as binding::virtual_size_t,
&raw mut record_list,
)
};
let map_result = status(target_status)
.map_err(SlabAllocationError::System)
.and_then(|()| {
NonNull::new(record_list).ok_or_else(|| {
SlabAllocationError::System(io::Error::from(io::ErrorKind::InvalidData))
})
});
match map_result {
Ok(record_list) => Ok(Slab(self, record_list, SlabState::Editable)),
Err(target_error) => {
Self::release_slab(self);
Err(target_error)
}
}
}
fn reserve_slab(&self) -> Result<(), SlabAllocationError> {
let Self(_, _, _, soft_limit, allocated_count) = self;
let limit_count = soft_limit.get();
let update_result =
allocated_count.fetch_update(Ordering::AcqRel, Ordering::Acquire, |allocated_count| {
match allocated_count < limit_count {
true => Some(allocated_count + 1),
false => None,
}
});
match update_result {
Ok(_) => Ok(()),
Err(_) => Err(SlabAllocationError::SoftLimitReached),
}
}
fn release_slab(&self) {
let Self(_, _, _, _, allocated_count) = self;
let previous_count = allocated_count.fetch_sub(1, Ordering::AcqRel);
debug_assert!(previous_count != 0);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SlabState {
Editable,
Published,
}
#[derive(Debug)]
pub struct Slab<'context>(
&'context Context,
NonNull<binding::mirilla_except_record>,
SlabState,
);
impl Slab<'_> {
#[inline]
pub const fn is_published(&self) -> bool {
let Self(_, _, slab_state) = self;
matches!(slab_state, SlabState::Published)
}
#[inline]
pub const fn record_list(&self) -> &[binding::mirilla_except_record] {
let Self(target_context, record_list, _) = self;
let record_count = target_context.slab_size().record_capacity();
unsafe { slice::from_raw_parts(record_list.as_ptr(), record_count) }
}
#[inline]
pub const fn record_list_mut(&mut self) -> Option<&mut [binding::mirilla_except_record]> {
let Self(target_context, record_list, slab_state) = self;
let record_count = target_context.slab_size().record_capacity();
match slab_state {
SlabState::Editable => {
Some(unsafe { slice::from_raw_parts_mut(record_list.as_ptr(), record_count) })
}
SlabState::Published => None,
}
}
#[inline]
pub fn publish(&mut self) -> io::Result<()> {
let Self(target_context, record_list, slab_state) = self;
match slab_state {
SlabState::Published => Ok(()),
SlabState::Editable => {
let target_status = unsafe {
binding::catalejo_except_slab_publish(
record_list.as_ptr(),
target_context.slab_size().get() as binding::virtual_size_t,
)
};
status(target_status)?;
*slab_state = SlabState::Published;
Ok(())
}
}
}
#[inline]
pub fn edit(&mut self) -> io::Result<()> {
let Self(target_context, record_list, slab_state) = self;
match slab_state {
SlabState::Editable => Ok(()),
SlabState::Published => {
let target_status = unsafe {
binding::catalejo_except_slab_edit(
record_list.as_ptr(),
target_context.slab_size().get() as binding::virtual_size_t,
)
};
status(target_status)?;
*slab_state = SlabState::Editable;
Ok(())
}
}
}
}
impl Drop for Slab<'_> {
#[inline]
fn drop(&mut self) {
let &mut Self(target_context, record_list, _) = self;
let unmap_status = unsafe {
binding::catalejo_except_slab_unmap(
record_list.as_ptr(),
target_context.slab_size().get() as binding::virtual_size_t,
)
};
if unmap_status == 0 {
Context::release_slab(target_context);
}
}
}
fn status(target_status: core::ffi::c_int) -> io::Result<()> {
match target_status {
0 => Ok(()),
..=-1 => Err(io::Error::from_raw_os_error(target_status.saturating_abs())),
_ => Err(io::Error::from(io::ErrorKind::InvalidData)),
}
}
const _: () = {
assert!(core::mem::size_of::<binding::mirilla_except_boundary>() == 16);
assert!(core::mem::size_of::<binding::mirilla_except_predicate>() == 16);
assert!(core::mem::size_of::<binding::mirilla_except_action>() == 16);
assert!(core::mem::size_of::<binding::mirilla_except_record>() == 48);
assert!(core::mem::align_of::<binding::mirilla_except_record>() == 16);
};