[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20240514131711.379322-19-wedsonaf@gmail.com>
Date: Tue, 14 May 2024 10:16:59 -0300
From: Wedson Almeida Filho <wedsonaf@...il.com>
To: Alexander Viro <viro@...iv.linux.org.uk>,
Christian Brauner <brauner@...nel.org>,
Matthew Wilcox <willy@...radead.org>,
Dave Chinner <david@...morbit.com>
Cc: Kent Overstreet <kent.overstreet@...il.com>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
linux-fsdevel@...r.kernel.org,
rust-for-linux@...r.kernel.org,
linux-kernel@...r.kernel.org,
Wedson Almeida Filho <walmeida@...rosoft.com>
Subject: [RFC PATCH v2 18/30] rust: fs: introduce `address_space::Operations::read_folio`
From: Wedson Almeida Filho <walmeida@...rosoft.com>
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@...rosoft.com>
---
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
Powered by blists - more mailing lists