// SPDX-License-Identifier: GPL-2.01// Copyright (C) 2025 Google LLC.23use crate::debugfs::file_ops::FileOps;4use crate::ffi::c_void;5use crate::str::CStr;6use crate::sync::Arc;7use core::marker::PhantomData;89/// Owning handle to a DebugFS entry.10///11/// # Invariants12///13/// The wrapped pointer will always be `NULL`, an error, or an owned DebugFS `dentry`.14pub(crate) struct Entry<'a> {15entry: *mut bindings::dentry,16// If we were created with an owning parent, this is the keep-alive17_parent: Option<Arc<Entry<'static>>>,18// If we were created with a non-owning parent, this prevents us from outliving it19_phantom: PhantomData<&'a ()>,20}2122// SAFETY: [`Entry`] is just a `dentry` under the hood, which the API promises can be transferred23// between threads.24unsafe impl Send for Entry<'_> {}2526// SAFETY: All the C functions we call on the `dentry` pointer are threadsafe.27unsafe impl Sync for Entry<'_> {}2829impl Entry<'static> {30pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self {31let parent_ptr = match &parent {32Some(entry) => entry.as_ptr(),33None => core::ptr::null_mut(),34};35// SAFETY: The invariants of this function's arguments ensure the safety of this call.36// * `name` is a valid C string by the invariants of `&CStr`.37// * `parent_ptr` is either `NULL` (if `parent` is `None`), or a pointer to a valid38// `dentry` by our invariant. `debugfs_create_dir` handles `NULL` pointers correctly.39let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };4041Entry {42entry,43_parent: parent,44_phantom: PhantomData,45}46}4748/// # Safety49///50/// * `data` must outlive the returned `Entry`.51pub(crate) unsafe fn dynamic_file<T>(52name: &CStr,53parent: Arc<Self>,54data: &T,55file_ops: &'static FileOps<T>,56) -> Self {57// SAFETY: The invariants of this function's arguments ensure the safety of this call.58// * `name` is a valid C string by the invariants of `&CStr`.59// * `parent.as_ptr()` is a pointer to a valid `dentry` by invariant.60// * The caller guarantees that `data` will outlive the returned `Entry`.61// * The guarantees on `FileOps` assert the vtable will be compatible with the data we have62// provided.63let entry = unsafe {64bindings::debugfs_create_file_full(65name.as_char_ptr(),66file_ops.mode(),67parent.as_ptr(),68core::ptr::from_ref(data) as *mut c_void,69core::ptr::null(),70&**file_ops,71)72};7374Entry {75entry,76_parent: Some(parent),77_phantom: PhantomData,78}79}80}8182impl<'a> Entry<'a> {83pub(crate) fn dir(name: &CStr, parent: Option<&'a Entry<'_>>) -> Self {84let parent_ptr = match &parent {85Some(entry) => entry.as_ptr(),86None => core::ptr::null_mut(),87};88// SAFETY: The invariants of this function's arguments ensure the safety of this call.89// * `name` is a valid C string by the invariants of `&CStr`.90// * `parent_ptr` is either `NULL` (if `parent` is `None`), or a pointer to a valid91// `dentry` (because `parent` is a valid reference to an `Entry`). The lifetime `'a`92// ensures that the parent outlives this entry.93let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) };9495Entry {96entry,97_parent: None,98_phantom: PhantomData,99}100}101102pub(crate) fn file<T>(103name: &CStr,104parent: &'a Entry<'_>,105data: &'a T,106file_ops: &FileOps<T>,107) -> Self {108// SAFETY: The invariants of this function's arguments ensure the safety of this call.109// * `name` is a valid C string by the invariants of `&CStr`.110// * `parent.as_ptr()` is a pointer to a valid `dentry` because we have `&'a Entry`.111// * `data` is a valid pointer to `T` for lifetime `'a`.112// * The returned `Entry` has lifetime `'a`, so it cannot outlive `parent` or `data`.113// * The caller guarantees that `vtable` is compatible with `data`.114// * The guarantees on `FileOps` assert the vtable will be compatible with the data we have115// provided.116let entry = unsafe {117bindings::debugfs_create_file_full(118name.as_char_ptr(),119file_ops.mode(),120parent.as_ptr(),121core::ptr::from_ref(data) as *mut c_void,122core::ptr::null(),123&**file_ops,124)125};126127Entry {128entry,129_parent: None,130_phantom: PhantomData,131}132}133}134135impl Entry<'_> {136/// Constructs a placeholder DebugFS [`Entry`].137pub(crate) fn empty() -> Self {138Self {139entry: core::ptr::null_mut(),140_parent: None,141_phantom: PhantomData,142}143}144145/// Returns the pointer representation of the DebugFS directory.146///147/// # Guarantees148///149/// Due to the type invariant, the value returned from this function will always be an error150/// code, NULL, or a live DebugFS directory. If it is live, it will remain live at least as151/// long as this entry lives.152pub(crate) fn as_ptr(&self) -> *mut bindings::dentry {153self.entry154}155}156157impl Drop for Entry<'_> {158fn drop(&mut self) {159// SAFETY: `debugfs_remove` can take `NULL`, error values, and legal DebugFS dentries.160// `as_ptr` guarantees that the pointer is of this form.161unsafe { bindings::debugfs_remove(self.as_ptr()) }162}163}164165166