Skip to content

Commit 5e40b59

Browse files
maurerDanilo Krummrich
authored andcommitted
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>
1 parent 7f201ca commit 5e40b59

4 files changed

Lines changed: 350 additions & 1 deletion

File tree

rust/kernel/debugfs.rs

Lines changed: 147 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@
88
// When DebugFS is disabled, many parameters are dead. Linting for this isn't helpful.
99
#![cfg_attr(not(CONFIG_DEBUG_FS), allow(unused_variables))]
1010

11-
#[cfg(CONFIG_DEBUG_FS)]
1211
use crate::prelude::*;
1312
use crate::str::CStr;
1413
#[cfg(CONFIG_DEBUG_FS)]
1514
use crate::sync::Arc;
15+
use core::marker::PhantomPinned;
16+
use core::ops::Deref;
17+
18+
mod traits;
19+
pub use traits::Writer;
1620

21+
mod file_ops;
22+
use file_ops::{FileOps, ReadFile};
1723
#[cfg(CONFIG_DEBUG_FS)]
1824
mod entry;
1925
#[cfg(CONFIG_DEBUG_FS)]
@@ -53,6 +59,34 @@ impl Dir {
5359
Self()
5460
}
5561

62+
/// Creates a DebugFS file which will own the data produced by the initializer provided in
63+
/// `data`.
64+
fn create_file<'a, T, E: 'a>(
65+
&'a self,
66+
name: &'a CStr,
67+
data: impl PinInit<T, E> + 'a,
68+
file_ops: &'static FileOps<T>,
69+
) -> impl PinInit<File<T>, E> + 'a
70+
where
71+
T: Sync + 'static,
72+
{
73+
let scope = Scope::<T>::new(data, move |data| {
74+
#[cfg(CONFIG_DEBUG_FS)]
75+
if let Some(parent) = &self.0 {
76+
// SAFETY: Because data derives from a scope, and our entry will be dropped before
77+
// the data is dropped, it is guaranteed to outlive the entry we return.
78+
unsafe { Entry::dynamic_file(name, parent.clone(), data, file_ops) }
79+
} else {
80+
Entry::empty()
81+
}
82+
});
83+
try_pin_init! {
84+
File {
85+
scope <- scope
86+
} ? E
87+
}
88+
}
89+
5690
/// Create a new directory in DebugFS at the root.
5791
///
5892
/// # Examples
@@ -79,4 +113,116 @@ impl Dir {
79113
pub fn subdir(&self, name: &CStr) -> Self {
80114
Dir::create(name, Some(self))
81115
}
116+
117+
/// Creates a read-only file in this directory.
118+
///
119+
/// The file's contents are produced by invoking [`Writer::write`] on the value initialized by
120+
/// `data`.
121+
///
122+
/// # Examples
123+
///
124+
/// ```
125+
/// # use kernel::c_str;
126+
/// # use kernel::debugfs::Dir;
127+
/// # use kernel::prelude::*;
128+
/// # let dir = Dir::new(c_str!("my_debugfs_dir"));
129+
/// let file = KBox::pin_init(dir.read_only_file(c_str!("foo"), 200), GFP_KERNEL)?;
130+
/// // "my_debugfs_dir/foo" now contains the number 200.
131+
/// // The file is removed when `file` is dropped.
132+
/// # Ok::<(), Error>(())
133+
/// ```
134+
pub fn read_only_file<'a, T, E: 'a>(
135+
&'a self,
136+
name: &'a CStr,
137+
data: impl PinInit<T, E> + 'a,
138+
) -> impl PinInit<File<T>, E> + 'a
139+
where
140+
T: Writer + Send + Sync + 'static,
141+
{
142+
let file_ops = &<T as ReadFile<_>>::FILE_OPS;
143+
self.create_file(name, data, file_ops)
144+
}
145+
}
146+
147+
#[pin_data]
148+
/// Handle to a DebugFS scope, which ensures that attached `data` will outlive the provided
149+
/// [`Entry`] without moving.
150+
/// Currently, this is used to back [`File`] so that its `read` and/or `write` implementations
151+
/// can assume that their backing data is still alive.
152+
struct Scope<T> {
153+
// This order is load-bearing for drops - `_entry` must be dropped before `data`.
154+
#[cfg(CONFIG_DEBUG_FS)]
155+
_entry: Entry,
156+
#[pin]
157+
data: T,
158+
// Even if `T` is `Unpin`, we still can't allow it to be moved.
159+
#[pin]
160+
_pin: PhantomPinned,
161+
}
162+
163+
#[pin_data]
164+
/// Handle to a DebugFS file, owning its backing data.
165+
///
166+
/// When dropped, the DebugFS file will be removed and the attached data will be dropped.
167+
pub struct File<T> {
168+
#[pin]
169+
scope: Scope<T>,
170+
}
171+
172+
#[cfg(not(CONFIG_DEBUG_FS))]
173+
impl<'b, T: 'b> Scope<T> {
174+
fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
175+
where
176+
F: for<'a> FnOnce(&'a T) + 'b,
177+
{
178+
try_pin_init! {
179+
Self {
180+
data <- data,
181+
_pin: PhantomPinned
182+
} ? E
183+
}
184+
.pin_chain(|scope| {
185+
init(&scope.data);
186+
Ok(())
187+
})
188+
}
189+
}
190+
191+
#[cfg(CONFIG_DEBUG_FS)]
192+
impl<'b, T: 'b> Scope<T> {
193+
fn entry_mut(self: Pin<&mut Self>) -> &mut Entry {
194+
// SAFETY: _entry is not structurally pinned.
195+
unsafe { &mut Pin::into_inner_unchecked(self)._entry }
196+
}
197+
198+
fn new<E: 'b, F>(data: impl PinInit<T, E> + 'b, init: F) -> impl PinInit<Self, E> + 'b
199+
where
200+
F: for<'a> FnOnce(&'a T) -> Entry + 'b,
201+
{
202+
try_pin_init! {
203+
Self {
204+
_entry: Entry::empty(),
205+
data <- data,
206+
_pin: PhantomPinned
207+
} ? E
208+
}
209+
.pin_chain(|scope| {
210+
*scope.entry_mut() = init(&scope.data);
211+
Ok(())
212+
})
213+
}
214+
}
215+
216+
impl<T> Deref for Scope<T> {
217+
type Target = T;
218+
fn deref(&self) -> &T {
219+
&self.data
220+
}
221+
}
222+
223+
impl<T> Deref for File<T> {
224+
type Target = T;
225+
fn deref(&self) -> &T {
226+
&self.scope
227+
}
82228
}

rust/kernel/debugfs/entry.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: GPL-2.0
22
// Copyright (C) 2025 Google LLC.
33

4+
use crate::debugfs::file_ops::FileOps;
5+
use crate::ffi::c_void;
46
use crate::str::CStr;
57
use crate::sync::Arc;
68

@@ -40,6 +42,46 @@ impl Entry {
4042
}
4143
}
4244

45+
/// # Safety
46+
///
47+
/// * `data` must outlive the returned `Entry`.
48+
pub(crate) unsafe fn dynamic_file<T>(
49+
name: &CStr,
50+
parent: Arc<Self>,
51+
data: &T,
52+
file_ops: &'static FileOps<T>,
53+
) -> Self {
54+
// SAFETY: The invariants of this function's arguments ensure the safety of this call.
55+
// * `name` is a valid C string by the invariants of `&CStr`.
56+
// * `parent.as_ptr()` is a pointer to a valid `dentry` by invariant.
57+
// * The caller guarantees that `data` will outlive the returned `Entry`.
58+
// * The guarantees on `FileOps` assert the vtable will be compatible with the data we have
59+
// provided.
60+
let entry = unsafe {
61+
bindings::debugfs_create_file_full(
62+
name.as_char_ptr(),
63+
file_ops.mode(),
64+
parent.as_ptr(),
65+
core::ptr::from_ref(data) as *mut c_void,
66+
core::ptr::null(),
67+
&**file_ops,
68+
)
69+
};
70+
71+
Entry {
72+
entry,
73+
_parent: Some(parent),
74+
}
75+
}
76+
77+
/// Constructs a placeholder DebugFS [`Entry`].
78+
pub(crate) fn empty() -> Self {
79+
Self {
80+
entry: core::ptr::null_mut(),
81+
_parent: None,
82+
}
83+
}
84+
4385
/// Returns the pointer representation of the DebugFS directory.
4486
///
4587
/// # Guarantees

rust/kernel/debugfs/file_ops.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
// Copyright (C) 2025 Google LLC.
3+
4+
use super::Writer;
5+
use crate::prelude::*;
6+
use crate::seq_file::SeqFile;
7+
use crate::seq_print;
8+
use core::fmt::{Display, Formatter, Result};
9+
use core::marker::PhantomData;
10+
11+
#[cfg(CONFIG_DEBUG_FS)]
12+
use core::ops::Deref;
13+
14+
/// # Invariant
15+
///
16+
/// `FileOps<T>` will always contain an `operations` which is safe to use for a file backed
17+
/// off an inode which has a pointer to a `T` in its private data that is safe to convert
18+
/// into a reference.
19+
pub(super) struct FileOps<T> {
20+
#[cfg(CONFIG_DEBUG_FS)]
21+
operations: bindings::file_operations,
22+
#[cfg(CONFIG_DEBUG_FS)]
23+
mode: u16,
24+
_phantom: PhantomData<T>,
25+
}
26+
27+
impl<T> FileOps<T> {
28+
/// # Safety
29+
///
30+
/// The caller asserts that the provided `operations` is safe to use for a file whose
31+
/// inode has a pointer to `T` in its private data that is safe to convert into a reference.
32+
const unsafe fn new(operations: bindings::file_operations, mode: u16) -> Self {
33+
Self {
34+
#[cfg(CONFIG_DEBUG_FS)]
35+
operations,
36+
#[cfg(CONFIG_DEBUG_FS)]
37+
mode,
38+
_phantom: PhantomData,
39+
}
40+
}
41+
42+
#[cfg(CONFIG_DEBUG_FS)]
43+
pub(crate) const fn mode(&self) -> u16 {
44+
self.mode
45+
}
46+
}
47+
48+
#[cfg(CONFIG_DEBUG_FS)]
49+
impl<T> Deref for FileOps<T> {
50+
type Target = bindings::file_operations;
51+
52+
fn deref(&self) -> &Self::Target {
53+
&self.operations
54+
}
55+
}
56+
57+
struct WriterAdapter<T>(T);
58+
59+
impl<'a, T: Writer> Display for WriterAdapter<&'a T> {
60+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
61+
self.0.write(f)
62+
}
63+
}
64+
65+
/// Implements `open` for `file_operations` via `single_open` to fill out a `seq_file`.
66+
///
67+
/// # Safety
68+
///
69+
/// * `inode`'s private pointer must point to a value of type `T` which will outlive the `inode`
70+
/// and will not have any unique references alias it during the call.
71+
/// * `file` must point to a live, not-yet-initialized file object.
72+
unsafe extern "C" fn writer_open<T: Writer + Sync>(
73+
inode: *mut bindings::inode,
74+
file: *mut bindings::file,
75+
) -> c_int {
76+
// SAFETY: The caller ensures that `inode` is a valid pointer.
77+
let data = unsafe { (*inode).i_private };
78+
// SAFETY:
79+
// * `file` is acceptable by caller precondition.
80+
// * `print_act` will be called on a `seq_file` with private data set to the third argument,
81+
// so we meet its safety requirements.
82+
// * The `data` pointer passed in the third argument is a valid `T` pointer that outlives
83+
// this call by caller preconditions.
84+
unsafe { bindings::single_open(file, Some(writer_act::<T>), data) }
85+
}
86+
87+
/// Prints private data stashed in a seq_file to that seq file.
88+
///
89+
/// # Safety
90+
///
91+
/// `seq` must point to a live `seq_file` whose private data is a valid pointer to a `T` which may
92+
/// not have any unique references alias it during the call.
93+
unsafe extern "C" fn writer_act<T: Writer + Sync>(
94+
seq: *mut bindings::seq_file,
95+
_: *mut c_void,
96+
) -> c_int {
97+
// SAFETY: By caller precondition, this pointer is valid pointer to a `T`, and
98+
// there are not and will not be any unique references until we are done.
99+
let data = unsafe { &*((*seq).private.cast::<T>()) };
100+
// SAFETY: By caller precondition, `seq_file` points to a live `seq_file`, so we can lift
101+
// it.
102+
let seq_file = unsafe { SeqFile::from_raw(seq) };
103+
seq_print!(seq_file, "{}", WriterAdapter(data));
104+
0
105+
}
106+
107+
// Work around lack of generic const items.
108+
pub(crate) trait ReadFile<T> {
109+
const FILE_OPS: FileOps<T>;
110+
}
111+
112+
impl<T: Writer + Sync> ReadFile<T> for T {
113+
const FILE_OPS: FileOps<T> = {
114+
let operations = bindings::file_operations {
115+
read: Some(bindings::seq_read),
116+
llseek: Some(bindings::seq_lseek),
117+
release: Some(bindings::single_release),
118+
open: Some(writer_open::<Self>),
119+
// SAFETY: `file_operations` supports zeroes in all fields.
120+
..unsafe { core::mem::zeroed() }
121+
};
122+
// SAFETY: `operations` is all stock `seq_file` implementations except for `writer_open`.
123+
// `open`'s only requirement beyond what is provided to all open functions is that the
124+
// inode's data pointer must point to a `T` that will outlive it, which matches the
125+
// `FileOps` requirements.
126+
unsafe { FileOps::new(operations, 0o400) }
127+
};
128+
}

rust/kernel/debugfs/traits.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
// Copyright (C) 2025 Google LLC.
3+
4+
//! Traits for rendering or updating values exported to DebugFS.
5+
6+
use crate::sync::Mutex;
7+
use core::fmt::{self, Debug, Formatter};
8+
9+
/// A trait for types that can be written into a string.
10+
///
11+
/// This works very similarly to `Debug`, and is automatically implemented if `Debug` is
12+
/// implemented for a type. It is also implemented for any writable type inside a `Mutex`.
13+
///
14+
/// The derived implementation of `Debug` [may
15+
/// change](https://doc.rust-lang.org/std/fmt/trait.Debug.html#stability)
16+
/// between Rust versions, so if stability is key for your use case, please implement `Writer`
17+
/// explicitly instead.
18+
pub trait Writer {
19+
/// Formats the value using the given formatter.
20+
fn write(&self, f: &mut Formatter<'_>) -> fmt::Result;
21+
}
22+
23+
impl<T: Writer> Writer for Mutex<T> {
24+
fn write(&self, f: &mut Formatter<'_>) -> fmt::Result {
25+
self.lock().write(f)
26+
}
27+
}
28+
29+
impl<T: Debug> Writer for T {
30+
fn write(&self, f: &mut Formatter<'_>) -> fmt::Result {
31+
writeln!(f, "{self:?}")
32+
}
33+
}

0 commit comments

Comments
 (0)