Add `LookupDir`, a non-owning handle to an existing DebugFS directory. Unlike `Dir`, which creates a new directory and removes it on drop, `LookupDir` looks up an existing directory and only releases the reference when dropped—the directory itself remains in the filesystem.
This is useful when a driver needs to add files to a directory created by another part of the system without taking ownership of that directory. Signed-off-by: Timur Tabi <[email protected]> --- rust/kernel/debugfs.rs | 127 +++++++++++++++++++++++++++++++++++ rust/kernel/debugfs/entry.rs | 84 +++++++++++++++++++++++ 2 files changed, 211 insertions(+) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index ed740eb90fc9..2ff70d72240c 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -34,6 +34,8 @@ mod entry; #[cfg(CONFIG_DEBUG_FS)] use entry::Entry; +#[cfg(CONFIG_DEBUG_FS)] +use entry::LookupEntry; /// Trait for DebugFS directory operations. /// @@ -393,6 +395,131 @@ pub fn scope<'a, T: 'a, E: 'a, F>( } } +/// Non-owning handle to an existing DebugFS directory. +/// +/// Unlike [`Dir`], a [`LookupDir`] does not create a new directory. Instead, it looks up an +/// existing directory in the DebugFS filesystem. When dropped, the directory is **not** removed +/// from the filesystem - only the reference is released. +/// +/// This is useful when you want to add files to an existing directory created by another part +/// of the system without taking ownership of that directory. +#[derive(Clone)] +pub struct LookupDir(#[cfg(CONFIG_DEBUG_FS)] Option<Arc<LookupEntry>>); + +impl LookupDir { + /// Looks up an existing directory in DebugFS. + /// + /// If `parent` is [`None`], the lookup is performed from the root of the debugfs filesystem. + /// + /// Returns [`Some(LookupDir)`] if the directory exists, [`None`] otherwise. + /// + /// # Examples + /// + /// ``` + /// # use kernel::c_str; + /// # use kernel::debugfs::LookupDir; + /// // Look up a top-level directory + /// if let Some(dir) = LookupDir::lookup(c_str!("existing_dir"), None) { + /// // Use the directory... + /// } + /// // When `dir` is dropped, the directory is NOT removed. + /// ``` + pub fn lookup(name: &CStr, parent: Option<&LookupDir>) -> Option<Self> { + #[cfg(CONFIG_DEBUG_FS)] + { + let parent_entry = match parent { + Some(LookupDir(None)) => return None, + Some(LookupDir(Some(entry))) => Some(entry.clone()), + None => None, + }; + let entry = LookupEntry::lookup(name, parent_entry)?; + Some(Self(Arc::new(entry, GFP_KERNEL).ok())) + } + #[cfg(not(CONFIG_DEBUG_FS))] + None + } + + // While this function is safe, it is intentionally not public because it's a bit of a + // footgun. + // + // Unless you also extract the `entry` later and schedule it for `Drop` at the appropriate + // time, a `ScopedDir` with a `LookupDir` parent will never be deleted. + fn scoped_dir<'data>(&self, name: &CStr) -> ScopedDir<'data, 'static> { + #[cfg(CONFIG_DEBUG_FS)] + { + let parent_entry = match &self.0 { + None => return ScopedDir::empty(), + Some(entry) => entry.clone(), + }; + ScopedDir { + entry: ManuallyDrop::new(Entry::dynamic_dir_with_lookup_parent(name, parent_entry)), + _phantom: PhantomData, + } + } + #[cfg(not(CONFIG_DEBUG_FS))] + ScopedDir::empty() + } + + /// Creates a new scope, which is a directory associated with some data `T`. + /// + /// The created directory will be a subdirectory of `self`. The `init` closure is called to + /// populate the directory with files and subdirectories. These files can reference the data + /// stored in the scope. + /// + /// The entire directory tree created within the scope will be removed when the returned + /// `Scope` handle is dropped. + /// + /// Note: Unlike [`Dir::scope`], this method consumes `self` because the parent entry + /// is kept alive by the created scope via [`DynParent`], not by the `LookupDir` handle. + pub fn scope<'a, T: 'a, E: 'a, F>( + self, + data: impl PinInit<T, E> + 'a, + name: &'a CStr, + init: F, + ) -> impl PinInit<Scope<T>, E> + 'a + where + F: for<'data, 'dir> FnOnce(&'data T, &'dir ScopedDir<'data, 'dir>) + 'a, + { + Scope::new(data, move |data| { + let scoped = self.scoped_dir(name); + init(data, &scoped); + scoped.into_entry() + }) + } +} + +impl Directory for LookupDir { + /// Looks up an existing directory at the DebugFS root. + /// + /// Note: Unlike [`Dir::new`], this will return an empty handle if the directory + /// does not exist, rather than creating it. + fn new(name: &CStr) -> Self { + Self::lookup(name, None).unwrap_or_else(|| { + #[cfg(CONFIG_DEBUG_FS)] + { + Self(None) + } + #[cfg(not(CONFIG_DEBUG_FS))] + Self() + }) + } + + /// Looks up a subdirectory within this directory. + /// + /// Note: Unlike [`Dir::subdir`], this will return an empty handle if the subdirectory + /// does not exist, rather than creating it. + fn subdir(&self, name: &CStr) -> Self { + Self::lookup(name, Some(self)).unwrap_or_else(|| { + #[cfg(CONFIG_DEBUG_FS)] + { + Self(None) + } + #[cfg(not(CONFIG_DEBUG_FS))] + Self() + }) + } +} + #[pin_data] /// Handle to a DebugFS scope, which ensures that attached `data` will outlive the DebugFS entry /// without moving. diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs index 3152e4f96219..1e8a5cedf69d 100644 --- a/rust/kernel/debugfs/entry.rs +++ b/rust/kernel/debugfs/entry.rs @@ -15,6 +15,8 @@ pub(crate) enum DynParent { /// Parent is an owned `Entry` (will be removed on drop). Entry(Arc<Entry<'static>>), + /// Parent is a looked-up `LookupEntry` (will NOT be removed on drop). + LookupEntry(Arc<LookupEntry>), } /// Owning handle to a DebugFS entry. @@ -56,6 +58,24 @@ pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self { } } + /// Creates a new directory entry with a `LookupEntry` as parent. + /// + /// The parent is kept alive via `Arc` reference counting. When this `Entry` is dropped, + /// the created directory will be removed, but the parent `LookupEntry` will only have + /// its reference count decremented. + pub(crate) fn dynamic_dir_with_lookup_parent(name: &CStr, parent: Arc<LookupEntry>) -> Self { + // SAFETY: The invariants of this function's arguments ensure the safety of this call. + // * `name` is a valid C string by the invariants of `&CStr`. + // * `parent.as_ptr()` is a pointer to a valid `dentry` by the invariants of `LookupEntry`. + let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent.as_ptr()) }; + + Entry { + entry, + _parent: Some(DynParent::LookupEntry(parent)), + _phantom: PhantomData, + } + } + /// # Safety /// /// * `data` must outlive the returned `Entry`. @@ -172,3 +192,67 @@ fn drop(&mut self) { unsafe { bindings::debugfs_remove(self.as_ptr()) } } } + +/// Non-owning handle to a DebugFS entry obtained via lookup. +/// +/// Unlike [`Entry`], dropping a [`LookupEntry`] does not remove the directory from the +/// filesystem. It only releases the reference obtained via `debugfs_lookup`. +/// +/// # Invariants +/// +/// The wrapped pointer will always be `NULL` or a valid DebugFS `dentry` obtained via +/// `debugfs_lookup`. +pub(crate) struct LookupEntry { + entry: *mut bindings::dentry, + // If we were created with an owning parent, this is the keep-alive + _parent: Option<Arc<LookupEntry>>, +} + +// SAFETY: [`LookupEntry`] is just a `dentry` under the hood, which the API promises can be +// transferred between threads. +unsafe impl Send for LookupEntry {} + +// SAFETY: All the C functions we call on the `dentry` pointer are threadsafe. +unsafe impl Sync for LookupEntry {} + +impl LookupEntry { + /// Looks up an existing directory in DebugFS. + /// + /// Returns `Some(LookupEntry)` if the directory exists, `None` otherwise. + pub(crate) fn lookup(name: &CStr, parent: Option<Arc<Self>>) -> Option<Self> { + let parent_ptr = match &parent { + Some(entry) => entry.as_ptr(), + None => core::ptr::null_mut(), + }; + // SAFETY: The invariants of this function's arguments ensure the safety of this call. + // * `name` is a valid C string by the invariants of `&CStr`. + // * `parent_ptr` is either `NULL` (if `parent` is `None`), or a pointer to a valid + // `dentry` by our invariant. `debugfs_lookup` handles `NULL` pointers correctly + // (searches from debugfs root). + let entry = unsafe { bindings::debugfs_lookup(name.as_char_ptr(), parent_ptr) }; + + if entry.is_null() { + None + } else { + Some(LookupEntry { + entry, + _parent: parent, + }) + } + } + + /// Returns the pointer representation of the DebugFS directory. + pub(crate) fn as_ptr(&self) -> *mut bindings::dentry { + self.entry + } +} + +impl Drop for LookupEntry { + fn drop(&mut self) { + if !self.entry.is_null() { + // SAFETY: `dput` decrements the reference count on a dentry. The pointer is valid + // because it was obtained via `debugfs_lookup` which increments the reference count. + unsafe { bindings::dput(self.entry) } + } + } +} -- 2.52.0
