Skip to content

Commit

Permalink
Merge pull request raspberrypi#818 from wedsonaf/fs-context
Browse files Browse the repository at this point in the history
rust: define fs context
  • Loading branch information
wedsonaf authored Jul 11, 2022
2 parents dab7c39 + 3c3ccce commit 1781c4c
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 7 deletions.
3 changes: 3 additions & 0 deletions rust/bindings/bindings_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <linux/uaccess.h>
#include <linux/uio.h>
#include <uapi/linux/android/binder.h>
#include <linux/fs_parser.h>

/* `bindgen` gets confused at certain things. */
const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL;
Expand All @@ -44,3 +45,5 @@ const __poll_t BINDINGS_EPOLLIN = EPOLLIN;
const __poll_t BINDINGS_EPOLLOUT = EPOLLOUT;
const __poll_t BINDINGS_EPOLLERR = EPOLLERR;
const __poll_t BINDINGS_EPOLLHUP = EPOLLHUP;

const loff_t BINDINGS_MAX_LFS_FILESIZE = MAX_LFS_FILESIZE;
2 changes: 2 additions & 0 deletions rust/bindings/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ pub use bindings_raw::*;
pub const GFP_KERNEL: gfp_t = BINDINGS_GFP_KERNEL;
pub const __GFP_ZERO: gfp_t = BINDINGS___GFP_ZERO;
pub const __GFP_HIGHMEM: gfp_t = ___GFP_HIGHMEM;

pub const MAX_LFS_FILESIZE: loff_t = BINDINGS_MAX_LFS_FILESIZE;
12 changes: 12 additions & 0 deletions rust/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,18 @@ struct dentry *rust_helper_dget(struct dentry *dentry)
}
EXPORT_SYMBOL_GPL(rust_helper_dget);

void rust_helper_lockdep_register_key(struct lock_class_key *key)
{
lockdep_register_key(key);
}
EXPORT_SYMBOL_GPL(rust_helper_lockdep_register_key);

void rust_helper_lockdep_unregister_key(struct lock_class_key *key)
{
lockdep_unregister_key(key);
}
EXPORT_SYMBOL_GPL(rust_helper_lockdep_unregister_key);

/*
* We use `bindgen`'s `--size_t-is-usize` option to bind the C `size_t` type
* as the Rust `usize` type, so we can use it in contexts where Rust
Expand Down
225 changes: 218 additions & 7 deletions rust/kernel/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,164 @@
//!
//! C headers: [`include/linux/fs.h`](../../../../include/linux/fs.h)
use crate::{bindings, error::code::*, str::CStr, to_result, AlwaysRefCounted, Result, ThisModule};
use crate::{
bindings, error::code::*, error::from_kernel_result, str::CStr, to_result,
types::PointerWrapper, AlwaysRefCounted, Result, ScopeGuard, ThisModule,
};
use core::{cell::UnsafeCell, marker::PhantomPinned, pin::Pin, ptr};
use macros::vtable;

/// A file system context.
///
/// It is used to gather configuration to then mount or reconfigure a file system.
#[vtable]
pub trait Context<T: Type + ?Sized> {
/// Type of the data associated with the context.
type Data: PointerWrapper + Send + Sync + 'static;

/// Creates a new context.
fn try_new() -> Result<Self::Data>;
}

struct Tables<T: Type + ?Sized>(T);
impl<T: Type + ?Sized> Tables<T> {
const CONTEXT: bindings::fs_context_operations = bindings::fs_context_operations {
free: Some(Self::free_callback),
parse_param: None,
get_tree: Some(Self::get_tree_callback),
reconfigure: Some(Self::reconfigure_callback),
parse_monolithic: None,
dup: None,
};

unsafe extern "C" fn free_callback(fc: *mut bindings::fs_context) {
// SAFETY: The callback contract guarantees that `fc` is valid.
let ptr = unsafe { (*fc).fs_private };
if !ptr.is_null() {
// SAFETY: `fs_private` was initialised with the result of a `to_pointer` call in
// `init_fs_context_callback`, so it's ok to call `from_pointer` here.
unsafe { <T::Context as Context<T>>::Data::from_pointer(ptr) };
}
}

unsafe extern "C" fn fill_super_callback(
sb_ptr: *mut bindings::super_block,
_fc: *mut bindings::fs_context,
) -> core::ffi::c_int {
from_kernel_result! {
// The following is temporary code to create the root inode and dentry. It will be
// replaced with calls to Rust code.

// SAFETY: The callback contract guarantees that `sb_ptr` is the only pointer to a
// newly-allocated superblock, so it is safe to mutably reference it.
let sb = unsafe { &mut *sb_ptr };

sb.s_maxbytes = bindings::MAX_LFS_FILESIZE;
sb.s_blocksize = crate::PAGE_SIZE as _;
sb.s_blocksize_bits = bindings::PAGE_SHIFT as _;
sb.s_magic = T::MAGIC as _;
sb.s_op = &Tables::<T>::SUPER_BLOCK;
sb.s_time_gran = 1;

// Create and initialise the root inode.
let inode = unsafe { bindings::new_inode(sb) };
if inode.is_null() {
return Err(ENOMEM);
}

{
// SAFETY: This is a newly-created inode. No other references to it exist, so it is
// safe to mutably dereference it.
let inode = unsafe { &mut *inode };

// SAFETY: `current_time` requires that `inode.sb` be valid, which is the case here
// since we allocated the inode through the superblock.
let time = unsafe { bindings::current_time(inode) };
inode.i_ino = 1;
inode.i_mode = (bindings::S_IFDIR | 0o755) as _;
inode.i_mtime = time;
inode.i_atime = time;
inode.i_ctime = time;

// SAFETY: `simple_dir_operations` never changes, it's safe to reference it.
inode.__bindgen_anon_3.i_fop = unsafe { &bindings::simple_dir_operations };

// SAFETY: `simple_dir_inode_operations` never changes, it's safe to reference it.
inode.i_op = unsafe { &bindings::simple_dir_inode_operations };

// SAFETY: `inode` is valid for write.
unsafe { bindings::set_nlink(inode, 2) };
}

// SAFETY: `d_make_root` requires that `inode` be valid and referenced, which is the
// case for this call.
//
// It takes over the inode, even on failure, so we don't need to clean it up.
let dentry = unsafe { bindings::d_make_root(inode) };
if dentry.is_null() {
return Err(ENOMEM);
}

sb.s_root = dentry;
Ok(0)
}
}

unsafe extern "C" fn get_tree_callback(fc: *mut bindings::fs_context) -> core::ffi::c_int {
// SAFETY: `fc` is valid per the callback contract. `fill_super_callback` also has the
// right type and is a valid callback.
unsafe { bindings::get_tree_nodev(fc, Some(Self::fill_super_callback)) }
}

unsafe extern "C" fn reconfigure_callback(_fc: *mut bindings::fs_context) -> core::ffi::c_int {
EINVAL.to_kernel_errno()
}

const SUPER_BLOCK: bindings::super_operations = bindings::super_operations {
alloc_inode: None,
destroy_inode: None,
free_inode: None,
dirty_inode: None,
write_inode: None,
drop_inode: None,
evict_inode: None,
put_super: None,
sync_fs: None,
freeze_super: None,
freeze_fs: None,
thaw_super: None,
unfreeze_fs: None,
statfs: None,
remount_fs: None,
umount_begin: None,
show_options: None,
show_devname: None,
show_path: None,
show_stats: None,
#[cfg(CONFIG_QUOTA)]
quota_read: None,
#[cfg(CONFIG_QUOTA)]
quota_write: None,
#[cfg(CONFIG_QUOTA)]
get_dquots: None,
nr_cached_objects: None,
free_cached_objects: None,
};
}

/// A file system type.
pub trait Type {
/// The context used to build fs configuration before it is mounted or reconfigured.
type Context: Context<Self> + ?Sized;

/// The name of the file system type.
const NAME: &'static CStr;

/// The magic number associated with the file system.
///
/// This is normally one of the values in `include/uapi/linux/magic.h`.
const MAGIC: u32;

/// The flags of this file system type.
///
/// It is a combination of the flags in the [`flags`] module.
Expand Down Expand Up @@ -78,7 +228,7 @@ impl Registration {
/// The file system is described by the [`Type`] argument.
///
/// It is automatically unregistered when the registration is dropped.
pub fn register<T: Type>(self: Pin<&mut Self>, module: &'static ThisModule) -> Result {
pub fn register<T: Type + ?Sized>(self: Pin<&mut Self>, module: &'static ThisModule) -> Result {
// SAFETY: We never move out of `this`.
let this = unsafe { self.get_unchecked_mut() };

Expand All @@ -92,20 +242,81 @@ impl Registration {
fs.fs_flags = T::FLAGS;
fs.init_fs_context = Some(Self::init_fs_context_callback::<T>);
fs.kill_sb = Some(Self::kill_sb_callback::<T>);

// SAFETY: This block registers all fs type keys with lockdep. We just need the memory
// locations to be owned by the caller, which is the case.
unsafe {
bindings::lockdep_register_key(&mut fs.s_lock_key);
bindings::lockdep_register_key(&mut fs.s_umount_key);
bindings::lockdep_register_key(&mut fs.s_vfs_rename_key);
bindings::lockdep_register_key(&mut fs.i_lock_key);
bindings::lockdep_register_key(&mut fs.i_mutex_key);
bindings::lockdep_register_key(&mut fs.invalidate_lock_key);
bindings::lockdep_register_key(&mut fs.i_mutex_dir_key);
for key in &mut fs.s_writers_key {
bindings::lockdep_register_key(key);
}
}

let ptr = this.fs.get();

// SAFETY: `ptr` as valid as it points to the `self.fs`.
let key_guard = ScopeGuard::new(|| unsafe { Self::unregister_keys(ptr) });

// SAFETY: Pointers stored in `fs` are either static so will live for as long as the
// registration is active (it is undone in `drop`).
to_result(unsafe { bindings::register_filesystem(this.fs.get()) })?;
to_result(unsafe { bindings::register_filesystem(ptr) })?;
key_guard.dismiss();
this.is_registered = true;
Ok(())
}

unsafe extern "C" fn init_fs_context_callback<T: Type>(
_fc_ptr: *mut bindings::fs_context,
/// Unregisters the lockdep keys in the file system type.
///
/// # Safety
///
/// `fs` must be non-null and valid.
unsafe fn unregister_keys(fs: *mut bindings::file_system_type) {
// SAFETY: This block unregisters all fs type keys from lockdep. They must have been
// registered before.
unsafe {
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).s_lock_key));
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).s_umount_key));
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).s_vfs_rename_key));
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).i_lock_key));
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).i_mutex_key));
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).invalidate_lock_key));
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).i_mutex_dir_key));
for i in 0..(*fs).s_writers_key.len() {
bindings::lockdep_unregister_key(ptr::addr_of_mut!((*fs).s_writers_key[i]));
}
}
}

unsafe extern "C" fn init_fs_context_callback<T: Type + ?Sized>(
fc_ptr: *mut bindings::fs_context,
) -> core::ffi::c_int {
EINVAL.to_kernel_errno()
from_kernel_result! {
let data = T::Context::try_new()?;
// SAFETY: The callback contract guarantees that `fc_ptr` is the only pointer to a
// newly-allocated fs context, so it is safe to mutably reference it.
let fc = unsafe { &mut *fc_ptr };
fc.fs_private = data.into_pointer() as _;
fc.ops = &Tables::<T>::CONTEXT;
Ok(0)
}
}

unsafe extern "C" fn kill_sb_callback<T: Type>(_sb_ptr: *mut bindings::super_block) {}
unsafe extern "C" fn kill_sb_callback<T: Type + ?Sized>(sb_ptr: *mut bindings::super_block) {
// SAFETY: We always call `get_tree_nodev` from `get_tree_callback`, so we never have a
// device, so it is ok to call the function below. Additionally, the callback contract
// guarantees that `sb_ptr` is valid.
unsafe { bindings::kill_anon_super(sb_ptr) }

// SAFETY: The callback contract guarantees that `sb_ptr` is valid, and the `kill_sb`
// callback being called implies that the `s_type` is also valid.
unsafe { Self::unregister_keys((*sb_ptr).s_type) };
}
}

impl Drop for Registration {
Expand Down
13 changes: 13 additions & 0 deletions samples/rust/rust_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,22 @@ module! {
}

struct RustFs;

#[vtable]
impl fs::Context<Self> for RustFs {
type Data = ();

fn try_new() -> Result {
pr_info!("context created!\n");
Ok(())
}
}

impl fs::Type for RustFs {
type Context = Self;
const NAME: &'static CStr = c_str!("rustfs");
const FLAGS: i32 = fs::flags::USERNS_MOUNT;
const MAGIC: u32 = 0x72757374;
}

struct FsModule {
Expand Down

0 comments on commit 1781c4c

Please sign in to comment.