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

Reply via email to