Add a new constructor for Dir that uses the debugfs_lookup() API to obtain a reference to an existing debugfs directory entry.
The key difference from Dir::new() and Dir::subdir() is the cleanup semantics: when a Dir obtained via lookup() is dropped, it calls dput() to release the reference rather than debugfs_remove() which would delete the directory. To implement this cleanup distinction, the Entry class now includes an is_lookup boolean that specifies how the entry was created and therefore how it should be dropped. Signed-off-by: Timur Tabi <[email protected]> --- rust/kernel/debugfs.rs | 43 +++++++++++++++++++++++++++++++ rust/kernel/debugfs/entry.rs | 49 +++++++++++++++++++++++++++++++++--- 2 files changed, 89 insertions(+), 3 deletions(-) diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs index facad81e8290..eee799f64f79 100644 --- a/rust/kernel/debugfs.rs +++ b/rust/kernel/debugfs.rs @@ -110,6 +110,49 @@ pub fn new(name: &CStr) -> Self { Dir::create(name, None) } + /// Looks up an existing directory in DebugFS. + /// + /// If `parent` is [`None`], the lookup is performed from the root of the debugfs filesystem. + /// + /// Returns [`Some(Dir)`] representing the looked-up directory if found, or [`None`] if the + /// directory does not exist or if debugfs is not enabled. When dropped, the [`Dir`] will + /// release its reference to the dentry without removing the directory from the filesystem. + /// + /// # Examples + /// + /// ``` + /// # use kernel::c_str; + /// # use kernel::debugfs::Dir; + /// // Look up a top-level directory + /// let nova_core = Dir::lookup(c_str!("nova_core"), None); + /// + /// // Look up a subdirectory within a parent + /// let parent = Dir::new(c_str!("parent")); + /// let child = parent.subdir(c_str!("child")); + /// let looked_up = Dir::lookup(c_str!("child"), Some(&parent)); + /// // `looked_up` now refers to the same directory as `child`. + /// // Dropping `looked_up` will not remove the directory. + /// ``` + pub fn lookup(name: &CStr, parent: Option<&Dir>) -> Option<Self> { + #[cfg(CONFIG_DEBUG_FS)] + { + let parent_entry = match parent { + // If the parent couldn't be allocated, just early-return + Some(Dir(None)) => return None, + Some(Dir(Some(entry))) => Some(entry.clone()), + None => None, + }; + let entry = Entry::lookup(name, parent_entry)?; + Some(Self( + // If Arc creation fails, the `Entry` will be dropped, so the reference will be + // released. + Arc::new(entry, GFP_KERNEL).ok(), + )) + } + #[cfg(not(CONFIG_DEBUG_FS))] + None + } + /// Creates a subdirectory within this directory. /// /// # Examples diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs index 706cb7f73d6c..455d7bbb8036 100644 --- a/rust/kernel/debugfs/entry.rs +++ b/rust/kernel/debugfs/entry.rs @@ -18,6 +18,9 @@ pub(crate) struct Entry<'a> { _parent: Option<Arc<Entry<'static>>>, // If we were created with a non-owning parent, this prevents us from outliving it _phantom: PhantomData<&'a ()>, + // If true, this entry was obtained via debugfs_lookup and should be released + // with dput() instead of debugfs_remove(). + is_lookup: bool, } // SAFETY: [`Entry`] is just a `dentry` under the hood, which the API promises can be transferred @@ -43,9 +46,38 @@ pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self { entry, _parent: parent, _phantom: PhantomData, + is_lookup: false, } } + /// Looks up an existing entry in debugfs. + /// + /// Returns [`Some(Entry)`] representing the looked-up dentry if the entry exists, or [`None`] + /// if the entry was not found. When dropped, the entry will release its reference via `dput()` + /// instead of removing the directory. + 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. + let entry = unsafe { bindings::debugfs_lookup(name.as_char_ptr(), parent_ptr) }; + + if entry.is_null() { + return None; + } + + Some(Entry { + entry, + _parent: parent, + _phantom: PhantomData, + is_lookup: true, + }) + } + /// # Safety /// /// * `data` must outlive the returned `Entry`. @@ -76,6 +108,7 @@ pub(crate) unsafe fn dynamic_file<T>( entry, _parent: Some(parent), _phantom: PhantomData, + is_lookup: false, } } } @@ -97,6 +130,7 @@ pub(crate) fn dir(name: &CStr, parent: Option<&'a Entry<'_>>) -> Self { entry, _parent: None, _phantom: PhantomData, + is_lookup: false, } } @@ -129,6 +163,7 @@ pub(crate) fn file<T>( entry, _parent: None, _phantom: PhantomData, + is_lookup: false, } } } @@ -140,6 +175,7 @@ pub(crate) fn empty() -> Self { entry: core::ptr::null_mut(), _parent: None, _phantom: PhantomData, + is_lookup: false, } } @@ -157,8 +193,15 @@ pub(crate) fn as_ptr(&self) -> *mut bindings::dentry { impl Drop for Entry<'_> { fn drop(&mut self) { - // SAFETY: `debugfs_remove` can take `NULL`, error values, and legal DebugFS dentries. - // `as_ptr` guarantees that the pointer is of this form. - unsafe { bindings::debugfs_remove(self.as_ptr()) } + if self.is_lookup { + // SAFETY: `dput` can take `NULL` and legal dentries. + // `as_ptr` guarantees that the pointer is of this form. + // This entry was obtained via `debugfs_lookup`, so we release the reference. + unsafe { bindings::dput(self.as_ptr()) } + } else { + // SAFETY: `debugfs_remove` can take `NULL`, error values, and legal DebugFS dentries. + // `as_ptr` guarantees that the pointer is of this form. + unsafe { bindings::debugfs_remove(self.as_ptr()) } + } } } -- 2.52.0
