[RFC PATCH 17/24] erofs: introduce Rust SBI to C

Yiyang Wu
2024-09-16T13:57:12Z

This patch introduces Rust opaque superblock info to C as s_fs_info.
The original erofs_sb_info is embedded inside the rust opaque type and
reexported by rewriting the original EROFS_SB/EROFS_I_SB macros.

This patch also provides a prototype of KernelInode,
KernelInodeCollection so that the code can compile and it also
implements the Metabuf Data Source by hooking up the original metabuf
API defined in EROFS.

Signed-off-by: Yiyang Wu <toolmanp@tlmp.cc>
---
fs/erofs/Makefile | 2 +-
fs/erofs/internal.h | 10 ++++++
fs/erofs/rust/kinode.rs | 49 ++++++++++++++++++++++++++
fs/erofs/rust/ksources.rs | 66 ++++++++++++++++++++++++++++++++++++
fs/erofs/rust/ksuperblock.rs | 30 ++++++++++++++++
fs/erofs/rust/mod.rs | 3 ++
fs/erofs/rust_bindings.h | 12 +++++++
fs/erofs/rust_helpers.c | 31 +++++++++++++++++
fs/erofs/rust_helpers.h | 21 ++++++++++++
fs/erofs/super.c | 15 ++++++--
fs/erofs/super_rs.rs | 50 +++++++++++++++++++++++++++
11 files changed, 285 insertions(+), 4 deletions(-)
create mode 100644 fs/erofs/rust/kinode.rs
create mode 100644 fs/erofs/rust/ksources.rs
create mode 100644 fs/erofs/rust/ksuperblock.rs
create mode 100644 fs/erofs/rust_bindings.h
create mode 100644 fs/erofs/rust_helpers.c
create mode 100644 fs/erofs/rust_helpers.h

diff --git a/fs/erofs/Makefile b/fs/erofs/Makefile
index fb46a2c7fb50..dfa03edbe29a 100644
--- a/fs/erofs/Makefile
+++ b/fs/erofs/Makefile
@@ -9,4 +9,4 @@ erofs-$(CONFIG_EROFS_FS_ZIP_DEFLATE) += decompressor_deflate.o
erofs-$(CONFIG_EROFS_FS_ZIP_ZSTD) += decompressor_zstd.o
erofs-$(CONFIG_EROFS_FS_BACKED_BY_FILE) += fileio.o
erofs-$(CONFIG_EROFS_FS_ONDEMAND) += fscache.o
-erofs-$(CONFIG_EROFS_FS_RUST) += super_rs.o
+erofs-$(CONFIG_EROFS_FS_RUST) += super_rs.o rust_helpers.o
diff --git a/fs/erofs/internal.h b/fs/erofs/internal.h
index 8674a4cb9d39..18e67219fbc8 100644
--- a/fs/erofs/internal.h
+++ b/fs/erofs/internal.h
@@ -20,6 +20,10 @@
#include <linux/iomap.h>
#include "erofs_fs.h"

+#ifdef CONFIG_EROFS_FS_RUST
+#include "rust_bindings.h"
+#endif
+
/* redefine pr_fmt "erofs: " */
#undef pr_fmt
#define pr_fmt(fmt) "erofs: " fmt
@@ -178,8 +182,14 @@ struct erofs_sb_info {
char *domain_id;
};

+#ifdef CONFIG_EROFS_FS_RUST
+#define EROFS_SB(sb) (*(struct erofs_sb_info **)(((void *)((sb)->s_fs_info)) + \
+ EROFS_SB_INFO_OFFSET_RUST))
+#define EROFS_I_SB(inode) EROFS_SB((inode)->i_sb)
+#else
#define EROFS_SB(sb) ((struct erofs_sb_info *)(sb)->s_fs_info)
#define EROFS_I_SB(inode) ((struct erofs_sb_info *)(inode)->i_sb->s_fs_info)
+#endif

/* Mount flags set via mount options or defaults */
#define EROFS_MOUNT_XATTR_USER 0x00000010
diff --git a/fs/erofs/rust/kinode.rs b/fs/erofs/rust/kinode.rs
new file mode 100644
index 000000000000..df6de40d0594
--- /dev/null
+++ b/fs/erofs/rust/kinode.rs
@@ -0,0 +1,49 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-or-later
+
+use core::ffi::c_void;
+use core::mem::MaybeUninit;
+use core::ptr::NonNull;
+
+use kernel::bindings::{inode, super_block};
+
+use super::erofs_sys::inode::*;
+use super::erofs_sys::superblock::*;
+use super::erofs_sys::*;
+
+#[repr(C)]
+pub(crate) struct KernelInode {
+ pub(crate) info: MaybeUninit<InodeInfo>,
+ pub(crate) nid: MaybeUninit<Nid>,
+ pub(crate) k_inode: MaybeUninit<inode>,
+ pub(crate) k_opaque: MaybeUninit<*mut c_void>,
+}
+
+impl Inode for KernelInode {
+ fn new(_sb: &SuperBlock, _info: InodeInfo, _nid: Nid) -> Self {
+ unimplemented!();
+ }
+ fn nid(&self) -> Nid {
+ unsafe { self.nid.assume_init() }
+ }
+ fn info(&self) -> &InodeInfo {
+ unsafe { self.info.assume_init_ref() }
+ }
+}
+
+pub(crate) struct KernelInodeCollection {
+ sb: NonNull<super_block>,
+}
+
+impl InodeCollection for KernelInodeCollection {
+ type I = KernelInode;
+ fn iget(&mut self, _nid: Nid, _f: &dyn FileSystem<Self::I>) -> PosixResult<&mut Self::I> {
+ unimplemented!();
+ }
+}
+
+impl KernelInodeCollection {
+ pub(crate) fn new(sb: NonNull<super_block>) -> Self {
+ Self { sb }
+ }
+}
diff --git a/fs/erofs/rust/ksources.rs b/fs/erofs/rust/ksources.rs
new file mode 100644
index 000000000000..08213e11239c
--- /dev/null
+++ b/fs/erofs/rust/ksources.rs
@@ -0,0 +1,66 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-or-later
+
+use core::ffi::*;
+use core::ptr::NonNull;
+
+use super::erofs_sys::data::*;
+use super::erofs_sys::errnos::*;
+use super::erofs_sys::*;
+
+use kernel::bindings::super_block;
+
+extern "C" {
+ #[link_name = "erofs_read_metabuf_rust_helper"]
+ pub(crate) fn read_metabuf(
+ sb: NonNull<c_void>,
+ sbi: NonNull<c_void>,
+ offset: c_ulonglong,
+ ) -> *mut c_void;
+ #[link_name = "erofs_put_metabuf_rust_helper"]
+ pub(crate) fn put_metabuf(addr: NonNull<c_void>);
+}
+
+fn try_read_metabuf(
+ sb: NonNull<super_block>,
+ sbi: NonNull<c_void>,
+ offset: c_ulonglong,
+) -> PosixResult<NonNull<c_void>> {
+ let ptr = unsafe { read_metabuf(sb.cast(), sbi.cast(), offset) };
+ if ptr.is_null() {
+ Err(Errno::ENOMEM)
+ } else if is_value_err(ptr) {
+ Err(Errno::from(ptr))
+ } else {
+ Ok(unsafe { NonNull::new_unchecked(ptr) })
+ }
+}
+
+pub(crate) struct MetabufSource {
+ sb: NonNull<super_block>,
+ opaque: NonNull<c_void>,
+}
+
+impl MetabufSource {
+ pub(crate) fn new(sb: NonNull<super_block>, opaque: NonNull<c_void>) -> Self {
+ Self { sb, opaque }
+ }
+}
+
+impl Source for MetabufSource {
+ fn fill(&self, data: &mut [u8], offset: Off) -> PosixResult<u64> {
+ self.as_buf(offset, data.len() as u64).map(|buf| {
+ data[..buf.content().len()].clone_from_slice(buf.content());
+ buf.content().len() as Off
+ })
+ }
+ fn as_buf<'a>(&'a self, offset: Off, len: Off) -> PosixResult<RefBuffer<'a>> {
+ try_read_metabuf(self.sb.clone(), self.opaque.clone(), offset).map(|ptr| {
+ let data: &'a [u8] =
+ unsafe { core::slice::from_raw_parts(ptr.as_ptr() as *const u8, len as usize) };
+ RefBuffer::new(data, 0, len as usize, |ptr| unsafe {
+ put_metabuf(NonNull::new_unchecked(ptr as *mut c_void))
+ })
+ })
+ }
+}
diff --git a/fs/erofs/rust/ksuperblock.rs b/fs/erofs/rust/ksuperblock.rs
new file mode 100644
index 000000000000..c1955fa136c6
--- /dev/null
+++ b/fs/erofs/rust/ksuperblock.rs
@@ -0,0 +1,30 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-or-later
+use super::erofs_sys::superblock::*;
+use super::kinode::*;
+use alloc::boxed::Box;
+use core::{ffi::c_void, ptr::NonNull};
+use kernel::bindings::super_block;
+use kernel::types::ForeignOwnable;
+
+pub(crate) type KernelOpaque = NonNull<*mut c_void>;
+/// KernelSuperblockInfo defined by embedded Kernel Inode
+pub(crate) type KernelSuperblockInfo =
+ SuperblockInfo<KernelInode, KernelInodeCollection, KernelOpaque>;
+
+/// SAFETY:
+/// Cast the c_void back to KernelSuperblockInfo.
+/// This seems to be prune to some concurrency issues
+/// but the fact is that only KernelInodeCollection field can have mutability.
+/// However, it's backed by the original iget_locked5 and it's already preventing
+/// any concurrency issues. So it's safe to be casted mutable here even if it's not backed by
+/// Arc/Mutex instead of using generic method from Foreign Ownable which only provides
+/// immutable reference casting which is not enough.
+/// Since the pointer always live as long as this module exists, it's safe to declare it as static.
+pub(crate) fn erofs_sbi(sb: NonNull<super_block>) -> &'static mut KernelSuperblockInfo {
+ unsafe { &mut *(sb.as_ref().s_fs_info).cast::<KernelSuperblockInfo>() }
+}
+
+pub(crate) fn free_sbi(sb: NonNull<super_block>) {
+ unsafe { Box::<KernelSuperblockInfo>::from_foreign(sb.as_ref().s_fs_info) };
+}
diff --git a/fs/erofs/rust/mod.rs b/fs/erofs/rust/mod.rs
index e6c0731f2533..a8b66c95261c 100644
--- a/fs/erofs/rust/mod.rs
+++ b/fs/erofs/rust/mod.rs
@@ -2,3 +2,6 @@
// SPDX-License-Identifier: MIT or GPL-2.0-or-later

pub(crate) mod erofs_sys;
+pub(crate) mod kinode;
+pub(crate) mod ksources;
+pub(crate) mod ksuperblock;
diff --git a/fs/erofs/rust_bindings.h b/fs/erofs/rust_bindings.h
new file mode 100644
index 000000000000..9695c5ed5a7c
--- /dev/null
+++ b/fs/erofs/rust_bindings.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-later
+// EROFS Rust Bindings Before VFS Patch Sets for Rust
+
+#ifndef __EROFS_RUST_BINDINGS_H
+#define __EROFS_RUST_BINDINGS_H
+
+#include <linux/fs.h>
+
+extern const unsigned long EROFS_SB_INFO_OFFSET_RUST;
+extern void *erofs_alloc_sbi_rust(struct super_block *sb);
+extern void *erofs_free_sbi_rust(struct super_block *sb);
+#endif
diff --git a/fs/erofs/rust_helpers.c b/fs/erofs/rust_helpers.c
new file mode 100644
index 000000000000..5fdc158ed9ef
--- /dev/null
+++ b/fs/erofs/rust_helpers.c
@@ -0,0 +1,31 @@
+#include "rust_helpers.h"
+
+static void erofs_init_metabuf_rust_helper(struct erofs_buf *buf,
+ struct super_block *sb,
+ struct erofs_sb_info *sbi)
+{
+ if (erofs_is_fileio_mode(sbi))
+ buf->mapping = file_inode(sbi->fdev)->i_mapping;
+ else if (erofs_is_fscache_mode_rust_helper(sb, sbi))
+ buf->mapping = sbi->s_fscache->inode->i_mapping;
+ else
+ buf->mapping = sb->s_bdev->bd_mapping;
+}
+
+void *erofs_read_metabuf_rust_helper(struct super_block *sb,
+ struct erofs_sb_info *sbi,
+ erofs_off_t offset)
+{
+ struct erofs_buf buf = __EROFS_BUF_INITIALIZER;
+ erofs_init_metabuf_rust_helper(&buf, sb, sbi);
+ return erofs_bread(&buf, offset, EROFS_KMAP);
+}
+
+void erofs_put_metabuf_rust_helper(void *addr)
+{
+ erofs_put_metabuf(&(struct erofs_buf){
+ .base = addr,
+ .page = kmap_to_page(addr),
+ .kmap_type = EROFS_KMAP,
+ });
+}
diff --git a/fs/erofs/rust_helpers.h b/fs/erofs/rust_helpers.h
new file mode 100644
index 000000000000..158b21438314
--- /dev/null
+++ b/fs/erofs/rust_helpers.h
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0-later
+// This is a helpers collection to dodge the missing macros or inline functions in bindgen
+
+#ifndef __EROFS_RUST_HELPERS_H
+#define __EROFS_RUST_HELPERS_H
+
+#include "internal.h"
+
+static inline bool erofs_is_fscache_mode_rust_helper(struct super_block *sb,
+ struct erofs_sb_info *sbi)
+{
+ return IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) &&
+ !erofs_is_fileio_mode(sbi) && !sb->s_bdev;
+}
+
+void *erofs_read_metabuf_rust_helper(struct super_block *sb,
+ struct erofs_sb_info *sbi,
+ erofs_off_t offset);
+void erofs_put_metabuf_rust_helper(void *addr);
+
+#endif
diff --git a/fs/erofs/super.c b/fs/erofs/super.c
index 666873f745da..61f138a7d8e2 100644
--- a/fs/erofs/super.c
+++ b/fs/erofs/super.c
@@ -586,9 +586,12 @@ static void erofs_set_sysfs_name(struct super_block *sb)
static int erofs_fc_fill_super(struct super_block *sb, struct fs_context *fc)
{
struct inode *inode;
- struct erofs_sb_info *sbi = EROFS_SB(sb);
+ struct erofs_sb_info *sbi;
int err;
-
+#ifdef CONFIG_EROFS_FS_RUST
+ sb->s_fs_info = erofs_alloc_sbi_rust(sb);
+#endif
+ sbi = EROFS_SB(sb);
sb->s_magic = EROFS_SUPER_MAGIC;
sb->s_flags |= SB_RDONLY | SB_NOATIME;
sb->s_maxbytes = MAX_LFS_FILESIZE;
@@ -809,7 +812,13 @@ static int erofs_init_fs_context(struct fs_context *fc)

static void erofs_kill_sb(struct super_block *sb)
{
- struct erofs_sb_info *sbi = EROFS_SB(sb);
+ struct erofs_sb_info *sbi;
+
+#ifdef CONFIG_EROFS_FS_RUST
+ sbi = erofs_free_sbi_rust(sb);
+#else
+ sbi = EROFS_SB(sb);
+#endif

if ((IS_ENABLED(CONFIG_EROFS_FS_ONDEMAND) && sbi->fsid) || sbi->fdev)
kill_anon_super(sb);
diff --git a/fs/erofs/super_rs.rs b/fs/erofs/super_rs.rs
index 4b8cbef507e3..7041f4011d4c 100644
--- a/fs/erofs/super_rs.rs
+++ b/fs/erofs/super_rs.rs
@@ -7,3 +7,53 @@
#[allow(dead_code)]
#[allow(missing_docs)]
pub(crate) mod rust;
+
+use core::ffi::*;
+use core::mem::offset_of;
+use core::ptr::NonNull;
+use kernel::{bindings::super_block, types::ForeignOwnable};
+use rust::{
+ erofs_sys::{
+ alloc_helper::*,
+ data::backends::uncompressed::*,
+ superblock::{mem::*, *},
+ *,
+ },
+ kinode::*,
+ ksources::*,
+ ksuperblock::*,
+};
+
+fn try_alloc_sbi(sb: NonNull<super_block>) -> PosixResult<*const c_void> {
+ // We have to use heap_alloc here to erase the signature of MemFileSystem
+ let sbi = heap_alloc(SuperblockInfo::new(
+ heap_alloc(KernelFileSystem::try_new(UncompressedBackend::new(
+ MetabufSource::new(sb, unsafe { NonNull::new_unchecked(sb.as_ref().s_fs_info) }),
+ ))?)?,
+ KernelInodeCollection::new(sb),
+ // SAFETY: The super_block is initialized when the erofs_alloc_sbi_rust is called.
+ unsafe { NonNull::new_unchecked(sb.as_ref().s_fs_info) },
+ ))?;
+ Ok(sbi.into_foreign())
+}
+/// Allocating a rust implementation of super_block_info c_void when calling from fill_super
+/// operations. Though we still need to embed original superblock info inside rust implementation
+/// for compatibility. This is left as it is for now.
+#[no_mangle]
+pub unsafe extern "C" fn erofs_alloc_sbi_rust(sb: NonNull<super_block>) -> *const c_void {
+ try_alloc_sbi(sb).unwrap_or_else(|err| err.into())
+}
+
+/// Freeing a rust implementation of super_block_info c_void when calling from kill_super
+/// Returning the original c_void pointer for outer C code to free.
+#[no_mangle]
+pub unsafe extern "C" fn erofs_free_sbi_rust(sb: NonNull<super_block>) -> *const c_void {
+ let opaque: *const c_void = erofs_sbi(sb).opaque.as_ptr().cast();
+ // This will be freed as it goes out of the scope.
+ free_sbi(sb);
+ opaque
+}
+
+/// Used as a hint offset to be exported so that EROFS_SB can find the correct the s_fs_info.
+#[no_mangle]
+pub static EROFS_SB_INFO_OFFSET_RUST: c_ulong = offset_of!(KernelSuperblockInfo, opaque) as c_ulong;
--
2.46.0