All of lore.kernel.org
 help / color / mirror / Atom feed
From: Matthew Maurer <mmaurer@google.com>
To: "Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
	"Rafael J. Wysocki" <rafael@kernel.org>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Miguel Ojeda" <ojeda@kernel.org>,
	"Alex Gaynor" <alex.gaynor@gmail.com>,
	"Boqun Feng" <boqun.feng@gmail.com>,
	"Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <benno.lossin@proton.me>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Sami Tolvanen" <samitolvanen@google.com>,
	"Timur Tabi" <ttabi@nvidia.com>
Cc: rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org,
	 Matthew Maurer <mmaurer@google.com>
Subject: [PATCH WIP 1/2] rust: debugfs: Add interface to build debugfs off pinned objects
Date: Sat, 03 May 2025 00:43:59 +0000	[thread overview]
Message-ID: <20250503-debugfs-rust-attach-v1-1-dc37081fbfbc@google.com> (raw)
In-Reply-To: <20250503-debugfs-rust-attach-v1-0-dc37081fbfbc@google.com>

Previously, we could only expose `'static` references to DebugFS because
we don't know how long the file could live. This provides a way to
package data alongside an owning directory to guarantee that it will
live long enough to be accessed by the DebugFS files, while still
allowing a dynamic lifecycle.

Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
 rust/kernel/debugfs.rs | 206 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 196 insertions(+), 10 deletions(-)

diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index cfdff63c10517f1aebf757c965289a49fed6ae85..f232598b510e036bd25e6846f153572ebfb459f3 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -6,10 +6,14 @@
 //!
 //! C header: [`include/linux/debugfs.h`](srctree/include/linux/debugfs.h)
 
+use crate::prelude::PinInit;
 use crate::str::CStr;
 use core::fmt;
 use core::fmt::Display;
-use core::marker::PhantomData;
+use core::marker::{PhantomData, PhantomPinned};
+use core::ops::Deref;
+use core::pin::Pin;
+use pin_init::pin_data;
 
 /// Owning handle to a DebugFS directory.
 ///
@@ -158,6 +162,8 @@ pub fn fmt_file<'b, T, F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result>(
         f: &'static F,
     ) -> File<'b> {
         // SAFETY: As `data` lives for the static lifetime, it outlives the file
+        // As we return `File<'b>`, and we have a borrow to a directory with lifetime 'b, we have a
+        // lower bound of `'b` on the directory.
         unsafe { self.fmt_file_raw(name, data, f) }
     }
 
@@ -169,11 +175,10 @@ pub fn fmt_file<'b, T, F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result>(
     /// This means that before `data` may become invalid, either:
     /// * The refcount must go to zero.
     /// * The file must be rendered inaccessible, e.g. via `debugfs_remove`.
-    unsafe fn display_file_raw<'b, T: Display + Sized>(
-        &'b self,
-        name: &CStr,
-        data: *const T,
-    ) -> File<'b> {
+    /// * If `self` may take longer than `'a` to be destroyed, the caller is responsible for
+    ///   shortening the lifetime of the return value to a lower bound for the lifetime of that
+    ///   object.
+    unsafe fn display_file_raw<T: Display + Sized>(&self, name: &CStr, data: *const T) -> File<'a> {
         // SAFETY:
         // * `name` is a NUL-terminated C string, living across the call, by `CStr` invariant.
         // * `parent` is a live `dentry` since we have a reference to it.
@@ -215,13 +220,16 @@ unsafe fn display_file_raw<'b, T: Display + Sized>(
     ///
     /// # Safety
     ///
-    /// `data` must outlive the resulting file's accessibility
-    unsafe fn fmt_file_raw<'b, T, F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result>(
-        &'b self,
+    /// * `data` must outlive the resulting file's accessibility
+    /// * If `self` may take longer than `'a` to be destroyed, the caller is responsible for
+    ///   shortening the lifetime of the return value to a lower bound for the lifetime of that
+    ///   object.
+    unsafe fn fmt_file_raw<T, F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result>(
+        &self,
         name: &CStr,
         data: &T,
         f: &'static F,
-    ) -> File<'b> {
+    ) -> File<'a> {
         #[cfg(CONFIG_DEBUG_FS)]
         let data_adapted = FormatAdapter::new(data, f);
         #[cfg(not(CONFIG_DEBUG_FS))]
@@ -303,6 +311,184 @@ pub fn remove(self) {
     }
 }
 
+/// A DebugFS directory combined with a backing store for data to implement it.
+#[pin_data]
+pub struct Values<T> {
+    dir: Dir<'static, false>,
+    // The order here is load-bearing - `dir` must be dropped before `backing`, as files under
+    // `dir` may point into `backing`.
+    #[pin]
+    backing: T,
+    // Since the files present under our directory may point into `backing`, we are `!Unpin`.
+    #[pin]
+    _pin: PhantomPinned,
+}
+
+impl<T> Deref for Values<T> {
+    type Target = T;
+    fn deref(&self) -> &T {
+        &self.backing
+    }
+}
+
+impl<T> Values<T> {
+    /// Attach backing data to a DebugFS directory.
+    ///
+    /// Attached data will be available inside [`Values::build`] to use when defining files in
+    /// the provided directory. When this object is dropped, it will remove the provided directory
+    /// before destroying the attached data.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use kernel::{c_str, new_mutex};
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let dir = Dir::new(c_str!("foo"));
+    /// let debugfs = KBox::pin_init(Values::attach(new_mutex!(0), dir), GFP_KERNEL)?;
+    /// // Files can be put inside `debugfs` which reference the constructed mutex.
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn attach(backing: impl PinInit<T>, dir: Dir<'static, false>) -> impl PinInit<Self> {
+        pin_init::pin_init! { Self {
+            dir: dir,
+            backing <- backing,
+            _pin: PhantomPinned,
+        }}
+    }
+
+    /// Runs a function which can create files from the backing data.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use kernel::{c_str, new_mutex};
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let dir = Dir::new(c_str!("foo"));
+    /// let debugfs = KBox::pin_init(Values::attach(new_mutex!(0), dir), GFP_KERNEL)?;
+    /// debugfs.as_ref().build(|mutex, dir| {
+    ///   // Can access `BoundDir` methods on `dir` here, with lifetime unified to the
+    ///   // lifetime of `mutex`
+    /// });
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn build<U, F: for<'b> FnOnce(&'b T, BoundDir<'b>) -> U>(self: Pin<&Self>, f: F) -> U {
+        // SAFETY: The `BoundDir` here is only being provided as a universally quantified argument
+        // to a function, so in the body, it will only be available in an existentially quantified
+        // context. This means that the function is legal to execute agains the true lifetime of
+        // the directory, even if we don't know exactly what that is.
+        f(&self.backing, unsafe { BoundDir::new(&self.dir) })
+    }
+}
+
+/// A `Dir`, bound to the lifetime for which it will exist. Unlike `&'a Dir`, this has an invariant
+/// lifetime - it cannot be shortened or lengthened.
+///
+/// # Invariants
+///
+/// * `BoundDir`'s lifetime must match the actual lifetime the directory lives for. In practice,
+///   this means that a `BoundDir` may only be used in an existentially quantified context.
+/// * `dir` will never be promoted to an owning reference (e.g. via calling `Dir::owning`)
+#[repr(transparent)]
+pub struct BoundDir<'a> {
+    dir: Dir<'a, true>,
+    _invariant: PhantomData<fn(&'a ()) -> &'a ()>,
+}
+
+impl<'a> BoundDir<'a> {
+    /// Create a new bound directory.
+    ///
+    /// # Safety
+    ///
+    /// `'a` is being used in a context where it is existentially quantified.
+    unsafe fn new(dir: &'a Dir<'_, false>) -> Self {
+        Self {
+            // We can create a non-owning `Dir` with our restricted lifetime because we do not
+            // allow this `dir` to be upgraded to an owning reference, and the owning reference
+            // provided necessarily outlives us.
+            dir: Dir {
+                dir: dir.dir,
+                _phantom: PhantomData,
+            },
+            _invariant: PhantomData,
+        }
+    }
+
+    /// Create a DebugFS subdirectory.
+    ///
+    /// This will produce another [`BoundDir`], scoped to the lifetime of the parent [`BoundDir`].
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let parent = Dir::new(c_str!("parent"));
+    /// let values = KBox::pin_init(Values::attach(0, parent), GFP_KERNEL)?;
+    /// values.as_ref().build(|value, builder| {
+    ///   builder.subdir(c_str!("child"));
+    /// });
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn subdir(&self, name: &CStr) -> Self {
+        Self {
+            dir: Dir::create(name, Some(&self.dir)),
+            _invariant: PhantomData,
+        }
+    }
+
+    /// Create a file in a DebugFS directory with the provided name, and contents from invoking `f`
+    /// on the provided reference.
+    ///
+    /// `f` must be a function item or a non-capturing closure, or this will fail to compile.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::{c_str, new_mutex};
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let parent = Dir::new(c_str!("parent"));
+    /// let values = KBox::pin_init(Values::attach(new_mutex!(0), parent), GFP_KERNEL)?;
+    /// values.as_ref().build(|value, builder| {
+    ///   builder.fmt_file(c_str!("child"), value, &|value, f| {
+    ///     writeln!(f, "Reading a mutex: {}", *value.lock())
+    ///   });
+    /// });
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn fmt_file<T, F: Fn(&T, &mut fmt::Formatter<'_>) -> fmt::Result>(
+        &self,
+        name: &CStr,
+        data: &'a T,
+        f: &'static F,
+    ) -> File<'a> {
+        // SAFETY: As `data` lives for the same lifetime as our `BoundDir` lifetime, which is
+        // instantiated as the actual lifetime of the directory, it lives long enough.
+        // We don't need to shorten the file handle because `BoundDir`'s lifetime parameter is both
+        // a lower *and* upper bound.
+        unsafe { self.dir.fmt_file_raw(name, data, f) }
+    }
+
+    /// Create a file in a DebugFS directory with the provided name, and contents from invoking
+    /// [`Display::fmt`] on the provided reference with a trailing newline.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::{c_str, new_mutex};
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let parent = Dir::new(c_str!("parent"));
+    /// let values = KBox::pin_init(Values::attach((1, 2), parent), GFP_KERNEL)?;
+    /// values.as_ref().build(|value, builder| {
+    ///   builder.display_file(c_str!("child_0"), &value.0);
+    ///   builder.display_file(c_str!("child_1"), &value.1);
+    /// });
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn display_file<T: Display>(&self, name: &CStr, data: &'a T) -> File<'a> {
+        self.fmt_file(name, data, &|data, f| writeln!(f, "{}", data))
+    }
+}
+
 #[cfg(CONFIG_DEBUG_FS)]
 mod helpers {
     use crate::seq_file::SeqFile;

-- 
2.49.0.906.g1f30a19c02-goog


  reply	other threads:[~2025-05-03  0:44 UTC|newest]

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-05-03  0:43 [PATCH WIP 0/2] rust: debugfs: Support attaching data to DebugFS directories Matthew Maurer
2025-05-03  0:43 ` Matthew Maurer [this message]
2025-05-04 16:58   ` [PATCH WIP 1/2] rust: debugfs: Add interface to build debugfs off pinned objects Danilo Krummrich
2025-05-05 17:32     ` Matthew Maurer
2025-05-07 18:16       ` Danilo Krummrich
2025-05-03  0:44 ` [PATCH WIP 2/2] rust: debugfs: Extend sample to use attached data Matthew Maurer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20250503-debugfs-rust-attach-v1-1-dc37081fbfbc@google.com \
    --to=mmaurer@google.com \
    --cc=a.hindborg@kernel.org \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=benno.lossin@proton.me \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=gary@garyguo.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rafael@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=samitolvanen@google.com \
    --cc=tmgross@umich.edu \
    --cc=ttabi@nvidia.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.