diff --git a/.github/workflows/busybox.config b/.github/workflows/busybox.config index c4175a522a3a60..81981ec17d1727 100644 --- a/.github/workflows/busybox.config +++ b/.github/workflows/busybox.config @@ -196,7 +196,7 @@ CONFIG_GZIP_FAST=0 # Coreutils # # CONFIG_BASENAME is not set -# CONFIG_CAT is not set +CONFIG_CAT=y # CONFIG_FEATURE_CATN is not set # CONFIG_FEATURE_CATV is not set # CONFIG_CHGRP is not set @@ -209,7 +209,7 @@ CONFIG_GZIP_FAST=0 # CONFIG_CP is not set # CONFIG_FEATURE_CP_LONG_OPTIONS is not set # CONFIG_FEATURE_CP_REFLINK is not set -# CONFIG_CUT is not set +CONFIG_CUT=y # CONFIG_DATE is not set # CONFIG_FEATURE_DATE_ISOFMT is not set # CONFIG_FEATURE_DATE_NANO is not set @@ -263,9 +263,9 @@ CONFIG_GZIP_FAST=0 # CONFIG_SHA512SUM is not set # CONFIG_SHA3SUM is not set # CONFIG_FEATURE_MD5_SHA1_SUM_CHECK is not set -# CONFIG_MKDIR is not set +CONFIG_MKDIR=y # CONFIG_MKFIFO is not set -# CONFIG_MKNOD is not set +CONFIG_MKNOD=y # CONFIG_MKTEMP is not set # CONFIG_MV is not set # CONFIG_NICE is not set @@ -280,7 +280,7 @@ CONFIG_GZIP_FAST=0 # CONFIG_READLINK is not set # CONFIG_FEATURE_READLINK_FOLLOW is not set # CONFIG_REALPATH is not set -# CONFIG_RM is not set +CONFIG_RM=y # CONFIG_RMDIR is not set # CONFIG_SEQ is not set # CONFIG_SHRED is not set @@ -449,7 +449,7 @@ CONFIG_FEATURE_VI_UNDO_QUEUE_MAX=0 # CONFIG_FEATURE_FIND_REGEX is not set # CONFIG_FEATURE_FIND_CONTEXT is not set # CONFIG_FEATURE_FIND_LINKS is not set -# CONFIG_GREP is not set +CONFIG_GREP=y # CONFIG_EGREP is not set # CONFIG_FGREP is not set # CONFIG_FEATURE_GREP_CONTEXT is not set @@ -635,7 +635,7 @@ CONFIG_DEFAULT_DEPMOD_FILE="modules.dep" # CONFIG_MKSWAP is not set # CONFIG_FEATURE_MKSWAP_UUID is not set # CONFIG_MORE is not set -# CONFIG_MOUNT is not set +CONFIG_MOUNT=y # CONFIG_FEATURE_MOUNT_FAKE is not set # CONFIG_FEATURE_MOUNT_VERBOSE is not set # CONFIG_FEATURE_MOUNT_HELPERS is not set diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6c933d4c9cd712..fcb6e6ee607eb5 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -324,6 +324,11 @@ jobs: grep '] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (init)$' qemu-stdout.log grep '] rust_semaphore_c: Rust semaphore sample (in C, for comparison) (exit)$' qemu-stdout.log + - run: | + grep '] rust_seq_file: Rust seq_file sample (init)$' qemu-stdout.log + grep '] rust_seq_file: Rust seq_file sample (exit)$' qemu-stdout.log + test $(grep -c 'rust_seq_file: device opened this many times: 2' qemu-stdout.log) -eq 2 + # Report - run: | ls -l \ diff --git a/.github/workflows/kernel-arm-debug.config b/.github/workflows/kernel-arm-debug.config index 258ff571ee4880..142774090ce646 100644 --- a/.github/workflows/kernel-arm-debug.config +++ b/.github/workflows/kernel-arm-debug.config @@ -1665,6 +1665,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-arm-release.config b/.github/workflows/kernel-arm-release.config index 8409ced66a1713..9be5bd281bddd0 100644 --- a/.github/workflows/kernel-arm-release.config +++ b/.github/workflows/kernel-arm-release.config @@ -1589,6 +1589,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_STRICT_DEVMEM is not set # diff --git a/.github/workflows/kernel-arm64-debug.config b/.github/workflows/kernel-arm64-debug.config index e5af726bf34233..1c67b93806e3fe 100644 --- a/.github/workflows/kernel-arm64-debug.config +++ b/.github/workflows/kernel-arm64-debug.config @@ -1069,7 +1069,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -1431,6 +1431,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-arm64-release.config b/.github/workflows/kernel-arm64-release.config index 2476670a0b3941..5bb0ee8a4eb9a2 100644 --- a/.github/workflows/kernel-arm64-release.config +++ b/.github/workflows/kernel-arm64-release.config @@ -1064,7 +1064,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set # CONFIG_SYSFS is not set # CONFIG_HUGETLBFS is not set @@ -1349,6 +1349,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # # arm64 Debugging diff --git a/.github/workflows/kernel-ppc64le-debug.config b/.github/workflows/kernel-ppc64le-debug.config index d211d9e79b575c..93cd35f89dc280 100644 --- a/.github/workflows/kernel-ppc64le-debug.config +++ b/.github/workflows/kernel-ppc64le-debug.config @@ -1491,6 +1491,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-ppc64le-release.config b/.github/workflows/kernel-ppc64le-release.config index a74314e6cdb86f..db4dc4419f3680 100644 --- a/.github/workflows/kernel-ppc64le-release.config +++ b/.github/workflows/kernel-ppc64le-release.config @@ -1453,6 +1453,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-riscv64-debug.config b/.github/workflows/kernel-riscv64-debug.config index 8614d9f5b95442..4ba60dfebf0d7c 100644 --- a/.github/workflows/kernel-riscv64-debug.config +++ b/.github/workflows/kernel-riscv64-debug.config @@ -1353,6 +1353,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_SAMPLE_RUST_RANDOM is not set # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-riscv64-release.config b/.github/workflows/kernel-riscv64-release.config index e7b87c788f9814..ce93579e9d55af 100644 --- a/.github/workflows/kernel-riscv64-release.config +++ b/.github/workflows/kernel-riscv64-release.config @@ -1318,6 +1318,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m # CONFIG_SAMPLE_RUST_RANDOM is not set # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-x86_64-debug.config b/.github/workflows/kernel-x86_64-debug.config index 4724cf56997cb5..15caaed80e7dd7 100644 --- a/.github/workflows/kernel-x86_64-debug.config +++ b/.github/workflows/kernel-x86_64-debug.config @@ -1059,7 +1059,7 @@ CONFIG_DCACHE_WORD_ACCESS=y # # Pseudo filesystems # -# CONFIG_PROC_FS is not set +CONFIG_PROC_FS=y # CONFIG_PROC_CHILDREN is not set CONFIG_KERNFS=y CONFIG_SYSFS=y @@ -1443,6 +1443,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/kernel-x86_64-release.config b/.github/workflows/kernel-x86_64-release.config index 5a9bc844a697ea..df4ce936f2df8d 100644 --- a/.github/workflows/kernel-x86_64-release.config +++ b/.github/workflows/kernel-x86_64-release.config @@ -1391,6 +1391,7 @@ CONFIG_SAMPLE_RUST_MISCDEV=m CONFIG_SAMPLE_RUST_STACK_PROBING=m CONFIG_SAMPLE_RUST_SEMAPHORE=m CONFIG_SAMPLE_RUST_SEMAPHORE_C=m +CONFIG_SAMPLE_RUST_SEQ_FILE=m CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y # CONFIG_STRICT_DEVMEM is not set diff --git a/.github/workflows/qemu-init.sh b/.github/workflows/qemu-init.sh index 078de0887c6dba..fe1f9cddd1aa01 100755 --- a/.github/workflows/qemu-init.sh +++ b/.github/workflows/qemu-init.sh @@ -37,4 +37,15 @@ busybox insmod rust_module_parameters_loadable_custom.ko \ busybox rmmod rust_module_parameters_loadable_default.ko busybox rmmod rust_module_parameters_loadable_custom.ko +busybox insmod rust_seq_file.ko +busybox mkdir proc +busybox mount -t proc proc /proc +export RUST_SEQ_MINOR=$(busybox cat /proc/misc | busybox grep rust_seq_file | busybox cut -d ' ' -f 1) +busybox mknod /dev/rust_seq_file0 c 10 $RUST_SEQ_MINOR +busybox cat /dev/rust_seq_file0 +busybox cat /dev/rust_seq_file0 +busybox cat /proc/rust_seq_file +busybox rm /dev/rust_seq_file0 +busybox rmmod rust_seq_file.ko + busybox reboot -f diff --git a/.github/workflows/qemu-initramfs.desc b/.github/workflows/qemu-initramfs.desc index 5fdcff849dc004..99a828e045d5db 100644 --- a/.github/workflows/qemu-initramfs.desc +++ b/.github/workflows/qemu-initramfs.desc @@ -14,6 +14,7 @@ file /rust_miscdev.ko samples/rust/rust_miscdev.ko 0755 file /rust_stack_probing.ko samples/rust/rust_stack_probing.ko 0755 0 0 file /rust_semaphore.ko samples/rust/rust_semaphore.ko 0755 0 0 file /rust_semaphore_c.ko samples/rust/rust_semaphore_c.ko 0755 0 0 +file /rust_seq_file.ko samples/rust/rust_seq_file.ko 0755 0 0 file /rust_module_parameters_loadable_default.ko samples/rust/rust_module_parameters_loadable_default.ko 0755 0 0 file /rust_module_parameters_loadable_custom.ko samples/rust/rust_module_parameters_loadable_custom.ko 0755 0 0 diff --git a/rust/kernel/bindings_helper.h b/rust/kernel/bindings_helper.h index 6b08f42cfcceb7..097baae98ee134 100644 --- a/rust/kernel/bindings_helper.h +++ b/rust/kernel/bindings_helper.h @@ -3,7 +3,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -13,6 +15,10 @@ #include #include +#ifdef CONFIG_PROC_FS +#include "../../fs/proc/internal.h" +#endif + // `bindgen` gets confused at certain things const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO; diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 96866504917cf4..3c86ec40284984 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -52,7 +52,12 @@ pub mod module_param; pub mod prelude; pub mod print; + +#[cfg(CONFIG_PROC_FS)] +pub mod proc_fs; + pub mod random; +pub mod seq_file; mod static_assert; pub mod sync; diff --git a/rust/kernel/proc_fs.rs b/rust/kernel/proc_fs.rs new file mode 100644 index 00000000000000..2e62371712fa90 --- /dev/null +++ b/rust/kernel/proc_fs.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Type for defining `proc` files. +//! +//! This module allows Rust devices to create entries in `/proc` from a +//! [`bindings::proc_ops`] vtable. +//! +//! C header: [`include/linux/proc_fs.h`](../../../include/linux/proc_fs.h) +//! +//! Reference: + +use alloc::boxed::Box; +use core::{ + marker::{PhantomData, Sync}, + ops::Deref, + ptr, +}; + +use crate::{bindings, c_types, CStr, Error, KernelResult}; + +/// An entry under `/proc` containing data of type `T`. +/// +/// This is the Rust equivalent to [`proc_dir_entry`] on the C side. +/// +/// # Invariants +/// +/// The pointer [`ProcDirEntry::proc_dir_entry`] is a valid pointer and +/// it's field [`bindings::proc_dir_entry::data`] is a valid pointer to +/// `T`. +/// +/// [`proc_dir_entry`]: ../../../fs/proc/internal.h +pub struct ProcDirEntry { + proc_dir_entry: *mut bindings::proc_dir_entry, + data: PhantomData, +} + +// SAFETY: The `proc_dir_entry` raw pointer isn't accessible. +unsafe impl Sync for ProcDirEntry {} + +impl Drop for ProcDirEntry { + fn drop(&mut self) { + // SAFETY: `ProcDirEntry` is guaranteed to have a valid pointer to `T` + // in the `data` field of `proc_dir_entry`. + let data = unsafe { Box::from_raw((*self.proc_dir_entry).data as *mut T) }; + // SAFETY: Calling a C function. `proc_dir_entry` is a valid pointer to + // a `bindings::proc_dir_entry` because it was created by a call to + // `proc_create_data` which only returns valid pointers. + unsafe { + bindings::proc_remove(self.proc_dir_entry); + } + drop(data); + } +} + +/// Create an entry in `/proc` containing data of type `T`. +/// +/// Corresponds to [`proc_create_data`] on the C side. +/// +/// [`proc_create_data]: ../../../fs/proc/generic.c +pub(crate) fn proc_create_data( + name: CStr<'static>, + proc_ops: &'static bindings::proc_ops, + data: T, +) -> KernelResult> { + let data_ptr = Box::into_raw(Box::try_new(data)?) as *mut c_types::c_void; + let name = name.deref().as_ptr() as *const u8 as *const c_types::c_char; + + // SAFETY: Calling a C function. `name` is guaranteed to be null terminated + // because it is of type `CStr`. + let proc_dir_entry = + unsafe { bindings::proc_create_data(name, 0, ptr::null_mut(), proc_ops, data_ptr) }; + if proc_dir_entry.is_null() { + Err(Error::ENOMEM) + } else { + // INVARIANT: `proc_dir_entry` is a valid pointer and it's data field + // is a valid pointer to `T`. + Ok(ProcDirEntry { + proc_dir_entry, + data: PhantomData, + }) + } +} diff --git a/rust/kernel/seq_file.rs b/rust/kernel/seq_file.rs new file mode 100644 index 00000000000000..fd337e65375127 --- /dev/null +++ b/rust/kernel/seq_file.rs @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Trait for defining `seq_file`s. +//! +//! This module allows Rust devices to implement [`struct seq_operations`] and +//! and create a file under `/proc` based on that implementation. +//! +//! C header: [`include/linux/seq_file.h`](../../../include/linux/seq_file.h) +//! +//! Reference: + +// Currently this module is only usable through proc_fs. +#![cfg_attr(not(CONFIG_PROC_FS), allow(dead_code))] + +use alloc::boxed::Box; +use core::{ + iter::{Iterator, Peekable}, + marker::PhantomData, + ptr, +}; + +use crate::{bindings, c_types, cstr, CStr}; + +#[cfg(CONFIG_PROC_FS)] +pub use proc::proc_create_seq; + +/// Rust equivalent of the [`seq_operations`] interface on the C side. +/// +/// # Example +/// +/// ``` +/// struct Data(&'static [String]); +/// +/// impl seq_file::SeqOperations for Data { +/// type Item = &'static String; +/// type Iterator = core::slice::Iter<'static, String>; +/// +/// fn start(arg: &Data) -> Option>> { +/// let iter = arg.0.iter(); +/// Box::try_new(iter.peekable()).ok() +/// } +/// +/// fn display(item: &Self::Item) -> &str { +/// &item[..] +/// } +/// } +/// ``` +/// +/// [`seq_operations`]: ../../../include/linux/seq_file.h +pub trait SeqOperations { + /// Type produced on each iteration. + type Item; + + /// Type created when the seq file is opened. + type Iterator: Iterator; + + /// Called once each time the `seq_file` is opened. + fn start(arg: &Self) -> Option>>; + + /// How the item will be displayed to the reader. + fn display(item: &Self::Item) -> &str; +} + +extern "C" fn stop_callback( + _m: *mut bindings::seq_file, + v: *mut c_types::c_void, +) { + if !v.is_null() { + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `Box>::into_raw`. + drop(unsafe { Box::from_raw(v as *mut Peekable) }) + } +} + +extern "C" fn next_callback( + _m: *mut bindings::seq_file, + v: *mut c_types::c_void, + pos: *mut bindings::loff_t, +) -> *mut c_types::c_void { + if v.is_null() { + return ptr::null_mut(); + } + + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `Box>::into_raw`. + // We already checked for he null pointer case above. + let mut iterator = unsafe { Box::from_raw(v as *mut Peekable) }; + + // SAFETY: The caller guarantees tha `pos` is a valid pointer to an + // `loff_t` and expects this function to mutate the value. + unsafe { + *pos += 1; + } + + iterator.next(); + match iterator.peek() { + Some(_next) => Box::into_raw(iterator) as *mut c_types::c_void, + None => ptr::null_mut(), + } +} + +extern "C" fn show_callback( + m: *mut bindings::seq_file, + v: *mut c_types::c_void, +) -> c_types::c_int { + const FORMAT: CStr<'static> = cstr!("%.*s"); + // SAFETY: `v` was created by a previous call to `next_callback` or + // `start_callback` and both functions return either a null pointer + // or pointer generated by `Box>::into_raw`. + if let Some(iterator) = unsafe { (v as *mut Peekable).as_mut() } { + if let Some(item) = iterator.peek() { + let s = T::display(item); + // SAFETY: Calling a C function. `FORMAT` is null terminated because + // it comes from a `CStr`. `s` does not need to be null terminated + // because we are only printing the first `s.len()` bytes. + unsafe { + bindings::seq_printf( + m, + FORMAT.as_ptr() as *const c_types::c_char, + s.len(), + s.as_ptr() as *const u8 as *const c_types::c_char, + ); + } + } + } + 0 +} + +extern "C" fn start_callback( + m: *mut bindings::seq_file, + pos: *mut bindings::loff_t, +) -> *mut c_types::c_void { + // SAFETY: This function will be called by opening a proc file generated + // using `proc_open_callback::`, so the `private` field + // of `m` is guaranteed to be a valid pointer to `T`. + let arg = unsafe { &*((*m).private as *const T) }; + // SAFETY: The caller guarantees that `pos` points to a valid `loff_t`. + let pos = unsafe { *pos }; + match T::start(arg) { + Some(mut wrapper) => { + for _ in 0..pos { + wrapper.next(); + } + match wrapper.peek() { + Some(_next) => Box::into_raw(wrapper) as *mut c_types::c_void, + None => ptr::null_mut(), + } + } + None => ptr::null_mut(), + } +} + +struct SeqFileOperationsVTable(PhantomData); + +impl SeqFileOperationsVTable { + const VTABLE: bindings::seq_operations = bindings::seq_operations { + start: Some(start_callback::), + stop: Some(stop_callback::), + next: Some(next_callback::), + show: Some(show_callback::), + }; + + const fn build() -> &'static bindings::seq_operations { + &Self::VTABLE + } +} + +#[cfg(CONFIG_PROC_FS)] +mod proc { + use super::*; + use crate::{proc_fs, proc_fs::ProcDirEntry, KernelResult}; + + extern "C" fn proc_open_callback( + inode: *mut bindings::inode, + file: *mut bindings::file, + ) -> c_types::c_int { + // SAFETY: Calling a C function. + let result = unsafe { bindings::seq_open(file, SeqFileOperationsVTable::::build()) }; + if result == 0 { + // SAFETY: Calling a C function. + let data = unsafe { bindings::PDE_DATA(inode) }; + // SAFETY: `file` is a valid pointer. + let seq_file = unsafe { (*file).private_data as *mut bindings::seq_file }; + // SAFETY: `seq_open` allocates a `seq_file` in `(*file).private_data` + // and we've checked that `seq_open` succeeded so `seq_file` is a valid + // pointer. + unsafe { (*seq_file).private = data }; + 0 + } else { + result + } + } + + struct SeqFileProcOperationsVTable(PhantomData); + + impl SeqFileProcOperationsVTable { + const VTABLE: bindings::proc_ops = bindings::proc_ops { + proc_flags: 0, + proc_open: Some(proc_open_callback::), + proc_read: Some(bindings::seq_read), + proc_read_iter: Some(bindings::seq_read_iter), + proc_write: None, + proc_lseek: Some(bindings::seq_lseek), + proc_release: Some(bindings::seq_release), + proc_poll: None, + proc_ioctl: None, + proc_mmap: None, + proc_get_unmapped_area: None, + }; + + const fn build() -> &'static bindings::proc_ops { + &Self::VTABLE + } + } + + /// Create an entry in `/proc` for a `seq_file`. + pub fn proc_create_seq( + name: CStr<'static>, + data: T, + ) -> KernelResult> { + proc_fs::proc_create_data(name, SeqFileProcOperationsVTable::::build(), data) + } +} diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig index 183a3c4dc80cd7..2a42d1a16f1a30 100644 --- a/samples/rust/Kconfig +++ b/samples/rust/Kconfig @@ -110,4 +110,15 @@ config SAMPLE_RUST_RANDOM If unsure, say N. +config SAMPLE_RUST_SEQ_FILE + tristate "Seq file" + depends on PROC_FS + help + This option builds the Rust seq_file sample. + + To compile this as a module, choose M here: + the module will be called rust_seq_file. + + If unsure, say N. + endif # SAMPLES_RUST diff --git a/samples/rust/Makefile b/samples/rust/Makefile index 48bc871ea1f8ff..b95e25dfa9099a 100644 --- a/samples/rust/Makefile +++ b/samples/rust/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_SAMPLE_RUST_STACK_PROBING) += rust_stack_probing.o obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE) += rust_semaphore.o obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE_C) += rust_semaphore_c.o obj-$(CONFIG_SAMPLE_RUST_RANDOM) += rust_random.o +obj-$(CONFIG_SAMPLE_RUST_SEQ_FILE) += rust_seq_file.o diff --git a/samples/rust/rust_seq_file.rs b/samples/rust/rust_seq_file.rs new file mode 100644 index 00000000000000..caa1191dd979d0 --- /dev/null +++ b/samples/rust/rust_seq_file.rs @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Example of using a [`seq_file`] in Rust. +//! +//! C header: [`include/linux/seq_file.h`](../../../include/linux/seq_file.h) +//! C header: [`include/linux/proc_fs.h`](../../../include/linux/proc_fs.h) +//! +//! Reference: + +#![no_std] +#![feature(allocator_api, global_asm, try_reserve)] + +use alloc::{boxed::Box, sync::Arc}; +use core::{ + convert::TryInto, + fmt::Write, + iter::{repeat, Peekable, Repeat, Take}, + pin::Pin, +}; +use kernel::{ + cstr, + file_operations::{File, FileOpener, FileOperations}, + io_buffer::IoBufferWriter, + miscdev, mutex_init, + prelude::*, + proc_fs, seq_file, + sync::Mutex, +}; + +module! { + type: RustSeqFileDev, + name: b"rust_seq_file", + author: b"Adam Bratschi-Kaye", + description: b"Rust sample using a seq_file", + license: b"GPL v2", + params: { + }, +} + +#[derive(Clone)] +struct SharedState(Arc>); + +impl SharedState { + fn try_new() -> KernelResult { + let state = Arc::try_new( + // SAFETY: `mutex_init!` is called below. + unsafe { Mutex::new(0) }, + )?; + // SAFETY: Mutex is pinned behind `Arc`. + let pin_state = unsafe { Pin::new_unchecked(state.as_ref()) }; + mutex_init!(pin_state, "SharedState::0"); + Ok(SharedState(state)) + } +} + +impl seq_file::SeqOperations for SharedState { + type Item = String; + type Iterator = Take>; + + fn display(item: &Self::Item) -> &str { + &item[..] + } + + fn start(arg: &SharedState) -> Option>> { + const MAX_DIGITS: usize = 3; + const MAX_LENGTH: usize = MAX_DIGITS + 1; + const MAX_COUNT: u32 = 10u32.pow(MAX_DIGITS as u32); + + let count = arg.0.lock(); + let mut message = String::new(); + + let template = "rust_seq_file: device opened this many times: "; + message + .try_reserve_exact(template.len() + MAX_LENGTH) + .ok()?; + // NOPANIC: We reserved space for `template` above. + message.push_str(template); + if *count < MAX_COUNT { + // NOPANIC: There are MAX_LENGTH characters remaining in the string which + // leaves space for a MAX_DIGITS digit number and the newline. + write!(&mut message, "{}\n", *count).ok()?; + } + + Box::try_new(repeat(message).take((*count).try_into().ok()?).peekable()).ok() + } +} + +impl FileOpener for SharedState { + fn open(ctx: &SharedState) -> KernelResult { + pr_info!("rust seq_file was opened!\n"); + Ok(Box::try_new(ctx.clone())?) + } +} + +impl FileOperations for SharedState { + type Wrapper = Box; + + kernel::declare_file_operations!(read); + + fn read(&self, _: &File, data: &mut T, offset: u64) -> KernelResult { + let message = b"incremented read count\n"; + if offset != 0 { + return Ok(0); + } + + { + let mut count = self.0.lock(); + *count += 1; + } + + data.write_slice(message)?; + Ok(message.len()) + } +} + +struct RustSeqFileDev { + #[cfg(CONFIG_PROC_FS)] + _proc: proc_fs::ProcDirEntry, + _dev: Pin>>, +} + +impl KernelModule for RustSeqFileDev { + fn init() -> KernelResult { + pr_info!("Rust seq_file sample (init)\n"); + + let state = SharedState::try_new()?; + + #[cfg(CONFIG_PROC_FS)] + let proc_dir_entry = + seq_file::proc_create_seq::(cstr!("rust_seq_file"), state.clone())?; + + let dev_reg = + miscdev::Registration::new_pinned::(cstr!("rust_seq_file"), None, state)?; + + let dev = RustSeqFileDev { + #[cfg(CONFIG_PROC_FS)] + _proc: proc_dir_entry, + _dev: dev_reg, + }; + + Ok(dev) + } +} + +impl Drop for RustSeqFileDev { + fn drop(&mut self) { + pr_info!("Rust seq_file sample (exit)\n"); + } +}