Skip to content

Commit 9fb571c

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 9fb571c

File tree

7 files changed

+472
-0
lines changed

7 files changed

+472
-0
lines changed

rust/kernel/bindings_helper.h

Lines changed: 6 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>
@@ -13,6 +15,10 @@
1315
#include <linux/mm.h>
1416
#include <uapi/linux/android/binder.h>
1517

18+
#ifdef CONFIG_PROC_FS
19+
#include "../../fs/proc/internal.h"
20+
#endif
21+
1622
// `bindgen` gets confused at certain things
1723
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;
1824
const gfp_t BINDINGS___GFP_ZERO = __GFP_ZERO;

rust/kernel/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,12 @@ pub mod module_param;
5252

5353
pub mod prelude;
5454
pub mod print;
55+
56+
#[cfg(CONFIG_PROC_FS)]
57+
pub mod proc_fs;
58+
5559
pub mod random;
60+
pub mod seq_file;
5661
mod static_assert;
5762
pub mod sync;
5863

rust/kernel/proc_fs.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 alloc::boxed::Box;
13+
use core::{
14+
marker::{PhantomData, Sync},
15+
ops::Deref,
16+
ptr,
17+
};
18+
19+
use crate::{bindings, c_types, CStr, Error, KernelResult};
20+
21+
/// An entry under `/proc` containing data of type `T`.
22+
///
23+
/// This is the Rust equivalent to [`proc_dir_entry`] on the C side.
24+
///
25+
/// # Invariants
26+
///
27+
/// The pointer [`ProcDirEntry::proc_dir_entry`] is a valid pointer and
28+
/// it's field [`bindings::proc_dir_entry::data`] is a valid pointer to
29+
/// `T`.
30+
///
31+
/// [`proc_dir_entry`]: ../../../fs/proc/internal.h
32+
pub struct ProcDirEntry<T> {
33+
proc_dir_entry: *mut bindings::proc_dir_entry,
34+
data: PhantomData<T>,
35+
}
36+
37+
// SAFETY: The `proc_dir_entry` raw pointer isn't accessible.
38+
unsafe impl<T> Sync for ProcDirEntry<T> {}
39+
40+
impl<T> Drop for ProcDirEntry<T> {
41+
fn drop(&mut self) {
42+
// SAFETY: `ProcDirEntry` is guaranteed to have a valid pointer to `T`
43+
// in the `data` field of `proc_dir_entry`.
44+
let data = unsafe { Box::from_raw((*self.proc_dir_entry).data as *mut T) };
45+
// SAFETY: Calling a C function. `proc_dir_entry` is a valid pointer to
46+
// a `bindings::proc_dir_entry` because it was created by a call to
47+
// `proc_create_data` which only returns valid pointers.
48+
unsafe {
49+
bindings::proc_remove(self.proc_dir_entry);
50+
}
51+
drop(data);
52+
}
53+
}
54+
55+
/// Create an entry in `/proc` containing data of type `T`.
56+
///
57+
/// Corresponds to [`proc_create_data`] on the C side.
58+
///
59+
/// [`proc_create_data]: ../../../fs/proc/generic.c
60+
pub(crate) fn proc_create_data<T>(
61+
name: CStr<'static>,
62+
proc_ops: &'static bindings::proc_ops,
63+
data: T,
64+
) -> KernelResult<ProcDirEntry<T>> {
65+
let data_ptr = Box::into_raw(Box::try_new(data)?) as *mut c_types::c_void;
66+
let name = name.deref().as_ptr() as *const u8 as *const c_types::c_char;
67+
68+
// SAFETY: Calling a C function. `name` is guaranteed to be null terminated
69+
// because it is of type `CStr`.
70+
let proc_dir_entry =
71+
unsafe { bindings::proc_create_data(name, 0, ptr::null_mut(), proc_ops, data_ptr) };
72+
if proc_dir_entry.is_null() {
73+
Err(Error::ENOMEM)
74+
} else {
75+
// INVARIANT: `proc_dir_entry` is a valid pointer and it's data field
76+
// is a valid pointer to `T`.
77+
Ok(ProcDirEntry {
78+
proc_dir_entry,
79+
data: PhantomData,
80+
})
81+
}
82+
}

rust/kernel/seq_file.rs

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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 alloc::boxed::Box;
16+
use core::{
17+
iter::{Iterator, Peekable},
18+
marker::PhantomData,
19+
ptr,
20+
};
21+
22+
use crate::{bindings, c_types, cstr, CStr};
23+
24+
#[cfg(CONFIG_PROC_FS)]
25+
pub use proc::proc_create_seq;
26+
27+
/// Rust equivalent of the [`seq_operations`] interface on the C side.
28+
///
29+
/// # Example
30+
///
31+
/// ```
32+
/// struct Data(&'static [String]);
33+
///
34+
/// impl seq_file::SeqOperations for Data {
35+
/// type Item = &'static String;
36+
/// type Iterator = core::slice::Iter<'static, String>;
37+
///
38+
/// fn start(arg: &Data) -> Option<Box<Peekable<Self::Iterator>>> {
39+
/// let iter = arg.0.iter();
40+
/// Box::try_new(iter.peekable()).ok()
41+
/// }
42+
///
43+
/// fn display(item: &Self::Item) -> &str {
44+
/// &item[..]
45+
/// }
46+
/// }
47+
/// ```
48+
///
49+
/// [`seq_operations`]: ../../../include/linux/seq_file.h
50+
pub trait SeqOperations {
51+
/// Type produced on each iteration.
52+
type Item;
53+
54+
/// Type created when the seq file is opened.
55+
type Iterator: Iterator<Item = Self::Item>;
56+
57+
/// Called once each time the `seq_file` is opened.
58+
fn start(arg: &Self) -> Option<Box<Peekable<Self::Iterator>>>;
59+
60+
/// How the item will be displayed to the reader.
61+
fn display(item: &Self::Item) -> &str;
62+
}
63+
64+
extern "C" fn stop_callback<T: SeqOperations>(
65+
_m: *mut bindings::seq_file,
66+
v: *mut c_types::c_void,
67+
) {
68+
if !v.is_null() {
69+
// SAFETY: `v` was created by a previous call to `next_callback` or
70+
// `start_callback` and both functions return either a null pointer
71+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
72+
drop(unsafe { Box::from_raw(v as *mut Peekable<T::Iterator>) })
73+
}
74+
}
75+
76+
extern "C" fn next_callback<T: SeqOperations>(
77+
_m: *mut bindings::seq_file,
78+
v: *mut c_types::c_void,
79+
pos: *mut bindings::loff_t,
80+
) -> *mut c_types::c_void {
81+
if v.is_null() {
82+
return ptr::null_mut();
83+
}
84+
85+
// SAFETY: `v` was created by a previous call to `next_callback` or
86+
// `start_callback` and both functions return either a null pointer
87+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
88+
// We already checked for he null pointer case above.
89+
let mut iterator = unsafe { Box::from_raw(v as *mut Peekable<T::Iterator>) };
90+
91+
// SAFETY: The caller guarantees tha `pos` is a valid pointer to an
92+
// `loff_t` and expects this function to mutate the value.
93+
unsafe {
94+
*pos += 1;
95+
}
96+
97+
iterator.next();
98+
match iterator.peek() {
99+
Some(_next) => Box::into_raw(iterator) as *mut c_types::c_void,
100+
None => ptr::null_mut(),
101+
}
102+
}
103+
104+
extern "C" fn show_callback<T: SeqOperations>(
105+
m: *mut bindings::seq_file,
106+
v: *mut c_types::c_void,
107+
) -> c_types::c_int {
108+
const FORMAT: CStr<'static> = cstr!("%.*s");
109+
// SAFETY: `v` was created by a previous call to `next_callback` or
110+
// `start_callback` and both functions return either a null pointer
111+
// or pointer generated by `Box<Peekable<T::Iterator>>::into_raw`.
112+
if let Some(iterator) = unsafe { (v as *mut Peekable<T::Iterator>).as_mut() } {
113+
if let Some(item) = iterator.peek() {
114+
let s = T::display(item);
115+
// SAFETY: Calling a C function. `FORMAT` is null terminated because
116+
// it comes from a `CStr`. `s` does not need to be null terminated
117+
// because we are only printing the first `s.len()` bytes.
118+
unsafe {
119+
bindings::seq_printf(
120+
m,
121+
FORMAT.as_ptr() as *const c_types::c_char,
122+
s.len(),
123+
s.as_ptr() as *const u8 as *const c_types::c_char,
124+
);
125+
}
126+
}
127+
}
128+
0
129+
}
130+
131+
extern "C" fn start_callback<T: SeqOperations>(
132+
m: *mut bindings::seq_file,
133+
pos: *mut bindings::loff_t,
134+
) -> *mut c_types::c_void {
135+
// SAFETY: This function will be called by opening a proc file generated
136+
// using `proc_open_callback::<T>`, so the `private` field
137+
// of `m` is guaranteed to be a valid pointer to `T`.
138+
let arg = unsafe { &*((*m).private as *const T) };
139+
// SAFETY: The caller guarantees that `pos` points to a valid `loff_t`.
140+
let pos = unsafe { *pos };
141+
match T::start(arg) {
142+
Some(mut wrapper) => {
143+
for _ in 0..pos {
144+
wrapper.next();
145+
}
146+
match wrapper.peek() {
147+
Some(_next) => Box::into_raw(wrapper) as *mut c_types::c_void,
148+
None => ptr::null_mut(),
149+
}
150+
}
151+
None => ptr::null_mut(),
152+
}
153+
}
154+
155+
struct SeqFileOperationsVTable<T>(PhantomData<T>);
156+
157+
impl<T: SeqOperations> SeqFileOperationsVTable<T> {
158+
const VTABLE: bindings::seq_operations = bindings::seq_operations {
159+
start: Some(start_callback::<T>),
160+
stop: Some(stop_callback::<T>),
161+
next: Some(next_callback::<T>),
162+
show: Some(show_callback::<T>),
163+
};
164+
165+
const fn build() -> &'static bindings::seq_operations {
166+
&Self::VTABLE
167+
}
168+
}
169+
170+
#[cfg(CONFIG_PROC_FS)]
171+
mod proc {
172+
use super::*;
173+
use crate::{proc_fs, proc_fs::ProcDirEntry, KernelResult};
174+
175+
extern "C" fn proc_open_callback<T: SeqOperations>(
176+
inode: *mut bindings::inode,
177+
file: *mut bindings::file,
178+
) -> c_types::c_int {
179+
// SAFETY: Calling a C function.
180+
let result = unsafe { bindings::seq_open(file, SeqFileOperationsVTable::<T>::build()) };
181+
if result == 0 {
182+
// SAFETY: Calling a C function.
183+
let data = unsafe { bindings::PDE_DATA(inode) };
184+
// SAFETY: `file` is a valid pointer.
185+
let seq_file = unsafe { (*file).private_data as *mut bindings::seq_file };
186+
// SAFETY: `seq_open` allocates a `seq_file` in `(*file).private_data`
187+
// and we've checked that `seq_open` succeeded so `seq_file` is a valid
188+
// pointer.
189+
unsafe { (*seq_file).private = data };
190+
0
191+
} else {
192+
result
193+
}
194+
}
195+
196+
struct SeqFileProcOperationsVTable<T>(PhantomData<T>);
197+
198+
impl<T: SeqOperations> SeqFileProcOperationsVTable<T> {
199+
const VTABLE: bindings::proc_ops = bindings::proc_ops {
200+
proc_flags: 0,
201+
proc_open: Some(proc_open_callback::<T>),
202+
proc_read: Some(bindings::seq_read),
203+
proc_read_iter: Some(bindings::seq_read_iter),
204+
proc_write: None,
205+
proc_lseek: Some(bindings::seq_lseek),
206+
proc_release: Some(bindings::seq_release),
207+
proc_poll: None,
208+
proc_ioctl: None,
209+
proc_mmap: None,
210+
proc_get_unmapped_area: None,
211+
};
212+
213+
const fn build() -> &'static bindings::proc_ops {
214+
&Self::VTABLE
215+
}
216+
}
217+
218+
/// Create an entry in `/proc` for a `seq_file`.
219+
pub fn proc_create_seq<T: SeqOperations>(
220+
name: CStr<'static>,
221+
data: T,
222+
) -> KernelResult<ProcDirEntry<T>> {
223+
proc_fs::proc_create_data(name, SeqFileProcOperationsVTable::<T>::build(), data)
224+
}
225+
}

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)