Skip to content

Commit 4d7434b

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 4d7434b

File tree

6 files changed

+347
-0
lines changed

6 files changed

+347
-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: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
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 {
31+
/// contents: Vec<u32>,
32+
/// }
33+
///
34+
/// impl SeqOperations for Data {
35+
/// type Item = u32;
36+
/// type Iterator = alloc::vec::IntoIter<u32>;
37+
///
38+
/// fn start(arg: &self) -> Option<Box<Peekable<Self::Iterator>>> {
39+
/// Box::try_new(arg.contents.clone().into_iter().peekable()).ok()
40+
/// }
41+
/// }
42+
/// ```
43+
///
44+
/// [`seq_operations`]: ../../../include/linux/seq_file.h
45+
pub trait SeqOperations {
46+
/// Type produced on each iteration.
47+
type Item: Display;
48+
49+
/// Type created when the seq file is opened.
50+
type Iterator: Iterator<Item = Self::Item>;
51+
52+
/// Called once each time the `seq_file` is opened.
53+
fn start(arg: &Self) -> Option<Box<Peekable<Self::Iterator>>>;
54+
}
55+
56+
extern "C" fn stop_callback<T: SeqOperations>(
57+
_m: *mut bindings::seq_file,
58+
v: *mut c_types::c_void,
59+
) {
60+
if !v.is_null() {
61+
// SAFETY: `v` was created by a previous call to `next_callback` or
62+
// `start_callback` and both functions return either a null pointer
63+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
64+
let _iterator = unsafe { Box::from_raw(v as *mut Peekable<T::Iterator>) };
65+
}
66+
}
67+
68+
extern "C" fn next_callback<T: SeqOperations>(
69+
_m: *mut bindings::seq_file,
70+
v: *mut c_types::c_void,
71+
pos: *mut bindings::loff_t,
72+
) -> *mut c_types::c_void {
73+
if !v.is_null() {
74+
// SAFETY: `v` was created by a previous call to `next_callback` or
75+
// `start_callback` and both functions return either a null pointer
76+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
77+
//
78+
// The caller guarantees tha `pos` is a valid pointer to an
79+
// `loff_t` and expects this function to mutate the value.
80+
let mut iterator = unsafe {
81+
*pos += 1;
82+
Box::from_raw(v as *mut Peekable<T::Iterator>)
83+
};
84+
match iterator.as_mut().next() {
85+
Some(_seen) => Box::into_raw(iterator) as *mut c_types::c_void,
86+
None => ptr::null_mut(),
87+
}
88+
} else {
89+
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+
context: T,
153+
}
154+
155+
impl<T> SeqFile<T> {
156+
fn new_pinned(seq_ops: bindings::seq_operations, context: T) -> KernelResult<Pin<Box<Self>>> {
157+
Ok(Pin::from(Box::try_new(SeqFile {
158+
seq_ops,
159+
_pin: PhantomPinned,
160+
context,
161+
})?))
162+
}
163+
164+
/// Retrieve the [`SeqFile::context`] associated with a [`bindings::seq_file`].
165+
///
166+
/// # Safety
167+
///
168+
/// `m` must have been created from a proc file generated by calling
169+
/// `proc_create::<T>`.
170+
unsafe fn convert(m: *mut bindings::seq_file) -> *const T {
171+
let reg = crate::container_of!((*m).op, Self, seq_ops);
172+
&(*reg).context
173+
}
174+
}
175+
176+
/// Create a `/proc` file.
177+
///
178+
/// The returned value must not be dropped until the module is unloaded.
179+
pub fn proc_create<T: SeqOperations + Sync>(
180+
name: CStr<'static>,
181+
context: T,
182+
) -> KernelResult<Pin<Box<SeqFile<T>>>> {
183+
let reg = SeqFile::new_pinned(SeqFileOperationsVTable::<T>::VTABLE, context)?;
184+
// SAFETY: Calling a C function. `name` is guaranteed to be null terminated
185+
// because it is of type `CStr`.
186+
unsafe {
187+
let _dir_entry = bindings::proc_create_seq_private(
188+
name.deref().as_ptr() as *const u8 as *const c_types::c_char,
189+
0,
190+
ptr::null_mut(),
191+
&reg.seq_ops,
192+
0,
193+
ptr::null_mut(),
194+
);
195+
Ok(reg)
196+
}
197+
}

samples/rust/Kconfig

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

111111
If unsure, say N.
112112

113+
config SAMPLE_RUST_SEQ_FILE
114+
tristate "Seq file"
115+
help
116+
This option builds the Rust seq_file sample.
117+
118+
To compile this as a module, choose M here:
119+
the module will be called rust_seq_file.
120+
121+
If unsure, say N.
122+
113123
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

samples/rust/rust_seq_file.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Example of using a [`seq_file`] in Rust.
4+
//!
5+
//! C header: [`include/linux/seq_file.h`](../../../include/linux/seq_file.h)
6+
//! C header: [`include/linux/proc_fs.h`](../../../include/linux/proc_fs.h)
7+
//!
8+
//! Reference: <https://www.kernel.org/doc/html/latest/filesystems/seq_file.html>
9+
10+
#![no_std]
11+
#![feature(allocator_api, global_asm, try_reserve)]
12+
13+
use alloc::{boxed::Box, sync::Arc};
14+
use core::{
15+
convert::TryInto,
16+
fmt::Write,
17+
iter::{repeat, Peekable, Repeat, Take},
18+
pin::Pin,
19+
};
20+
use kernel::{
21+
cstr,
22+
file_operations::{File, FileOpener, FileOperations},
23+
io_buffer::IoBufferWriter,
24+
miscdev, mutex_init, seq_file,
25+
sync::Mutex,
26+
};
27+
28+
use kernel::prelude::*;
29+
30+
module! {
31+
type: RustSeqFileDev,
32+
name: b"rust_seq_file",
33+
author: b"Adam Bratschi-Kaye",
34+
description: b"Rust sample using a seq_file",
35+
license: b"GPL v2",
36+
params: {
37+
},
38+
}
39+
40+
#[derive(Clone)]
41+
struct SharedState(Arc<Mutex<u32>>);
42+
43+
impl SharedState {
44+
fn try_new() -> KernelResult<Self> {
45+
let state = Arc::try_new(
46+
// SAFETY: `mutex_init!` is called below.
47+
unsafe { Mutex::new(0) },
48+
)?;
49+
// SAFETY: Mutex is pinned behind `Arc`.
50+
let pin_state = unsafe { Pin::new_unchecked(state.as_ref()) };
51+
mutex_init!(pin_state, "SharedState::0");
52+
Ok(SharedState(state))
53+
}
54+
}
55+
56+
impl seq_file::SeqOperations for SharedState {
57+
type Item = String;
58+
type Iterator = Take<Repeat<String>>;
59+
60+
fn start(arg: &SharedState) -> Option<Box<Peekable<Self::Iterator>>> {
61+
let count = arg.0.lock();
62+
let mut message = String::new();
63+
64+
let template = "rust_seq_file: device opened this many times: ";
65+
message.try_reserve_exact(template.len() + 4).ok()?;
66+
// NO PANIC: We reserved space for `template` above.
67+
message.push_str(template);
68+
if *count < 1000 {
69+
// NO PANIC: There are 4 characters remaining in the string which
70+
// leaves space for a 3 digit number and the newline.
71+
write!(&mut message, "{}\n", *count).ok()?;
72+
}
73+
74+
Box::try_new(repeat(message).take((*count).try_into().ok()?).peekable()).ok()
75+
}
76+
}
77+
78+
impl FileOpener<SharedState> for SharedState {
79+
fn open(ctx: &SharedState) -> KernelResult<Self::Wrapper> {
80+
pr_info!("rust seq_file was opened!\n");
81+
Ok(Box::try_new(ctx.clone())?)
82+
}
83+
}
84+
85+
impl FileOperations for SharedState {
86+
type Wrapper = Box<Self>;
87+
88+
kernel::declare_file_operations!(read);
89+
90+
fn read<T: IoBufferWriter>(&self, _: &File, data: &mut T, offset: u64) -> KernelResult<usize> {
91+
let message = b"incremented read count\n";
92+
if offset != 0 {
93+
return Ok(0);
94+
}
95+
96+
{
97+
let mut count = self.0.lock();
98+
*count += 1;
99+
}
100+
101+
data.write_slice(message)?;
102+
Ok(message.len())
103+
}
104+
}
105+
106+
struct RustSeqFileDev {
107+
_seq: Pin<Box<seq_file::SeqFile<SharedState>>>,
108+
_dev: Pin<Box<miscdev::Registration<SharedState>>>,
109+
}
110+
111+
impl KernelModule for RustSeqFileDev {
112+
fn init() -> KernelResult<Self> {
113+
pr_info!("Rust seq_file sample (init)\n");
114+
115+
let state = SharedState::try_new()?;
116+
117+
let seq_file =
118+
kernel::seq_file::proc_create::<SharedState>(cstr!("rust_seq_file"), state.clone())?;
119+
120+
let dev_reg =
121+
miscdev::Registration::new_pinned::<SharedState>(cstr!("rust_seq_file"), None, state)?;
122+
123+
let dev = RustSeqFileDev {
124+
_seq: seq_file,
125+
_dev: dev_reg,
126+
};
127+
128+
Ok(dev)
129+
}
130+
}
131+
132+
impl Drop for RustSeqFileDev {
133+
fn drop(&mut self) {
134+
pr_info!("Rust seq_file sample (exit)\n");
135+
}
136+
}

0 commit comments

Comments
 (0)