From: Wedson Almeida Filho <walmeida@xxxxxxxxxxxxx> Allow Rust file systems to create regular file inodes backed by the page cache. The contents of such files are read into folios via `read_folio`. Signed-off-by: Wedson Almeida Filho <walmeida@xxxxxxxxxxxxx> --- rust/helpers.c | 6 +++ rust/kernel/folio.rs | 1 - rust/kernel/fs/address_space.rs | 40 ++++++++++++++++++-- rust/kernel/fs/file.rs | 7 ++++ rust/kernel/fs/inode.rs | 20 +++++++++- samples/rust/rust_rofs.rs | 67 ++++++++++++++++++++++++++------- 6 files changed, 122 insertions(+), 19 deletions(-) diff --git a/rust/helpers.c b/rust/helpers.c index acff58e6caff..2db5df578df2 100644 --- a/rust/helpers.c +++ b/rust/helpers.c @@ -282,6 +282,12 @@ loff_t rust_helper_i_size_read(const struct inode *inode) } EXPORT_SYMBOL_GPL(rust_helper_i_size_read); +void rust_helper_mapping_set_large_folios(struct address_space *mapping) +{ + mapping_set_large_folios(mapping); +} +EXPORT_SYMBOL_GPL(rust_helper_mapping_set_large_folios); + unsigned long rust_helper_copy_to_user(void __user *to, const void *from, unsigned long n) { diff --git a/rust/kernel/folio.rs b/rust/kernel/folio.rs index 20f51db920e4..077328b733e4 100644 --- a/rust/kernel/folio.rs +++ b/rust/kernel/folio.rs @@ -49,7 +49,6 @@ impl<S> Folio<S> { /// Callers must ensure that: /// * `ptr` is valid and remains so for the lifetime of the returned reference. /// * The folio has the right state. - #[allow(dead_code)] pub(crate) unsafe fn from_raw<'a>(ptr: *mut bindings::folio) -> &'a Self { // SAFETY: The safety requirements guarantee that the cast below is ok. unsafe { &*ptr.cast::<Self>() } diff --git a/rust/kernel/fs/address_space.rs b/rust/kernel/fs/address_space.rs index 5b4fcb568f46..e539d690235b 100644 --- a/rust/kernel/fs/address_space.rs +++ b/rust/kernel/fs/address_space.rs @@ -6,8 +6,9 @@ //! //! C headers: [`include/linux/fs.h`](srctree/include/linux/fs.h) -use super::FileSystem; -use crate::bindings; +use super::{file::File, FileSystem}; +use crate::error::{from_result, Result}; +use crate::{bindings, folio::Folio, folio::PageCache, types::Locked}; use core::marker::PhantomData; use macros::vtable; @@ -16,10 +17,15 @@ pub trait Operations { /// File system that these operations are compatible with. type FileSystem: FileSystem + ?Sized; + + /// Reads the contents of the inode into the given folio. + fn read_folio( + file: Option<&File<Self::FileSystem>>, + folio: Locked<&Folio<PageCache<Self::FileSystem>>>, + ) -> Result; } /// Represents address space operations. -#[allow(dead_code)] pub struct Ops<T: FileSystem + ?Sized>( pub(crate) *const bindings::address_space_operations, pub(crate) PhantomData<T>, @@ -32,7 +38,11 @@ pub const fn new<U: Operations<FileSystem = T> + ?Sized>() -> Self { impl<T: Operations + ?Sized> Table<T> { const TABLE: bindings::address_space_operations = bindings::address_space_operations { writepage: None, - read_folio: None, + read_folio: if T::HAS_READ_FOLIO { + Some(Self::read_folio_callback) + } else { + None + }, writepages: None, dirty_folio: None, readahead: None, @@ -52,6 +62,28 @@ impl<T: Operations + ?Sized> Table<T> { swap_deactivate: None, swap_rw: None, }; + + extern "C" fn read_folio_callback( + file_ptr: *mut bindings::file, + folio_ptr: *mut bindings::folio, + ) -> i32 { + from_result(|| { + let file = if file_ptr.is_null() { + None + } else { + // SAFETY: The C API guarantees that `file_ptr` is a valid file if non-null. + Some(unsafe { File::from_raw(file_ptr) }) + }; + + // SAFETY: The C API guarantees that `folio_ptr` is a valid folio. + let folio = unsafe { Folio::from_raw(folio_ptr) }; + + // SAFETY: The C contract guarantees that the folio is valid and locked, with + // ownership of the lock transferred to the callee (this function). + T::read_folio(file, unsafe { Locked::new(folio) })?; + Ok(0) + }) + } } Self(&Table::<U>::TABLE, PhantomData) } diff --git a/rust/kernel/fs/file.rs b/rust/kernel/fs/file.rs index 2ba456a1eee1..0828676eae1c 100644 --- a/rust/kernel/fs/file.rs +++ b/rust/kernel/fs/file.rs @@ -355,6 +355,12 @@ fn read_dir( pub struct Ops<T: FileSystem + ?Sized>(pub(crate) *const bindings::file_operations, PhantomData<T>); impl<T: FileSystem + ?Sized> Ops<T> { + /// Returns file operations for page-cache-based ro files. + pub fn generic_ro_file() -> Self { + // SAFETY: This is a constant in C, it never changes. + Self(unsafe { &bindings::generic_ro_fops }, PhantomData) + } + /// Creates file operations from a type that implements the [`Operations`] trait. pub const fn new<U: Operations<FileSystem = T> + ?Sized>() -> Self { struct Table<T: Operations + ?Sized>(PhantomData<T>); @@ -516,6 +522,7 @@ impl From<inode::Type> for DirEntryType { fn from(value: inode::Type) -> Self { match value { inode::Type::Dir => DirEntryType::Dir, + inode::Type::Reg => DirEntryType::Reg, } } } diff --git a/rust/kernel/fs/inode.rs b/rust/kernel/fs/inode.rs index c314d036c87e..1a41c824d30d 100644 --- a/rust/kernel/fs/inode.rs +++ b/rust/kernel/fs/inode.rs @@ -6,7 +6,9 @@ //! //! C headers: [`include/linux/fs.h`](srctree/include/linux/fs.h) -use super::{dentry, dentry::DEntry, file, sb::SuperBlock, FileSystem, Offset, UnspecifiedFS}; +use super::{ + address_space, dentry, dentry::DEntry, file, sb::SuperBlock, FileSystem, Offset, UnspecifiedFS, +}; use crate::error::{code::*, Result}; use crate::types::{ARef, AlwaysRefCounted, Lockable, Locked, Opaque}; use crate::{bindings, block, time::Timespec}; @@ -127,6 +129,11 @@ pub fn init(mut self, params: Params) -> Result<ARef<INode<T>>> { let inode = unsafe { self.0.as_mut() }; let mode = match params.typ { Type::Dir => bindings::S_IFDIR, + Type::Reg => { + // SAFETY: The `i_mapping` pointer doesn't change and is valid. + unsafe { bindings::mapping_set_large_folios(inode.i_mapping) }; + bindings::S_IFREG + } }; inode.i_mode = (params.mode & 0o777) | u16::try_from(mode)?; @@ -166,6 +173,14 @@ pub fn set_fops(&mut self, fops: file::Ops<T>) -> &mut Self { inode.__bindgen_anon_3.i_fop = fops.0; self } + + /// Sets the address space operations on this new inode. + pub fn set_aops(&mut self, aops: address_space::Ops<T>) -> &mut Self { + // SAFETY: By the type invariants, it's ok to modify the inode. + let inode = unsafe { self.0.as_mut() }; + inode.i_data.a_ops = aops.0; + self + } } impl<T: FileSystem + ?Sized> Drop for New<T> { @@ -181,6 +196,9 @@ fn drop(&mut self) { pub enum Type { /// Directory type. Dir, + + /// Regular file type. + Reg, } /// Required inode parameters. diff --git a/samples/rust/rust_rofs.rs b/samples/rust/rust_rofs.rs index 2a87e524e0e1..8005fd14b2e1 100644 --- a/samples/rust/rust_rofs.rs +++ b/samples/rust/rust_rofs.rs @@ -3,10 +3,11 @@ //! Rust read-only file system sample. use kernel::fs::{ - dentry, dentry::DEntry, file, file::File, inode, inode::INode, sb::SuperBlock, Offset, + address_space, dentry, dentry::DEntry, file, file::File, inode, inode::INode, sb, Offset, }; use kernel::prelude::*; -use kernel::{c_str, fs, time::UNIX_EPOCH, types::ARef, types::Either, types::Locked, user}; +use kernel::types::{ARef, Either, Locked}; +use kernel::{c_str, folio::Folio, folio::PageCache, fs, time::UNIX_EPOCH, user}; kernel::module_fs! { type: RoFs, @@ -20,6 +21,7 @@ struct Entry { name: &'static [u8], ino: u64, etype: inode::Type, + contents: &'static [u8], } const ENTRIES: [Entry; 3] = [ @@ -27,41 +29,53 @@ struct Entry { name: b".", ino: 1, etype: inode::Type::Dir, + contents: b"", }, Entry { name: b"..", ino: 1, etype: inode::Type::Dir, + contents: b"", }, Entry { - name: b"subdir", + name: b"test.txt", ino: 2, - etype: inode::Type::Dir, + etype: inode::Type::Reg, + contents: b"hello world\n", }, ]; const DIR_FOPS: file::Ops<RoFs> = file::Ops::new::<RoFs>(); const DIR_IOPS: inode::Ops<RoFs> = inode::Ops::new::<RoFs>(); +const FILE_AOPS: address_space::Ops<RoFs> = address_space::Ops::new::<RoFs>(); struct RoFs; impl RoFs { - fn iget(sb: &SuperBlock<Self>, e: &'static Entry) -> Result<ARef<INode<Self>>> { + fn iget(sb: &sb::SuperBlock<Self>, e: &'static Entry) -> Result<ARef<INode<Self>>> { let mut new = match sb.get_or_create_inode(e.ino)? { Either::Left(existing) => return Ok(existing), Either::Right(new) => new, }; - match e.etype { - inode::Type::Dir => new.set_iops(DIR_IOPS).set_fops(DIR_FOPS), + let (mode, nlink, size) = match e.etype { + inode::Type::Dir => { + new.set_iops(DIR_IOPS).set_fops(DIR_FOPS); + (0o555, 2, ENTRIES.len().try_into()?) + } + inode::Type::Reg => { + new.set_fops(file::Ops::generic_ro_file()) + .set_aops(FILE_AOPS); + (0o444, 1, e.contents.len().try_into()?) + } }; new.init(inode::Params { typ: e.etype, - mode: 0o555, - size: ENTRIES.len().try_into()?, - blocks: 1, - nlink: 2, + mode, + size, + blocks: (u64::try_from(size)? + 511) / 512, + nlink, uid: 0, gid: 0, atime: UNIX_EPOCH, @@ -74,12 +88,12 @@ fn iget(sb: &SuperBlock<Self>, e: &'static Entry) -> Result<ARef<INode<Self>>> { impl fs::FileSystem for RoFs { const NAME: &'static CStr = c_str!("rust_rofs"); - fn fill_super(sb: &mut SuperBlock<Self>) -> Result { + fn fill_super(sb: &mut sb::SuperBlock<Self>) -> Result { sb.set_magic(0x52555354); Ok(()) } - fn init_root(sb: &SuperBlock<Self>) -> Result<dentry::Root<Self>> { + fn init_root(sb: &sb::SuperBlock<Self>) -> Result<dentry::Root<Self>> { let inode = Self::iget(sb, &ENTRIES[0])?; dentry::Root::try_new(inode) } @@ -109,6 +123,33 @@ fn lookup( } } +#[vtable] +impl address_space::Operations for RoFs { + type FileSystem = Self; + + fn read_folio(_: Option<&File<Self>>, mut folio: Locked<&Folio<PageCache<Self>>>) -> Result { + let data = match folio.inode().ino() { + 2 => ENTRIES[2].contents, + _ => return Err(EINVAL), + }; + + let pos = usize::try_from(folio.pos()).unwrap_or(usize::MAX); + let copied = if pos >= data.len() { + 0 + } else { + let to_copy = core::cmp::min(data.len() - pos, folio.size()); + folio.write(0, &data[pos..][..to_copy])?; + to_copy + }; + + folio.zero_out(copied, folio.size() - copied)?; + folio.mark_uptodate(); + folio.flush_dcache(); + + Ok(()) + } +} + #[vtable] impl file::Operations for RoFs { type FileSystem = Self; -- 2.34.1