Skip to content

rust: define fs context #818

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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