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>
This commit is contained in:
parent
5e40b591cb
commit
839dc1d15b
|
|
@ -16,10 +16,10 @@ use core::marker::PhantomPinned;
|
|||
use core::ops::Deref;
|
||||
|
||||
mod traits;
|
||||
pub use traits::Writer;
|
||||
pub use traits::{Reader, Writer};
|
||||
|
||||
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)]
|
||||
|
|
@ -142,6 +142,39 @@ impl Dir {
|
|||
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 [`Writer`] implementation.
|
||||
/// Writing to the file uses the [`Reader`] implementation.
|
||||
pub fn read_write_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 + Reader + Send + Sync + 'static,
|
||||
{
|
||||
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 [`Reader`]
|
||||
/// implementation.
|
||||
///
|
||||
/// The file is removed when the returned [`File`] is dropped.
|
||||
pub fn write_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: Reader + Send + Sync + 'static,
|
||||
{
|
||||
self.create_file(name, data, &T::FILE_OPS)
|
||||
}
|
||||
}
|
||||
|
||||
#[pin_data]
|
||||
|
|
|
|||
|
|
@ -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<T: Writer + Sync> ReadFile<T> for T {
|
|||
unsafe { FileOps::new(operations, 0o400) }
|
||||
};
|
||||
}
|
||||
|
||||
fn read<T: Reader + 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.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<T: Reader + 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) };
|
||||
read(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: Writer + Reader + Sync> ReadWriteFile<T> for T {
|
||||
const FILE_OPS: FileOps<T> = {
|
||||
let operations = bindings::file_operations {
|
||||
open: Some(writer_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 `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<T: Reader + 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) };
|
||||
read(data, buf, count)
|
||||
}
|
||||
|
||||
pub(crate) trait WriteFile<T> {
|
||||
const FILE_OPS: FileOps<T>;
|
||||
}
|
||||
|
||||
impl<T: Reader + 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) }
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<T: Debug> 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<T: FromStr> Reader for Mutex<T> {
|
||||
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::<T>().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)
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue