On Wed, Dec 17, 2025 at 5:40 PM Timur Tabi <[email protected]> wrote:
>
> 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.
> +    /// ```

This also breaks the assumption people were able to have with `Dir`
previously that if they have a `Dir` and debugfs is enabled, it's
usable for creating subdirs/files.

This might be considered an issue, but at minimum it needs to be documented.

> +    pub fn lookup(name: &CStr, parent: Option<&Dir>) -> Option<Self> {

We were explicitly asked by Greg *not* to expose error conditions in
directory constructors. I would expect that to extend to `lookup` as
well, so this would return a `Self`, not allowing the caller to find
out if the file was actually there. This might be a bit of a grey area
as the comment he cited on `debugfs_create_file` and friends has
explicit verbiage about it being bad form to check errors here and
`debugfs_lookup` mentions explicitly a null return for the file not
being there.

Another point in favor of just returning `Self` rather than
`Option<Self>` is that in your proposed real-world usage of this, you
create a dummy directory in the `None` case.

> +        #[cfg(CONFIG_DEBUG_FS)]
> +        {

We shouldn't need to track a parent entry here, and so shouldn't need
to do the whole cloning dance.

> +            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)?;

There's no guarantee that this lookup has returned a directory - a
lookup can return files too afaict

> +            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,

I agree with Danilo - I think this would be cleaner as a different
type (or at least an enum variant) rather than adding another field
(if we need it). Notably:

1. `_parent` is not required for a lookup entry - the lookup doesn't
need to keep it alive, because it's not using the "If I have the
directory handle, I can create real files in it" semantics we had
before.
2. The `_phantom` is irrelevant for a lookup entry because they can't be scoped
3. The drop behavior is different (which you're gating with `is_lookup`).

Basically the only part that is the same is the presence of a dentry
pointer under the hood.


>  }
>
>  // 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