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>,
	"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>,
	"Timur Tabi" <ttabi@nvidia.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Dirk Beheme" <dirk.behme@de.bosch.com>
Cc: linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org,
	 Matthew Maurer <mmaurer@google.com>
Subject: [PATCH v10 2/7] rust: debugfs: Add support for read-only files
Date: Tue, 19 Aug 2025 22:53:37 +0000	[thread overview]
Message-ID: <20250819-debugfs-rust-v10-2-86e20f3cf3bb@google.com> (raw)
In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com>

Extends the `debugfs` API to support creating read-only files. This
is done via the `Dir::read_only_file` method, which takes a data object
that implements the `Render` trait.

The file's content is generated by the `Render` implementation, and the
file is automatically removed when the returned `File` handle is
dropped.

Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
 rust/kernel/debugfs.rs          | 142 +++++++++++++++++++++++++++++++++++++++-
 rust/kernel/debugfs/entry.rs    |  42 ++++++++++++
 rust/kernel/debugfs/file_ops.rs | 125 +++++++++++++++++++++++++++++++++++
 rust/kernel/debugfs/traits.rs   |  28 ++++++++
 4 files changed, 336 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index 893aee54b920bac80f77c2726567da76929b7244..875d433fc3608cc9ffcf022d7c00cb207016f146 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -8,12 +8,18 @@
 // When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful.
 #![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))]
 
-#[cfg(CONFIG_DEBUG_FS)]
 use crate::prelude::*;
 use crate::str::CStr;
 #[cfg(CONFIG_DEBUG_FS)]
 use crate::sync::Arc;
+use core::marker::PhantomPinned;
+use core::ops::Deref;
+
+mod traits;
+pub use traits::Render;
 
+mod file_ops;
+use file_ops::{FileOps, ReadFile};
 #[cfg(CONFIG_DEBUG_FS)]
 mod entry;
 #[cfg(CONFIG_DEBUG_FS)]
@@ -53,6 +59,31 @@ fn create(name: &CStr, parent: Option<&Dir>) -> Self {
         Self()
     }
 
+    /// Creates a DebugFS file which will own the data produced by the initializer provided in
+    /// `data`.
+    fn create_file<'a, T: Sync + 'static, E: 'a, TI: PinInit<T, E> + 'a>(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+        file_ops: &'static FileOps<T>,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let scope = Scope::<T>::new(data, move |data| {
+            #[cfg(CONFIG_DEBUG_FS)]
+            if let Some(parent) = &self.0 {
+                // SAFETY: Because data derives from a scope, and our entry will be dropped before
+                // the data is dropped, it is guaranteed to outlive the entry we return.
+                unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) }
+            } else {
+                Entry::empty()
+            }
+        });
+        try_pin_init! {
+            File {
+                scope <- scope
+            } ? E
+        }
+    }
+
     /// Create a new directory in DebugFS at the root.
     ///
     /// # Examples
@@ -79,4 +110,113 @@ pub fn new(name: &CStr) -> Self {
     pub fn subdir(&self, name: &CStr) -> Self {
         Dir::create(name, Some(self))
     }
+
+    /// Creates a read-only file in this directory.
+    ///
+    /// The file's contents are produced by invoking [`Render::render`] on the value initialized by
+    /// `data`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # use kernel::c_str;
+    /// # use kernel::debugfs::Dir;
+    /// # use kernel::prelude::*;
+    /// # let dir = Dir::new(c_str!("my_debugfs_dir"));
+    /// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?;
+    /// // "my_debugfs_dir/foo" now contains the number 200.
+    /// // The file is removed when `file` is dropped.
+    /// # Ok::<(), Error>(())
+    /// ```
+    pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<T, E> + 'a>(
+        &'a self,
+        name: &'a CStr,
+        data: TI,
+    ) -> impl PinInit<File<T>, E> + 'a {
+        let file_ops = &<T as ReadFile<_>>::FILE_OPS;
+        self.create_file(name, data, file_ops)
+    }
+}
+
+#[pin_data]
+/// Handle to a DebugFS scope, which allows a variety of DebugFS files/directories to hang off a
+/// single structure.
+pub struct Scope<T> {
+    // This order is load-bearing for drops - `_entry` must be dropped before `data`.
+    #[cfg(CONFIG_DEBUG_FS)]
+    _entry: Entry,
+    #[pin]
+    data: T,
+    // Even if `T` is `Unpin`, we still can't allow it to be moved.
+    #[pin]
+    _pin: PhantomPinned,
+}
+
+#[pin_data]
+/// Handle to a DebugFS file, owning its backing data.
+///
+/// When dropped, the DebugFS file will be removed and the attached data will be dropped.
+pub struct File<T> {
+    #[pin]
+    scope: Scope<T>,
+}
+
+#[cfg(not(CONFIG_DEBUG_FS))]
+impl<T> Scope<T> {
+    fn new<E, TI: PinInit<T, E>, F: for<'a> FnOnce(&'a T)>(
+        data: TI,
+        init: F,
+    ) -> impl PinInit<Self, E> {
+        try_pin_init! {
+            Self {
+                data <- data,
+                _pin: PhantomPinned
+            } ? E
+        }
+        .pin_chain(|scope| {
+            init(&scope.data);
+            Ok(())
+        })
+    }
+}
+
+#[cfg(CONFIG_DEBUG_FS)]
+impl<T> Scope<T> {
+    fn entry_mut(self: Pin<&mut Self>) -> &mut Entry {
+        // SAFETY: _entry is not structurally pinned
+        unsafe { &mut Pin::into_inner_unchecked(self)._entry }
+    }
+    fn new<'b, E: 'b, TI: PinInit<T, E> + 'b, F: for<'a> FnOnce(&'a T) -> Entry + 'b>(
+        data: TI,
+        init: F,
+    ) -> impl PinInit<Self, E> + 'b
+    where
+        T: 'b,
+    {
+        try_pin_init! {
+            Self {
+                _entry: Entry::empty(),
+                data <- data,
+                _pin: PhantomPinned
+            } ? E
+        }
+        .pin_chain(|scope| {
+            *scope.entry_mut() = init(&scope.data);
+            Ok(())
+        })
+    }
+}
+
+impl<T> Deref for Scope<T> {
+    type Target = T;
+    fn deref(&self) -> &T {
+        &self.data
+    }
+}
+
+impl<T> Deref for File<T> {
+    type Target = T;
+    fn deref(&self) -> &T {
+        &self.scope
+    }
 }
diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs
index d2fba0e65e20e954e2a33e776b872bac4adb12e8..227fa50b7a79aeab49779e54b8c4241f455777c3 100644
--- a/rust/kernel/debugfs/entry.rs
+++ b/rust/kernel/debugfs/entry.rs
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0
 // Copyright (C) 2025 Google LLC.
 
+use crate::debugfs::file_ops::FileOps;
+use crate::ffi::c_void;
 use crate::str::CStr;
 use crate::sync::Arc;
 
@@ -40,6 +42,46 @@ pub(crate) fn dynamic_dir(name: &CStr, parent: Option<Arc<Self>>) -> Self {
         }
     }
 
+    /// # Safety
+    ///
+    /// * `data` must outlive the returned `Entry`.
+    pub(crate) unsafe fn dynamic_file<T>(
+        name: &CStr,
+        parent: Arc<Self>,
+        data: &T,
+        file_ops: &'static FileOps<T>,
+    ) -> 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 invariant.
+        // * The caller guarantees that `data` will outlive the returned `Entry`.
+        // * The guarantees on `FileOps` assert the vtable will be compatible with the data we have
+        //   provided.
+        let entry = unsafe {
+            bindings::debugfs_create_file_full(
+                name.as_char_ptr(),
+                file_ops.mode(),
+                parent.as_ptr(),
+                core::ptr::from_ref(data) as *mut c_void,
+                core::ptr::null(),
+                &**file_ops,
+            )
+        };
+
+        Entry {
+            entry,
+            _parent: Some(parent),
+        }
+    }
+
+    /// Constructs a placeholder DebugFS [`Entry`].
+    pub(crate) fn empty() -> Self {
+        Self {
+            entry: core::ptr::null_mut(),
+            _parent: None,
+        }
+    }
+
     /// Returns the pointer representation of the DebugFS directory.
     ///
     /// # Guarantees
diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs
new file mode 100644
index 0000000000000000000000000000000000000000..134ac26e80f2e5b9cae53ed5a00462af7ce1aa38
--- /dev/null
+++ b/rust/kernel/debugfs/file_ops.rs
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+use super::Render;
+use crate::prelude::*;
+use crate::seq_file::SeqFile;
+use crate::seq_print;
+use core::fmt::{Display, Formatter, Result};
+use core::marker::PhantomData;
+
+#[cfg(CONFIG_DEBUG_FS)]
+use core::ops::Deref;
+
+/// # Invariant
+///
+/// `FileOps<T>` will always contain an `operations` which is safe to use for a file backed
+/// off an inode which has a pointer to a `T` in its private data that is safe to convert
+/// into a reference.
+pub(super) struct FileOps<T> {
+    #[cfg(CONFIG_DEBUG_FS)]
+    operations: bindings::file_operations,
+    #[cfg(CONFIG_DEBUG_FS)]
+    mode: u16,
+    _phantom: PhantomData<T>,
+}
+
+impl<T> FileOps<T> {
+    /// # Safety
+    /// The caller asserts that the provided `operations` is safe to use for a file whose
+    /// inode has a pointer to `T` in its private data that is safe to convert into a reference.
+    const unsafe fn new(operations: bindings::file_operations, mode: u16) -> Self {
+        Self {
+            #[cfg(CONFIG_DEBUG_FS)]
+            operations,
+            #[cfg(CONFIG_DEBUG_FS)]
+            mode,
+            _phantom: PhantomData,
+        }
+    }
+    #[cfg(CONFIG_DEBUG_FS)]
+    pub(crate) const fn mode(&self) -> u16 {
+        self.mode
+    }
+}
+
+#[cfg(CONFIG_DEBUG_FS)]
+impl<T> Deref for FileOps<T> {
+    type Target = bindings::file_operations;
+    fn deref(&self) -> &Self::Target {
+        &self.operations
+    }
+}
+
+struct RenderAdapter<T>(T);
+
+impl<'a, T: Render> Display for RenderAdapter<&'a T> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        self.0.render(f)
+    }
+}
+
+/// Implements `open` for `file_operations` via `single_open` to fill out a `seq_file`.
+///
+/// # Safety
+///
+/// * `inode`'s private pointer must point to a value of type `T` which will outlive the `inode`
+///   and will not have any unique references alias it during the call.
+/// * `file` must point to a live, not-yet-initialized file object.
+unsafe extern "C" fn render_open<T: Render + Sync>(
+    inode: *mut bindings::inode,
+    file: *mut bindings::file,
+) -> c_int {
+    // SAFETY: The caller ensures that `inode` is a valid pointer.
+    let data = unsafe { (*inode).i_private };
+    // SAFETY:
+    // * `file` is acceptable by caller precondition.
+    // * `print_act` will be called on a `seq_file` with private data set to the third argument,
+    //   so we meet its safety requirements.
+    // * The `data` pointer passed in the third argument is a valid `T` pointer that outlives
+    //   this call by caller preconditions.
+    unsafe { bindings::single_open(file, Some(render_act::<T>), data) }
+}
+
+/// Prints private data stashed in a seq_file to that seq file.
+///
+/// # Safety
+///
+/// `seq` must point to a live `seq_file` whose private data is a live pointer to a `T` which may
+/// not have any unique references alias it during the call.
+unsafe extern "C" fn render_act<T: Render + Sync>(
+    seq: *mut bindings::seq_file,
+    _: *mut c_void,
+) -> c_int {
+    // SAFETY: By caller precondition, this pointer is live, points to a value of type `T`, and
+    // there are not and will not be any unique references until we are done.
+    let data = unsafe { &*((*seq).private.cast::<T>()) };
+    // SAFETY: By caller precondition, `seq_file` points to a live `seq_file`, so we can lift
+    // it.
+    let seq_file = unsafe { SeqFile::from_raw(seq) };
+    seq_print!(seq_file, "{}", RenderAdapter(data));
+    0
+}
+
+// Work around lack of generic const items.
+pub(crate) trait ReadFile<T> {
+    const FILE_OPS: FileOps<T>;
+}
+
+impl<T: Render + Sync> ReadFile<T> for T {
+    const FILE_OPS: FileOps<T> = {
+        let operations = bindings::file_operations {
+            read: Some(bindings::seq_read),
+            llseek: Some(bindings::seq_lseek),
+            release: Some(bindings::single_release),
+            open: Some(render_open::<Self>),
+            // SAFETY: `file_operations` supports zeroes in all fields.
+            ..unsafe { core::mem::zeroed() }
+        };
+        // SAFETY: `operations` is all stock `seq_file` implementations except for `render_open`.
+        // `open`'s only requirement beyond what is provided to all open functions is that the
+        // inode's data pointer must point to a `T` that will outlive it, which matches the
+        // `FileOps` requirements.
+        unsafe { FileOps::new(operations, 0o400) }
+    };
+}
diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2939e18e3dda39571cd7255505e5f605f0e3d154
--- /dev/null
+++ b/rust/kernel/debugfs/traits.rs
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Google LLC.
+
+//! Traits for rendering or updating values exported to DebugFS.
+
+use crate::sync::Mutex;
+use core::fmt::{self, Debug, Formatter};
+
+/// A trait for types that can be rendered into a string.
+///
+/// This works very similarly to `Debug`, and is automatically implemented if `Debug` is
+/// implemented for a type. It is also implemented for any renderable type inside a `Mutex`.
+pub trait Render {
+    /// Formats the value using the given formatter.
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result;
+}
+
+impl<T: Render> Render for Mutex<T> {
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        self.lock().render(f)
+    }
+}
+
+impl<T: Debug> Render for T {
+    fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        writeln!(f, "{self:?}")
+    }
+}

-- 
2.51.0.rc1.167.g924127e9c0-goog


  parent reply	other threads:[~2025-08-19 22:53 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-08-19 22:53 [PATCH v10 0/7] rust: DebugFS Bindings Matthew Maurer
2025-08-19 22:53 ` [PATCH v10 1/7] rust: debugfs: Add initial support for directories Matthew Maurer
2025-08-26 15:39   ` Danilo Krummrich
2025-08-19 22:53 ` Matthew Maurer [this message]
2025-08-26 18:45   ` [PATCH v10 2/7] rust: debugfs: Add support for read-only files Danilo Krummrich
2025-08-19 22:53 ` [PATCH v10 3/7] rust: debugfs: Add support for writable files Matthew Maurer
2025-08-26 19:38   ` Danilo Krummrich
2025-08-19 22:53 ` [PATCH v10 4/7] rust: debugfs: Add support for callback-based files Matthew Maurer
2025-08-19 22:53 ` [PATCH v10 5/7] samples: rust: Add debugfs sample driver Matthew Maurer
2025-08-20  0:34   ` Danilo Krummrich
2025-08-20  0:40     ` Matthew Maurer
2025-08-20  0:42       ` Matthew Maurer
2025-08-20  7:46       ` Benno Lossin
2025-08-19 22:53 ` [PATCH v10 6/7] rust: debugfs: Add support for scoped directories Matthew Maurer
2025-08-19 22:53 ` [PATCH v10 7/7] samples: rust: Add scoped debugfs sample driver Matthew Maurer
2025-08-19 23:14 ` [PATCH v10 0/7] rust: DebugFS Bindings Matthew Maurer
2025-08-25 11:51 ` Dirk Behme

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=20250819-debugfs-rust-v10-2-86e20f3cf3bb@google.com \
    --to=mmaurer@google.com \
    --cc=a.hindborg@kernel.org \
    --cc=alex.gaynor@gmail.com \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun.feng@gmail.com \
    --cc=dakr@kernel.org \
    --cc=dirk.behme@de.bosch.com \
    --cc=gary@garyguo.net \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=lossin@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 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).