Skip to content

Commit 839dc1d

Browse files
maurerDanilo Krummrich
authored andcommitted
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 <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-3-7d12a165685a@google.com [ Fix up Result<()> -> Result. - Danilo ] Signed-off-by: Danilo Krummrich <dakr@kernel.org>
1 parent 5e40b59 commit 839dc1d

3 files changed

Lines changed: 216 additions & 3 deletions

File tree

rust/kernel/debugfs.rs

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ use core::marker::PhantomPinned;
1616
use core::ops::Deref;
1717

1818
mod traits;
19-
pub use traits::Writer;
19+
pub use traits::{Reader, Writer};
2020

2121
mod file_ops;
22-
use file_ops::{FileOps, ReadFile};
22+
use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};
2323
#[cfg(CONFIG_DEBUG_FS)]
2424
mod entry;
2525
#[cfg(CONFIG_DEBUG_FS)]
@@ -142,6 +142,39 @@ impl Dir {
142142
let file_ops = &<T as ReadFile<_>>::FILE_OPS;
143143
self.create_file(name, data, file_ops)
144144
}
145+
146+
/// Creates a read-write file in this directory.
147+
///
148+
/// Reading the file uses the [`Writer`] implementation.
149+
/// Writing to the file uses the [`Reader`] implementation.
150+
pub fn read_write_file<'a, T, E: 'a>(
151+
&'a self,
152+
name: &'a CStr,
153+
data: impl PinInit<T, E> + 'a,
154+
) -> impl PinInit<File<T>, E> + 'a
155+
where
156+
T: Writer + Reader + Send + Sync + 'static,
157+
{
158+
let file_ops = &<T as ReadWriteFile<_>>::FILE_OPS;
159+
self.create_file(name, data, file_ops)
160+
}
161+
162+
/// Creates a write-only file in this directory.
163+
///
164+
/// The file owns its backing data. Writing to the file uses the [`Reader`]
165+
/// implementation.
166+
///
167+
/// The file is removed when the returned [`File`] is dropped.
168+
pub fn write_only_file<'a, T, E: 'a>(
169+
&'a self,
170+
name: &'a CStr,
171+
data: impl PinInit<T, E> + 'a,
172+
) -> impl PinInit<File<T>, E> + 'a
173+
where
174+
T: Reader + Send + Sync + 'static,
175+
{
176+
self.create_file(name, data, &T::FILE_OPS)
177+
}
145178
}
146179

147180
#[pin_data]

rust/kernel/debugfs/file_ops.rs

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

4-
use super::Writer;
4+
use super::{Reader, Writer};
55
use crate::prelude::*;
66
use crate::seq_file::SeqFile;
77
use crate::seq_print;
8+
use crate::uaccess::UserSlice;
89
use core::fmt::{Display, Formatter, Result};
910
use core::marker::PhantomData;
1011

@@ -126,3 +127,113 @@ impl<T: Writer + Sync> ReadFile<T> for T {
126127
unsafe { FileOps::new(operations, 0o400) }
127128
};
128129
}
130+
131+
fn read<T: Reader + Sync>(data: &T, buf: *const c_char, count: usize) -> isize {
132+
let mut reader = UserSlice::new(UserPtr::from_ptr(buf as *mut c_void), count).reader();
133+
134+
if let Err(e) = data.read_from_slice(&mut reader) {
135+
return e.to_errno() as isize;
136+
}
137+
138+
count as isize
139+
}
140+
141+
/// # Safety
142+
///
143+
/// `file` must be a valid pointer to a `file` struct.
144+
/// The `private_data` of the file must contain a valid pointer to a `seq_file` whose
145+
/// `private` data in turn points to a `T` that implements `Reader`.
146+
/// `buf` must be a valid user-space buffer.
147+
pub(crate) unsafe extern "C" fn write<T: Reader + Sync>(
148+
file: *mut bindings::file,
149+
buf: *const c_char,
150+
count: usize,
151+
_ppos: *mut bindings::loff_t,
152+
) -> isize {
153+
// SAFETY: The file was opened with `single_open`, which sets `private_data` to a `seq_file`.
154+
let seq = unsafe { &mut *((*file).private_data.cast::<bindings::seq_file>()) };
155+
// SAFETY: By caller precondition, this pointer is live and points to a value of type `T`.
156+
let data = unsafe { &*(seq.private as *const T) };
157+
read(data, buf, count)
158+
}
159+
160+
// A trait to get the file operations for a type.
161+
pub(crate) trait ReadWriteFile<T> {
162+
const FILE_OPS: FileOps<T>;
163+
}
164+
165+
impl<T: Writer + Reader + Sync> ReadWriteFile<T> for T {
166+
const FILE_OPS: FileOps<T> = {
167+
let operations = bindings::file_operations {
168+
open: Some(writer_open::<T>),
169+
read: Some(bindings::seq_read),
170+
write: Some(write::<T>),
171+
llseek: Some(bindings::seq_lseek),
172+
release: Some(bindings::single_release),
173+
// SAFETY: `file_operations` supports zeroes in all fields.
174+
..unsafe { core::mem::zeroed() }
175+
};
176+
// SAFETY: `operations` is all stock `seq_file` implementations except for `writer_open`
177+
// and `write`.
178+
// `writer_open`'s only requirement beyond what is provided to all open functions is that
179+
// the inode's data pointer must point to a `T` that will outlive it, which matches the
180+
// `FileOps` requirements.
181+
// `write` only requires that the file's private data pointer points to `seq_file`
182+
// which points to a `T` that will outlive it, which matches what `writer_open`
183+
// provides.
184+
unsafe { FileOps::new(operations, 0o600) }
185+
};
186+
}
187+
188+
/// # Safety
189+
///
190+
/// `inode` must be a valid pointer to an `inode` struct.
191+
/// `file` must be a valid pointer to a `file` struct.
192+
unsafe extern "C" fn write_only_open(
193+
inode: *mut bindings::inode,
194+
file: *mut bindings::file,
195+
) -> c_int {
196+
// SAFETY: The caller ensures that `inode` and `file` are valid pointers.
197+
unsafe { (*file).private_data = (*inode).i_private };
198+
0
199+
}
200+
201+
/// # Safety
202+
///
203+
/// * `file` must be a valid pointer to a `file` struct.
204+
/// * The `private_data` of the file must contain a valid pointer to a `T` that implements
205+
/// `Reader`.
206+
/// * `buf` must be a valid user-space buffer.
207+
pub(crate) unsafe extern "C" fn write_only_write<T: Reader + Sync>(
208+
file: *mut bindings::file,
209+
buf: *const c_char,
210+
count: usize,
211+
_ppos: *mut bindings::loff_t,
212+
) -> isize {
213+
// SAFETY: The caller ensures that `file` is a valid pointer and that `private_data` holds a
214+
// valid pointer to `T`.
215+
let data = unsafe { &*((*file).private_data as *const T) };
216+
read(data, buf, count)
217+
}
218+
219+
pub(crate) trait WriteFile<T> {
220+
const FILE_OPS: FileOps<T>;
221+
}
222+
223+
impl<T: Reader + Sync> WriteFile<T> for T {
224+
const FILE_OPS: FileOps<T> = {
225+
let operations = bindings::file_operations {
226+
open: Some(write_only_open),
227+
write: Some(write_only_write::<T>),
228+
llseek: Some(bindings::noop_llseek),
229+
// SAFETY: `file_operations` supports zeroes in all fields.
230+
..unsafe { core::mem::zeroed() }
231+
};
232+
// SAFETY:
233+
// * `write_only_open` populates the file private data with the inode private data
234+
// * `write_only_write`'s only requirement is that the private data of the file point to
235+
// a `T` and be legal to convert to a shared reference, which `write_only_open`
236+
// satisfies.
237+
unsafe { FileOps::new(operations, 0o200) }
238+
};
239+
}

rust/kernel/debugfs/traits.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,15 @@
33

44
//! Traits for rendering or updating values exported to DebugFS.
55
6+
use crate::prelude::*;
67
use crate::sync::Mutex;
8+
use crate::uaccess::UserSliceReader;
79
use core::fmt::{self, Debug, Formatter};
10+
use core::str::FromStr;
11+
use core::sync::atomic::{
12+
AtomicI16, AtomicI32, AtomicI64, AtomicI8, AtomicIsize, AtomicU16, AtomicU32, AtomicU64,
13+
AtomicU8, AtomicUsize, Ordering,
14+
};
815

916
/// A trait for types that can be written into a string.
1017
///
@@ -31,3 +38,65 @@ impl<T: Debug> Writer for T {
3138
writeln!(f, "{self:?}")
3239
}
3340
}
41+
42+
/// A trait for types that can be updated from a user slice.
43+
///
44+
/// This works similarly to `FromStr`, but operates on a `UserSliceReader` rather than a &str.
45+
///
46+
/// It is automatically implemented for all atomic integers, or any type that implements `FromStr`
47+
/// wrapped in a `Mutex`.
48+
pub trait Reader {
49+
/// Updates the value from the given user slice.
50+
fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result;
51+
}
52+
53+
impl<T: FromStr> Reader for Mutex<T> {
54+
fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result {
55+
let mut buf = [0u8; 128];
56+
if reader.len() > buf.len() {
57+
return Err(EINVAL);
58+
}
59+
let n = reader.len();
60+
reader.read_slice(&mut buf[..n])?;
61+
62+
let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?;
63+
let val = s.trim().parse::<T>().map_err(|_| EINVAL)?;
64+
*self.lock() = val;
65+
Ok(())
66+
}
67+
}
68+
69+
macro_rules! impl_reader_for_atomic {
70+
($(($atomic_type:ty, $int_type:ty)),*) => {
71+
$(
72+
impl Reader for $atomic_type {
73+
fn read_from_slice(&self, reader: &mut UserSliceReader) -> Result {
74+
let mut buf = [0u8; 21]; // Enough for a 64-bit number.
75+
if reader.len() > buf.len() {
76+
return Err(EINVAL);
77+
}
78+
let n = reader.len();
79+
reader.read_slice(&mut buf[..n])?;
80+
81+
let s = core::str::from_utf8(&buf[..n]).map_err(|_| EINVAL)?;
82+
let val = s.trim().parse::<$int_type>().map_err(|_| EINVAL)?;
83+
self.store(val, Ordering::Relaxed);
84+
Ok(())
85+
}
86+
}
87+
)*
88+
};
89+
}
90+
91+
impl_reader_for_atomic!(
92+
(AtomicI16, i16),
93+
(AtomicI32, i32),
94+
(AtomicI64, i64),
95+
(AtomicI8, i8),
96+
(AtomicIsize, isize),
97+
(AtomicU16, u16),
98+
(AtomicU32, u32),
99+
(AtomicU64, u64),
100+
(AtomicU8, u8),
101+
(AtomicUsize, usize)
102+
);

0 commit comments

Comments
 (0)