mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
rust: debugfs: support for binary large objects
Introduce support for read-only, write-only, and read-write binary files in Rust debugfs. This adds: - BinaryWriter and BinaryReader traits for writing to and reading from user slices in binary form. - New Dir methods: read_binary_file(), write_binary_file(), `read_write_binary_file`. - Corresponding FileOps implementations: BinaryReadFile, BinaryWriteFile, BinaryReadWriteFile. This allows kernel modules to expose arbitrary binary data through debugfs, with proper support for offsets and partial reads/writes. Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> Reviewed-by: Matthew Maurer <mmaurer@google.com> Reviewed-by: Alice Ryhl <aliceryhl@google.com> Signed-off-by: Danilo Krummrich <dakr@kernel.org>
This commit is contained in:
@@ -21,12 +21,15 @@ use core::mem::ManuallyDrop;
|
||||
use core::ops::Deref;
|
||||
|
||||
mod traits;
|
||||
pub use traits::{Reader, Writer};
|
||||
pub use traits::{BinaryReader, BinaryWriter, Reader, Writer};
|
||||
|
||||
mod callback_adapters;
|
||||
use callback_adapters::{FormatAdapter, NoWriter, WritableAdapter};
|
||||
mod file_ops;
|
||||
use file_ops::{FileOps, ReadFile, ReadWriteFile, WriteFile};
|
||||
use file_ops::{
|
||||
BinaryReadFile, BinaryReadWriteFile, BinaryWriteFile, FileOps, ReadFile, ReadWriteFile,
|
||||
WriteFile,
|
||||
};
|
||||
#[cfg(CONFIG_DEBUG_FS)]
|
||||
mod entry;
|
||||
#[cfg(CONFIG_DEBUG_FS)]
|
||||
@@ -150,6 +153,32 @@ impl Dir {
|
||||
self.create_file(name, data, file_ops)
|
||||
}
|
||||
|
||||
/// Creates a read-only binary file in this directory.
|
||||
///
|
||||
/// The file's contents are produced by invoking [`BinaryWriter::write_to_slice`] 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_binary_file(c_str!("foo"), [0x1, 0x2]), GFP_KERNEL)?;
|
||||
/// # Ok::<(), Error>(())
|
||||
/// ```
|
||||
pub fn read_binary_file<'a, T, E: 'a>(
|
||||
&'a self,
|
||||
name: &'a CStr,
|
||||
data: impl PinInit<T, E> + 'a,
|
||||
) -> impl PinInit<File<T>, E> + 'a
|
||||
where
|
||||
T: BinaryWriter + Send + Sync + 'static,
|
||||
{
|
||||
self.create_file(name, data, &T::FILE_OPS)
|
||||
}
|
||||
|
||||
/// Creates a read-only file in this directory, with contents from a callback.
|
||||
///
|
||||
/// `f` must be a function item or a non-capturing closure.
|
||||
@@ -206,6 +235,22 @@ impl Dir {
|
||||
self.create_file(name, data, file_ops)
|
||||
}
|
||||
|
||||
/// Creates a read-write binary file in this directory.
|
||||
///
|
||||
/// Reading the file uses the [`BinaryWriter`] implementation.
|
||||
/// Writing to the file uses the [`BinaryReader`] implementation.
|
||||
pub fn read_write_binary_file<'a, T, E: 'a>(
|
||||
&'a self,
|
||||
name: &'a CStr,
|
||||
data: impl PinInit<T, E> + 'a,
|
||||
) -> impl PinInit<File<T>, E> + 'a
|
||||
where
|
||||
T: BinaryWriter + BinaryReader + Send + Sync + 'static,
|
||||
{
|
||||
let file_ops = &<T as BinaryReadWriteFile<_>>::FILE_OPS;
|
||||
self.create_file(name, data, file_ops)
|
||||
}
|
||||
|
||||
/// Creates a read-write file in this directory, with logic from callbacks.
|
||||
///
|
||||
/// Reading from the file is handled by `f`. Writing to the file is handled by `w`.
|
||||
@@ -248,6 +293,23 @@ impl Dir {
|
||||
self.create_file(name, data, &T::FILE_OPS)
|
||||
}
|
||||
|
||||
/// Creates a write-only binary file in this directory.
|
||||
///
|
||||
/// The file owns its backing data. Writing to the file uses the [`BinaryReader`]
|
||||
/// implementation.
|
||||
///
|
||||
/// The file is removed when the returned [`File`] is dropped.
|
||||
pub fn write_binary_file<'a, T, E: 'a>(
|
||||
&'a self,
|
||||
name: &'a CStr,
|
||||
data: impl PinInit<T, E> + 'a,
|
||||
) -> impl PinInit<File<T>, E> + 'a
|
||||
where
|
||||
T: BinaryReader + Send + Sync + 'static,
|
||||
{
|
||||
self.create_file(name, data, &T::FILE_OPS)
|
||||
}
|
||||
|
||||
/// Creates a write-only file in this directory, with write logic from a callback.
|
||||
///
|
||||
/// `w` must be a function item or a non-capturing closure.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
// Copyright (C) 2025 Google LLC.
|
||||
|
||||
use super::{Reader, Writer};
|
||||
use super::{BinaryReader, BinaryWriter, Reader, Writer};
|
||||
use crate::debugfs::callback_adapters::Adapter;
|
||||
use crate::fs::file;
|
||||
use crate::prelude::*;
|
||||
use crate::seq_file::SeqFile;
|
||||
use crate::seq_print;
|
||||
use crate::uaccess::UserSlice;
|
||||
use core::fmt::{Display, Formatter, Result};
|
||||
use core::fmt;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
#[cfg(CONFIG_DEBUG_FS)]
|
||||
@@ -65,8 +66,8 @@ impl<T> Deref for FileOps<T> {
|
||||
|
||||
struct WriterAdapter<T>(T);
|
||||
|
||||
impl<'a, T: Writer> Display for WriterAdapter<&'a T> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
impl<'a, T: Writer> fmt::Display for WriterAdapter<&'a T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.write(f)
|
||||
}
|
||||
}
|
||||
@@ -245,3 +246,140 @@ impl<T: Reader + Sync> WriteFile<T> for T {
|
||||
unsafe { FileOps::new(operations, 0o200) }
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn blob_read<T: BinaryWriter>(
|
||||
file: *mut bindings::file,
|
||||
buf: *mut c_char,
|
||||
count: usize,
|
||||
ppos: *mut bindings::loff_t,
|
||||
) -> isize {
|
||||
// SAFETY:
|
||||
// - `file` is a valid pointer to a `struct file`.
|
||||
// - The type invariant of `FileOps` guarantees that `private_data` points to a valid `T`.
|
||||
let this = unsafe { &*((*file).private_data.cast::<T>()) };
|
||||
|
||||
// SAFETY:
|
||||
// - `ppos` is a valid `file::Offset` pointer.
|
||||
// - We have exclusive access to `ppos`.
|
||||
let pos: &mut file::Offset = unsafe { &mut *ppos };
|
||||
|
||||
let mut writer = UserSlice::new(UserPtr::from_ptr(buf.cast()), count).writer();
|
||||
|
||||
let ret = || -> Result<isize> {
|
||||
let written = this.write_to_slice(&mut writer, pos)?;
|
||||
|
||||
Ok(written.try_into()?)
|
||||
}();
|
||||
|
||||
match ret {
|
||||
Ok(n) => n,
|
||||
Err(e) => e.to_errno() as isize,
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of [`FileOps`] for read only binary files.
|
||||
pub(crate) trait BinaryReadFile<T> {
|
||||
const FILE_OPS: FileOps<T>;
|
||||
}
|
||||
|
||||
impl<T: BinaryWriter + Sync> BinaryReadFile<T> for T {
|
||||
const FILE_OPS: FileOps<T> = {
|
||||
let operations = bindings::file_operations {
|
||||
read: Some(blob_read::<T>),
|
||||
llseek: Some(bindings::default_llseek),
|
||||
open: Some(bindings::simple_open),
|
||||
// SAFETY: `file_operations` supports zeroes in all fields.
|
||||
..unsafe { core::mem::zeroed() }
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// - The private data of `struct inode` does always contain a pointer to a valid `T`.
|
||||
// - `simple_open()` stores the `struct inode`'s private data in the private data of the
|
||||
// corresponding `struct file`.
|
||||
// - `blob_read()` re-creates a reference to `T` from the `struct file`'s private data.
|
||||
// - `default_llseek()` does not access the `struct file`'s private data.
|
||||
unsafe { FileOps::new(operations, 0o400) }
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn blob_write<T: BinaryReader>(
|
||||
file: *mut bindings::file,
|
||||
buf: *const c_char,
|
||||
count: usize,
|
||||
ppos: *mut bindings::loff_t,
|
||||
) -> isize {
|
||||
// SAFETY:
|
||||
// - `file` is a valid pointer to a `struct file`.
|
||||
// - The type invariant of `FileOps` guarantees that `private_data` points to a valid `T`.
|
||||
let this = unsafe { &*((*file).private_data.cast::<T>()) };
|
||||
|
||||
// SAFETY:
|
||||
// - `ppos` is a valid `file::Offset` pointer.
|
||||
// - We have exclusive access to `ppos`.
|
||||
let pos: &mut file::Offset = unsafe { &mut *ppos };
|
||||
|
||||
let mut reader = UserSlice::new(UserPtr::from_ptr(buf.cast_mut().cast()), count).reader();
|
||||
|
||||
let ret = || -> Result<isize> {
|
||||
let read = this.read_from_slice(&mut reader, pos)?;
|
||||
|
||||
Ok(read.try_into()?)
|
||||
}();
|
||||
|
||||
match ret {
|
||||
Ok(n) => n,
|
||||
Err(e) => e.to_errno() as isize,
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of [`FileOps`] for write only binary files.
|
||||
pub(crate) trait BinaryWriteFile<T> {
|
||||
const FILE_OPS: FileOps<T>;
|
||||
}
|
||||
|
||||
impl<T: BinaryReader + Sync> BinaryWriteFile<T> for T {
|
||||
const FILE_OPS: FileOps<T> = {
|
||||
let operations = bindings::file_operations {
|
||||
write: Some(blob_write::<T>),
|
||||
llseek: Some(bindings::default_llseek),
|
||||
open: Some(bindings::simple_open),
|
||||
// SAFETY: `file_operations` supports zeroes in all fields.
|
||||
..unsafe { core::mem::zeroed() }
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// - The private data of `struct inode` does always contain a pointer to a valid `T`.
|
||||
// - `simple_open()` stores the `struct inode`'s private data in the private data of the
|
||||
// corresponding `struct file`.
|
||||
// - `blob_write()` re-creates a reference to `T` from the `struct file`'s private data.
|
||||
// - `default_llseek()` does not access the `struct file`'s private data.
|
||||
unsafe { FileOps::new(operations, 0o200) }
|
||||
};
|
||||
}
|
||||
|
||||
/// Representation of [`FileOps`] for read/write binary files.
|
||||
pub(crate) trait BinaryReadWriteFile<T> {
|
||||
const FILE_OPS: FileOps<T>;
|
||||
}
|
||||
|
||||
impl<T: BinaryWriter + BinaryReader + Sync> BinaryReadWriteFile<T> for T {
|
||||
const FILE_OPS: FileOps<T> = {
|
||||
let operations = bindings::file_operations {
|
||||
read: Some(blob_read::<T>),
|
||||
write: Some(blob_write::<T>),
|
||||
llseek: Some(bindings::default_llseek),
|
||||
open: Some(bindings::simple_open),
|
||||
// SAFETY: `file_operations` supports zeroes in all fields.
|
||||
..unsafe { core::mem::zeroed() }
|
||||
};
|
||||
|
||||
// SAFETY:
|
||||
// - The private data of `struct inode` does always contain a pointer to a valid `T`.
|
||||
// - `simple_open()` stores the `struct inode`'s private data in the private data of the
|
||||
// corresponding `struct file`.
|
||||
// - `blob_read()` re-creates a reference to `T` from the `struct file`'s private data.
|
||||
// - `blob_write()` re-creates a reference to `T` from the `struct file`'s private data.
|
||||
// - `default_llseek()` does not access the `struct file`'s private data.
|
||||
unsafe { FileOps::new(operations, 0o600) }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
|
||||
//! Traits for rendering or updating values exported to DebugFS.
|
||||
|
||||
use crate::fs::file;
|
||||
use crate::prelude::*;
|
||||
use crate::sync::Mutex;
|
||||
use crate::uaccess::UserSliceReader;
|
||||
use crate::transmute::{AsBytes, FromBytes};
|
||||
use crate::uaccess::{UserSliceReader, UserSliceWriter};
|
||||
use core::fmt::{self, Debug, Formatter};
|
||||
use core::str::FromStr;
|
||||
use core::sync::atomic::{
|
||||
@@ -39,6 +41,44 @@ impl<T: Debug> Writer for T {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can be written out as binary.
|
||||
pub trait BinaryWriter {
|
||||
/// Writes the binary form of `self` into `writer`.
|
||||
///
|
||||
/// `offset` is the requested offset into the binary representation of `self`.
|
||||
///
|
||||
/// On success, returns the number of bytes written in to `writer`.
|
||||
fn write_to_slice(
|
||||
&self,
|
||||
writer: &mut UserSliceWriter,
|
||||
offset: &mut file::Offset,
|
||||
) -> Result<usize>;
|
||||
}
|
||||
|
||||
// Base implementation for any `T: AsBytes`.
|
||||
impl<T: AsBytes> BinaryWriter for T {
|
||||
fn write_to_slice(
|
||||
&self,
|
||||
writer: &mut UserSliceWriter,
|
||||
offset: &mut file::Offset,
|
||||
) -> Result<usize> {
|
||||
writer.write_slice_file(self.as_bytes(), offset)
|
||||
}
|
||||
}
|
||||
|
||||
// Delegate for `Mutex<T>`: Support a `T` with an outer mutex.
|
||||
impl<T: BinaryWriter> BinaryWriter for Mutex<T> {
|
||||
fn write_to_slice(
|
||||
&self,
|
||||
writer: &mut UserSliceWriter,
|
||||
offset: &mut file::Offset,
|
||||
) -> Result<usize> {
|
||||
let guard = self.lock();
|
||||
|
||||
guard.write_to_slice(writer, offset)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -66,6 +106,32 @@ impl<T: FromStr> Reader for Mutex<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that can be constructed from a binary representation.
|
||||
pub trait BinaryReader {
|
||||
/// Reads the binary form of `self` from `reader`.
|
||||
///
|
||||
/// `offset` is the requested offset into the binary representation of `self`.
|
||||
///
|
||||
/// On success, returns the number of bytes read from `reader`.
|
||||
fn read_from_slice(
|
||||
&self,
|
||||
reader: &mut UserSliceReader,
|
||||
offset: &mut file::Offset,
|
||||
) -> Result<usize>;
|
||||
}
|
||||
|
||||
impl<T: AsBytes + FromBytes> BinaryReader for Mutex<T> {
|
||||
fn read_from_slice(
|
||||
&self,
|
||||
reader: &mut UserSliceReader,
|
||||
offset: &mut file::Offset,
|
||||
) -> Result<usize> {
|
||||
let mut this = self.lock();
|
||||
|
||||
reader.read_slice_file(this.as_bytes_mut(), offset)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_reader_for_atomic {
|
||||
($(($atomic_type:ty, $int_type:ty)),*) => {
|
||||
$(
|
||||
|
||||
Reference in New Issue
Block a user