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 3/7] rust: debugfs: Add support for writable files
Date: Tue, 19 Aug 2025 22:53:38 +0000 [thread overview]
Message-ID: <20250819-debugfs-rust-v10-3-86e20f3cf3bb@google.com> (raw)
In-Reply-To: <20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com>
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 `UpdateFromSlice`
trait.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/kernel/debugfs.rs | 41 +++++++++++++-
rust/kernel/debugfs/file_ops.rs | 115 +++++++++++++++++++++++++++++++++++++++-
rust/kernel/debugfs/traits.rs | 69 ++++++++++++++++++++++++
3 files changed, 222 insertions(+), 3 deletions(-)
diff --git a/rust/kernel/debugfs.rs b/rust/kernel/debugfs.rs
index 875d433fc3608cc9ffcf022d7c00cb207016f146..62bc2b1d4e5a4b21441a09e03bff74c32c6781d2 100644
--- a/rust/kernel/debugfs.rs
+++ b/rust/kernel/debugfs.rs
@@ -16,10 +16,10 @@
use core::ops::Deref;
mod traits;
-pub use traits::Render;
+pub use traits::{Render, UpdateFromSlice};
mod file_ops;
-use file_ops::{FileOps, ReadFile};
+use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};
#[cfg(CONFIG_DEBUG_FS)]
mod entry;
#[cfg(CONFIG_DEBUG_FS)]
@@ -136,6 +136,43 @@ pub fn read_only_file<'a, T: Render + Send + Sync + 'static, E: 'a, TI: PinInit<
let file_ops = &<T as ReadFile<_>>::FILE_OPS;
self.create_file(name, data, file_ops)
}
+
+ /// Creates a read-write file in this directory.
+ ///
+ /// Reading the file uses the [`Render`] implementation.
+ /// Writing to the file uses the [`UpdateFromSlice`] implementation.
+ pub fn read_write_file<
+ 'a,
+ T: Render + UpdateFromSlice + 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 ReadWriteFile<_>>::FILE_OPS;
+ self.create_file(name, data, file_ops)
+ }
+
+ /// Creates a write-only file in this directory.
+ ///
+ /// The file owns its backing data. Writing to the file uses the [`UpdateFromSlice`]
+ /// implementation.
+ ///
+ /// The file is removed when the returned [`File`] is dropped.
+ pub fn write_only_file<
+ 'a,
+ T: UpdateFromSlice + Send + Sync + 'static,
+ E: 'a,
+ TI: PinInit<T, E> + 'a,
+ >(
+ &'a self,
+ name: &'a CStr,
+ data: TI,
+ ) -> impl PinInit<File<T>, E> + 'a {
+ self.create_file(name, data, &T::FILE_OPS)
+ }
}
#[pin_data]
diff --git a/rust/kernel/debugfs/file_ops.rs b/rust/kernel/debugfs/file_ops.rs
index 134ac26e80f2e5b9cae53ed5a00462af7ce1aa38..30f6a0532c7f5f4a2974edc8f1100f5485aa8da9 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::Render;
+use super::{Render, UpdateFromSlice};
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;
@@ -123,3 +124,115 @@ impl<T: Render + Sync> ReadFile<T> for T {
unsafe { FileOps::new(operations, 0o400) }
};
}
+
+fn update<T: UpdateFromSlice + Sync>(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.update_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 `UpdateFromSlice`.
+/// `buf` must be a valid user-space buffer.
+pub(crate) unsafe extern "C" fn write<T: UpdateFromSlice + Sync>(
+ 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::<bindings::seq_file>()) };
+ // SAFETY: By caller precondition, this pointer is live and points to a value of type `T`.
+ let data = unsafe { &*(seq.private as *const T) };
+ update(data, buf, count)
+}
+
+// A trait to get the file operations for a type.
+pub(crate) trait ReadWriteFile<T> {
+ const FILE_OPS: FileOps<T>;
+}
+
+impl<T: Render + UpdateFromSlice + Sync> ReadWriteFile<T> for T {
+ const FILE_OPS: FileOps<T> = {
+ let operations = bindings::file_operations {
+ open: Some(render_open::<T>),
+ read: Some(bindings::seq_read),
+ write: Some(write::<T>),
+ 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 `render_open`
+ // and `write`.
+ // `render_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 `render_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
+/// `UpdateFromSlice`.
+/// * `buf` must be a valid user-space buffer.
+pub(crate) unsafe extern "C" fn write_only_write<T: UpdateFromSlice + Sync>(
+ 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) };
+ update(data, buf, count)
+}
+
+pub(crate) trait WriteFile<T> {
+ const FILE_OPS: FileOps<T>;
+}
+
+impl<T: UpdateFromSlice + Sync> WriteFile<T> for T {
+ const FILE_OPS: FileOps<T> = {
+ let operations = bindings::file_operations {
+ open: Some(write_only_open),
+ write: Some(write_only_write::<T>),
+ 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 2939e18e3dda39571cd7255505e5f605f0e3d154..d64638898faaa1a6a9898c374b8c1114993376c9 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 rendered into a string.
///
@@ -26,3 +33,65 @@ fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
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 UpdateFromSlice {
+ /// Updates the value from the given user slice.
+ fn update_from_slice(&self, reader: &mut UserSliceReader) -> Result<()>;
+}
+
+impl<T: FromStr> UpdateFromSlice for Mutex<T> {
+ fn update_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::<T>().map_err(|_| EINVAL)?;
+ *self.lock() = val;
+ Ok(())
+ }
+}
+
+macro_rules! impl_update_from_slice_for_atomic {
+ ($(($atomic_type:ty, $int_type:ty)),*) => {
+ $(
+ impl UpdateFromSlice for $atomic_type {
+ fn update_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_update_from_slice_for_atomic!(
+ (AtomicI16, i16),
+ (AtomicI32, i32),
+ (AtomicI64, i64),
+ (AtomicI8, i8),
+ (AtomicIsize, isize),
+ (AtomicU16, u16),
+ (AtomicU32, u32),
+ (AtomicU64, u64),
+ (AtomicU8, u8),
+ (AtomicUsize, usize)
+);
--
2.51.0.rc1.167.g924127e9c0-goog
next prev 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 ` [PATCH v10 2/7] rust: debugfs: Add support for read-only files Matthew Maurer
2025-08-26 18:45 ` Danilo Krummrich
2025-08-19 22:53 ` Matthew Maurer [this message]
2025-08-26 19:38 ` [PATCH v10 3/7] rust: debugfs: Add support for writable files 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-3-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.