linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Matthew Maurer <mmaurer@google.com>
To: "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>,
	"Danilo Krummrich" <dakr@kernel.org>,
	"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
	"Rafael J. Wysocki" <rafael@kernel.org>,
	"Sami Tolvanen" <samitolvanen@google.com>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	 Matthew Maurer <mmaurer@google.com>
Subject: [PATCH 3/8] rust: debugfs: Add scoped builder interface
Date: Tue, 29 Apr 2025 23:15:57 +0000	[thread overview]
Message-ID: <20250429-debugfs-rust-v1-3-6b6e7cb7929f@google.com> (raw)
In-Reply-To: <20250429-debugfs-rust-v1-0-6b6e7cb7929f@google.com>

This adds an interface which allows access to references which may not
live indefinitely by forcing them to live at least as long as the
DebugFS directory itself.

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

diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index b20df5fce692b3047c804f7ad5af90fc4248979b..f6240fd056f8598d5ef06bdaf61c5c33eab5b734 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -12,7 +12,12 @@
 use crate::str::CStr;
 use crate::types::{ARef, AlwaysRefCounted, Opaque};
 use core::fmt::Display;
+use core::marker::{PhantomData, PhantomPinned};
+use core::mem::ManuallyDrop;
+use core::ops::Deref;
+use core::pin::Pin;
 use core::ptr::NonNull;
+use pin_init::{pin_data, pinned_drop, PinInit};
 
 /// Handle to a DebugFS directory.
 pub struct Dir {
@@ -117,6 +122,22 @@ pub fn display_file<T: Display + Sized>(
         &self,
         name: &CStr,
         data: &'static T,
+    ) -> Result<ARef<Self>> {
+        // SAFETY: As `data` lives for the static lifetime, it outlives the file.
+        unsafe { self.display_file_raw(name, data) }
+    }
+
+    /// Creates a DebugFS file backed by the display implementation of the provided pointer.
+    ///
+    /// # Safety
+    /// The pointee of `data` must outlive the accessibility of the `Dir` returned by this function.
+    /// 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<T: Display + Sized>(
+        &self,
+        name: &CStr,
+        data: *const T,
     ) -> Result<ARef<Self>> {
         // SAFETY:
         // * `name` is a NUL-terminated C string, living across the call, by CStr invariant
@@ -192,3 +213,145 @@ trait DisplayFile: Display + Sized {
 }
 
 impl<T: Display + Sized> DisplayFile for T {}
+
+#[pin_data(PinnedDrop)]
+/// A DebugFS directory combined with a backing store for data to implement it
+pub struct Values<T> {
+    #[pin]
+    backing: T,
+    // Calling `debugfs_remove`, as we will do in our `Drop` impl, will consume the refcount we
+    // hold after cleaning up all the child directories.
+    dir: ManuallyDrop<ARef<Dir>>,
+    // 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. When the resulting object is destroyed, the
+    /// DebugFS directory will be recursively removed as well.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let dir = Dir::new(c_str!("foo"), None)?;
+    /// let _foo = KBox::pin_init(Values::attach(0, dir), GFP_KERNEL)?;
+    /// // foo can now be used with `Values::build` to allow access to the attached value when
+    /// // printing
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn attach(backing: impl PinInit<T>, dir: ARef<Dir>) -> impl PinInit<Self> {
+        pin_init::pin_init! { Self {
+            backing <- backing,
+            dir: ManuallyDrop::new(dir),
+            _pin: PhantomPinned,
+        } }
+    }
+
+    /// Runs a closure which has access to the backing data and a builder that will allow you to
+    /// build a DebugFS structure off the backing data using its methods.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let dir = Dir::new(c_str!("foo"), None)?;
+    /// let foo = KBox::pin_init(Values::attach(0, dir), GFP_KERNEL)?;
+    /// foo.as_ref().build(|_value, _builder| {
+    ///   // Construct debugfs with access to the attached value here
+    /// });
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn build<U, F: for<'a> FnOnce(&'a T, Builder<'a>) -> U>(self: Pin<&Self>, f: F) -> U {
+        // SAFETY: The Builder produced here is technically at the lifetime of self, but is
+        // being used only in a universal context, so that information is immediately erased and
+        // replaced with the universally quantified 'a. By taking a Pin<&Self>, we enforce that
+        // self.backing remains alive for any 'a less than the lifetime of the struct. By not
+        // providing any mutable access to self.backing, we ensure that it's always safe to
+        // materialize a read-only reference to &self.backing for any 'a less than the lifetime of
+        // the struct.
+        f(&self.backing, unsafe { Builder::new(&self.dir) })
+    }
+}
+
+#[pinned_drop]
+impl<T> PinnedDrop for Values<T> {
+    fn drop(self: Pin<&mut Self>) {
+        // SAFETY: Our internal dir holds its own reference count, so it's always valid.
+        unsafe { kernel::bindings::debugfs_remove(self.dir.as_ptr()) }
+    }
+}
+
+/// A Dir, scoped to the lifetime for which it will exist. Unlike `&'a Dir`, this is equivariant,
+/// preventing the shortening of the lifetime.
+///
+/// # Invariants
+/// Builder will only ever be used with 'static or a universally quantified lifetime that is
+/// unified only with the lifetime of data structures guaranteed to outlive it and not have mutable
+/// references taken.
+#[repr(transparent)]
+#[derive(Copy, Clone)]
+pub struct Builder<'a> {
+    inner: &'a Dir,
+    _equivariant: PhantomData<fn(&'a ()) -> &'a ()>,
+}
+
+impl<'a> Builder<'a> {
+    /// # Safety
+    /// Caller must promise to use this function at static lifetime or only expose it to
+    /// universally quantified functions, unified only with lifetimes guaranteed to extend beyond
+    /// when the directory will be rendered inaccessible.
+    unsafe fn new(inner: &'a Dir) -> Self {
+        Self {
+            inner,
+            _equivariant: PhantomData,
+        }
+    }
+
+    /// Create a file in a DebugFS directory with the provided name, and contents from invoking
+    /// [`Display::fmt`] on the provided reference.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use kernel::{try_pin_init, c_str};
+    /// # use pin_init::stack_try_pin_init;
+    /// # use kernel::debugfs::{Dir, Values};
+    /// let dir = Dir::new(c_str!("foo"), None)?;
+    /// stack_try_pin_init!(let foo =? Values::attach((1, 2), dir));
+    /// foo.as_ref().build(|value, builder| {
+    ///   builder.display_file(c_str!("bar"), &value.0)?;
+    ///   builder.display_file(c_str!("baz"), &value.1)
+    /// })?;
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn display_file<T: Display + Sized>(&self, name: &CStr, data: &'a T) -> Result<()> {
+        // We forget the reference because its reference count is implicitly "owned" by the root
+        // builder, which we know will use `debugfs_remove` to clean this up. If we release the
+        // file here, it will be immediately deleted.
+        // SAFETY:
+        // Because `Builder`'s invariant says that our lifetime is how long the directory will
+        // be available, and is equivariant, `'a` will outlive the base directory, which will be
+        // torn down by `debugfs_remove` to prevent access even if an extra refcount is held
+        // somewhere.
+        core::mem::forget(unsafe { self.inner.display_file_raw(name, data)? });
+        Ok(())
+    }
+}
+
+impl<'a> Deref for Builder<'a> {
+    type Target = Dir;
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}

-- 
2.49.0.901.g37484f566f-goog


  parent reply	other threads:[~2025-04-29 23:16 UTC|newest]

Thread overview: 31+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-04-29 23:15 [PATCH 0/8] rust: DebugFS Bindings Matthew Maurer
2025-04-29 23:15 ` [PATCH 1/8] rust: debugfs: Bind DebugFS directory creation Matthew Maurer
2025-04-30  7:50   ` Greg Kroah-Hartman
2025-04-30 15:10     ` Matthew Maurer
2025-04-30 15:23       ` Greg Kroah-Hartman
2025-04-30 15:31         ` Matthew Maurer
2025-04-30 16:58           ` Greg Kroah-Hartman
2025-04-29 23:15 ` [PATCH 2/8] rust: debugfs: Bind file creation for long-lived Display Matthew Maurer
2025-04-30  3:27   ` Miguel Ojeda
2025-04-30 15:26     ` Matthew Maurer
2025-04-30  7:52   ` Greg Kroah-Hartman
2025-04-30 15:15     ` Matthew Maurer
2025-04-30  7:55   ` Greg Kroah-Hartman
2025-04-30 15:12     ` Matthew Maurer
2025-04-30 15:24       ` Greg Kroah-Hartman
2025-04-29 23:15 ` Matthew Maurer [this message]
2025-04-30  7:57   ` [PATCH 3/8] rust: debugfs: Add scoped builder interface Greg Kroah-Hartman
2025-04-29 23:15 ` [PATCH 4/8] rust: debugfs: Allow subdir creation in " Matthew Maurer
2025-04-30  7:58   ` Greg Kroah-Hartman
2025-04-29 23:15 ` [PATCH 5/8] rust: debugfs: Support format hooks Matthew Maurer
2025-04-30  3:17   ` Miguel Ojeda
2025-04-29 23:16 ` [PATCH 6/8] rust: debugfs: Implement display_file in terms of fmt_file Matthew Maurer
2025-04-29 23:16 ` [PATCH 7/8] rust: debugfs: Helper macro for common case implementations Matthew Maurer
2025-04-29 23:16 ` [PATCH 8/8] rust: samples: Add debugfs sample Matthew Maurer
2025-04-30  8:01   ` Greg Kroah-Hartman
2025-04-30  0:04 ` [PATCH 0/8] rust: DebugFS Bindings John Hubbard
2025-04-30  8:03 ` Greg Kroah-Hartman
2025-04-30 15:01   ` Matthew Maurer
2025-04-30 15:21     ` Greg Kroah-Hartman
2025-04-30 15:24       ` Matthew Maurer
2025-04-30 15:30         ` Greg Kroah-Hartman

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=20250429-debugfs-rust-v1-3-6b6e7cb7929f@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 \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).