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
next prev 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).