blob: 12ab5c2716cbf2ce52d7448a688669c8c8277c4e [file]
// Copyright 2026 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
use super::dispatcher::{Dispatcher, DispatcherOps};
use super::handle::{HandleOwner, HandleRef, HandleValue};
use super::handle_table_ffi::{
cpp_handle_table_add_handle_locked, cpp_handle_table_for_each_handle_batched,
cpp_handle_table_for_each_handle_locked, cpp_handle_table_get_handle_locked,
cpp_handle_table_handle_count, cpp_handle_table_handle_count_locked, cpp_handle_table_koid,
cpp_handle_table_lock, cpp_handle_table_map_handle_to_value,
cpp_handle_table_remove_handle_locked,
};
use super::process_dispatcher::ProcessDispatcher;
use crate::kernel::thread::AutoExpiringPreemptDisabler;
use core::convert::Infallible;
use core::ffi::c_void;
use core::fmt::{self, Debug, Formatter};
use core::mem::size_of;
use core::ptr::{self, NonNull};
use fbl::{Array, HasRefCount, Recyclable, RefPtr};
use ksync::{BrwLockPi, BrwLockPiReadGuard, BrwLockPiWriteGuard, LockClass, LockToken};
use pin_init::PinInit;
use zr::OpaqueFacade;
use zx_status::Status;
use zx_types::{
ZX_HANDLE_INVALID, ZX_OBJ_TYPE_NONE, ZX_RIGHT_NONE, zx_handle_t, zx_info_handle_extended_t,
zx_koid_t, zx_rights_t, zx_status_t,
};
/// Lock class tag for the handle table's reader-writer lock.
#[derive(Debug, Default, Copy, Clone)]
pub struct HandleTableLockClass;
impl LockClass for HandleTableLockClass {
const ID: *mut c_void = ptr::null_mut();
}
/// Facade for the C++ `HandleTable` class.
#[repr(C)]
pub struct HandleTable {
_facade: OpaqueFacade,
}
zr::static_assert!(size_of::<HandleTable>() == 0);
impl Debug for HandleTable {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("HandleTable").field("koid", &self.get_koid()).finish()
}
}
/// A mutable entry for a handle in a [`HandleTable`] while holding the handle table's write lock.
///
/// Holding this entry exclusively borrows the write lock token for `'a`, preventing any other
/// [`HandleRef`] or table mutation from coexisting until the entry is dropped or consumed by
/// [`HandleEntry::remove`].
pub struct HandleEntry<'a> {
table: &'a HandleTable,
ptr: NonNull<c_void>,
_marker: core::marker::PhantomData<&'a mut LockToken<'a, HandleTableLockClass>>,
}
impl<'a> HandleEntry<'a> {
/// Borrows this entry as a [`HandleRef`] tied to the borrow of `self`.
#[inline]
pub fn as_ref(&self) -> HandleRef<'_> {
// SAFETY: `self.ptr` is a valid handle pointer in `self.table` and the handle table write
// lock is held for the lifetime of `&self`.
unsafe { HandleRef::from_raw(self.ptr) }
}
/// Removes the handle from the handle table and returns the owned handle.
#[inline]
pub fn remove(self) -> HandleOwner {
// SAFETY: `self.table` is valid, the handle table write lock is exclusively held via
// `self`, and `self.ptr` is a valid handle in `self.table` looked up under the same lock.
let raw = unsafe {
cpp_handle_table_remove_handle_locked(self.table.as_ffi_mut(), self.ptr.as_ptr())
};
// SAFETY: `cpp_handle_table_remove_handle_locked` returns the non-null owned handle pointer
// corresponding to `self.ptr`.
unsafe { HandleOwner::from_raw_non_null(NonNull::new_unchecked(raw)) }
}
}
impl HandleTable {
/// Returns a `*const HandleTable` pointer suitable for passing to FFI routines.
#[inline]
pub fn as_ffi(&self) -> *const Self {
self as *const Self
}
/// Returns a `*mut HandleTable` pointer suitable for passing to FFI routines that
/// take a non-const `HandleTable*`.
#[inline]
pub fn as_ffi_mut(&self) -> *mut Self {
self.as_ffi() as *mut Self
}
/// Returns a reference to the handle table's priority-inheriting reader-writer lock.
#[inline]
fn lock(&self) -> &BrwLockPi<HandleTableLockClass> {
// SAFETY: `self` is a valid `HandleTable`, and its lock is a valid `BrwLockPi`.
unsafe {
let lock_ptr = cpp_handle_table_lock(self.as_ffi());
&*(lock_ptr as *const BrwLockPi<HandleTableLockClass>)
}
}
/// Acquires a reader lock on this handle table.
#[inline]
pub fn read_lock(
&self,
) -> impl PinInit<BrwLockPiReadGuard<'_, HandleTableLockClass>, Infallible> {
self.lock().read_lock()
}
/// Acquires a writer lock on this handle table.
#[inline]
pub fn write_lock(
&self,
) -> impl PinInit<BrwLockPiWriteGuard<'_, HandleTableLockClass>, Infallible> {
self.lock().write_lock()
}
/// Returns the KOID of this handle table.
#[inline]
pub fn get_koid(&self) -> zx_koid_t {
// SAFETY: `self` is a valid `HandleTable` reference.
unsafe { cpp_handle_table_koid(self.as_ffi()) }
}
/// Returns the number of outstanding handles in this handle table.
#[inline]
pub fn handle_count(&self) -> u32 {
// SAFETY: `self` is a valid `HandleTable` reference.
unsafe { cpp_handle_table_handle_count(self.as_ffi()) }
}
/// Returns the number of outstanding handles in this handle table while holding its lock.
#[inline]
pub fn handle_count_locked(&self, _token: &LockToken<'_, HandleTableLockClass>) -> u32 {
// SAFETY: `self` is a valid `HandleTable` reference and the handle table lock is held.
unsafe { cpp_handle_table_handle_count_locked(self.as_ffi()) }
}
/// Maps a handle to its user-visible `HandleValue` for this handle table.
#[inline]
pub fn map_handle_to_value(&self, handle: HandleRef<'_>) -> HandleValue {
// SAFETY: `self` is a valid `HandleTable` and `handle` is a valid `HandleRef`.
let raw = unsafe { cpp_handle_table_map_handle_to_value(self.as_ffi(), handle.as_ptr()) };
HandleValue::new(raw)
}
/// Retrieves a handle reference while holding the handle table lock.
#[inline]
pub fn get_handle_locked<'a>(
&'a self,
token: &'a LockToken<'_, HandleTableLockClass>,
caller: &ProcessDispatcher,
handle_value: HandleValue,
) -> Option<HandleRef<'a>> {
self.get_handle_locked_opt(token, Some(caller), handle_value)
}
/// Same as [`Self::get_handle_locked`], except `caller` can be `None` to skip
/// `ZX_POL_BAD_HANDLE` policy enforcement.
#[inline]
fn get_handle_locked_opt<'a>(
&'a self,
_token: &'a LockToken<'_, HandleTableLockClass>,
caller: Option<&ProcessDispatcher>,
handle_value: HandleValue,
) -> Option<HandleRef<'a>> {
let caller_ptr = caller.map_or(ptr::null_mut(), ProcessDispatcher::as_ffi_mut);
// SAFETY: `self` is valid, `caller_ptr` is null or a valid `ProcessDispatcher`, and the
// handle table lock is held for `'a`.
let ptr = unsafe {
cpp_handle_table_get_handle_locked(
self.as_ffi_mut(),
caller_ptr,
handle_value.raw_value(),
)
};
// SAFETY: `ptr` is a valid handle pointer in `self` while the lock is held.
NonNull::new(ptr).map(|ptr| unsafe { HandleRef::from_raw(ptr) })
}
/// Retrieves a mutable handle entry while holding the handle table write lock.
#[inline]
pub fn get_handle_entry_locked<'a>(
&'a self,
token: &'a mut LockToken<'_, HandleTableLockClass>,
caller: &ProcessDispatcher,
handle_value: HandleValue,
) -> Option<HandleEntry<'a>> {
let ptr = self.get_handle_locked(token, caller, handle_value)?.as_non_null();
Some(HandleEntry { table: self, ptr, _marker: core::marker::PhantomData })
}
/// Adds an owned handle to the handle table while holding the write lock.
#[inline]
pub fn add_handle_locked(
&self,
_token: &mut LockToken<'_, HandleTableLockClass>,
handle: HandleOwner,
) {
// SAFETY: `self` is valid, the handle table write lock is held, and `handle` ownership is
// transferred to C++.
unsafe {
cpp_handle_table_add_handle_locked(self.as_ffi_mut(), handle.release());
}
}
/// Removes a handle by value from the handle table while holding the write lock.
#[inline]
pub fn remove_handle_locked(
&self,
token: &mut LockToken<'_, HandleTableLockClass>,
caller: &ProcessDispatcher,
handle_value: HandleValue,
) -> Option<HandleOwner> {
self.get_handle_entry_locked(token, caller, handle_value).map(HandleEntry::remove)
}
/// Adds an owned handle to this handle table.
pub fn add_handle(&self, handle: HandleOwner) {
let _preempt_disable = AutoExpiringPreemptDisabler::with_default_timeslice_extension();
ksync::lock!(let mut guard = self.write_lock());
self.add_handle_locked(guard.as_mut().token_mut(), handle);
}
/// Removes a handle from this handle table and returns the owned handle if found.
pub fn remove_handle(
&self,
caller: &ProcessDispatcher,
handle: HandleValue,
) -> Option<HandleOwner> {
let _preempt_disable = AutoExpiringPreemptDisabler::with_default_timeslice_extension();
ksync::lock!(let mut guard = self.write_lock());
self.remove_handle_locked(guard.as_mut().token_mut(), caller, handle)
}
/// Removes a slice of raw handle values from this handle table.
///
/// Matching C++ `HandleTable::RemoveHandles(ProcessDispatcher&, ktl::span<const zx_handle_t>)`,
/// `ZX_HANDLE_INVALID` entries are skipped, and if any non-zero handle is missing, returns
/// `ZX_ERR_BAD_HANDLE` after processing the remaining handles.
#[inline]
pub fn remove_handles(
&self,
caller: &ProcessDispatcher,
handles: &[zx_handle_t],
) -> Result<(), Status> {
let mut status = Ok(());
let _preempt_disable = AutoExpiringPreemptDisabler::with_default_timeslice_extension();
ksync::lock!(let mut guard = self.write_lock());
for &handle in handles {
if handle != ZX_HANDLE_INVALID
&& self
.remove_handle_locked(
guard.as_mut().token_mut(),
caller,
HandleValue::new(handle),
)
.is_none()
{
status = Err(Status::BAD_HANDLE);
}
}
status
}
/// Get the dispatcher corresponding to this handle value, after checking that this handle has
/// the desired rights.
/// `ZX_ERR_WRONG_TYPE` is returned before `ZX_ERR_ACCESS_DENIED`, because if the wrong handle
/// was passed, evaluating its rights does not have much meaning and also this aids in
/// debugging.
/// If successful, returns the dispatcher and the rights the handle currently has.
/// If `caller` is `None`, `ZX_POL_BAD_HANDLE` will not be enforced.
#[inline]
fn get_dispatcher_with_rights_impl<T>(
&self,
caller: Option<&ProcessDispatcher>,
handle_value: HandleValue,
desired_rights: zx_rights_t,
) -> Result<(RefPtr<T>, zx_rights_t), Status>
where
T: DispatcherOps + HasRefCount + Recyclable,
{
let (has_desired_rights, rights, generic_dispatcher) = {
// Scope utilized to reduce lock duration.
ksync::lock!(let guard = self.read_lock());
let handle = self
.get_handle_locked_opt(guard.token(), caller, handle_value)
.ok_or(Status::BAD_HANDLE)?;
(handle.has_rights(desired_rights), handle.rights(), handle.dispatcher())
};
// Wrong type takes precedence over access denied.
if T::TYPE != ZX_OBJ_TYPE_NONE && generic_dispatcher.get_type() != T::TYPE {
return Err(Status::WRONG_TYPE);
}
if !has_desired_rights {
return Err(Status::ACCESS_DENIED);
}
// SAFETY: We verified the type of the dispatcher matches `T::TYPE`, so it is safe to cast.
Ok((unsafe { generic_dispatcher.cast::<T>() }, rights))
}
/// Resolves a handle to a dispatcher of type `T` without requiring any rights.
///
/// # Errors
///
/// - `ZX_ERR_BAD_HANDLE` if `handle_value` is not valid.
/// - `ZX_ERR_WRONG_TYPE` if the dispatcher's type does not match `T::TYPE`.
#[inline]
pub fn get_dispatcher<T>(
&self,
caller: &ProcessDispatcher,
handle_value: HandleValue,
) -> Result<RefPtr<T>, Status>
where
T: DispatcherOps + HasRefCount + Recyclable,
{
self.get_dispatcher_with_rights::<T>(caller, handle_value, ZX_RIGHT_NONE)
}
/// Resolves a handle to a dispatcher of type `T` and returns its associated rights.
///
/// # Errors
///
/// - `ZX_ERR_BAD_HANDLE` if `handle_value` is not valid.
/// - `ZX_ERR_WRONG_TYPE` if the dispatcher's type does not match `T::TYPE`.
#[inline]
pub fn get_dispatcher_and_rights<T>(
&self,
caller: &ProcessDispatcher,
handle_value: HandleValue,
) -> Result<(RefPtr<T>, zx_rights_t), Status>
where
T: DispatcherOps + HasRefCount + Recyclable,
{
self.get_dispatcher_with_rights_impl::<T>(Some(caller), handle_value, ZX_RIGHT_NONE)
}
/// Resolves a handle to a dispatcher of type `T` with the required `rights` and returns its
/// actual rights.
///
/// # Errors
///
/// - `ZX_ERR_BAD_HANDLE` if `handle_value` is not valid.
/// - `ZX_ERR_WRONG_TYPE` if the dispatcher's type does not match `T::TYPE`.
/// - `ZX_ERR_ACCESS_DENIED` if `handle_value` lacks the requested `rights`.
#[inline]
pub fn get_dispatcher_with_rights_and_actual<T>(
&self,
caller: &ProcessDispatcher,
handle_value: HandleValue,
rights: zx_rights_t,
) -> Result<(RefPtr<T>, zx_rights_t), Status>
where
T: DispatcherOps + HasRefCount + Recyclable,
{
self.get_dispatcher_with_rights_impl::<T>(Some(caller), handle_value, rights)
}
/// Resolves a handle to a dispatcher of type `T` with the required `rights` in this handle
/// table.
///
/// # Errors
///
/// - `ZX_ERR_BAD_HANDLE` if `handle_value` is not valid.
/// - `ZX_ERR_WRONG_TYPE` if the dispatcher's type does not match `T::TYPE`.
/// - `ZX_ERR_ACCESS_DENIED` if `handle_value` lacks the requested `rights`.
#[inline]
pub fn get_dispatcher_with_rights<T>(
&self,
caller: &ProcessDispatcher,
handle_value: HandleValue,
rights: zx_rights_t,
) -> Result<RefPtr<T>, Status>
where
T: DispatcherOps + HasRefCount + Recyclable,
{
let (dispatcher, _actual_rights) =
self.get_dispatcher_with_rights_impl::<T>(Some(caller), handle_value, rights)?;
Ok(dispatcher)
}
/// Resolves a handle to a dispatcher of type `T` with the required `rights` in this handle
/// table, without enforcing the `ZX_POL_BAD_HANDLE` job policy on lookup failure.
///
/// Mirrors C++ `HandleTable::GetDispatcherWithRightsNoPolicyCheck`. As with
/// [`Self::get_dispatcher_with_rights`], `ZX_ERR_WRONG_TYPE` takes precedence over
/// `ZX_ERR_ACCESS_DENIED`.
#[inline]
pub fn get_dispatcher_with_rights_no_policy_check<T>(
&self,
handle_value: HandleValue,
rights: zx_rights_t,
) -> Result<RefPtr<T>, Status>
where
T: DispatcherOps + HasRefCount + Recyclable,
{
let (dispatcher, _actual_rights) =
self.get_dispatcher_with_rights_impl::<T>(None, handle_value, rights)?;
Ok(dispatcher)
}
/// Calls `func` on every handle owned by the handle table while holding the handle table lock.
/// Stops if `func` returns an error, returning the error value.
pub fn for_each_handle<F>(&self, func: F) -> Result<(), Status>
where
F: FnMut(zx_handle_t, zx_rights_t, &Dispatcher) -> Result<(), Status>,
{
ksync::lock!(let guard = self.read_lock());
self.for_each_handle_locked(guard.token(), func)
}
/// Similar to [`Self::for_each_handle`], but requires the caller to be holding the handle
/// table lock.
pub fn for_each_handle_locked<F>(
&self,
_token: &LockToken<'_, HandleTableLockClass>,
mut func: F,
) -> Result<(), Status>
where
F: FnMut(zx_handle_t, zx_rights_t, &Dispatcher) -> Result<(), Status>,
{
/// # Safety
///
/// `ctx` must point to a valid, unaliased `F` on the caller's stack and `disp` must point
/// to a valid `Dispatcher` for the duration of the synchronous callback invocation.
unsafe extern "C" fn trampoline<F>(
ctx: *mut c_void,
handle: zx_handle_t,
rights: zx_rights_t,
disp: *const Dispatcher,
) -> zx_status_t
where
F: FnMut(zx_handle_t, zx_rights_t, &Dispatcher) -> Result<(), Status>,
{
// SAFETY: `cpp_handle_table_for_each_handle_locked` invokes `trampoline` synchronously
// and sequentially on the same thread before returning, so `ctx` points to the live,
// unaliased `func` on `for_each_handle_locked`'s stack, and `disp` points to a live
// `Dispatcher` owned by a `Handle` in `handles_` while the handle table lock is held.
let (cb, disp) = unsafe { (&mut *ctx.cast::<F>(), &*disp) };
Status::result_into_raw(cb(handle, rights, disp))
}
// SAFETY: `self` is a valid `HandleTable`, the handle table lock is held via `_token`, and
// `cpp_handle_table_for_each_handle_locked` only invokes `trampoline` synchronously before
// returning, while `func` remains live and unaliased on this stack frame.
let status = unsafe {
cpp_handle_table_for_each_handle_locked(
self.as_ffi(),
(&raw mut func).cast(),
trampoline::<F>,
)
};
Status::ok(status)
}
/// Iterates over every handle owned by this handle table in batches and calls `func` on each
/// one without holding the handle table lock during `func`.
pub fn for_each_handle_batched<F>(&self, mut func: F) -> Result<(), Status>
where
F: FnMut(zx_handle_t, zx_rights_t, &Dispatcher) -> Result<(), Status>,
{
/// # Safety
///
/// `ctx` must point to a valid, unaliased `F` on the caller's stack and `disp` must point
/// to a valid `Dispatcher` for the duration of the synchronous callback invocation.
unsafe extern "C" fn trampoline<F>(
ctx: *mut c_void,
handle: zx_handle_t,
rights: zx_rights_t,
disp: *const Dispatcher,
) -> zx_status_t
where
F: FnMut(zx_handle_t, zx_rights_t, &Dispatcher) -> Result<(), Status>,
{
// SAFETY: `cpp_handle_table_for_each_handle_batched` invokes `trampoline`
// synchronously and sequentially on the same thread before returning, so `ctx` points
// to the live, unaliased `func` on `for_each_handle_batched`'s stack, and `disp`
// points to a live `Dispatcher` kept alive by the batch's
// `fbl::RefPtr<const Dispatcher>`.
let (cb, disp) = unsafe { (&mut *ctx.cast::<F>(), &*disp) };
Status::result_into_raw(cb(handle, rights, disp))
}
// SAFETY: `self` is a valid `HandleTable`, and `cpp_handle_table_for_each_handle_batched`
// only invokes `trampoline` synchronously before returning, while `func` remains live and
// unaliased on this stack frame.
let status = unsafe {
cpp_handle_table_for_each_handle_batched(
self.as_ffi_mut(),
(&raw mut func).cast(),
trampoline::<F>,
)
};
Status::ok(status)
}
/// Returns extended handle information for all handles in this handle table.
pub fn get_handle_info(&self) -> Result<Array<zx_info_handle_extended_t>, Status> {
loop {
let count = self.handle_count() as usize;
// TODO: Bug 45685. This memory allocation should come from a different pool since it
// can be larger than one page.
let mut handles = Array::<zx_info_handle_extended_t>::try_new_zeroed_slice(count)
.map_err(|_| Status::NO_MEMORY)?;
{
ksync::lock!(let guard = self.read_lock());
if count != self.handle_count_locked(guard.token()) as usize {
continue;
}
let mut index = 0;
let _ = self.for_each_handle_locked(guard.token(), |handle, rights, disp| {
let entry = &mut handles[index];
index += 1;
*entry = zx_info_handle_extended_t {
type_: disp.get_type(),
handle_value: handle,
rights,
reserved: 0,
koid: disp.get_koid(),
related_koid: disp.get_related_koid(),
peer_owner_koid: 0,
};
Ok(())
});
}
return Ok(handles);
}
}
}