From 7f201ca18c825592e392596a2fca2374dd2a4dfe Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Thu, 4 Sep 2025 21:13:52 +0000 Subject: rust: debugfs: Add initial support for directories Adds a `debugfs::Dir` type that can be used to create and remove DebugFS directories. The `Dir` handle automatically cleans up the directory on `Drop`. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250904-debugfs-rust-v11-1-7d12a165685a@google.com Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs/entry.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 rust/kernel/debugfs/entry.rs (limited to 'rust/kernel/debugfs') diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs new file mode 100644 index 000000000000..d2fba0e65e20 --- /dev/null +++ b/rust/kernel/debugfs/entry.rs @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +use crate::str::CStr; +use crate::sync::Arc; + +/// Owning handle to a DebugFS entry. +/// +/// # Invariants +/// +/// The wrapped pointer will always be `NULL`, an error, or an owned DebugFS `dentry`. +pub(crate) struct Entry { + entry: *mut bindings::dentry, + // If we were created with an owning parent, this is the keep-alive + _parent: Option>, +} + +// SAFETY: [`Entry`] is just a `dentry` under the hood, which the API promises can be transferred +// between threads. +unsafe impl Send for Entry {} + +// SAFETY: All the C functions we call on the `dentry` pointer are threadsafe. +unsafe impl Sync for Entry {} + +impl Entry { + pub(crate) fn dynamic_dir(name: &CStr, parent: 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_create_dir` handles `NULL` pointers correctly. + let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) }; + + Entry { + entry, + _parent: parent, + } + } + + /// Returns the pointer representation of the DebugFS directory. + /// + /// # Guarantees + /// + /// Due to the type invariant, the value returned from this function will always be an error + /// code, NULL, or a live DebugFS directory. If it is live, it will remain live at least as + /// long as this entry lives. + pub(crate) fn as_ptr(&self) -> *mut bindings::dentry { + self.entry + } +} + +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()) } + } +} -- cgit v1.2.3 From 5e40b591cb46c0379d5406fa5548c9b2a3801353 Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Thu, 4 Sep 2025 21:13:53 +0000 Subject: rust: debugfs: Add support for read-only files 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 `Writer` trait. The file's content is generated by the `Writer` implementation, and the file is automatically removed when the returned `File` handle is dropped. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250904-debugfs-rust-v11-2-7d12a165685a@google.com [ Fixup build failure when CONFIG_DEBUGFS=n. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs/entry.rs | 42 +++++++++++++ rust/kernel/debugfs/file_ops.rs | 128 ++++++++++++++++++++++++++++++++++++++++ rust/kernel/debugfs/traits.rs | 33 +++++++++++ 3 files changed, 203 insertions(+) create mode 100644 rust/kernel/debugfs/file_ops.rs create mode 100644 rust/kernel/debugfs/traits.rs (limited to 'rust/kernel/debugfs') diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs index d2fba0e65e20..227fa50b7a79 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 @@ impl Entry { } } + /// # Safety + /// + /// * `data` must outlive the returned `Entry`. + pub(crate) unsafe fn dynamic_file( + name: &CStr, + parent: Arc, + data: &T, + file_ops: &'static FileOps, + ) -> 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 000000000000..c2fbef96580e --- /dev/null +++ b/rust/kernel/debugfs/file_ops.rs @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +use super::Writer; +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` 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 { + #[cfg(CONFIG_DEBUG_FS)] + operations: bindings::file_operations, + #[cfg(CONFIG_DEBUG_FS)] + mode: u16, + _phantom: PhantomData, +} + +impl FileOps { + /// # 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 Deref for FileOps { + type Target = bindings::file_operations; + + fn deref(&self) -> &Self::Target { + &self.operations + } +} + +struct WriterAdapter(T); + +impl<'a, T: Writer> Display for WriterAdapter<&'a T> { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + self.0.write(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 writer_open( + 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(writer_act::), 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 valid pointer to a `T` which may +/// not have any unique references alias it during the call. +unsafe extern "C" fn writer_act( + seq: *mut bindings::seq_file, + _: *mut c_void, +) -> c_int { + // SAFETY: By caller precondition, this pointer is valid pointer to a `T`, and + // there are not and will not be any unique references until we are done. + let data = unsafe { &*((*seq).private.cast::()) }; + // 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, "{}", WriterAdapter(data)); + 0 +} + +// Work around lack of generic const items. +pub(crate) trait ReadFile { + const FILE_OPS: FileOps; +} + +impl ReadFile for T { + const FILE_OPS: FileOps = { + let operations = bindings::file_operations { + read: Some(bindings::seq_read), + llseek: Some(bindings::seq_lseek), + release: Some(bindings::single_release), + open: Some(writer_open::), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + // SAFETY: `operations` is all stock `seq_file` implementations except for `writer_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 000000000000..0e6e461324de --- /dev/null +++ b/rust/kernel/debugfs/traits.rs @@ -0,0 +1,33 @@ +// 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 written 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 writable type inside a `Mutex`. +/// +/// The derived implementation of `Debug` [may +/// change](https://doc.rust-lang.org/std/fmt/trait.Debug.html#stability) +/// between Rust versions, so if stability is key for your use case, please implement `Writer` +/// explicitly instead. +pub trait Writer { + /// Formats the value using the given formatter. + fn write(&self, f: &mut Formatter<'_>) -> fmt::Result; +} + +impl Writer for Mutex { + fn write(&self, f: &mut Formatter<'_>) -> fmt::Result { + self.lock().write(f) + } +} + +impl Writer for T { + fn write(&self, f: &mut Formatter<'_>) -> fmt::Result { + writeln!(f, "{self:?}") + } +} -- cgit v1.2.3 From 839dc1d15b9ba5318ed145b20efffcfa91c02a3d Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Thu, 4 Sep 2025 21:13:54 +0000 Subject: rust: debugfs: Add support for writable files Extends the `debugfs` API to support creating writable files. This is done via the `Dir::write_only_file` and `Dir::read_write_file` methods, which take a data object that implements the `Reader` trait. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250904-debugfs-rust-v11-3-7d12a165685a@google.com [ Fix up Result<()> -> Result. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs/file_ops.rs | 113 +++++++++++++++++++++++++++++++++++++++- rust/kernel/debugfs/traits.rs | 69 ++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 1 deletion(-) (limited to 'rust/kernel/debugfs') diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs index c2fbef96580e..2060c8d14d83 100644 --- a/rust/kernel/debugfs/file_ops.rs +++ b/rust/kernel/debugfs/file_ops.rs @@ -1,10 +1,11 @@ // SPDX-License-Identifier: GPL-2.0 // Copyright (C) 2025 Google LLC. -use super::Writer; +use super::{Reader, Writer}; use crate::prelude::*; use crate::seq_file::SeqFile; use crate::seq_print; +use crate::uaccess::UserSlice; use core::fmt::{Display, Formatter, Result}; use core::marker::PhantomData; @@ -126,3 +127,113 @@ impl ReadFile for T { unsafe { FileOps::new(operations, 0o400) } }; } + +fn read(data: &T, buf: *const c_char, count: usize) -> isize { + let mut reader = UserSlice::new(UserPtr::from_ptr(buf as *mut c_void), count).reader(); + + if let Err(e) = data.read_from_slice(&mut reader) { + return e.to_errno() as isize; + } + + count as isize +} + +/// # Safety +/// +/// `file` must be a valid pointer to a `file` struct. +/// The `private_data` of the file must contain a valid pointer to a `seq_file` whose +/// `private` data in turn points to a `T` that implements `Reader`. +/// `buf` must be a valid user-space buffer. +pub(crate) unsafe extern "C" fn write( + file: *mut bindings::file, + buf: *const c_char, + count: usize, + _ppos: *mut bindings::loff_t, +) -> isize { + // SAFETY: The file was opened with `single_open`, which sets `private_data` to a `seq_file`. + let seq = unsafe { &mut *((*file).private_data.cast::()) }; + // SAFETY: By caller precondition, this pointer is live and points to a value of type `T`. + let data = unsafe { &*(seq.private as *const T) }; + read(data, buf, count) +} + +// A trait to get the file operations for a type. +pub(crate) trait ReadWriteFile { + const FILE_OPS: FileOps; +} + +impl ReadWriteFile for T { + const FILE_OPS: FileOps = { + let operations = bindings::file_operations { + open: Some(writer_open::), + read: Some(bindings::seq_read), + write: Some(write::), + llseek: Some(bindings::seq_lseek), + release: Some(bindings::single_release), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + // SAFETY: `operations` is all stock `seq_file` implementations except for `writer_open` + // and `write`. + // `writer_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. + // `write` only requires that the file's private data pointer points to `seq_file` + // which points to a `T` that will outlive it, which matches what `writer_open` + // provides. + unsafe { FileOps::new(operations, 0o600) } + }; +} + +/// # Safety +/// +/// `inode` must be a valid pointer to an `inode` struct. +/// `file` must be a valid pointer to a `file` struct. +unsafe extern "C" fn write_only_open( + inode: *mut bindings::inode, + file: *mut bindings::file, +) -> c_int { + // SAFETY: The caller ensures that `inode` and `file` are valid pointers. + unsafe { (*file).private_data = (*inode).i_private }; + 0 +} + +/// # Safety +/// +/// * `file` must be a valid pointer to a `file` struct. +/// * The `private_data` of the file must contain a valid pointer to a `T` that implements +/// `Reader`. +/// * `buf` must be a valid user-space buffer. +pub(crate) unsafe extern "C" fn write_only_write( + file: *mut bindings::file, + buf: *const c_char, + count: usize, + _ppos: *mut bindings::loff_t, +) -> isize { + // SAFETY: The caller ensures that `file` is a valid pointer and that `private_data` holds a + // valid pointer to `T`. + let data = unsafe { &*((*file).private_data as *const T) }; + read(data, buf, count) +} + +pub(crate) trait WriteFile { + const FILE_OPS: FileOps; +} + +impl WriteFile for T { + const FILE_OPS: FileOps = { + let operations = bindings::file_operations { + open: Some(write_only_open), + write: Some(write_only_write::), + llseek: Some(bindings::noop_llseek), + // SAFETY: `file_operations` supports zeroes in all fields. + ..unsafe { core::mem::zeroed() } + }; + // SAFETY: + // * `write_only_open` populates the file private data with the inode private data + // * `write_only_write`'s only requirement is that the private data of the file point to + // a `T` and be legal to convert to a shared reference, which `write_only_open` + // satisfies. + unsafe { FileOps::new(operations, 0o200) } + }; +} diff --git a/rust/kernel/debugfs/traits.rs b/rust/kernel/debugfs/traits.rs index 0e6e461324de..ab009eb254b3 100644 --- a/rust/kernel/debugfs/traits.rs +++ b/rust/kernel/debugfs/traits.rs @@ -3,8 +3,15 @@ //! Traits for rendering or updating values exported to DebugFS. +use crate::prelude::*; use crate::sync::Mutex; +use crate::uaccess::UserSliceReader; use core::fmt::{self, Debug, Formatter}; +use core::str::FromStr; +use core::sync::atomic::{ + AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, AtomicU64, + AtomicU8, AtomicUsize, Ordering, +}; /// A trait for types that can be written into a string. /// @@ -31,3 +38,65 @@ impl Writer for T { writeln!(f, "{self:?}") } } + +/// A trait for types that can be updated from a user slice. +/// +/// This works similarly to `FromStr`, but operates on a `UserSliceReader` rather than a &str. +/// +/// It is automatically implemented for all atomic integers, or any type that implements `FromStr` +/// wrapped in a `Mutex`. +pub trait Reader { + /// Updates the value from the given user slice. + fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result; +} + +impl Reader for Mutex { + fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result { + let mut buf = [0u8; 128]; + if reader.len() > buf.len() { + return Err(EINVAL); + } + let n = reader.len(); + reader.read_slice(&mut buf[..n])?; + + let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?; + let val = s.trim().parse::().map_err(|_| EINVAL)?; + *self.lock() = val; + Ok(()) + } +} + +macro_rules! impl_reader_for_atomic { + ($(($atomic_type:ty, $int_type:ty)),*) => { + $( + impl Reader for $atomic_type { + fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result { + let mut buf = [0u8; 21]; // Enough for a 64-bit number. + if reader.len() > buf.len() { + return Err(EINVAL); + } + let n = reader.len(); + reader.read_slice(&mut buf[..n])?; + + let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?; + let val = s.trim().parse::<$int_type>().map_err(|_| EINVAL)?; + self.store(val, Ordering::Relaxed); + Ok(()) + } + } + )* + }; +} + +impl_reader_for_atomic!( + (AtomicI16, i16), + (AtomicI32, i32), + (AtomicI64, i64), + (AtomicI8, i8), + (AtomicIsize, isize), + (AtomicU16, u16), + (AtomicU32, u32), + (AtomicU64, u64), + (AtomicU8, u8), + (AtomicUsize, usize) +); -- cgit v1.2.3 From 40ecc49466c8b7f9518c5fbbcfb24cb7e26c36c7 Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Thu, 4 Sep 2025 21:13:55 +0000 Subject: rust: debugfs: Add support for callback-based files Extends the `debugfs` API to support creating files with content generated and updated by callbacks. This is done via the `read_callback_file`, `write_callback_file`, and `read_write_callback_file` methods. These methods allow for more flexible file definition, either because the type already has a `Writer` or `Reader` method that doesn't do what you'd like, or because you cannot implement it (e.g. because it's a type defined in another crate or a primitive type). Signed-off-by: Matthew Maurer Tested-by: Dirk Behme Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250904-debugfs-rust-v11-4-7d12a165685a@google.com [ Fix up Result<(), Error> -> Result. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs/callback_adapters.rs | 122 +++++++++++++++++++++++++++++++ rust/kernel/debugfs/file_ops.rs | 8 ++ 2 files changed, 130 insertions(+) create mode 100644 rust/kernel/debugfs/callback_adapters.rs (limited to 'rust/kernel/debugfs') diff --git a/rust/kernel/debugfs/callback_adapters.rs b/rust/kernel/debugfs/callback_adapters.rs new file mode 100644 index 000000000000..6c024230f676 --- /dev/null +++ b/rust/kernel/debugfs/callback_adapters.rs @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (C) 2025 Google LLC. + +//! Adapters which allow the user to supply a write or read implementation as a value rather +//! than a trait implementation. If provided, it will override the trait implementation. + +use super::{Reader, Writer}; +use crate::prelude::*; +use crate::uaccess::UserSliceReader; +use core::fmt; +use core::fmt::Formatter; +use core::marker::PhantomData; +use core::ops::Deref; + +/// # Safety +/// +/// To implement this trait, it must be safe to cast a `&Self` to a `&Inner`. +/// It is intended for use in unstacking adapters out of `FileOps` backings. +pub(crate) unsafe trait Adapter { + type Inner; +} + +/// Adapter to implement `Reader` via a callback with the same representation as `T`. +/// +/// * Layer it on top of `WriterAdapter` if you want to add a custom callback for `write`. +/// * Layer it on top of `NoWriter` to pass through any support present on the underlying type. +/// +/// # Invariants +/// +/// If an instance for `WritableAdapter<_, W>` is constructed, `W` is inhabited. +#[repr(transparent)] +pub(crate) struct WritableAdapter { + inner: D, + _writer: PhantomData, +} + +// SAFETY: Stripping off the adapter only removes constraints +unsafe impl Adapter for WritableAdapter { + type Inner = D; +} + +impl Writer for WritableAdapter { + fn write(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.write(fmt) + } +} + +impl Reader for WritableAdapter +where + W: Fn(&D::Target, &mut UserSliceReader) -> Result + Send + Sync + 'static, +{ + fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result { + // SAFETY: WritableAdapter<_, W> can only be constructed if W is inhabited + let w: &W = unsafe { materialize_zst() }; + w(self.inner.deref(), reader) + } +} + +/// Adapter to implement `Writer` via a callback with the same representation as `T`. +/// +/// # Invariants +/// +/// If an instance for `FormatAdapter<_, F>` is constructed, `F` is inhabited. +#[repr(transparent)] +pub(crate) struct FormatAdapter { + inner: D, + _formatter: PhantomData, +} + +impl Deref for FormatAdapter { + type Target = D; + fn deref(&self) -> &D { + &self.inner + } +} + +impl Writer for FormatAdapter +where + F: Fn(&D, &mut Formatter<'_>) -> fmt::Result + 'static, +{ + fn write(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + // SAFETY: FormatAdapter<_, F> can only be constructed if F is inhabited + let f: &F = unsafe { materialize_zst() }; + f(&self.inner, fmt) + } +} + +// SAFETY: Stripping off the adapter only removes constraints +unsafe impl Adapter for FormatAdapter { + type Inner = D; +} + +#[repr(transparent)] +pub(crate) struct NoWriter { + inner: D, +} + +// SAFETY: Stripping off the adapter only removes constraints +unsafe impl Adapter for NoWriter { + type Inner = D; +} + +impl Deref for NoWriter { + type Target = D; + fn deref(&self) -> &D { + &self.inner + } +} + +/// For types with a unique value, produce a static reference to it. +/// +/// # Safety +/// +/// The caller asserts that F is inhabited +unsafe fn materialize_zst() -> &'static F { + const { assert!(core::mem::size_of::() == 0) }; + let zst_dangle: core::ptr::NonNull = core::ptr::NonNull::dangling(); + // SAFETY: While the pointer is dangling, it is a dangling pointer to a ZST, based on the + // assertion above. The type is also inhabited, by the caller's assertion. This means + // we can materialize it. + unsafe { zst_dangle.as_ref() } +} diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs index 2060c8d14d83..50fead17b6f3 100644 --- a/rust/kernel/debugfs/file_ops.rs +++ b/rust/kernel/debugfs/file_ops.rs @@ -2,6 +2,7 @@ // Copyright (C) 2025 Google LLC. use super::{Reader, Writer}; +use crate::debugfs::callback_adapters::Adapter; use crate::prelude::*; use crate::seq_file::SeqFile; use crate::seq_print; @@ -46,6 +47,13 @@ impl FileOps { } } +impl FileOps { + pub(super) const fn adapt(&self) -> &FileOps { + // SAFETY: `Adapter` asserts that `T` can be legally cast to `T::Inner`. + unsafe { core::mem::transmute(self) } + } +} + #[cfg(CONFIG_DEBUG_FS)] impl Deref for FileOps { type Target = bindings::file_operations; -- cgit v1.2.3 From 5f0942581dd0218c4449dac0639cf362e943c302 Mon Sep 17 00:00:00 2001 From: Matthew Maurer Date: Thu, 4 Sep 2025 21:13:57 +0000 Subject: rust: debugfs: Add support for scoped directories Introduces the concept of a `ScopedDir`, which allows for the creation of debugfs directories and files that are tied to the lifetime of a particular data structure. This ensures that debugfs entries do not outlive the data they refer to. The new `Dir::scope` method creates a new directory that is owned by a `Scope` handle. All files and subdirectories created within this scope are automatically cleaned up when the `Scope` is dropped. Signed-off-by: Matthew Maurer Tested-by: Dirk Behme Acked-by: Greg Kroah-Hartman Link: https://lore.kernel.org/r/20250904-debugfs-rust-v11-6-7d12a165685a@google.com [ Fix up Result<(), Error> -> Result; fix spurious backtick in doc-comment. - Danilo ] Signed-off-by: Danilo Krummrich --- rust/kernel/debugfs/entry.rs | 73 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 67 insertions(+), 6 deletions(-) (limited to 'rust/kernel/debugfs') diff --git a/rust/kernel/debugfs/entry.rs b/rust/kernel/debugfs/entry.rs index 227fa50b7a79..f99402cd3ba0 100644 --- a/rust/kernel/debugfs/entry.rs +++ b/rust/kernel/debugfs/entry.rs @@ -5,26 +5,29 @@ use crate::debugfs::file_ops::FileOps; use crate::ffi::c_void; use crate::str::CStr; use crate::sync::Arc; +use core::marker::PhantomData; /// Owning handle to a DebugFS entry. /// /// # Invariants /// /// The wrapped pointer will always be `NULL`, an error, or an owned DebugFS `dentry`. -pub(crate) struct Entry { +pub(crate) struct Entry<'a> { entry: *mut bindings::dentry, // If we were created with an owning parent, this is the keep-alive - _parent: Option>, + _parent: Option>>, + // If we were created with a non-owning parent, this prevents us from outliving it + _phantom: PhantomData<&'a ()>, } // SAFETY: [`Entry`] is just a `dentry` under the hood, which the API promises can be transferred // between threads. -unsafe impl Send for Entry {} +unsafe impl Send for Entry<'_> {} // SAFETY: All the C functions we call on the `dentry` pointer are threadsafe. -unsafe impl Sync for Entry {} +unsafe impl Sync for Entry<'_> {} -impl Entry { +impl Entry<'static> { pub(crate) fn dynamic_dir(name: &CStr, parent: Option>) -> Self { let parent_ptr = match &parent { Some(entry) => entry.as_ptr(), @@ -39,6 +42,7 @@ impl Entry { Entry { entry, _parent: parent, + _phantom: PhantomData, } } @@ -71,14 +75,71 @@ impl Entry { Entry { entry, _parent: Some(parent), + _phantom: PhantomData, } } +} + +impl<'a> Entry<'a> { + pub(crate) fn dir(name: &CStr, parent: Option<&'a Entry<'_>>) -> 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` (because `parent` is a valid reference to an `Entry`). The lifetime `'a` + // ensures that the parent outlives this entry. + let entry = unsafe { bindings::debugfs_create_dir(name.as_char_ptr(), parent_ptr) }; + + Entry { + entry, + _parent: None, + _phantom: PhantomData, + } + } + + pub(crate) fn file( + name: &CStr, + parent: &'a Entry<'_>, + data: &'a T, + file_ops: &FileOps, + ) -> 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` because we have `&'a Entry`. + // * `data` is a valid pointer to `T` for lifetime `'a`. + // * The returned `Entry` has lifetime `'a`, so it cannot outlive `parent` or `data`. + // * The caller guarantees that `vtable` is compatible with `data`. + // * 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: None, + _phantom: PhantomData, + } + } +} +impl Entry<'_> { /// Constructs a placeholder DebugFS [`Entry`]. pub(crate) fn empty() -> Self { Self { entry: core::ptr::null_mut(), _parent: None, + _phantom: PhantomData, } } @@ -94,7 +155,7 @@ impl Entry { } } -impl Drop for Entry { +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. -- cgit v1.2.3