//! Native fixed-capacity handle table. See os-handles/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 } } #[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 enum Error { BadFd, InvalidObject, Full, IdExhausted, } impl Error { pub const fn errno(self) -> u64 { match self { Self::BadFd => 9, Self::InvalidObject => 22, Self::Full => 24, Self::IdExhausted => 75, } } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct Entry

{ handle: Handle, owner: P, object: Object, } /// Complete scalar audit state. It does not expose a mutable table entry. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Snapshot { pub next: u64, pub entries: [Option<(u64, P, Object)>; N], } pub struct Table { entries: [Option>; N], next: u64, } impl Table { pub const fn new() -> Self { Self { entries: [None; N], next: 1 } } 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 { if object.id() == 0 { return Err(Error::InvalidObject); } let slot = self.entries.iter().position(Option::is_none).ok_or(Error::Full)?; let following = self.next.checked_add(1).ok_or(Error::IdExhausted)?; let handle = Handle(self.next); self.entries[slot] = Some(Entry { handle, owner, object }); self.next = following; Ok(handle) } fn object(&self, owner: P, handle: Handle) -> Result { if handle.raw() == 0 { return Err(Error::BadFd); } self.entries.iter().flatten() .find(|entry| entry.owner == owner && entry.handle == handle) .map(|entry| entry.object).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 { match self.object(owner, handle)? { Object::File(id) => Ok(id), _ => Err(Error::BadFd), } } pub fn channel_send(&self, owner: P, handle: Handle) -> Result { match self.object(owner, handle)? { Object::Channel(id) => Ok(id), _ => Err(Error::BadFd), } } pub fn task_inspect(&self, owner: P, handle: Handle) -> Result { match self.object(owner, handle)? { Object::Task(id) => Ok(id), _ => Err(Error::BadFd), } } 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 { next: self.next, entries: self.entries.map(|entry| entry.map( |entry| (entry.handle.raw(), entry.owner, entry.object))) } } } impl Default for Table { fn default() -> Self { Self::new() } }