Skip to content

Commit f5cd0ea

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 ffdacf7 commit f5cd0ea

File tree

7 files changed

+478
-0
lines changed

7 files changed

+478
-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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ pub mod module_param;
5454
mod build_assert;
5555
pub mod prelude;
5656
pub mod print;
57+
58+
#[cfg(CONFIG_PROC_FS)]
59+
pub mod proc_fs;
60+
5761
pub mod random;
62+
pub mod seq_file;
5863
mod static_assert;
5964
pub mod sync;
6065

rust/kernel/proc_fs.rs

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Type for defining `proc` files.
4+
//!
5+
//! This module allows Rust devices to create entries in `/proc` from a
6+
//! [`bindings::proc_ops`] vtable.
7+
//!
8+
//! C header: [`include/linux/proc_fs.h`](../../../include/linux/proc_fs.h)
9+
//!
10+
//! Reference: <https://www.kernel.org/doc/html/latest/filesystems/proc.html>
11+
12+
use core::{
13+
marker::{PhantomData, Sync},
14+
ops::Deref,
15+
ptr,
16+
};
17+
18+
use crate::{bindings, c_types, types::PointerWrapper, CStr, Error, Result};
19+
20+
/// An entry under `/proc` containing data of type `T`.
21+
///
22+
/// This is the Rust equivalent to [`proc_dir_entry`] on the C side.
23+
///
24+
/// # Invariants
25+
///
26+
/// The [`ProcDirEntry::proc_dir_entry`] is a valid pointer.
27+
/// [`ProcDirEntry::data`] points to the PDE data of
28+
/// [`ProcDirEntry::proc_dir_entry`].
29+
/// [`ProcDirEntry::data`] was created by a call to `T::into_pointer`.
30+
///
31+
/// [`proc_dir_entry`]: ../../../fs/proc/internal.h
32+
pub struct ProcDirEntry<T: PointerWrapper> {
33+
proc_dir_entry: *mut bindings::proc_dir_entry,
34+
data: *const c_types::c_void,
35+
_wrapper: PhantomData<T>,
36+
}
37+
38+
// SAFETY: The `proc_dir_entry` and `data` raw pointers aren't accessible.
39+
unsafe impl<T: PointerWrapper> Sync for ProcDirEntry<T> {}
40+
41+
impl<T: PointerWrapper> Drop for ProcDirEntry<T> {
42+
fn drop(&mut self) {
43+
// SAFETY: Calling a C function. `proc_dir_entry` is a valid pointer to
44+
// a `bindings::proc_dir_entry` because it was created by a call to
45+
// `proc_create_data` which only returns valid pointers.
46+
unsafe {
47+
bindings::proc_remove(self.proc_dir_entry);
48+
}
49+
// SAFETY: `self.data` was created by a call to `T::into_pointer`.
50+
unsafe { drop(T::from_pointer(self.data)) }
51+
}
52+
}
53+
54+
/// Create a seq_file entry in `/proc` containing data of type `T`.
55+
///
56+
/// Corresponds to [`proc_create_seq_private`] on the C side.
57+
///
58+
/// # Safety
59+
///
60+
/// The caller must ensure that `T` is the type `seq_ops` expects to be stored in
61+
/// the data of the resulting `proc_dir_entry`.
62+
///
63+
/// [`proc_create_seq_private`]: ../../../fs/proc/generic.c
64+
pub(crate) unsafe fn proc_create_seq_private<T: PointerWrapper>(
65+
name: CStr<'static>,
66+
seq_ops: &'static bindings::seq_operations,
67+
data: T,
68+
) -> Result<ProcDirEntry<T>> {
69+
let data = data.into_pointer();
70+
let name = name.deref().as_ptr() as *const u8 as *const c_types::c_char;
71+
72+
// SAFETY: Calling a C function. `name` is guaranteed to be null terminated
73+
// because it is of type `CStr`.
74+
let proc_dir_entry = bindings::proc_create_seq_private(
75+
name,
76+
0,
77+
ptr::null_mut(),
78+
seq_ops,
79+
0,
80+
data as *mut c_types::c_void,
81+
);
82+
if proc_dir_entry.is_null() {
83+
Err(Error::ENOMEM)
84+
} else {
85+
// INVARIANT: `proc_dir_entry` is a valid pointer.
86+
// The `data` points to the data stored in `proc_dir_entry`, and
87+
// `data` was created by `T::into_pointer`.
88+
Ok(ProcDirEntry {
89+
proc_dir_entry,
90+
data,
91+
_wrapper: PhantomData,
92+
})
93+
}
94+
}

rust/kernel/seq_file.rs

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
//! Trait for defining `seq_file`s.
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+
//!
10+
//! Reference: <https://www.kernel.org/doc/html/latest/filesystems/seq_file.html>
11+
12+
// Currently this module is only usable through proc_fs.
13+
#![cfg_attr(not(CONFIG_PROC_FS), allow(dead_code))]
14+
15+
use core::{
16+
iter::{Iterator, Peekable},
17+
marker::PhantomData,
18+
mem,
19+
ops::{Deref, DerefMut},
20+
ptr,
21+
};
22+
23+
use crate::{bindings, c_types, cstr, types::PointerWrapper, CStr};
24+
25+
#[cfg(CONFIG_PROC_FS)]
26+
pub use proc::proc_create_seq;
27+
28+
/// Rust equivalent of the [`seq_operations`] interface on the C side.
29+
///
30+
/// # Example
31+
///
32+
/// ```
33+
/// struct Data(&'static [String]);
34+
///
35+
/// impl seq_file::SeqOperations for Data {
36+
/// type Item = &'static String;
37+
/// type Iterator = core::slice::Iter<'static, String>;
38+
/// type DataWrapper = Box<Self>;
39+
/// type IteratorWrapper = Box<Peekable<Self::Iterator>>;
40+
///
41+
/// fn start(arg: &Data) -> Option<Self::IteratorWrapper> {
42+
/// let iter = arg.0.iter();
43+
/// Box::try_new(iter.peekable()).ok()
44+
/// }
45+
///
46+
/// fn display(item: &Self::Item) -> &str {
47+
/// &item[..]
48+
/// }
49+
/// }
50+
/// ```
51+
///
52+
/// [`seq_operations`]: ../../../include/linux/seq_file.h
53+
pub trait SeqOperations {
54+
/// Type produced on each iteration.
55+
type Item;
56+
57+
/// Type created when the seq file is opened.
58+
type Iterator: Iterator<Item = Self::Item>;
59+
60+
/// Wrapper used to store a pointer to `T` on the C side.
61+
type DataWrapper: PointerWrapper + Deref<Target = Self>;
62+
63+
/// Wrapper used to store a pointer to the iterator on the C side.
64+
type IteratorWrapper: PointerWrapper + DerefMut<Target = Peekable<Self::Iterator>>;
65+
66+
/// Called once each time the `seq_file` is opened.
67+
fn start(arg: &Self) -> Option<Self::IteratorWrapper>;
68+
69+
/// How the item will be displayed to the reader.
70+
fn display(item: &Self::Item) -> &str;
71+
}
72+
73+
extern "C" fn stop_callback<T: SeqOperations>(
74+
_m: *mut bindings::seq_file,
75+
v: *mut c_types::c_void,
76+
) {
77+
if !v.is_null() {
78+
// SAFETY: `v` was created by a previous call to `next_callback` or
79+
// `start_callback` and both functions return either a null pointer
80+
// or pointer generated by `T::IteratorWrapper::into_pointer`.
81+
drop(unsafe { T::IteratorWrapper::from_pointer(v) })
82+
}
83+
}
84+
85+
extern "C" fn next_callback<T: SeqOperations>(
86+
_m: *mut bindings::seq_file,
87+
v: *mut c_types::c_void,
88+
pos: *mut bindings::loff_t,
89+
) -> *mut c_types::c_void {
90+
if v.is_null() {
91+
return ptr::null_mut();
92+
}
93+
94+
// SAFETY: `v` was created by a previous call to `next_callback` or
95+
// `start_callback` and both functions return either a null pointer
96+
// or pointer generated by `T::IteratorWrapper::into_pointer`.
97+
// We already checked for he null pointer case above.
98+
let mut iterator = unsafe { T::IteratorWrapper::from_pointer(v) };
99+
100+
// SAFETY: The caller guarantees tha `pos` is a valid pointer to an
101+
// `loff_t` and expects this function to mutate the value.
102+
unsafe {
103+
*pos += 1;
104+
}
105+
106+
if iterator.next().is_none() {
107+
return ptr::null_mut();
108+
}
109+
110+
match iterator.peek() {
111+
Some(_next) => T::IteratorWrapper::into_pointer(iterator) as *mut _,
112+
None => ptr::null_mut(),
113+
}
114+
}
115+
116+
extern "C" fn show_callback<T: SeqOperations>(
117+
m: *mut bindings::seq_file,
118+
v: *mut c_types::c_void,
119+
) -> c_types::c_int {
120+
const FORMAT: CStr<'static> = cstr!("%.*s");
121+
if v.is_null() {
122+
return 0;
123+
}
124+
// SAFETY: `v` was created by a previous call to `next_callback` or
125+
// `start_callback` and both functions return either a null pointer
126+
// or pointer generated by `T::IteratorWrapper::into_pointer`. We
127+
// checked for null pointers above. The iterator is forgotten below
128+
// so the pointer on the C side stays valid.
129+
let mut iterator = unsafe { T::IteratorWrapper::from_pointer(v) };
130+
if let Some(item) = iterator.peek() {
131+
let s = T::display(item);
132+
// SAFETY: Calling a C function. `FORMAT` is null terminated because
133+
// it comes from a `CStr`. `s` does not need to be null terminated
134+
// because we are only printing the first `s.len()` bytes.
135+
unsafe {
136+
bindings::seq_printf(
137+
m,
138+
FORMAT.as_ptr() as *const c_types::c_char,
139+
s.len(),
140+
s.as_ptr() as *const u8 as *const c_types::c_char,
141+
);
142+
}
143+
}
144+
// Need to forget the iterator because the C side is still using a
145+
// reference to it.
146+
mem::forget(iterator);
147+
0
148+
}
149+
150+
extern "C" fn start_callback<T: SeqOperations>(
151+
m: *mut bindings::seq_file,
152+
pos: *mut bindings::loff_t,
153+
) -> *mut c_types::c_void {
154+
// SAFETY: This function will be called by opening a proc file generated
155+
// from `proc_create_seq_private` on the C side with data created via
156+
// `T::DataWrapper::into_pointer`. We don't move the data in the wrapper
157+
// so the pointer will remain valid for later calls.
158+
let data_wrapper =
159+
unsafe { T::DataWrapper::from_pointer(bindings::PDE_DATA((*(*m).file).f_inode)) };
160+
let iterator = T::start(&data_wrapper);
161+
// Data is still used in the `proc_dir_entry`.
162+
mem::forget(data_wrapper);
163+
// SAFETY: The caller guarantees that `pos` points to a valid `loff_t`.
164+
let pos = unsafe { *pos };
165+
match iterator {
166+
Some(mut wrapper) => {
167+
for _ in 0..pos {
168+
wrapper.next();
169+
}
170+
match wrapper.peek() {
171+
Some(_next) => T::IteratorWrapper::into_pointer(wrapper) as *mut _,
172+
None => ptr::null_mut(),
173+
}
174+
}
175+
None => ptr::null_mut(),
176+
}
177+
}
178+
179+
struct SeqFileOperationsVTable<T>(PhantomData<T>);
180+
181+
impl<T: SeqOperations> SeqFileOperationsVTable<T> {
182+
const VTABLE: bindings::seq_operations = bindings::seq_operations {
183+
start: Some(start_callback::<T>),
184+
stop: Some(stop_callback::<T>),
185+
next: Some(next_callback::<T>),
186+
show: Some(show_callback::<T>),
187+
};
188+
189+
const fn build() -> &'static bindings::seq_operations {
190+
&Self::VTABLE
191+
}
192+
}
193+
194+
#[cfg(CONFIG_PROC_FS)]
195+
mod proc {
196+
use super::*;
197+
use crate::{
198+
proc_fs::{self, ProcDirEntry},
199+
Result,
200+
};
201+
202+
/// Create an entry in `/proc` for a `seq_file`.
203+
pub fn proc_create_seq<T: SeqOperations>(
204+
name: CStr<'static>,
205+
data: T::DataWrapper,
206+
) -> Result<ProcDirEntry<T::DataWrapper>> {
207+
// SAFETY: The vtable for `T` expects a `T::DataWrapper` pointer in
208+
// the data field of the associated `proc_dir_entry`.
209+
unsafe {
210+
proc_fs::proc_create_seq_private(name, SeqFileOperationsVTable::<T>::build(), data)
211+
}
212+
}
213+
}

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)