From 3c3ccce85af9aae9dfc0e17e2331674da520177c Mon Sep 17 00:00:00 2001 From: Wedson Almeida Filho Date: Thu, 7 Jul 2022 22:21:04 +0000 Subject: [PATCH] rust: define fs context Also make fs mountable, but empty for now. Signed-off-by: Wedson Almeida Filho --- rust/bindings/bindings_helper.h | 3 + rust/bindings/lib.rs | 2 + rust/helpers.c | 12 ++ rust/kernel/fs.rs | 225 +++++++++++++++++++++++++++++++- samples/rust/rust_fs.rs | 13 ++ 5 files changed, 248 insertions(+), 7 deletions(-) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index 73100fa139ebb5..214cff169137c0 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -36,6 +36,7 @@ #include #include #include +#include /* `bindgen` gets confused at certain things. */ const gfp_t BINDINGS_GFP_KERNEL = GFP_KERNEL; @@ -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; diff --git a/rust/bindings/lib.rs b/rust/bindings/lib.rs index c68de267b643ad..e5df198512a26b 100644 --- a/rust/bindings/lib.rs +++ b/rust/bindings/lib.rs @@ -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; diff --git a/rust/helpers.c b/rust/helpers.c index eedeae2d366387..38876c3259673d 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -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 diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index dd67385510390d..92a8efc1916409 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -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 { + /// Type of the data associated with the context. + type Data: PointerWrapper + Send + Sync + 'static; + + /// Creates a new context. + fn try_new() -> Result; +} + +struct Tables(T); +impl Tables { + 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 { >::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::::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 + ?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. @@ -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(self: Pin<&mut Self>, module: &'static ThisModule) -> Result { + pub fn register(self: Pin<&mut Self>, module: &'static ThisModule) -> Result { // SAFETY: We never move out of `this`. let this = unsafe { self.get_unchecked_mut() }; @@ -92,20 +242,81 @@ impl Registration { fs.fs_flags = T::FLAGS; fs.init_fs_context = Some(Self::init_fs_context_callback::); fs.kill_sb = Some(Self::kill_sb_callback::); + + // 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( - _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( + 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::::CONTEXT; + Ok(0) + } } - unsafe extern "C" fn kill_sb_callback(_sb_ptr: *mut bindings::super_block) {} + unsafe extern "C" fn kill_sb_callback(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 { diff --git a/samples/rust/rust_fs.rs b/samples/rust/rust_fs.rs index a0b32f41de488b..2c41f5d62117a6 100644 --- a/samples/rust/rust_fs.rs +++ b/samples/rust/rust_fs.rs @@ -13,9 +13,22 @@ module! { } struct RustFs; + +#[vtable] +impl fs::Context 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 {