Skip to content

Commit a5003f2

Browse files
committed
rust: implement a #[vtable] macro
Use a single `#[vtable]` macro to replace the current boilerplating of `ToUse`, `USE_NONE`, `declare_file_operations` required for declaring and implementing traits that maps to Linux's pure vtables and contains optional methods. Signed-off-by: Gary Guo <[email protected]>
1 parent 7884043 commit a5003f2

File tree

9 files changed

+159
-90
lines changed

9 files changed

+159
-90
lines changed

drivers/android/process.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -802,11 +802,10 @@ impl FileOpener<Arc<Context>> for Process {
802802
}
803803
}
804804

805+
#[vtable]
805806
impl FileOperations for Process {
806807
type Wrapper = Pin<Ref<Self>>;
807808

808-
kernel::declare_file_operations!(ioctl, compat_ioctl, mmap, poll);
809-
810809
fn release(obj: Self::Wrapper, _file: &File) {
811810
// Mark this process as dead. We'll do the same for the threads later.
812811
obj.inner.lock().is_dead = true;

rust/kernel/file_operations.rs

+12-76
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
from_kernel_result,
1717
io_buffer::{IoBufferReader, IoBufferWriter},
1818
iov_iter::IovIter,
19+
prelude::*,
1920
sync::CondVar,
2021
types::PointerWrapper,
2122
user_ptr::{UserSlicePtr, UserSlicePtrReader, UserSlicePtrWriter},
@@ -254,24 +255,24 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
254255
const VTABLE: bindings::file_operations = bindings::file_operations {
255256
open: Some(open_callback::<A, T>),
256257
release: Some(release_callback::<T>),
257-
read: if T::TO_USE.read {
258+
read: if T::HAS_READ {
258259
Some(read_callback::<T>)
259260
} else {
260261
None
261262
},
262-
write: if T::TO_USE.write {
263+
write: if T::HAS_WRITE {
263264
Some(write_callback::<T>)
264265
} else {
265266
None
266267
},
267-
llseek: if T::TO_USE.seek {
268+
llseek: if T::HAS_SEEK {
268269
Some(llseek_callback::<T>)
269270
} else {
270271
None
271272
},
272273

273274
check_flags: None,
274-
compat_ioctl: if T::TO_USE.compat_ioctl {
275+
compat_ioctl: if T::HAS_COMPAT_IOCTL {
275276
Some(compat_ioctl_callback::<T>)
276277
} else {
277278
None
@@ -282,7 +283,7 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
282283
fasync: None,
283284
flock: None,
284285
flush: None,
285-
fsync: if T::TO_USE.fsync {
286+
fsync: if T::HAS_FSYNC {
286287
Some(fsync_callback::<T>)
287288
} else {
288289
None
@@ -292,19 +293,19 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
292293
iterate_shared: None,
293294
iopoll: None,
294295
lock: None,
295-
mmap: if T::TO_USE.mmap {
296+
mmap: if T::HAS_MMAP {
296297
Some(mmap_callback::<T>)
297298
} else {
298299
None
299300
},
300301
mmap_supported_flags: 0,
301302
owner: ptr::null_mut(),
302-
poll: if T::TO_USE.poll {
303+
poll: if T::HAS_POLL {
303304
Some(poll_callback::<T>)
304305
} else {
305306
None
306307
},
307-
read_iter: if T::TO_USE.read_iter {
308+
read_iter: if T::HAS_READ {
308309
Some(read_iter_callback::<T>)
309310
} else {
310311
None
@@ -315,12 +316,12 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
315316
show_fdinfo: None,
316317
splice_read: None,
317318
splice_write: None,
318-
unlocked_ioctl: if T::TO_USE.ioctl {
319+
unlocked_ioctl: if T::HAS_IOCTL {
319320
Some(unlocked_ioctl_callback::<T>)
320321
} else {
321322
None
322323
},
323-
write_iter: if T::TO_USE.write_iter {
324+
write_iter: if T::HAS_WRITE {
324325
Some(write_iter_callback::<T>)
325326
} else {
326327
None
@@ -337,69 +338,6 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
337338
}
338339
}
339340

340-
/// Represents which fields of [`struct file_operations`] should be populated with pointers.
341-
pub struct ToUse {
342-
/// The `read` field of [`struct file_operations`].
343-
pub read: bool,
344-
345-
/// The `read_iter` field of [`struct file_operations`].
346-
pub read_iter: bool,
347-
348-
/// The `write` field of [`struct file_operations`].
349-
pub write: bool,
350-
351-
/// The `write_iter` field of [`struct file_operations`].
352-
pub write_iter: bool,
353-
354-
/// The `llseek` field of [`struct file_operations`].
355-
pub seek: bool,
356-
357-
/// The `unlocked_ioctl` field of [`struct file_operations`].
358-
pub ioctl: bool,
359-
360-
/// The `compat_ioctl` field of [`struct file_operations`].
361-
pub compat_ioctl: bool,
362-
363-
/// The `fsync` field of [`struct file_operations`].
364-
pub fsync: bool,
365-
366-
/// The `mmap` field of [`struct file_operations`].
367-
pub mmap: bool,
368-
369-
/// The `poll` field of [`struct file_operations`].
370-
pub poll: bool,
371-
}
372-
373-
/// A constant version where all values are to set to `false`, that is, all supported fields will
374-
/// be set to null pointers.
375-
pub const USE_NONE: ToUse = ToUse {
376-
read: false,
377-
read_iter: false,
378-
write: false,
379-
write_iter: false,
380-
seek: false,
381-
ioctl: false,
382-
compat_ioctl: false,
383-
fsync: false,
384-
mmap: false,
385-
poll: false,
386-
};
387-
388-
/// Defines the [`FileOperations::TO_USE`] field based on a list of fields to be populated.
389-
#[macro_export]
390-
macro_rules! declare_file_operations {
391-
() => {
392-
const TO_USE: $crate::file_operations::ToUse = $crate::file_operations::USE_NONE;
393-
};
394-
($($i:ident),+) => {
395-
const TO_USE: kernel::file_operations::ToUse =
396-
$crate::file_operations::ToUse {
397-
$($i: true),+ ,
398-
..$crate::file_operations::USE_NONE
399-
};
400-
};
401-
}
402-
403341
/// Allows the handling of ioctls defined with the `_IO`, `_IOR`, `_IOW`, and `_IOWR` macros.
404342
///
405343
/// For each macro, there is a handler function that takes the appropriate types as arguments.
@@ -527,10 +465,8 @@ impl<T: FileOperations<Wrapper = Box<T>> + Default> FileOpener<()> for T {
527465
/// File descriptors may be used from multiple threads/processes concurrently, so your type must be
528466
/// [`Sync`]. It must also be [`Send`] because [`FileOperations::release`] will be called from the
529467
/// thread that decrements that associated file's refcount to zero.
468+
#[vtable]
530469
pub trait FileOperations: Send + Sync + Sized {
531-
/// The methods to use to populate [`struct file_operations`].
532-
const TO_USE: ToUse;
533-
534470
/// The pointer type that will be used to hold ourselves.
535471
type Wrapper: PointerWrapper = Box<Self>;
536472

rust/kernel/prelude.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ pub use alloc::{borrow::ToOwned, string::String};
1515

1616
pub use super::build_assert;
1717

18-
pub use macros::{module, module_misc_device};
18+
pub use macros::{module, module_misc_device, vtable};
1919

2020
pub use super::{pr_alert, pr_crit, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
2121

rust/macros/lib.rs

+53-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#![deny(clippy::style)]
99

1010
mod module;
11+
mod vtable;
1112

1213
use proc_macro::TokenStream;
1314

@@ -122,11 +123,62 @@ pub fn module(ts: TokenStream) -> TokenStream {
122123
/// #[derive(Default)]
123124
/// struct MyFile;
124125
///
126+
/// #[vtable]
125127
/// impl kernel::file_operations::FileOperations for MyFile {
126-
/// kernel::declare_file_operations!();
127128
/// }
128129
/// ```
129130
#[proc_macro]
130131
pub fn module_misc_device(ts: TokenStream) -> TokenStream {
131132
module::module_misc_device(ts)
132133
}
134+
135+
/// Declares or implements a vtable trait.
136+
///
137+
/// Linux's use of pure vtables is very close to Rust traits, but they differ
138+
/// in how unimplemented functions are represented. In Rust, traits can provide
139+
/// default implementation for all non-required methods (and the default
140+
/// implementation could just return `Error::EINVAL`); Linux typically use C
141+
/// `NULL` pointers to represent these functions.
142+
///
143+
/// This attribute is intended to close the gap. Traits can be declared and
144+
/// implemented with the `#[vtable]` attribute, and a `HAS_*` associated constant
145+
/// will be generated for each method in the trait, indicating if the implementor
146+
/// has overriden a method.
147+
///
148+
/// This attribute is not needed if all methods are required.
149+
///
150+
/// # Examples
151+
///
152+
/// ```rust,no_run
153+
/// use kernel::prelude::*;
154+
///
155+
/// // Declares a `#[vtable]` trait
156+
/// #[vtable]
157+
/// pub trait FileOperations: Send + Sync + Sized {
158+
/// fn read<T: IoBufferWriter>(&self, _file: &File, _data: &mut T, _offset: u64) -> Result<usize> {
159+
/// Err(Error::EINVAL)
160+
/// }
161+
///
162+
/// fn write<T: IoBufferWriter>(&self, _file: &File, _data: &mut T, _offset: u64) -> Result<usize> {
163+
/// Err(Error::EINVAL)
164+
/// }
165+
/// }
166+
///
167+
/// struct RustFile;
168+
///
169+
/// // Implements the `#[vtable]` trait
170+
/// #[vtable]
171+
/// impl FileOperations for RustFile {
172+
/// fn read<T: IoBufferWriter>(&self, _file: &File, _data: &mut T, _offset: u64) -> Result<usize> {
173+
/// # Err(Error::EINVAL)
174+
/// /* ... */
175+
/// }
176+
/// }
177+
///
178+
/// assert_eq!(<RustFile as FileOperations>::HAS_READ, true);
179+
/// assert_eq!(<RustFile as FileOperations>::HAS_WRITE, false);
180+
/// ```
181+
#[proc_macro_attribute]
182+
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
183+
vtable::vtable(attr, ts)
184+
}

rust/macros/vtable.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
4+
use std::fmt::Write;
5+
6+
pub fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
7+
let mut it = ts.into_iter();
8+
9+
let mut tokens = Vec::new();
10+
11+
// Scan for the `trait` or `impl` keyword
12+
let is_trait = loop {
13+
let token = it.next().expect("unexpected end");
14+
let keyword = match &token {
15+
TokenTree::Ident(ident) => match ident.to_string().as_str() {
16+
"trait" => Some(true),
17+
"impl" => Some(false),
18+
_ => None,
19+
},
20+
_ => None,
21+
};
22+
tokens.push(token);
23+
if let Some(v) = keyword {
24+
break v;
25+
}
26+
};
27+
28+
// Scan for the main body
29+
// FIXME: `{}` is allowed within type as well, e.g. `impl Foo for Bar<{0}>`.
30+
// but these are very rare and we don't want to have generics parsing code.
31+
let body = loop {
32+
match it.next() {
33+
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => {
34+
break group;
35+
}
36+
Some(token) => tokens.push(token),
37+
None => panic!("unexpected end"),
38+
}
39+
};
40+
41+
assert!(it.next().is_none(), "expected end");
42+
43+
let mut body_it = body.stream().into_iter();
44+
let mut functions = Vec::new();
45+
while let Some(token) = body_it.next() {
46+
match token {
47+
TokenTree::Ident(ident) if ident.to_string() == "fn" => {
48+
let fn_name = match body_it.next() {
49+
Some(TokenTree::Ident(ident)) => ident.to_string(),
50+
_ => panic!("expected identifier after `fn`"),
51+
};
52+
functions.push(fn_name);
53+
}
54+
_ => (),
55+
}
56+
}
57+
58+
let mut const_items;
59+
if is_trait {
60+
const_items = "/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable) attribute when implementing this trait.
61+
const USE_VTABLE_ATTR: ();".to_owned();
62+
63+
for f in functions {
64+
write!(
65+
const_items,
66+
"/// Indicates if the `{}` method is overriden by the implementor.
67+
const HAS_{}: bool = false;",
68+
f,
69+
f.to_uppercase()
70+
)
71+
.unwrap();
72+
}
73+
} else {
74+
const_items = "const USE_VTABLE_ATTR: () = ();".to_owned();
75+
76+
for f in functions {
77+
write!(const_items, "const HAS_{}: bool = true;", f.to_uppercase()).unwrap();
78+
}
79+
}
80+
81+
let new_body = vec![const_items.parse().unwrap(), body.stream()]
82+
.into_iter()
83+
.collect();
84+
tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
85+
tokens.into_iter().collect()
86+
}

samples/rust/rust_chrdev.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,8 @@ module! {
2121
#[derive(Default)]
2222
struct RustFile;
2323

24-
impl FileOperations for RustFile {
25-
kernel::declare_file_operations!();
26-
}
24+
#[vtable]
25+
impl FileOperations for RustFile {}
2726

2827
struct RustChrdev {
2928
_dev: Pin<Box<chrdev::Registration<2>>>,

samples/rust/rust_miscdev.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,10 @@ impl FileOpener<Pin<Arc<SharedState>>> for Token {
6767
}
6868
}
6969

70+
#[vtable]
7071
impl FileOperations for Token {
7172
type Wrapper = Box<Self>;
7273

73-
kernel::declare_file_operations!(read, write);
74-
7574
fn read<T: IoBufferWriter>(&self, _: &File, data: &mut T, offset: u64) -> Result<usize> {
7675
// Succeed if the caller doesn't provide a buffer or if not at the start.
7776
if data.is_empty() || offset != 0 {

samples/rust/rust_random.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ use kernel::{
1818
#[derive(Default)]
1919
struct RandomFile;
2020

21+
#[vtable]
2122
impl FileOperations for RandomFile {
22-
kernel::declare_file_operations!(read, write, read_iter, write_iter);
23-
2423
fn read<T: IoBufferWriter>(&self, file: &File, buf: &mut T, _offset: u64) -> Result<usize> {
2524
let total_len = buf.len();
2625
let mut chunkbuf = [0; 256];

0 commit comments

Comments
 (0)