[RFC PATCH 2/3] erofs: add implementation for erofs_sys

Yiyang Wu toolmanp at tlmp.cc
Thu Aug 8 04:47:02 AEST 2024


Implements Inode, InodeCollection, MemorySource in Kernel Space.
Exposes VFS Compatible Function Pointers to the Kernel Space.

Note that currently vfs abstraction support is not completed,
a lot of c bindings are used here.

This commit also provides some helper functions to be exported
to rust so that rust can call inline functions not generated by bindgen.

Signed-off-by: Yiyang Wu <toolmanp at tlmp.cc>
---
 fs/erofs/erofs_rust.rs                | 294 ++++++++++++++++++++++++++
 fs/erofs/erofs_rust_bindings.h        |  47 ++++
 fs/erofs/erofs_rust_helper.c          | 107 ++++++++++
 fs/erofs/erofs_rust_helper.h          |  40 ++++
 fs/erofs/rust/erofs_sys.rs            |  67 ++++++
 fs/erofs/rust/kinode.rs               | 103 +++++++++
 fs/erofs/rust/kinode/kinode_helper.rs |  26 +++
 fs/erofs/rust/mod.rs                  |   6 +
 fs/erofs/rust/sources.rs              |   5 +
 fs/erofs/rust/sources/mm.rs           |  62 ++++++
 fs/erofs/rust/sources/page_helper.rs  |  12 ++
 11 files changed, 769 insertions(+)
 create mode 100644 fs/erofs/erofs_rust.rs
 create mode 100644 fs/erofs/erofs_rust_bindings.h
 create mode 100644 fs/erofs/erofs_rust_helper.c
 create mode 100644 fs/erofs/erofs_rust_helper.h
 create mode 100644 fs/erofs/rust/erofs_sys.rs
 create mode 100644 fs/erofs/rust/kinode.rs
 create mode 100644 fs/erofs/rust/kinode/kinode_helper.rs
 create mode 100644 fs/erofs/rust/mod.rs
 create mode 100644 fs/erofs/rust/sources.rs
 create mode 100644 fs/erofs/rust/sources/mm.rs
 create mode 100644 fs/erofs/rust/sources/page_helper.rs

diff --git a/fs/erofs/erofs_rust.rs b/fs/erofs/erofs_rust.rs
new file mode 100644
index 000000000000..c46876ded0ef
--- /dev/null
+++ b/fs/erofs/erofs_rust.rs
@@ -0,0 +1,294 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+
+//! EROFS Rust Kernel Module Helpers Implementation
+//! This is only for experimental purpose. This is not guaranteed to work.
+//! Note that this module must be rewritten after the rust VFS abstraction is merged.
+//! Currently a lot of c bindings are used which violate the principles provided by Rust-For-Linux
+
+#[allow(dead_code)]
+#[allow(missing_docs)]
+pub(crate) mod rust;
+use core::ffi::c_void;
+use core::ptr::NonNull;
+use kernel::bindings::{
+    address_space, d_make_root, d_obtain_alias, d_splice_alias, dentry, dir_context, file, inode,
+    inode_init_once, super_block,
+};
+use kernel::types::ForeignOwnable;
+use kernel::{
+    container_of,
+    prelude::{Box, Vec},
+};
+use rust::{
+    erofs_sys::{
+        alloc_helper::*,
+        data::uncompressed::*,
+        data::*,
+        inode::*,
+        map::*,
+        operations::*,
+        superblock::{mem::*, *},
+        *,
+    },
+    kinode::{KernelInode, KernelInodeCollection},
+    sources::mm::FolioSource,
+};
+
+/// This is just a log prefix for kernel module so that pr_info can be used
+pub const __LOG_PREFIX: &[u8] = b"erofs_rust: \0";
+
+/// KernelSuperblockInfo defined by embedded Kernel Inode
+pub(crate) type KernelSuperblockInfo = SuperblockInfo<KernelInode, KernelInodeCollection>;
+
+/// Allocating a rust implementation of super_block_info c_void when calling from fill_super
+/// Note that this function needs a fixed address_space before we can use the vfs vtable to bind the
+/// operations. This is left as it is for now.
+#[no_mangle]
+pub unsafe extern "C" fn erofs_alloc_sbi_rust(
+    sb: NonNull<super_block>,
+    address_space: NonNull<address_space>,
+) -> *const c_void {
+    //  We have to use heap_alloc here to erase the signature of MemFileSystem
+    let sb = heap_alloc(SuperblockInfo::new(
+        heap_alloc(MemFileSystem::new(UncompressedBackend::new(
+            FolioSource::new(address_space),
+        ))),
+        KernelInodeCollection::new(sb),
+    ));
+    return sb.into_foreign();
+}
+
+/// SAFETY:
+/// Cast the c_void back to KernelSuperblockInfo.
+/// This seems to 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 Foriegn Ownable which only provides
+/// immutable reference casting.
+fn as_sbi(f: *mut c_void) -> &'static mut KernelSuperblockInfo {
+    unsafe { &mut *(f as *mut KernelSuperblockInfo) }
+}
+
+/// Free the KernelSuperblockInfo that was embedded into the superblock in the c implementation
+#[no_mangle]
+pub unsafe extern "C" fn erofs_free_sbi_rust(sb: *mut super_block) {
+    // this will simply get dropped immediately after receiving the ownership.
+    unsafe { drop(Box::<KernelSuperblockInfo>::from_foreign((*sb).s_fs_info)) };
+}
+
+/// A helper sturct to map blocks for iomap_begin because iomap is not generated by bindgen
+#[repr(C)]
+pub struct ErofsRustMap {
+    m_pa: u64,
+    m_la: u64,
+    m_plen: u64,
+    m_llen: u64,
+    m_flags: u32,
+    inline_data: *const core::ffi::c_void,
+    private: *const core::ffi::c_void,
+}
+
+/// Lookup function for dentry-inode lookup replacement
+#[no_mangle]
+pub unsafe extern "C" fn erofs_lookup_rust(
+    k_inode: *mut inode,
+    dentry: *mut dentry,
+    _flags: u32,
+) -> *mut dentry {
+    // SAFETY: We are sure that the inode is a Kernel Inode since alloc_inode is called
+    let inode = unsafe { &*container_of!(k_inode, KernelInode, k_inode) };
+    let erofs_sbi = as_sbi(unsafe { (*(*k_inode).i_sb).s_fs_info });
+
+    // SAFETY: this is backed by qstr which is c representation of a valid slice.
+    let name = unsafe {
+        core::str::from_utf8_unchecked(core::slice::from_raw_parts(
+            (*dentry).d_name.name,
+            (*dentry).d_name.__bindgen_anon_1.__bindgen_anon_1.len as usize,
+        ))
+    };
+
+    let k_inode: *mut inode = dir_lookup(
+        erofs_sbi.filesystem.as_ref(),
+        &mut erofs_sbi.inodes,
+        inode,
+        name,
+    )
+    .map_or(core::ptr::null_mut(), |result| result.k_inode.as_mut_ptr());
+
+    // SAFETY: We are sure that the inner k_inode has already been initialized
+    unsafe { d_splice_alias(k_inode, dentry) }
+}
+
+/// Get parent inode
+#[no_mangle]
+pub unsafe extern "C" fn erofs_get_parent_rust(child: *mut dentry) -> *mut dentry {
+    // SAFETY: We are sure that the inode is a Kernel Inode since alloc_inode is called
+    let k_inode = unsafe { (*child).d_inode };
+    let erofs_sbi = as_sbi(unsafe { (*(*k_inode).i_sb).s_fs_info });
+    let inode = unsafe { &*container_of!(k_inode, KernelInode, k_inode) };
+
+    let k_inode: *mut inode = dir_lookup(
+        erofs_sbi.filesystem.as_ref(),
+        &mut erofs_sbi.inodes,
+        inode,
+        "..",
+    )
+    .map_or(core::ptr::null_mut(), |result| result.k_inode.as_mut_ptr());
+
+    // SAFETY: We are sure that the inner k_inode has already been initialized
+    unsafe { d_obtain_alias(k_inode) }
+}
+
+/// Readdir
+#[no_mangle]
+pub unsafe extern "C" fn erofs_readdir_rust(f: *mut file, ctx: *mut dir_context) -> i32 {
+    let inode = unsafe { &*container_of!((*f).f_inode, KernelInode, k_inode) };
+    let vnode = unsafe { (*f).f_inode };
+    let sb = unsafe { (*vnode).i_sb };
+    let erofs_sbi = as_sbi(unsafe { (*sb).s_fs_info });
+    let offset = unsafe { (*ctx).pos };
+    erofs_sbi
+        .filesystem
+        .fill_dentries(inode, offset as Off, &mut |dir, pos| unsafe {
+            // inline expansion from dir_emit
+            (*ctx).actor.unwrap()(
+                ctx,
+                dir.name.as_ptr().cast(),
+                dir.name.len() as i32,
+                pos as i64,
+                dir.desc.nid as u64,
+                dir.desc.file_type as u32,
+            );
+            (*ctx).pos = pos as i64;
+        });
+    unsafe { (*ctx).pos = inode.info().file_size() as i64 }
+    0
+}
+
+/// MapBlocks used for iomap_begin
+#[no_mangle]
+pub unsafe extern "C" fn erofs_iomap_begin_rust(k_inode: *mut inode, map: *mut ErofsRustMap) {
+    let erofs_sbi = as_sbi(unsafe { (*(*k_inode).i_sb).s_fs_info });
+    let k_inode = unsafe { &*container_of!(k_inode, KernelInode, k_inode) };
+    let m = erofs_sbi.filesystem.map(k_inode, unsafe { (*map).m_la });
+
+    unsafe {
+        (*map).m_pa = m.physical.start;
+        (*map).m_la = m.logical.start;
+        (*map).m_plen = m.physical.len;
+        (*map).m_llen = m.logical.len;
+        (*map).m_flags = m.flags;
+    }
+    if m.flags & MAP_META != 0 && m.logical.len != 0 {
+        let buffer = erofs_sbi
+            .filesystem
+            .continous_iter(m.physical.start, m.physical.len)
+            .next()
+            .unwrap();
+        unsafe {
+            (*map).inline_data = buffer.content().as_ptr().cast();
+            (*map).private = heap_alloc(buffer).into_foreign();
+        }
+    } else {
+        unsafe {
+            (*map).inline_data = core::ptr::null_mut();
+            (*map).private = core::ptr::null_mut();
+        }
+    }
+}
+
+/// iomap_end
+#[no_mangle]
+pub unsafe extern "C" fn erofs_iomap_end_rust(map: *mut ErofsRustMap) {
+    unsafe {
+        if !(*map).private.is_null() {
+            drop(Box::<Box<dyn Buffer + 'static>>::from_foreign(
+                (*map).private,
+            ))
+        }
+    }
+}
+
+/// Map blocks in inline mode exported for iget5_locked to be actually functional
+#[no_mangle]
+pub unsafe extern "C" fn erofs_iget5_eq_rust(kinode: *const c_void, opaque: *const c_void) -> i32 {
+    let nid = unsafe { *(opaque as *const Nid) };
+    let inode = unsafe { container_of!(kinode, KernelInode, k_inode) };
+    (unsafe { (*inode).nid() == nid }) as i32
+}
+
+/// init_once replacement
+#[no_mangle]
+pub unsafe extern "C" fn erofs_inode_init_once_rust(ptr: *const c_void) {
+    let inode = unsafe { &mut *(ptr as *mut KernelInode) };
+    unsafe { inode_init_once(inode.k_inode.as_mut_ptr()) }
+}
+
+/// iget replacement
+#[no_mangle]
+pub unsafe extern "C" fn erofs_iget_rust(sb: *mut super_block, nid: Nid) -> *mut inode {
+    let erofs_sbi = as_sbi(unsafe { (*sb).s_fs_info });
+    read_inode(erofs_sbi.filesystem.as_ref(), &mut erofs_sbi.inodes, nid)
+        .k_inode
+        .as_mut_ptr()
+}
+
+///make_root_call for setting up the root dentry for superblock
+#[no_mangle]
+pub unsafe extern "C" fn erofs_make_root_rust(sb: *mut super_block) -> *mut dentry {
+    let erofs_sbi = as_sbi(unsafe { (*sb).s_fs_info });
+    unsafe {
+        d_make_root(
+            read_inode(
+                erofs_sbi.filesystem.as_ref(),
+                &mut erofs_sbi.inodes,
+                erofs_sbi.filesystem.superblock().root_nid as u64,
+            )
+            .k_inode
+            .as_mut_ptr(),
+        )
+    }
+}
+
+/// get_symlink_data_inline
+#[no_mangle]
+pub unsafe extern "C" fn erofs_get_inline_symlink_data_rust(k_inode: *mut inode) -> *mut c_void {
+    let inode = unsafe { &*container_of!(k_inode, KernelInode, k_inode) };
+    let erofs_sbi = as_sbi(unsafe { (*(*k_inode).i_sb).s_fs_info });
+
+    if inode.info().format().layout() != Layout::FlatInline
+        || inode.info().inode_size() >= erofs_sbi.filesystem.blksz()
+    {
+        core::ptr::null_mut()
+    } else {
+        let mut cursor: usize = 0;
+        let mut symlink: Vec<u8> = vec_with_capacity(inode.info().inode_size() as usize + 1);
+        for block in erofs_sbi.filesystem.mapped_iter(inode, 0) {
+            let data = block.content();
+            let len = data.len();
+            unsafe {
+                symlink
+                    .as_mut_ptr()
+                    .add(cursor)
+                    .copy_from(data.as_ptr(), len);
+            }
+            cursor += len;
+        }
+
+        unsafe {
+            *(symlink.as_mut_ptr().add(cursor)) = '\0' as u8;
+        }
+
+        symlink.leak().as_mut_ptr().cast()
+    }
+}
+
+/// Exposed as the inode_size which is used as a param to guide the kmem_cache_create.
+#[no_mangle]
+pub static EROFS_INODE_SIZE: Off = core::mem::size_of::<KernelInode>() as Off;
+
+/// Exposed as the inode_offset so that when freeing the inode, the correct offset can be used.
+#[no_mangle]
+pub static EROFS_INODE_OFFSET: Off = core::mem::offset_of!(KernelInode, k_inode) as Off;
diff --git a/fs/erofs/erofs_rust_bindings.h b/fs/erofs/erofs_rust_bindings.h
new file mode 100644
index 000000000000..8193e18bd7e2
--- /dev/null
+++ b/fs/erofs/erofs_rust_bindings.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// EROFS Rust Bindings Before VFS Patch Sets for Rust
+
+#ifndef EROFS_RUST_BINDINGS_H
+#define EROFS_RUST_BINDINGS_H
+
+#include <linux/fs.h>
+#include <linux/dax.h>
+#include <linux/dcache.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/bio.h>
+#include <linux/magic.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/iomap.h>
+
+#include "erofs_fs.h"
+
+struct erofs_rust_map {
+	__le64 m_pa;
+	__le64 m_la;
+	__le64 m_plen;
+	__le64 m_llen;
+	__le32 m_flags;
+	void *inline_data;
+	void *private;
+};
+
+extern const __le64 EROFS_INODE_SIZE;
+extern const __le64 EROFS_INODE_OFFSET;
+extern struct dentry *erofs_lookup_rust(struct inode *inode,
+					struct dentry *dentry,
+					unsigned int flags);
+extern int erofs_readdir_rust(struct file *f, struct dir_context *ctx);
+extern void *erofs_alloc_sbi_rust(struct super_block *sb,
+				  struct address_space *address_space);
+extern void erofs_free_sbi_rust(struct super_block *sb);
+extern void erofs_inode_init_once_rust(void *ptr);
+extern void erofs_iomap_begin_rust(struct inode *inode,
+				   struct erofs_rust_map *m);
+extern void erofs_iomap_end_rust(struct erofs_rust_map *m);
+extern struct inode *erofs_iget_rust(struct super_block *sb, ino_t ino);
+extern struct dentry *erofs_get_parent_rust(struct dentry *child);
+extern struct dentry *erofs_make_root_rust(struct super_block *sb);
+#endif
diff --git a/fs/erofs/erofs_rust_helper.c b/fs/erofs/erofs_rust_helper.c
new file mode 100644
index 000000000000..faf1377ec4ca
--- /dev/null
+++ b/fs/erofs/erofs_rust_helper.c
@@ -0,0 +1,107 @@
+#include "erofs_rust_helper.h"
+
+void *erofs_get_page(void *mapping, pgoff_t index)
+{
+	unsigned int nofs_flag;
+	struct folio *folio;
+	struct page *page;
+
+	nofs_flag = memalloc_nofs_save();
+	folio = read_cache_folio((struct address_space *)mapping, index, NULL,
+				 NULL);
+	memalloc_nofs_restore(nofs_flag);
+	page = folio_file_page(folio, index);
+	return kmap_local_page(page);
+}
+
+void erofs_put_page(void *address)
+{
+	kunmap_local(address);
+	put_page(virt_to_page(address));
+}
+
+extern int erofs_iget5_eq_rust(void *inode, void *opaque);
+
+static ino_t erofs_squash_ino(erofs_nid_t nid)
+{
+	ino_t ino = (ino_t)nid;
+
+	if (sizeof(ino_t) < sizeof(erofs_nid_t))
+		ino ^= nid >> (sizeof(erofs_nid_t) - sizeof(ino_t)) * 8;
+	return ino;
+}
+
+static int erofs_iget5_eq(struct inode *inode, void *opaque)
+{
+	return erofs_iget5_eq_rust(inode, opaque);
+}
+
+static int erofs_iget5_set(struct inode *inode, void *opaque)
+{
+	const erofs_nid_t nid = *(erofs_nid_t *)opaque;
+	inode->i_ino = nid;
+	return 0;
+}
+
+void erofs_init_inode_unlock(struct inode *inode,
+			     const struct erofs_inode_init_param_rust *param)
+{
+	inode->i_mode = param->i_mode;
+	inode->i_size = param->i_size;
+	i_uid_write(inode, param->i_uid);
+	i_gid_write(inode, param->i_gid);
+	set_nlink(inode, param->i_nlink);
+	inode_set_ctime(inode, param->i_mtime, param->i_mtime_nsec);
+	inode_set_mtime_to_ts(
+		inode, inode_set_atime_to_ts(inode, inode_get_ctime(inode)));
+
+	switch (inode->i_mode & S_IFMT) {
+	case S_IFREG:
+		inode->i_op = &erofs_generic_iops;
+		inode->i_fop = &erofs_file_fops;
+		break;
+	case S_IFDIR:
+		inode->i_op = &erofs_dir_iops;
+		inode->i_fop = &erofs_dir_fops;
+		inode_nohighmem(inode);
+		break;
+	case S_IFLNK:
+		inode->i_link = erofs_get_inline_symlink_data_rust(inode);
+		if (inode->i_link != NULL)
+			inode->i_op = &erofs_fast_symlink_iops;
+		else
+			inode->i_op = &erofs_symlink_iops;
+		inode_nohighmem(inode);
+		break;
+	case S_IFCHR:
+	case S_IFBLK:
+	case S_IFIFO:
+	case S_IFSOCK:
+		inode->i_op = &erofs_generic_iops;
+		init_special_inode(inode, inode->i_mode, inode->i_rdev);
+		break;
+	default:
+		panic("Not supported");
+	}
+
+	inode->i_mapping->a_ops = &erofs_raw_access_aops;
+	inode->i_blocks = param->i_blocks;
+	mapping_set_large_folios(inode->i_mapping);
+#ifdef CONFIG_EROFS_FS_ONDEMAND
+	if (erofs_is_fscache_mode(inode->i_sb))
+		inode->i_mapping->a_ops = &erofs_fscache_access_aops;
+#endif
+	unlock_new_inode(inode);
+}
+
+struct inode *erofs_iget_locked_noinit(struct super_block *sb, erofs_nid_t nid,
+				       int *is_new)
+{
+	struct inode *inode;
+
+	inode = iget5_locked(sb, erofs_squash_ino(nid), erofs_iget5_eq,
+			     erofs_iget5_set, &nid);
+
+	*is_new = inode->i_state & I_NEW;
+	return inode;
+}
diff --git a/fs/erofs/erofs_rust_helper.h b/fs/erofs/erofs_rust_helper.h
new file mode 100644
index 000000000000..b9f0175bc319
--- /dev/null
+++ b/fs/erofs/erofs_rust_helper.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// This is a helper header to dodge the missing static inline in bindgen
+
+#ifndef __EROFS_RUST_HELPER_H
+#define __EROFS_RUST_HELPER_H
+
+#include <linux/fs.h>
+#include <linux/dax.h>
+#include <linux/dcache.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+#include <linux/bio.h>
+#include <linux/magic.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/iomap.h>
+
+#include "internal.h"
+
+
+struct erofs_inode_init_param_rust {
+	__le16 i_mode;
+	__le32 i_uid;
+	__le32 i_gid;
+	__le32 i_nlink;
+	__le32 i_mtime_nsec;
+	__le64 i_mtime;
+	__le64 i_size;
+	__le64 i_blocks;
+};
+void *erofs_get_page(void *mapping, pgoff_t index);
+void erofs_put_page(void *address);
+void erofs_init_inode_unlock(struct inode *inode, const struct erofs_inode_init_param_rust *param);
+struct inode *erofs_iget_locked_noinit(struct super_block *sb, erofs_nid_t nid,
+				int *init);
+
+void *erofs_get_inline_symlink_data_rust(struct inode *inode);
+
+#endif
diff --git a/fs/erofs/rust/erofs_sys.rs b/fs/erofs/rust/erofs_sys.rs
new file mode 100644
index 000000000000..8ac02c95b7f0
--- /dev/null
+++ b/fs/erofs/rust/erofs_sys.rs
@@ -0,0 +1,67 @@
+#![allow(dead_code)]
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+
+//! A pure Rust implementation of the EROFS filesystem.
+//! Technical Details are documented in the [EROFS Documentation](https://erofs.docs.kernel.org/en/latest/)
+
+pub(crate) const EROFS_PAGE_SZ: u64 = 4096;
+pub(crate) const EROFS_PAGE: Page = [0; EROFS_PAGE_SZ as usize];
+pub(crate) const EROFS_SUPER_OFFSET: Off = 1024;
+pub(crate) const EROFS_PAGE_BITS: u64 = 12;
+pub(crate) const EROFS_PAGE_MASK: u64 = EROFS_PAGE_SZ - 1;
+
+pub(crate) struct PageAddress {
+    pub(crate) page: u64,
+    pub(crate) pg_index: u64,
+    pub(crate) pg_off: u64,
+    pub(crate) pg_len: u64,
+}
+
+impl From<u64> for PageAddress {
+    fn from(address: u64) -> Self {
+        PageAddress {
+            page: (address >> EROFS_PAGE_BITS) << EROFS_PAGE_BITS,
+            pg_index: address >> EROFS_PAGE_BITS,
+            pg_off: address & EROFS_PAGE_MASK,
+            pg_len: EROFS_PAGE_SZ - (address & EROFS_PAGE_MASK),
+        }
+    }
+}
+
+// It's unavoidable to import alloc here. Since there are so many backends there and if we want to
+// to use trait object to export Filesystem pointer. The alloc crate here is necessary.
+
+#[cfg(not(CONFIG_EROFS_FS = "y"))]
+extern crate alloc;
+
+/// Erofs Operates on the block/page size of 4096 we respect that.
+pub(crate) type Page = [u8; EROFS_PAGE_SZ as usize];
+/// Erofs requires block index to a 32 bit unsigned integer.
+pub(crate) type Blk = u32;
+/// Erofs requires normal offset to be a 64bit unsigned integer.
+pub(crate) type Off = u64;
+/// Erofs requires inode nid to be a 64bit unsigned integer.
+pub(crate) type Nid = u64;
+
+pub(crate) mod alloc_helper;
+pub(crate) mod compression;
+pub(crate) mod data;
+pub(crate) mod devices;
+pub(crate) mod dir;
+pub(crate) mod inode;
+pub(crate) mod map;
+pub(crate) mod operations;
+pub(crate) mod superblock;
+pub(crate) mod xattrs;
+
+/// Helper macro to round up or down a number.
+#[macro_export]
+macro_rules! round {
+    (UP, $x: expr, $y: expr) => {
+        ($x + $y - 1) / $y * $y
+    };
+    (DOWN, $x: expr, $y: expr) => {
+        ($x / $y) * $y
+    };
+}
diff --git a/fs/erofs/rust/kinode.rs b/fs/erofs/rust/kinode.rs
new file mode 100644
index 000000000000..16f58c3814a5
--- /dev/null
+++ b/fs/erofs/rust/kinode.rs
@@ -0,0 +1,103 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+
+pub(crate) mod kinode_helper;
+
+use core::mem::MaybeUninit;
+use core::ptr::NonNull;
+
+use super::erofs_sys::inode::*;
+use super::erofs_sys::superblock::*;
+use super::erofs_sys::xattrs::*;
+use super::erofs_sys::*;
+
+use kernel::bindings::{inode, super_block};
+use kernel::container_of;
+use kinode_helper::*;
+
+#[repr(C)]
+pub(crate) struct KernelInode {
+    pub(crate) info: MaybeUninit<InodeInfo>,
+    pub(crate) xattrs_header: MaybeUninit<MemEntryIndexHeader>,
+    pub(crate) nid: MaybeUninit<Nid>,
+    pub(crate) k_inode: MaybeUninit<inode>,
+}
+
+impl Inode for KernelInode {
+    fn new(
+        _sb: &SuperBlock,
+        _info: InodeInfo,
+        _nid: Nid,
+        _xattrs_header: xattrs::MemEntryIndexHeader,
+    ) -> Self {
+        Self {
+            info: MaybeUninit::uninit(),
+            xattrs_header: MaybeUninit::uninit(),
+            nid: MaybeUninit::uninit(),
+            k_inode: MaybeUninit::uninit(),
+        }
+    }
+    fn nid(&self) -> Nid {
+        unsafe { self.nid.assume_init() }
+    }
+    fn info(&self) -> &InodeInfo {
+        unsafe { self.info.assume_init_ref() }
+    }
+    fn xattrs_header(&self) -> &xattrs::MemEntryIndexHeader {
+        unsafe { self.xattrs_header.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>) -> &mut Self::I {
+        let mut is_new: i32 = 0;
+        let k_inode = unsafe { iget_locked(self.sb.as_ptr().cast(), nid, &mut is_new as *mut i32) };
+        let inode: &mut KernelInode =
+            unsafe { &mut *(container_of!(k_inode, KernelInode, k_inode) as *mut KernelInode) };
+
+        if is_new != 0 {
+            let info = f.read_inode_info(nid);
+            inode.info.write(info.clone());
+            inode.xattrs_header.write(f.read_inode_xattrs_index(nid));
+            inode.nid.write(nid);
+            let sb = f.superblock();
+            let param = match info {
+                InodeInfo::Compact(compact) => ErofsInodeInitParam {
+                    i_mode: compact.i_mode,
+                    i_uid: compact.i_uid as u32,
+                    i_gid: compact.i_gid as u32,
+                    i_nlink: compact.i_nlink as u32,
+                    i_mtime_nsec: sb.build_time_nsec as u32,
+                    i_mtime: sb.build_time as u64,
+                    i_size: compact.i_size as u64,
+                    i_blocks: compact.i_size as u64 >> ((sb.blkszbits - 9) as u64),
+                },
+                InodeInfo::Extended(extended) => ErofsInodeInitParam {
+                    i_mode: extended.i_mode,
+                    i_uid: extended.i_uid,
+                    i_gid: extended.i_gid,
+                    i_nlink: extended.i_nlink,
+                    i_mtime_nsec: extended.i_mtime_nsec as u32,
+                    i_mtime: extended.i_mtime as u64,
+                    i_size: extended.i_size as u64,
+                    i_blocks: extended.i_size >> ((sb.blkszbits - 9) as u64),
+                },
+            };
+            unsafe {
+                iget_unlock(k_inode, &param as *const ErofsInodeInitParam);
+            }
+        }
+        return inode;
+    }
+}
+
+impl KernelInodeCollection {
+    pub(crate) fn new(sb: NonNull<super_block>) -> Self {
+        Self { sb }
+    }
+}
diff --git a/fs/erofs/rust/kinode/kinode_helper.rs b/fs/erofs/rust/kinode/kinode_helper.rs
new file mode 100644
index 000000000000..56c22a393aa0
--- /dev/null
+++ b/fs/erofs/rust/kinode/kinode_helper.rs
@@ -0,0 +1,26 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+
+use super::super::erofs_sys::Off;
+use core::ffi::{c_int, c_void};
+
+#[repr(C)]
+pub(crate) struct ErofsInodeInitParam {
+    pub(crate) i_mode: u16,
+    pub(crate) i_uid: u32,
+    pub(crate) i_gid: u32,
+    pub(crate) i_nlink: u32,
+    pub(crate) i_mtime_nsec: u32,
+    pub(crate) i_mtime: u64,
+    pub(crate) i_size: u64,
+    pub(crate) i_blocks: u64
+}
+
+extern "C" {
+
+    #[link_name = "erofs_init_inode_unlock"]
+    pub(crate) fn iget_unlock(inode: *mut c_void, param: *const ErofsInodeInitParam);
+
+    #[link_name = "erofs_iget_locked_noinit"]
+    pub(crate) fn iget_locked(sb: *mut c_void, nid: Off, is_new: *mut c_int) -> *mut c_void;
+}
diff --git a/fs/erofs/rust/mod.rs b/fs/erofs/rust/mod.rs
new file mode 100644
index 000000000000..5ca116294f51
--- /dev/null
+++ b/fs/erofs/rust/mod.rs
@@ -0,0 +1,6 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+
+pub(crate) mod erofs_sys;
+pub(crate) mod kinode;
+pub(crate) mod sources;
diff --git a/fs/erofs/rust/sources.rs b/fs/erofs/rust/sources.rs
new file mode 100644
index 000000000000..16bd37b80166
--- /dev/null
+++ b/fs/erofs/rust/sources.rs
@@ -0,0 +1,5 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+
+pub(crate) mod mm;
+mod page_helper;
diff --git a/fs/erofs/rust/sources/mm.rs b/fs/erofs/rust/sources/mm.rs
new file mode 100644
index 000000000000..6176adfb2a76
--- /dev/null
+++ b/fs/erofs/rust/sources/mm.rs
@@ -0,0 +1,62 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+use super::super::erofs_sys::data::*;
+use super::super::erofs_sys::*;
+use super::page_helper::*;
+
+use core::{
+    ffi::{c_ulong, c_void},
+    ptr::NonNull,
+    unimplemented,
+};
+
+use kernel::bindings::address_space;
+
+pub(crate) struct FolioSource {
+    address_space: NonNull<address_space>,
+}
+
+impl FolioSource {
+    pub(crate) fn new(address_space: NonNull<address_space>) -> Self {
+        Self { address_space }
+    }
+}
+
+impl Source for FolioSource {
+    fn fill(&self, data: &mut [u8], offset: Off) -> SourceResult<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 put_page_safe(buf: *mut c_void) {
+    unsafe { put_page(buf) }
+}
+
+fn try_get_page(address_space: *mut c_void, index: c_ulong) -> SourceResult<*mut c_void> {
+    let ptr = unsafe { get_page(address_space, index as c_ulong) };
+    if !ptr.is_null() {
+        Ok(ptr)
+    } else {
+        Err(SourceError::Dummy)
+    }
+}
+
+impl<'a> PageSource<'a> for FolioSource {
+    fn as_buf(&'a self, offset: Off, len: Off) -> SourceResult<RefBuffer<'a>> {
+        let pa = PageAddress::from(offset);
+        if pa.pg_off + len > EROFS_PAGE_SZ {
+            return Err(SourceError::OutBound);
+        }
+        try_get_page(self.address_space.as_ptr().cast(), pa.pg_index as c_ulong).map(|ptr| {
+            let buf: &'a [u8] =
+                unsafe { core::slice::from_raw_parts(ptr as *const u8, EROFS_PAGE_SZ as usize) };
+            RefBuffer::new(buf, pa.pg_off as usize, len as usize, put_page_safe)
+        })
+    }
+    fn as_buf_mut(&'a mut self, _offset: Off, _len: Off) -> SourceResult<RefBufferMut<'a>> {
+        unimplemented!()
+    }
+}
diff --git a/fs/erofs/rust/sources/page_helper.rs b/fs/erofs/rust/sources/page_helper.rs
new file mode 100644
index 000000000000..c451021aa70f
--- /dev/null
+++ b/fs/erofs/rust/sources/page_helper.rs
@@ -0,0 +1,12 @@
+// Copyright 2024 Yiyang Wu
+// SPDX-License-Identifier: MIT or GPL-2.0-only
+
+use core::ffi::{c_ulong, c_void};
+
+extern "C" {
+    #[link_name = "erofs_put_page"]
+    pub(crate) fn put_page(addr: *mut c_void);
+
+    #[link_name = "erofs_get_page"]
+    pub(crate) fn get_page(mapping: *mut c_void, index: c_ulong) -> *mut c_void;
+}
-- 
2.45.2



More information about the Linux-erofs mailing list