Commit 839dc1d1 authored by Matthew Maurer's avatar Matthew Maurer Committed by Danilo Krummrich
Browse files

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: default avatarMatthew Maurer <mmaurer@google.com>
Tested-by: default avatarDirk Behme <dirk.behme@de.bosch.com>
Acked-by: default avatarGreg 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: default avatarDanilo Krummrich <dakr@kernel.org>
parent 5e40b591
Loading
Loading
Loading
Loading
+35 −2
Original line number Diff line number Diff line
@@ -16,10 +16,10 @@
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 @@ pub fn read_only_file<'a, T, E: 'a>(
        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]
+112 −1
Original line number Diff line number Diff line
// 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) }
    };
}
+69 −0
Original line number Diff line number Diff line
@@ -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 @@ fn write(&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 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)
);