//! Native owned-message channel and shared handles. See os-handles/owned/CONTRACT.md.
//! A caller must bind `P` to a live kernel process ID before invoking this
//! table. `Object` IDs are kernel-owned names, never user pointers.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Handle(u64);
impl Handle {
pub const fn from_raw(raw: u64) -> Self { Self(raw) }
pub const fn raw(self) -> u64 { self.0 }
pub const fn slot(self) -> u32 { self.0 as u32 }
pub const fn generation(self) -> u32 { (self.0 >> 32) as u32 }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Object {
File(u64),
Channel(u64),
Task(u64),
}
impl Object {
const fn id(self) -> u64 {
match self {
Self::File(id) | Self::Channel(id) | Self::Task(id) => id,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Rights(u8);
impl Rights {
pub const READ: Self = Self(1);
pub const WRITE: Self = Self(2);
pub const READ_WRITE: Self = Self(3);
pub const fn contains(self, required: Self) -> bool {
self.0 & required.0 == required.0
}
pub const fn is_nonempty_subset_of(self, original: Self) -> bool {
self.0 != 0 && original.contains(self)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
BadFd,
Access,
InvalidObject,
Full,
IdExhausted,
MessageTooLarge,
WouldBlock,
Fault,
}
impl Error {
pub const fn errno(self) -> u64 {
match self {
Self::BadFd => 9,
Self::Access => 13,
Self::InvalidObject => 22,
Self::Full => 24,
Self::IdExhausted => 75,
Self::MessageTooLarge => 90,
Self::WouldBlock => 11,
Self::Fault => 14,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Entry
{
handle: Handle,
owner: P,
object: Object,
rights: Rights,
}
/// Complete scalar audit state. It does not expose a mutable table entry.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Snapshot
{
pub generations: [u32; N],
pub entries: [Option<(u64, P, Object, Rights)>; N],
}
pub struct Table {
entries: [Option>; N],
generations: [u32; N],
}
impl Table {
pub const fn new() -> Self {
Self { entries: [None; N], generations: [0; N] }
}
/// Kernel-only deterministic seed for an isolated overflow probe. A user
/// syscall adapter must never take the seed from caller input.
pub const fn with_generation_seed(generations: [u32; N]) -> Self {
Self { entries: [None; N], generations }
}
pub fn len(&self) -> usize {
self.entries.iter().filter(|entry| entry.is_some()).count()
}
pub fn owned(&self, owner: P) -> usize {
self.entries.iter().flatten().filter(|entry| entry.owner == owner).count()
}
/// Validate all fallible conditions before publishing the entry.
pub fn open(&mut self, owner: P, object: Object) -> Result {
let rights = match object {
Object::File(_) => Rights::READ_WRITE,
Object::Channel(_) => Rights::WRITE,
Object::Task(_) => Rights::READ,
};
self.open_with_rights(owner, object, rights)
}
pub fn open_with_rights(&mut self, owner: P, object: Object,
rights: Rights) -> Result {
if object.id() == 0 { return Err(Error::InvalidObject); }
let allowed = match object {
Object::File(_) => Rights::READ_WRITE,
Object::Channel(_) => Rights::WRITE,
Object::Task(_) => Rights::READ,
};
if !rights.is_nonempty_subset_of(allowed) {
return Err(Error::Access);
}
let slot = self.entries.iter().position(Option::is_none).ok_or(Error::Full)?;
let slot_id = u32::try_from(slot).ok()
.and_then(|value| value.checked_add(1)).ok_or(Error::IdExhausted)?;
let generation = self.generations[slot]
.checked_add(1).ok_or(Error::IdExhausted)?;
let handle = Handle((u64::from(generation) << 32) | u64::from(slot_id));
self.entries[slot] = Some(Entry { handle, owner, object, rights });
self.generations[slot] = generation;
Ok(handle)
}
fn entry(&self, owner: P, handle: Handle) -> Result, Error> {
let slot = handle.slot().checked_sub(1)
.and_then(|value| usize::try_from(value).ok()).ok_or(Error::BadFd)?;
self.entries.get(slot).copied().flatten()
.filter(|entry| entry.owner == owner && entry.handle == handle)
.ok_or(Error::BadFd)
}
/// A File handle cannot be used through the Channel or Task API.
pub fn file_read(&self, owner: P, handle: Handle) -> Result {
let entry = self.entry(owner, handle)?;
match entry.object {
Object::File(id) if entry.rights.contains(Rights::READ) => Ok(id),
Object::File(_) => Err(Error::Access),
_ => Err(Error::BadFd),
}
}
pub fn file_write(&self, owner: P, handle: Handle) -> Result {
let entry = self.entry(owner, handle)?;
match entry.object {
Object::File(id) if entry.rights.contains(Rights::WRITE) => Ok(id),
Object::File(_) => Err(Error::Access),
_ => Err(Error::BadFd),
}
}
pub fn channel_send(&self, owner: P, handle: Handle) -> Result {
let entry = self.entry(owner, handle)?;
match entry.object {
Object::Channel(id) if entry.rights.contains(Rights::WRITE) => Ok(id),
Object::Channel(_) => Err(Error::Access),
_ => Err(Error::BadFd),
}
}
pub fn task_inspect(&self, owner: P, handle: Handle) -> Result {
let entry = self.entry(owner, handle)?;
match entry.object {
Object::Task(id) if entry.rights.contains(Rights::READ) => Ok(id),
Object::Task(_) => Err(Error::Access),
_ => Err(Error::BadFd),
}
}
/// Atomically publish a second reference only with a rights subset.
pub fn duplicate(&mut self, owner: P, source: Handle,
requested: Rights) -> Result {
let original = self.entry(owner, source)?;
if !requested.is_nonempty_subset_of(original.rights) {
return Err(Error::Access);
}
let slot = self.entries.iter().position(Option::is_none).ok_or(Error::Full)?;
let slot_id = u32::try_from(slot).ok()
.and_then(|value| value.checked_add(1)).ok_or(Error::IdExhausted)?;
let generation = self.generations[slot]
.checked_add(1).ok_or(Error::IdExhausted)?;
let handle = Handle((u64::from(generation) << 32) | u64::from(slot_id));
self.entries[slot] = Some(Entry {handle, owner,
object: original.object, rights: requested});
self.generations[slot] = generation;
Ok(handle)
}
pub fn close(&mut self, owner: P, handle: Handle) -> Result<(), Error> {
let slot = self.entries.iter().position(|entry| entry.is_some_and(
|entry| entry.owner == owner && entry.handle == handle))
.ok_or(Error::BadFd)?;
self.entries[slot] = None;
Ok(())
}
pub fn close_owner(&mut self, owner: P) -> usize {
let mut closed = 0;
for entry in &mut self.entries {
if entry.is_some_and(|entry| entry.owner == owner) {
*entry = None;
closed += 1;
}
}
closed
}
pub fn snapshot(&self) -> Snapshot {
Snapshot { generations: self.generations,
entries: self.entries.map(|entry| entry.map(
|entry| (entry.handle.raw(), entry.owner, entry.object, entry.rights))) }
}
}
impl Default for Table {
fn default() -> Self { Self::new() }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WaitToken(u64);
impl WaitToken {
pub const fn from_raw(raw: u64) -> Self { Self(raw) }
pub const fn raw(self) -> u64 { self.0 }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum WaitResult { Pending, Closed }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CloseResult { Remaining(u32), LastClosed }
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct SharedEntry
{
handle: Handle,
owner: P,
resource: usize,
rights: Rights,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct ResourceRecord {
object: Object,
refs: u32,
closed: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
struct Watcher
{
token: WaitToken,
owner: P,
resource: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SharedSnapshot
{
pub generations: [u32; N],
pub entries: [Option<(u64, P, usize, Rights)>; N],
pub resources: [Option<(Object, u32, bool)>; N],
pub watchers: [Option<(u64, P, usize)>; N],
pub next_wait: u64,
}
pub struct SharedObjects {
entries: [Option>; N],
generations: [u32; N],
resources: [Option; N],
watchers: [Option>; N],
next_wait: u64,
}
impl SharedObjects {
pub const fn new() -> Self {
Self {entries: [None; N], generations: [0; N],
resources: [None; N], watchers: [None; N], next_wait: 1}
}
pub fn handles(&self) -> usize {
self.entries.iter().flatten().count()
}
pub fn owned(&self, owner: P) -> usize {
self.entries.iter().flatten().filter(|entry| entry.owner == owner).count()
}
pub fn live_resources(&self) -> usize {
self.resources.iter().flatten().filter(|item| item.refs > 0).count()
}
fn next_handle(&self, slot: usize) -> Result<(Handle, u32), Error> {
let slot_id = u32::try_from(slot).ok()
.and_then(|value| value.checked_add(1)).ok_or(Error::IdExhausted)?;
let generation = self.generations[slot]
.checked_add(1).ok_or(Error::IdExhausted)?;
Ok((Handle((u64::from(generation) << 32) | u64::from(slot_id)),
generation))
}
fn lookup(&self, owner: P, handle: Handle) -> Result, Error> {
let slot = handle.slot().checked_sub(1)
.and_then(|value| usize::try_from(value).ok()).ok_or(Error::BadFd)?;
self.entries.get(slot).copied().flatten()
.filter(|entry| entry.owner == owner && entry.handle == handle)
.ok_or(Error::BadFd)
}
/// The caller must obtain the new object and owner from kernel authority.
pub fn open_kernel(&mut self, owner: P, object: Object,
rights: Rights) -> Result {
if object.id() == 0 { return Err(Error::InvalidObject); }
let allowed = match object {
Object::File(_) => Rights::READ_WRITE,
Object::Channel(_) => Rights::READ_WRITE,
Object::Task(_) => Rights::READ,
};
if !rights.is_nonempty_subset_of(allowed) { return Err(Error::Access); }
if self.resources.iter().flatten().any(|item| item.object == object) {
return Err(Error::InvalidObject);
}
let handle_slot = self.entries.iter().position(Option::is_none)
.ok_or(Error::Full)?;
let resource_slot = self.resources.iter().position(Option::is_none)
.ok_or(Error::Full)?;
let (handle, generation) = self.next_handle(handle_slot)?;
self.resources[resource_slot] = Some(ResourceRecord
{object, refs: 1, closed: false});
self.entries[handle_slot] = Some(SharedEntry
{handle, owner, resource: resource_slot, rights});
self.generations[handle_slot] = generation;
Ok(handle)
}
/// Kernel-mediated second reference for another live process generation.
pub fn grant_kernel(&mut self, owner: P, recipient: P, source: Handle,
requested: Rights) -> Result {
let source = self.lookup(owner, source)?;
if recipient == owner || !requested.is_nonempty_subset_of(source.rights) {
return Err(Error::Access);
}
let resource = self.resources[source.resource].ok_or(Error::BadFd)?;
if resource.closed || resource.refs == 0 { return Err(Error::BadFd); }
let following = resource.refs.checked_add(1).ok_or(Error::IdExhausted)?;
let slot = self.entries.iter().position(Option::is_none).ok_or(Error::Full)?;
let (handle, generation) = self.next_handle(slot)?;
self.entries[slot] = Some(SharedEntry
{handle, owner: recipient, resource: source.resource, rights: requested});
self.resources[source.resource] = Some(ResourceRecord {refs: following, ..resource});
self.generations[slot] = generation;
Ok(handle)
}
pub fn query(&self, owner: P, handle: Handle) -> Result