Skip to content

Commit

Permalink
rust: implement a #[vtable] macro
Browse files Browse the repository at this point in the history
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]>
  • Loading branch information
nbdd0121 committed May 29, 2021
1 parent 7884043 commit a5003f2
Show file tree
Hide file tree
Showing 9 changed files with 159 additions and 90 deletions.
3 changes: 1 addition & 2 deletions drivers/android/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -802,11 +802,10 @@ impl FileOpener<Arc<Context>> for Process {
}
}

#[vtable]
impl FileOperations for Process {
type Wrapper = Pin<Ref<Self>>;

kernel::declare_file_operations!(ioctl, compat_ioctl, mmap, poll);

fn release(obj: Self::Wrapper, _file: &File) {
// Mark this process as dead. We'll do the same for the threads later.
obj.inner.lock().is_dead = true;
Expand Down
88 changes: 12 additions & 76 deletions rust/kernel/file_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{
from_kernel_result,
io_buffer::{IoBufferReader, IoBufferWriter},
iov_iter::IovIter,
prelude::*,
sync::CondVar,
types::PointerWrapper,
user_ptr::{UserSlicePtr, UserSlicePtrReader, UserSlicePtrWriter},
Expand Down Expand Up @@ -254,24 +255,24 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
const VTABLE: bindings::file_operations = bindings::file_operations {
open: Some(open_callback::<A, T>),
release: Some(release_callback::<T>),
read: if T::TO_USE.read {
read: if T::HAS_READ {
Some(read_callback::<T>)
} else {
None
},
write: if T::TO_USE.write {
write: if T::HAS_WRITE {
Some(write_callback::<T>)
} else {
None
},
llseek: if T::TO_USE.seek {
llseek: if T::HAS_SEEK {
Some(llseek_callback::<T>)
} else {
None
},

check_flags: None,
compat_ioctl: if T::TO_USE.compat_ioctl {
compat_ioctl: if T::HAS_COMPAT_IOCTL {
Some(compat_ioctl_callback::<T>)
} else {
None
Expand All @@ -282,7 +283,7 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
fasync: None,
flock: None,
flush: None,
fsync: if T::TO_USE.fsync {
fsync: if T::HAS_FSYNC {
Some(fsync_callback::<T>)
} else {
None
Expand All @@ -292,19 +293,19 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
iterate_shared: None,
iopoll: None,
lock: None,
mmap: if T::TO_USE.mmap {
mmap: if T::HAS_MMAP {
Some(mmap_callback::<T>)
} else {
None
},
mmap_supported_flags: 0,
owner: ptr::null_mut(),
poll: if T::TO_USE.poll {
poll: if T::HAS_POLL {
Some(poll_callback::<T>)
} else {
None
},
read_iter: if T::TO_USE.read_iter {
read_iter: if T::HAS_READ {
Some(read_iter_callback::<T>)
} else {
None
Expand All @@ -315,12 +316,12 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
show_fdinfo: None,
splice_read: None,
splice_write: None,
unlocked_ioctl: if T::TO_USE.ioctl {
unlocked_ioctl: if T::HAS_IOCTL {
Some(unlocked_ioctl_callback::<T>)
} else {
None
},
write_iter: if T::TO_USE.write_iter {
write_iter: if T::HAS_WRITE {
Some(write_iter_callback::<T>)
} else {
None
Expand All @@ -337,69 +338,6 @@ impl<A: FileOpenAdapter, T: FileOpener<A::Arg>> FileOperationsVtable<A, T> {
}
}

/// Represents which fields of [`struct file_operations`] should be populated with pointers.
pub struct ToUse {
/// The `read` field of [`struct file_operations`].
pub read: bool,

/// The `read_iter` field of [`struct file_operations`].
pub read_iter: bool,

/// The `write` field of [`struct file_operations`].
pub write: bool,

/// The `write_iter` field of [`struct file_operations`].
pub write_iter: bool,

/// The `llseek` field of [`struct file_operations`].
pub seek: bool,

/// The `unlocked_ioctl` field of [`struct file_operations`].
pub ioctl: bool,

/// The `compat_ioctl` field of [`struct file_operations`].
pub compat_ioctl: bool,

/// The `fsync` field of [`struct file_operations`].
pub fsync: bool,

/// The `mmap` field of [`struct file_operations`].
pub mmap: bool,

/// The `poll` field of [`struct file_operations`].
pub poll: bool,
}

/// A constant version where all values are to set to `false`, that is, all supported fields will
/// be set to null pointers.
pub const USE_NONE: ToUse = ToUse {
read: false,
read_iter: false,
write: false,
write_iter: false,
seek: false,
ioctl: false,
compat_ioctl: false,
fsync: false,
mmap: false,
poll: false,
};

/// Defines the [`FileOperations::TO_USE`] field based on a list of fields to be populated.
#[macro_export]
macro_rules! declare_file_operations {
() => {
const TO_USE: $crate::file_operations::ToUse = $crate::file_operations::USE_NONE;
};
($($i:ident),+) => {
const TO_USE: kernel::file_operations::ToUse =
$crate::file_operations::ToUse {
$($i: true),+ ,
..$crate::file_operations::USE_NONE
};
};
}

/// Allows the handling of ioctls defined with the `_IO`, `_IOR`, `_IOW`, and `_IOWR` macros.
///
/// For each macro, there is a handler function that takes the appropriate types as arguments.
Expand Down Expand Up @@ -527,10 +465,8 @@ impl<T: FileOperations<Wrapper = Box<T>> + Default> FileOpener<()> for T {
/// File descriptors may be used from multiple threads/processes concurrently, so your type must be
/// [`Sync`]. It must also be [`Send`] because [`FileOperations::release`] will be called from the
/// thread that decrements that associated file's refcount to zero.
#[vtable]
pub trait FileOperations: Send + Sync + Sized {
/// The methods to use to populate [`struct file_operations`].
const TO_USE: ToUse;

/// The pointer type that will be used to hold ourselves.
type Wrapper: PointerWrapper = Box<Self>;

Expand Down
2 changes: 1 addition & 1 deletion rust/kernel/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use alloc::{borrow::ToOwned, string::String};

pub use super::build_assert;

pub use macros::{module, module_misc_device};
pub use macros::{module, module_misc_device, vtable};

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

Expand Down
54 changes: 53 additions & 1 deletion rust/macros/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#![deny(clippy::style)]

mod module;
mod vtable;

use proc_macro::TokenStream;

Expand Down Expand Up @@ -122,11 +123,62 @@ pub fn module(ts: TokenStream) -> TokenStream {
/// #[derive(Default)]
/// struct MyFile;
///
/// #[vtable]
/// impl kernel::file_operations::FileOperations for MyFile {
/// kernel::declare_file_operations!();
/// }
/// ```
#[proc_macro]
pub fn module_misc_device(ts: TokenStream) -> TokenStream {
module::module_misc_device(ts)
}

/// Declares or implements a vtable trait.
///
/// Linux's use of pure vtables is very close to Rust traits, but they differ
/// in how unimplemented functions are represented. In Rust, traits can provide
/// default implementation for all non-required methods (and the default
/// implementation could just return `Error::EINVAL`); Linux typically use C
/// `NULL` pointers to represent these functions.
///
/// This attribute is intended to close the gap. Traits can be declared and
/// implemented with the `#[vtable]` attribute, and a `HAS_*` associated constant
/// will be generated for each method in the trait, indicating if the implementor
/// has overriden a method.
///
/// This attribute is not needed if all methods are required.
///
/// # Examples
///
/// ```rust,no_run
/// use kernel::prelude::*;
///
/// // Declares a `#[vtable]` trait
/// #[vtable]
/// pub trait FileOperations: Send + Sync + Sized {
/// fn read<T: IoBufferWriter>(&self, _file: &File, _data: &mut T, _offset: u64) -> Result<usize> {
/// Err(Error::EINVAL)
/// }
///
/// fn write<T: IoBufferWriter>(&self, _file: &File, _data: &mut T, _offset: u64) -> Result<usize> {
/// Err(Error::EINVAL)
/// }
/// }
///
/// struct RustFile;
///
/// // Implements the `#[vtable]` trait
/// #[vtable]
/// impl FileOperations for RustFile {
/// fn read<T: IoBufferWriter>(&self, _file: &File, _data: &mut T, _offset: u64) -> Result<usize> {
/// # Err(Error::EINVAL)
/// /* ... */
/// }
/// }
///
/// assert_eq!(<RustFile as FileOperations>::HAS_READ, true);
/// assert_eq!(<RustFile as FileOperations>::HAS_WRITE, false);
/// ```
#[proc_macro_attribute]
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
vtable::vtable(attr, ts)
}
86 changes: 86 additions & 0 deletions rust/macros/vtable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: GPL-2.0

use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use std::fmt::Write;

pub fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
let mut it = ts.into_iter();

let mut tokens = Vec::new();

// Scan for the `trait` or `impl` keyword
let is_trait = loop {
let token = it.next().expect("unexpected end");
let keyword = match &token {
TokenTree::Ident(ident) => match ident.to_string().as_str() {
"trait" => Some(true),
"impl" => Some(false),
_ => None,
},
_ => None,
};
tokens.push(token);
if let Some(v) = keyword {
break v;
}
};

// Scan for the main body
// FIXME: `{}` is allowed within type as well, e.g. `impl Foo for Bar<{0}>`.
// but these are very rare and we don't want to have generics parsing code.
let body = loop {
match it.next() {
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => {
break group;
}
Some(token) => tokens.push(token),
None => panic!("unexpected end"),
}
};

assert!(it.next().is_none(), "expected end");

let mut body_it = body.stream().into_iter();
let mut functions = Vec::new();
while let Some(token) = body_it.next() {
match token {
TokenTree::Ident(ident) if ident.to_string() == "fn" => {
let fn_name = match body_it.next() {
Some(TokenTree::Ident(ident)) => ident.to_string(),
_ => panic!("expected identifier after `fn`"),
};
functions.push(fn_name);
}
_ => (),
}
}

let mut const_items;
if is_trait {
const_items = "/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable) attribute when implementing this trait.
const USE_VTABLE_ATTR: ();".to_owned();

for f in functions {
write!(
const_items,
"/// Indicates if the `{}` method is overriden by the implementor.
const HAS_{}: bool = false;",
f,
f.to_uppercase()
)
.unwrap();
}
} else {
const_items = "const USE_VTABLE_ATTR: () = ();".to_owned();

for f in functions {
write!(const_items, "const HAS_{}: bool = true;", f.to_uppercase()).unwrap();
}
}

let new_body = vec![const_items.parse().unwrap(), body.stream()]
.into_iter()
.collect();
tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
tokens.into_iter().collect()
}
5 changes: 2 additions & 3 deletions samples/rust/rust_chrdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ module! {
#[derive(Default)]
struct RustFile;

impl FileOperations for RustFile {
kernel::declare_file_operations!();
}
#[vtable]
impl FileOperations for RustFile {}

struct RustChrdev {
_dev: Pin<Box<chrdev::Registration<2>>>,
Expand Down
3 changes: 1 addition & 2 deletions samples/rust/rust_miscdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,10 @@ impl FileOpener<Pin<Arc<SharedState>>> for Token {
}
}

#[vtable]
impl FileOperations for Token {
type Wrapper = Box<Self>;

kernel::declare_file_operations!(read, write);

fn read<T: IoBufferWriter>(&self, _: &File, data: &mut T, offset: u64) -> Result<usize> {
// Succeed if the caller doesn't provide a buffer or if not at the start.
if data.is_empty() || offset != 0 {
Expand Down
3 changes: 1 addition & 2 deletions samples/rust/rust_random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ use kernel::{
#[derive(Default)]
struct RandomFile;

#[vtable]
impl FileOperations for RandomFile {
kernel::declare_file_operations!(read, write, read_iter, write_iter);

fn read<T: IoBufferWriter>(&self, file: &File, buf: &mut T, _offset: u64) -> Result<usize> {
let total_len = buf.len();
let mut chunkbuf = [0; 256];
Expand Down
Loading

0 comments on commit a5003f2

Please sign in to comment.