Skip to content

Commit 4f0aeff

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 c76ea62 commit 4f0aeff

12 files changed

+467
-0
lines changed

.github/workflows/kernel-arm64-debug.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m
14321432
CONFIG_SAMPLE_RUST_SEMAPHORE=m
14331433
CONFIG_SAMPLE_RUST_SEMAPHORE_C=m
14341434
CONFIG_SAMPLE_RUST_RANDOM=m
1435+
CONFIG_SAMPLE_RUST_SEQ_FILE=m
14351436

14361437
#
14371438
# arm64 Debugging

.github/workflows/kernel-arm64-release.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m
13501350
CONFIG_SAMPLE_RUST_SEMAPHORE=m
13511351
CONFIG_SAMPLE_RUST_SEMAPHORE_C=m
13521352
CONFIG_SAMPLE_RUST_RANDOM=m
1353+
CONFIG_SAMPLE_RUST_SEQ_FILE=m
13531354

13541355
#
13551356
# arm64 Debugging

.github/workflows/kernel-ppc64le-debug.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,6 +1492,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m
14921492
CONFIG_SAMPLE_RUST_SEMAPHORE=m
14931493
CONFIG_SAMPLE_RUST_SEMAPHORE_C=m
14941494
CONFIG_SAMPLE_RUST_RANDOM=m
1495+
CONFIG_SAMPLE_RUST_SEQ_FILE=m
14951496
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
14961497
# CONFIG_STRICT_DEVMEM is not set
14971498

.github/workflows/kernel-ppc64le-release.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1454,6 +1454,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m
14541454
CONFIG_SAMPLE_RUST_SEMAPHORE=m
14551455
CONFIG_SAMPLE_RUST_SEMAPHORE_C=m
14561456
CONFIG_SAMPLE_RUST_RANDOM=m
1457+
CONFIG_SAMPLE_RUST_SEQ_FILE=m
14571458
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
14581459
# CONFIG_STRICT_DEVMEM is not set
14591460

.github/workflows/kernel-x86_64-debug.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,7 @@ CONFIG_SAMPLE_RUST_STACK_PROBING=m
14441444
CONFIG_SAMPLE_RUST_SEMAPHORE=m
14451445
CONFIG_SAMPLE_RUST_SEMAPHORE_C=m
14461446
CONFIG_SAMPLE_RUST_RANDOM=m
1447+
CONFIG_SAMPLE_RUST_SEQ_FILE=m
14471448
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y
14481449
# CONFIG_STRICT_DEVMEM is not set
14491450

rust/kernel/bindings_helper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
#include <linux/errname.h>
55
#include <linux/fs.h>
66
#include <linux/module.h>
7+
#include <linux/proc_fs.h>
78
#include <linux/random.h>
9+
#include <linux/seq_file.h>
810
#include <linux/slab.h>
911
#include <linux/sysctl.h>
1012
#include <linux/uaccess.h>

rust/kernel/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ pub mod module_param;
6262
mod build_assert;
6363
pub mod prelude;
6464
pub mod print;
65+
66+
#[cfg(CONFIG_PROC_FS)]
67+
pub mod proc_fs;
68+
6569
pub mod random;
70+
pub mod seq_file;
6671
mod static_assert;
6772
pub mod sync;
6873

rust/kernel/proc_fs.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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+
ptr,
15+
};
16+
17+
use crate::{
18+
bindings, c_types,
19+
seq_file::{SeqFileOperationsVTable, SeqOperations},
20+
str::CStr,
21+
types::PointerWrapper,
22+
Error, Result,
23+
};
24+
25+
/// An entry under `/proc` containing data of type `T`.
26+
///
27+
/// This is the Rust equivalent to [`proc_dir_entry`] on the C side.
28+
///
29+
/// # Invariants
30+
///
31+
/// The [`ProcDirEntry::proc_dir_entry`] is a valid pointer.
32+
/// [`ProcDirEntry::data`] points to the PDE data of
33+
/// [`ProcDirEntry::proc_dir_entry`].
34+
/// [`ProcDirEntry::data`] was created by a call to `T::into_pointer`.
35+
///
36+
/// [`proc_dir_entry`]: ../../../fs/proc/internal.h
37+
pub struct ProcDirEntry<T: PointerWrapper> {
38+
proc_dir_entry: *mut bindings::proc_dir_entry,
39+
data: *const c_types::c_void,
40+
_wrapper: PhantomData<T>,
41+
}
42+
43+
// SAFETY: The `proc_dir_entry` and `data` raw pointers aren't accessible.
44+
unsafe impl<T: PointerWrapper> Sync for ProcDirEntry<T> {}
45+
46+
impl<T: PointerWrapper> Drop for ProcDirEntry<T> {
47+
fn drop(&mut self) {
48+
// SAFETY: Calling a C function. `proc_dir_entry` is a valid pointer to
49+
// a `bindings::proc_dir_entry` because it was created by a call to
50+
// `proc_create_data` which only returns valid pointers.
51+
unsafe {
52+
bindings::proc_remove(self.proc_dir_entry);
53+
}
54+
// SAFETY: `self.data` was created by a call to `T::into_pointer`.
55+
unsafe { drop(T::from_pointer(self.data)) }
56+
}
57+
}
58+
59+
impl<T: PointerWrapper> ProcDirEntry<T> {
60+
/// Create a seq_file entry in `/proc` containing data of type `S`.
61+
///
62+
/// Corresponds to [`proc_create_seq_private`] on the C side.
63+
///
64+
/// [`proc_create_seq_private`]: ../../../fs/proc/generic.c
65+
pub fn new_seq_private<S>(name: &CStr, data: T) -> Result<Self>
66+
where
67+
S: SeqOperations<DataWrapper = T>,
68+
{
69+
let data = data.into_pointer();
70+
let name = name.as_char_ptr();
71+
72+
// SAFETY: Calling a C function. The vtable for `S` expects a
73+
// `S::DataWrapper = T` pointer in the data field of the associated
74+
// `proc_dir_entry`. `name` is guaranteed to be null terminated
75+
// because it is of type `CStr`.
76+
let proc_dir_entry = unsafe {
77+
bindings::proc_create_seq_private(
78+
name,
79+
0,
80+
ptr::null_mut(),
81+
SeqFileOperationsVTable::<S>::build(),
82+
0,
83+
data as *mut c_types::c_void,
84+
)
85+
};
86+
if proc_dir_entry.is_null() {
87+
// SAFETY: `data` was created with a call to `T::into_pointer`.
88+
drop(unsafe { T::from_pointer(data) });
89+
Err(Error::ENOMEM)
90+
} else {
91+
// INVARIANT: `proc_dir_entry` is a valid pointer.
92+
// The `data` points to the data stored in `proc_dir_entry`, and
93+
// `data` was created by `T::into_pointer`.
94+
Ok(ProcDirEntry {
95+
proc_dir_entry,
96+
data,
97+
_wrapper: PhantomData,
98+
})
99+
}
100+
}
101+
}

rust/kernel/seq_file.rs

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

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)