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

Reply via email to