mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/herbert/cryptodev-2.6.git
synced 2026-04-21 04:53:46 -04:00
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 <mmaurer@google.com> Tested-by: Dirk Behme <dirk.behme@de.bosch.com> Acked-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> 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 <dakr@kernel.org>
This commit is contained in:
committed by
Danilo Krummrich
parent
7f201ca18c
commit
5e40b591cb
@@ -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::Writer;
|
||||
|
||||
mod file_ops;
|
||||
use file_ops::{FileOps, ReadFile};
|
||||
#[cfg(CONFIG_DEBUG_FS)]
|
||||
mod entry;
|
||||
#[cfg(CONFIG_DEBUG_FS)]
|
||||
@@ -53,6 +59,34 @@ impl Dir {
|
||||
Self()
|
||||
}
|
||||
|
||||
/// Creates a DebugFS file which will own the data produced by the initializer provided in
|
||||
/// `data`.
|
||||
fn create_file<'a, T, E: 'a>(
|
||||
&'a self,
|
||||
name: &'a CStr,
|
||||
data: impl PinInit<T, E> + 'a,
|
||||
file_ops: &'static FileOps<T>,
|
||||
) -> impl PinInit<File<T>, E> + 'a
|
||||
where
|
||||
T: Sync + 'static,
|
||||
{
|
||||
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 +113,116 @@ impl Dir {
|
||||
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 [`Writer::write`] 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, E: 'a>(
|
||||
&'a self,
|
||||
name: &'a CStr,
|
||||
data: impl PinInit<T, E> + 'a,
|
||||
) -> impl PinInit<File<T>, E> + 'a
|
||||
where
|
||||
T: Writer + Send + Sync + 'static,
|
||||
{
|
||||
let file_ops = &<T as ReadFile<_>>::FILE_OPS;
|
||||
self.create_file(name, data, file_ops)
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_data]
|
||||
/// Handle to a DebugFS scope, which ensures that attached `data` will outlive the provided
|
||||
/// [`Entry`] without moving.
|
||||
/// Currently, this is used to back [`File`] so that its `read` and/or `write` implementations
|
||||
/// can assume that their backing data is still alive.
|
||||
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<'b, T: 'b> Scope<T> {
|
||||
fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
|
||||
where
|
||||
F: for<'a> FnOnce(&'a T) + 'b,
|
||||
{
|
||||
try_pin_init! {
|
||||
Self {
|
||||
data <- data,
|
||||
_pin: PhantomPinned
|
||||
} ? E
|
||||
}
|
||||
.pin_chain(|scope| {
|
||||
init(&scope.data);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(CONFIG_DEBUG_FS)]
|
||||
impl<'b, T: 'b> 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<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
|
||||
where
|
||||
F: for<'a> FnOnce(&'a T) -> Entry + '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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user