Skip to content

Commit 6f6020f

Browse files
committed
seq_file.rs: Rust version of seq_operations
Adds a trait which allows Rust modules to implement the seq_operations interface and use it to create a `/proc` file. Signed-off-by: Adam Bratschi-Kaye <[email protected]>
1 parent a6efc40 commit 6f6020f

File tree

6 files changed

+378
-0
lines changed

6 files changed

+378
-0
lines changed

rust/kernel/bindings_helper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
#include <linux/cdev.h>
44
#include <linux/fs.h>
55
#include <linux/module.h>
6+
#include <linux/proc_fs.h>
67
#include <linux/random.h>
8+
#include <linux/seq_file.h>
79
#include <linux/slab.h>
810
#include <linux/sysctl.h>
911
#include <linux/uaccess.h>

rust/kernel/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub mod module_param;
5353
pub mod prelude;
5454
pub mod print;
5555
pub mod random;
56+
pub mod seq_file;
5657
mod static_assert;
5758
pub mod sync;
5859

rust/kernel/seq_file.rs

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Trait for defining `seq_file`s under `/proc`.
4+
//!
5+
//! This module allows Rust devices to implement [`struct seq_operations`] and
6+
//! and create a file under `/proc` based on that implementation.
7+
//!
8+
//! C header: [`include/linux/seq_file.h`](../../../include/linux/seq_file.h)
9+
//! C header: [`include/linux/proc_fs.h`](../../../include/linux/proc_fs.h)
10+
//!
11+
//! Reference: <https://www.kernel.org/doc/html/latest/filesystems/seq_file.html>
12+
13+
use alloc::{boxed::Box, string::ToString};
14+
use core::{
15+
fmt::Display,
16+
iter::{Iterator, Peekable},
17+
marker::{PhantomData, PhantomPinned},
18+
ops::Deref,
19+
pin::Pin,
20+
ptr,
21+
};
22+
23+
use crate::{bindings, c_types, CStr, KernelResult};
24+
25+
/// Rust equivalent of the [`seq_operations`] interface on the C side.
26+
///
27+
/// # Example
28+
///
29+
/// ```
30+
/// struct Data(&'static [u32]);
31+
///
32+
/// impl seq_file::SeqOperations for Data {
33+
/// type Item = &'static u32;
34+
/// type Iterator = core::slice::Iter<'static, u32>;
35+
///
36+
/// fn start(arg: &Data) -> Option<Box<Peekable<Self::Iterator>>> {
37+
/// let iter = arg.0.iter();
38+
/// Box::try_new(iter.peekable()).ok()
39+
/// }
40+
/// }
41+
/// ```
42+
///
43+
/// [`seq_operations`]: ../../../include/linux/seq_file.h
44+
pub trait SeqOperations {
45+
/// Type produced on each iteration.
46+
type Item: Display;
47+
48+
/// Type created when the seq file is opened.
49+
type Iterator: Iterator<Item = Self::Item>;
50+
51+
/// Called once each time the `seq_file` is opened.
52+
fn start(arg: &Self) -> Option<Box<Peekable<Self::Iterator>>>;
53+
}
54+
55+
extern "C" fn stop_callback<T: SeqOperations>(
56+
_m: *mut bindings::seq_file,
57+
v: *mut c_types::c_void,
58+
) {
59+
if !v.is_null() {
60+
// SAFETY: `v` was created by a previous call to `next_callback` or
61+
// `start_callback` and both functions return either a null pointer
62+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
63+
let _iterator = unsafe { Box::from_raw(v as *mut Peekable<T::Iterator>) };
64+
}
65+
}
66+
67+
extern "C" fn next_callback<T: SeqOperations>(
68+
_m: *mut bindings::seq_file,
69+
v: *mut c_types::c_void,
70+
pos: *mut bindings::loff_t,
71+
) -> *mut c_types::c_void {
72+
if v.is_null() {
73+
return ptr::null_mut();
74+
}
75+
76+
// SAFETY: `v` was created by a previous call to `next_callback` or
77+
// `start_callback` and both functions return either a null pointer
78+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
79+
let mut iterator = unsafe { Box::from_raw(v as *mut Peekable<T::Iterator>) };
80+
81+
// SAFETY: The caller guarantees tha `pos` is a valid pointer to an
82+
// `loff_t` and expects this function to mutate the value.
83+
unsafe {
84+
*pos += 1;
85+
}
86+
87+
match iterator.as_mut().next() {
88+
Some(_seen) => Box::into_raw(iterator) as *mut c_types::c_void,
89+
None => ptr::null_mut(),
90+
}
91+
}
92+
93+
extern "C" fn show_callback<T: SeqOperations>(
94+
m: *mut bindings::seq_file,
95+
v: *mut c_types::c_void,
96+
) -> c_types::c_int {
97+
// SAFETY: `v` was created by a previous call to `next_callback` or
98+
// `start_callback` and both functions return either a null pointer
99+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
100+
let iterator = unsafe { (v as *mut Peekable<T::Iterator>).as_mut() };
101+
if let Some(iterator) = iterator {
102+
let s = match iterator.peek() {
103+
// TODO: Replace with fallible `to_string` when available.
104+
Some(item) => item.to_string() + "\0",
105+
None => "\0".to_string(),
106+
};
107+
// SAFETY: Calling a C function. `s` is guaranteed to be null terminated
108+
// because we explicitly constructed it just above.
109+
unsafe {
110+
bindings::seq_puts(m, s.as_ptr() as *const u8 as *const c_types::c_char);
111+
}
112+
}
113+
0
114+
}
115+
116+
extern "C" fn start_callback<T: SeqOperations>(
117+
m: *mut bindings::seq_file,
118+
pos: *mut bindings::loff_t,
119+
) -> *mut c_types::c_void {
120+
// SAFETY: This function will be called by opening a proc file generated
121+
// by a call to `proc_create::<T>`, so `m` can be converted using
122+
// `SeqFile<T>`.
123+
let arg = unsafe { &*SeqFile::<T>::convert(m) };
124+
// SAFETY: The caller guarantees that `pos` points to a valid `loff_t`.
125+
let pos = unsafe { *pos };
126+
match T::start(arg) {
127+
Some(mut wrapper) => {
128+
for _ in 0..pos {
129+
wrapper.as_mut().next();
130+
}
131+
Box::into_raw(wrapper) as *mut c_types::c_void
132+
}
133+
None => ptr::null_mut(),
134+
}
135+
}
136+
137+
struct SeqFileOperationsVTable<T>(PhantomData<T>);
138+
139+
impl<T: SeqOperations> SeqFileOperationsVTable<T> {
140+
const VTABLE: bindings::seq_operations = bindings::seq_operations {
141+
start: Some(start_callback::<T>),
142+
stop: Some(stop_callback::<T>),
143+
next: Some(next_callback::<T>),
144+
show: Some(show_callback::<T>),
145+
};
146+
}
147+
148+
/// A `seq_file` referencing data of type `T`.
149+
pub struct SeqFile<T> {
150+
seq_ops: bindings::seq_operations,
151+
_pin: PhantomPinned,
152+
dir_entry: *mut bindings::proc_dir_entry,
153+
context: T,
154+
}
155+
156+
impl<T> Drop for SeqFile<T> {
157+
fn drop(&mut self) {
158+
if !self.dir_entry.is_null() {
159+
// SAFETY: Calling a C function. `dir_entry` is a valid pointer to a
160+
// `proc_dir_entry` because if it is not null it was created by a
161+
// call to `bindings::proc_create_seq_private`.
162+
unsafe {
163+
bindings::proc_remove(self.dir_entry);
164+
}
165+
}
166+
}
167+
}
168+
169+
impl<T> SeqFile<T> {
170+
fn new(seq_ops: bindings::seq_operations, context: T) -> KernelResult<Box<Self>> {
171+
Ok(Box::try_new(SeqFile {
172+
seq_ops,
173+
_pin: PhantomPinned,
174+
dir_entry: ptr::null_mut::<bindings::proc_dir_entry>(),
175+
context,
176+
})?)
177+
}
178+
179+
/// Retrieve the [`SeqFile::context`] associated with a [`bindings::seq_file`].
180+
///
181+
/// # Safety
182+
///
183+
/// `m` must have been created from a proc file generated by calling
184+
/// `proc_create::<T>`.
185+
unsafe fn convert(m: *mut bindings::seq_file) -> *const T {
186+
let reg = crate::container_of!((*m).op, Self, seq_ops);
187+
&(*reg).context
188+
}
189+
}
190+
191+
// SAFETY: The fields of `SeqFile` that are not `Sync` are `dir_entry` and
192+
// potentially `context`, but `SeqFile` provides no access to these fields.
193+
unsafe impl<T> Sync for SeqFile<T> {}
194+
195+
/// Create a `/proc` file.
196+
///
197+
/// The returned value must not be dropped until the module is unloaded.
198+
pub fn proc_create<T: SeqOperations + Sync>(
199+
name: CStr<'static>,
200+
context: T,
201+
) -> KernelResult<Pin<Box<SeqFile<T>>>> {
202+
let mut reg = SeqFile::new(SeqFileOperationsVTable::<T>::VTABLE, context)?;
203+
204+
let name = name.deref().as_ptr() as *const u8 as *const c_types::c_char;
205+
206+
// SAFETY: Calling a C function. `name` is guaranteed to be null terminated because it is of type
207+
// `CStr`.
208+
let dir_entry = unsafe {
209+
bindings::proc_create_seq_private(
210+
name,
211+
0,
212+
ptr::null_mut(),
213+
&reg.seq_ops,
214+
0,
215+
ptr::null_mut(),
216+
)
217+
};
218+
reg.dir_entry = dir_entry;
219+
let reg = Pin::from(reg);
220+
Ok(reg)
221+
}

samples/rust/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,15 @@ config SAMPLE_RUST_RANDOM
110110

111111
If unsure, say N.
112112

113+
config SAMPLE_RUST_SEQ_FILE
114+
tristate "Seq file"
115+
depends on PROC_FS
116+
help
117+
This option builds the Rust seq_file sample.
118+
119+
To compile this as a module, choose M here:
120+
the module will be called rust_seq_file.
121+
122+
If unsure, say N.
123+
113124
endif # SAMPLES_RUST

samples/rust/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ obj-$(CONFIG_SAMPLE_RUST_STACK_PROBING) += rust_stack_probing.o
1010
obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE) += rust_semaphore.o
1111
obj-$(CONFIG_SAMPLE_RUST_SEMAPHORE_C) += rust_semaphore_c.o
1212
obj-$(CONFIG_SAMPLE_RUST_RANDOM) += rust_random.o
13+
obj-$(CONFIG_SAMPLE_RUST_SEQ_FILE) += rust_seq_file.o

0 commit comments

Comments
 (0)