Explorar el Código

vfs: rework of inode and page cache system

- Bump rust compiler version to nightly-2026-01-09.
- Inode rework: add a generic Inode struct.
- Add a macro to help function tweaks.
- PageCache rework: reduce complexity and try to decouple.
- Adapt fat32, tmpfs to the new page cache system.
- Change the way we process mapped pages and load ELF executables.
- Refine handling flags in `MMArea::handle_mmap`.

Signed-off-by: greatbridf <greatbridf@icloud.com>
greatbridf hace 3 semanas
padre
commit
45268e7d33

+ 27 - 35
crates/eonix_hal/src/arch/riscv64/bootstrap.rs

@@ -1,39 +1,31 @@
-use super::{
-    config::{self, mm::*},
-    console::write_str,
-    cpu::{CPUID, CPU_COUNT},
-    time::set_next_timer,
-};
-use crate::{
-    arch::{
-        cpu::CPU,
-        fdt::{init_dtb_and_fdt, FdtExt, FDT},
-        mm::{ArchPhysAccess, FreeRam, PageAttribute64, GLOBAL_PAGE_TABLE},
-    },
-    bootstrap::BootStrapData,
-    mm::{ArchMemory, ArchPagingMode, BasicPageAlloc, BasicPageAllocRef, ScopedAllocator},
-};
-use core::{
-    alloc::Allocator,
-    arch::asm,
-    cell::RefCell,
-    sync::atomic::{AtomicBool, AtomicUsize},
-};
-use core::{
-    arch::{global_asm, naked_asm},
-    hint::spin_loop,
-    sync::atomic::{AtomicPtr, Ordering},
-};
+use core::alloc::Allocator;
+use core::arch::{asm, global_asm, naked_asm};
+use core::cell::RefCell;
+use core::hint::spin_loop;
+use core::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
+
 use eonix_hal_traits::mm::Memory;
-use eonix_mm::{
-    address::{Addr as _, PAddr, PRange, PhysAccess, VAddr, VRange},
-    page_table::{PageAttribute, PagingMode, PTE as _},
-    paging::{Page, PageAccess, PageAlloc, PAGE_SIZE, PFN},
-};
+use eonix_mm::address::{Addr as _, PAddr, PRange, PhysAccess, VAddr, VRange};
+use eonix_mm::page_table::{PageAttribute, PagingMode, PTE as _};
+use eonix_mm::paging::{Page, PageAccess, PageAlloc, PAGE_SIZE, PFN};
 use eonix_percpu::PercpuArea;
 use fdt::Fdt;
-use riscv::{asm::sfence_vma_all, register::satp};
-use sbi::{hsm::hart_start, legacy::console_putchar, PhysicalAddress};
+use riscv::asm::sfence_vma_all;
+use riscv::register::satp;
+use sbi::hsm::hart_start;
+use sbi::legacy::console_putchar;
+use sbi::PhysicalAddress;
+
+use super::config::mm::*;
+use super::config::{self};
+use super::console::write_str;
+use super::cpu::{CPUID, CPU_COUNT};
+use super::time::set_next_timer;
+use crate::arch::cpu::CPU;
+use crate::arch::fdt::{init_dtb_and_fdt, FdtExt, FDT};
+use crate::arch::mm::{ArchPhysAccess, FreeRam, PageAttribute64, GLOBAL_PAGE_TABLE};
+use crate::bootstrap::BootStrapData;
+use crate::mm::{ArchMemory, ArchPagingMode, BasicPageAlloc, BasicPageAllocRef, ScopedAllocator};
 
 #[unsafe(link_section = ".bootstrap.stack")]
 static BOOT_STACK: [u8; 4096 * 16] = [0; 4096 * 16];
@@ -78,7 +70,7 @@ static AP_SEM: AtomicBool = AtomicBool::new(false);
 #[unsafe(naked)]
 #[unsafe(no_mangle)]
 #[unsafe(link_section = ".bootstrap.entry")]
-unsafe extern "C" fn _start(hart_id: usize, dtb_addr: usize) -> ! {
+unsafe extern "C" fn _start(hart_id: usize, dtb_addr: usize) {
     naked_asm!(
         "
             ld    sp, 2f
@@ -289,7 +281,7 @@ fn bootstrap_smp(alloc: impl Allocator, page_alloc: &RefCell<BasicPageAlloc>) {
 #[unsafe(naked)]
 #[unsafe(no_mangle)]
 #[unsafe(link_section = ".bootstrap.apentry")]
-unsafe extern "C" fn _ap_start(hart_id: usize) -> ! {
+unsafe extern "C" fn _ap_start(hart_id: usize) {
     naked_asm!(
         "
             la    sp, 1f        // set temp stack

+ 1 - 1
rust-toolchain

@@ -1 +1 @@
-nightly-2025-05-16
+nightly-2026-01-09

+ 112 - 164
src/fs/fat32.rs

@@ -1,26 +1,22 @@
 mod dir;
 mod file;
 
-use alloc::sync::{Arc, Weak};
-use core::future::Future;
+use alloc::sync::Arc;
 use core::ops::Deref;
 
 use async_trait::async_trait;
 use dir::{as_raw_dirents, ParseDirent};
+use eonix_mm::paging::PAGE_SIZE;
 use eonix_sync::RwLock;
 use itertools::Itertools;
 
 use crate::io::{Buffer, ByteBuffer, UninitBuffer};
 use crate::kernel::block::{BlockDevice, BlockDeviceRequest};
 use crate::kernel::constants::{EINVAL, EIO};
-use crate::kernel::mem::{
-    CachePage, CachePageStream, Page, PageCache, PageCacheBackendOps, PageExcl, PageExt,
-};
+use crate::kernel::mem::{CachePage, Page, PageExcl, PageExt, PageOffset};
 use crate::kernel::timer::Instant;
 use crate::kernel::vfs::dentry::Dentry;
-use crate::kernel::vfs::inode::{
-    Ino, Inode, InodeDirOps, InodeFileOps, InodeInfo, InodeOps, InodeUse,
-};
+use crate::kernel::vfs::inode::{Ino, InodeInfo, InodeOps, InodeUse};
 use crate::kernel::vfs::mount::{register_filesystem, Mount, MountCreator};
 use crate::kernel::vfs::types::{DeviceId, Format, Permission};
 use crate::kernel::vfs::{SbRef, SbUse, SuperBlock, SuperBlockInfo};
@@ -56,6 +52,10 @@ impl Cluster {
         Ino::new(self.0 as _)
     }
 
+    pub fn from_ino(ino: Ino) -> Self {
+        Self(ino.as_raw() as u32)
+    }
+
     fn normalized(self) -> Self {
         Self(self.0 - 2)
     }
@@ -130,7 +130,7 @@ impl FatFs {
 }
 
 impl FatFs {
-    pub async fn create(device: DeviceId) -> KResult<(SbUse<Self>, InodeUse<dyn Inode>)> {
+    pub async fn create(device: DeviceId) -> KResult<(SbUse<Self>, InodeUse)> {
         let device = BlockDevice::get(device)?;
 
         let mut info = UninitBuffer::<Bootsector>::new();
@@ -217,18 +217,15 @@ impl<'fat> Iterator for ClusterIterator<'fat> {
     }
 }
 
-struct FileInode {
-    cluster: Cluster,
-    info: Spin<InodeInfo>,
-    sb: SbRef<FatFs>,
-    page_cache: PageCache,
-}
+struct FileInode;
 
 impl FileInode {
-    fn new(cluster: Cluster, sb: SbRef<FatFs>, size: u32) -> InodeUse<FileInode> {
-        InodeUse::new_cyclic(|weak: &Weak<FileInode>| Self {
-            cluster,
-            info: Spin::new(InodeInfo {
+    fn new(cluster: Cluster, sb: SbRef<FatFs>, size: u32) -> InodeUse {
+        InodeUse::new(
+            sb,
+            cluster.as_ino(),
+            Format::REG,
+            InodeInfo {
                 size: size as u64,
                 nlink: 1,
                 uid: 0,
@@ -237,108 +234,75 @@ impl FileInode {
                 atime: Instant::UNIX_EPOCH,
                 ctime: Instant::UNIX_EPOCH,
                 mtime: Instant::UNIX_EPOCH,
-            }),
-            sb,
-            page_cache: PageCache::new(weak.clone()),
-        })
+            },
+            Self,
+        )
     }
 }
 
 impl InodeOps for FileInode {
     type SuperBlock = FatFs;
 
-    fn ino(&self) -> Ino {
-        self.cluster.as_ino()
-    }
-
-    fn format(&self) -> Format {
-        Format::REG
-    }
-
-    fn info(&self) -> &Spin<InodeInfo> {
-        &self.info
-    }
-
-    fn super_block(&self) -> &SbRef<Self::SuperBlock> {
-        &self.sb
-    }
-
-    fn page_cache(&self) -> Option<&PageCache> {
-        Some(&self.page_cache)
-    }
-}
-
-impl InodeDirOps for FileInode {}
-impl InodeFileOps for FileInode {
-    async fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
-        self.page_cache.read(buffer, offset).await
+    async fn read(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        buffer: &mut dyn Buffer,
+        offset: usize,
+    ) -> KResult<usize> {
+        inode.get_page_cache().read(buffer, offset).await
     }
 
-    async fn read_direct(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
-        let sb = self.sb.get()?;
+    async fn read_page(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        page: &mut CachePage,
+        offset: PageOffset,
+    ) -> KResult<()> {
         let fs = &sb.backend;
         let fat = sb.backend.fat.read().await;
 
-        if offset >= self.info.lock().size as usize {
-            return Ok(0);
+        if offset >= PageOffset::from_byte_ceil(inode.info.lock().size as usize) {
+            unreachable!("read_page called with offset beyond file size");
         }
 
         let cluster_size = fs.sectors_per_cluster as usize * SECTOR_SIZE;
-        assert!(cluster_size <= 0x1000, "Cluster size is too large");
-
-        let skip_clusters = offset / cluster_size;
-        let inner_offset = offset % cluster_size;
-
-        let cluster_iter = ClusterIterator::new(fat.as_ref(), self.cluster).skip(skip_clusters);
-
-        let buffer_page = Page::alloc();
-        for cluster in cluster_iter {
-            fs.read_cluster(cluster, &buffer_page).await?;
-
-            let pg = buffer_page.lock();
-            let data = &pg.as_bytes()[inner_offset..];
-
-            let end = offset + data.len();
-            let real_end = end.min(self.info.lock().size as usize);
-            let real_size = real_end - offset;
-
-            if buffer.fill(&data[..real_size])?.should_stop() {
-                break;
-            }
+        if cluster_size != PAGE_SIZE {
+            unimplemented!("cluster size != PAGE_SIZE");
         }
 
-        Ok(buffer.wrote())
-    }
-}
+        // XXX: Ugly and inefficient O(n^2) algorithm for sequential file read.
+        let cluster = ClusterIterator::new(fat.as_ref(), Cluster::from_ino(inode.ino))
+            .skip(offset.page_count())
+            .next()
+            .ok_or(EIO)?;
 
-impl PageCacheBackendOps for FileInode {
-    async fn read_page(&self, page: &mut CachePage, offset: usize) -> KResult<usize> {
-        self.read_direct(page, offset).await
-    }
+        let page = page.get_page();
+        fs.read_cluster(cluster, &page).await?;
 
-    async fn write_page(&self, _page: &mut CachePageStream, _offset: usize) -> KResult<usize> {
-        todo!()
-    }
+        let real_len = (inode.info.lock().size as usize) - offset.byte_count();
+        if real_len < PAGE_SIZE {
+            let mut page = page.lock();
+            page.as_bytes_mut()[real_len..].fill(0);
+        }
 
-    fn size(&self) -> usize {
-        self.info.lock().size as usize
+        Ok(())
     }
 }
 
 struct DirInode {
-    cluster: Cluster,
-    info: Spin<InodeInfo>,
-    sb: SbRef<FatFs>,
-
     // TODO: Use the new PageCache...
     dir_pages: RwLock<Vec<PageExcl>>,
 }
 
 impl DirInode {
-    fn new(cluster: Cluster, sb: SbRef<FatFs>, size: u32) -> InodeUse<Self> {
-        InodeUse::new(Self {
-            cluster,
-            info: Spin::new(InodeInfo {
+    fn new(cluster: Cluster, sb: SbRef<FatFs>, size: u32) -> InodeUse {
+        InodeUse::new(
+            sb,
+            cluster.as_ino(),
+            Format::DIR,
+            InodeInfo {
                 size: size as u64,
                 nlink: 2, // '.' and '..'
                 uid: 0,
@@ -347,23 +311,23 @@ impl DirInode {
                 atime: Instant::UNIX_EPOCH,
                 ctime: Instant::UNIX_EPOCH,
                 mtime: Instant::UNIX_EPOCH,
-            }),
-            sb,
-            dir_pages: RwLock::new(Vec::new()),
-        })
+            },
+            Self {
+                dir_pages: RwLock::new(Vec::new()),
+            },
+        )
     }
 
-    async fn read_dir_pages(&self) -> KResult<()> {
+    async fn read_dir_pages(&self, sb: &SbUse<FatFs>, inode: &InodeUse) -> KResult<()> {
         let mut dir_pages = self.dir_pages.write().await;
         if !dir_pages.is_empty() {
             return Ok(());
         }
 
-        let sb = self.sb.get()?;
         let fs = &sb.backend;
         let fat = fs.fat.read().await;
 
-        let clusters = ClusterIterator::new(fat.as_ref(), self.cluster);
+        let clusters = ClusterIterator::new(fat.as_ref(), Cluster::from_ino(inode.ino));
 
         for cluster in clusters {
             let page = PageExcl::alloc();
@@ -375,7 +339,11 @@ impl DirInode {
         Ok(())
     }
 
-    async fn get_dir_pages(&self) -> KResult<impl Deref<Target = Vec<PageExcl>> + use<'_>> {
+    async fn get_dir_pages(
+        &self,
+        sb: &SbUse<FatFs>,
+        inode: &InodeUse,
+    ) -> KResult<impl Deref<Target = Vec<PageExcl>> + use<'_>> {
         {
             let dir_pages = self.dir_pages.read().await;
             if !dir_pages.is_empty() {
@@ -383,7 +351,7 @@ impl DirInode {
             }
         }
 
-        self.read_dir_pages().await?;
+        self.read_dir_pages(sb, inode).await?;
 
         if let Some(dir_pages) = self.dir_pages.try_read() {
             return Ok(dir_pages);
@@ -396,32 +364,13 @@ impl DirInode {
 impl InodeOps for DirInode {
     type SuperBlock = FatFs;
 
-    fn ino(&self) -> Ino {
-        self.cluster.as_ino()
-    }
-
-    fn format(&self) -> Format {
-        Format::DIR
-    }
-
-    fn info(&self) -> &Spin<InodeInfo> {
-        &self.info
-    }
-
-    fn super_block(&self) -> &SbRef<Self::SuperBlock> {
-        &self.sb
-    }
-
-    fn page_cache(&self) -> Option<&PageCache> {
-        None
-    }
-}
-
-impl InodeFileOps for DirInode {}
-impl InodeDirOps for DirInode {
-    async fn lookup(&self, dentry: &Arc<Dentry>) -> KResult<Option<InodeUse<dyn Inode>>> {
-        let sb = self.sb.get()?;
-        let dir_pages = self.get_dir_pages().await?;
+    async fn lookup(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        dentry: &Arc<Dentry>,
+    ) -> KResult<Option<InodeUse>> {
+        let dir_pages = self.get_dir_pages(&sb, inode).await?;
 
         let dir_data = dir_pages.iter().map(|pg| pg.as_bytes());
 
@@ -451,48 +400,47 @@ impl InodeDirOps for DirInode {
         Ok(None)
     }
 
-    fn readdir<'r, 'a: 'r, 'b: 'r>(
-        &'a self,
+    async fn readdir(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
         offset: usize,
-        callback: &'b mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
-    ) -> impl Future<Output = KResult<KResult<usize>>> + Send + 'r {
-        async move {
-            let sb = self.sb.get()?;
-            let fs = &sb.backend;
-            let dir_pages = self.get_dir_pages().await?;
+        callback: &mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
+    ) -> KResult<KResult<usize>> {
+        let fs = &sb.backend;
+        let dir_pages = self.get_dir_pages(&sb, inode).await?;
 
-            let cluster_size = fs.sectors_per_cluster as usize * SECTOR_SIZE;
+        let cluster_size = fs.sectors_per_cluster as usize * SECTOR_SIZE;
 
-            let cluster_offset = offset / cluster_size;
-            let inner_offset = offset % cluster_size;
-            let inner_raw_dirent_offset = inner_offset / core::mem::size_of::<dir::RawDirEntry>();
+        let cluster_offset = offset / cluster_size;
+        let inner_offset = offset % cluster_size;
+        let inner_raw_dirent_offset = inner_offset / core::mem::size_of::<dir::RawDirEntry>();
 
-            let dir_data = dir_pages
-                .iter()
-                .skip(cluster_offset)
-                .map(|pg| pg.as_bytes());
-
-            let raw_dirents = dir_data
-                .map(as_raw_dirents)
-                .take_while_inclusive(Result::is_ok)
-                .flatten_ok()
-                .skip(inner_raw_dirent_offset);
-
-            let mut dirents = futures::stream::iter(raw_dirents);
-
-            let mut nread = 0;
-            while let Some(result) = dirents.next_dirent().await {
-                let entry = result?;
-
-                match callback(&entry.filename, entry.cluster.as_ino()) {
-                    Err(err) => return Ok(Err(err)),
-                    Ok(true) => nread += entry.entry_offset as usize,
-                    Ok(false) => break,
-                }
-            }
+        let dir_data = dir_pages
+            .iter()
+            .skip(cluster_offset)
+            .map(|pg| pg.as_bytes());
 
-            Ok(Ok(nread))
+        let raw_dirents = dir_data
+            .map(as_raw_dirents)
+            .take_while_inclusive(Result::is_ok)
+            .flatten_ok()
+            .skip(inner_raw_dirent_offset);
+
+        let mut dirents = futures::stream::iter(raw_dirents);
+
+        let mut nread = 0;
+        while let Some(result) = dirents.next_dirent().await {
+            let entry = result?;
+
+            match callback(&entry.filename, entry.cluster.as_ino()) {
+                Err(err) => return Ok(Err(err)),
+                Ok(true) => nread += entry.entry_offset as usize,
+                Ok(false) => break,
+            }
         }
+
+        Ok(Ok(nread))
     }
 }
 

+ 68 - 87
src/fs/procfs.rs

@@ -1,30 +1,21 @@
+use alloc::sync::Arc;
+use core::sync::atomic::{AtomicU64, Ordering};
+
+use async_trait::async_trait;
+use eonix_sync::{LazyLock, RwLock};
+
+use crate::io::Buffer;
 use crate::kernel::constants::{EACCES, EISDIR, ENOTDIR};
+use crate::kernel::mem::paging::PageBuffer;
 use crate::kernel::timer::Instant;
-use crate::kernel::vfs::inode::{InodeDirOps, InodeFileOps, InodeInfo, InodeOps, InodeUse};
+use crate::kernel::vfs::dentry::Dentry;
+use crate::kernel::vfs::inode::{Ino, InodeInfo, InodeOps, InodeUse};
+use crate::kernel::vfs::mount::{dump_mounts, register_filesystem, Mount, MountCreator};
 use crate::kernel::vfs::types::{DeviceId, Format, Permission};
 use crate::kernel::vfs::{SbRef, SbUse, SuperBlock, SuperBlockInfo};
-use crate::{
-    io::Buffer,
-    kernel::{
-        mem::paging::PageBuffer,
-        vfs::{
-            dentry::Dentry,
-            inode::{Ino, Inode},
-            mount::{dump_mounts, register_filesystem, Mount, MountCreator},
-        },
-    },
-    prelude::*,
-};
-use alloc::sync::Arc;
-use async_trait::async_trait;
-use core::future::Future;
-use core::sync::atomic::{AtomicU64, Ordering};
-use eonix_sync::{LazyLock, RwLock};
+use crate::prelude::*;
 
 struct Node {
-    ino: Ino,
-    sb: SbRef<ProcFs>,
-    info: Spin<InodeInfo>,
     kind: NodeKind,
 }
 
@@ -39,38 +30,19 @@ struct FileInode {
 }
 
 struct DirInode {
-    entries: RwLock<Vec<(Arc<[u8]>, InodeUse<Node>)>>,
+    entries: RwLock<Vec<(Arc<[u8]>, InodeUse)>>,
 }
 
 impl InodeOps for Node {
     type SuperBlock = ProcFs;
 
-    fn ino(&self) -> Ino {
-        self.ino
-    }
-
-    fn format(&self) -> Format {
-        match &self.kind {
-            NodeKind::File(_) => Format::REG,
-            NodeKind::Dir(_) => Format::DIR,
-        }
-    }
-
-    fn info(&self) -> &Spin<InodeInfo> {
-        &self.info
-    }
-
-    fn super_block(&self) -> &SbRef<Self::SuperBlock> {
-        &self.sb
-    }
-
-    fn page_cache(&self) -> Option<&crate::kernel::mem::PageCache> {
-        None
-    }
-}
-
-impl InodeFileOps for Node {
-    async fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
+    async fn read(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        _: &InodeUse,
+        buffer: &mut dyn Buffer,
+        offset: usize,
+    ) -> KResult<usize> {
         let NodeKind::File(file_inode) = &self.kind else {
             return Err(EISDIR);
         };
@@ -88,10 +60,13 @@ impl InodeFileOps for Node {
 
         Ok(buffer.fill(data)?.allow_partial())
     }
-}
 
-impl InodeDirOps for Node {
-    async fn lookup(&self, dentry: &Arc<Dentry>) -> KResult<Option<InodeUse<dyn Inode>>> {
+    async fn lookup(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        _: &InodeUse,
+        dentry: &Arc<Dentry>,
+    ) -> KResult<Option<InodeUse>> {
         let NodeKind::Dir(dir) = &self.kind else {
             return Err(ENOTDIR);
         };
@@ -108,29 +83,29 @@ impl InodeDirOps for Node {
         Ok(None)
     }
 
-    fn readdir<'r, 'a: 'r, 'b: 'r>(
-        &'a self,
+    async fn readdir(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        _: &InodeUse,
         offset: usize,
-        callback: &'b mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
-    ) -> impl Future<Output = KResult<KResult<usize>>> + Send + 'r {
-        Box::pin(async move {
-            let NodeKind::Dir(dir) = &self.kind else {
-                return Err(ENOTDIR);
-            };
-
-            let entries = dir.entries.read().await;
-
-            let mut count = 0;
-            for (name, node) in entries.iter().skip(offset) {
-                match callback(name.as_ref(), node.ino) {
-                    Err(err) => return Ok(Err(err)),
-                    Ok(true) => count += 1,
-                    Ok(false) => break,
-                }
+        callback: &mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
+    ) -> KResult<KResult<usize>> {
+        let NodeKind::Dir(dir) = &self.kind else {
+            return Err(ENOTDIR);
+        };
+
+        let entries = dir.entries.read().await;
+
+        let mut count = 0;
+        for (name, node) in entries.iter().skip(offset) {
+            match callback(name.as_ref(), node.ino) {
+                Err(err) => return Ok(Err(err)),
+                Ok(true) => count += 1,
+                Ok(false) => break,
             }
+        }
 
-            Ok(Ok(count))
-        })
+        Ok(Ok(count))
     }
 }
 
@@ -139,11 +114,12 @@ impl Node {
         ino: Ino,
         sb: SbRef<ProcFs>,
         read: impl Fn(&mut PageBuffer) -> KResult<()> + Send + Sync + 'static,
-    ) -> InodeUse<Self> {
-        InodeUse::new(Self {
-            ino,
+    ) -> InodeUse {
+        InodeUse::new(
             sb,
-            info: Spin::new(InodeInfo {
+            ino,
+            Format::REG,
+            InodeInfo {
                 size: 0,
                 nlink: 1,
                 uid: 0,
@@ -152,16 +128,19 @@ impl Node {
                 atime: Instant::UNIX_EPOCH,
                 ctime: Instant::UNIX_EPOCH,
                 mtime: Instant::UNIX_EPOCH,
-            }),
-            kind: NodeKind::File(FileInode::new(Box::new(read))),
-        })
+            },
+            Self {
+                kind: NodeKind::File(FileInode::new(Box::new(read))),
+            },
+        )
     }
 
-    fn new_dir(ino: Ino, sb: SbRef<ProcFs>) -> InodeUse<Self> {
-        InodeUse::new(Self {
-            ino,
+    fn new_dir(ino: Ino, sb: SbRef<ProcFs>) -> InodeUse {
+        InodeUse::new(
             sb,
-            info: Spin::new(InodeInfo {
+            ino,
+            Format::DIR,
+            InodeInfo {
                 size: 0,
                 nlink: 1,
                 uid: 0,
@@ -170,9 +149,11 @@ impl Node {
                 atime: Instant::UNIX_EPOCH,
                 ctime: Instant::UNIX_EPOCH,
                 mtime: Instant::UNIX_EPOCH,
-            }),
-            kind: NodeKind::Dir(DirInode::new()),
-        })
+            },
+            Self {
+                kind: NodeKind::Dir(DirInode::new()),
+            },
+        )
     }
 }
 
@@ -194,7 +175,7 @@ impl DirInode {
 }
 
 pub struct ProcFs {
-    root: InodeUse<Node>,
+    root: InodeUse,
     next_ino: AtomicU64,
 }
 
@@ -240,7 +221,7 @@ where
     F: Send + Sync + Fn(&mut PageBuffer) -> KResult<()> + 'static,
 {
     let procfs = &GLOBAL_PROCFS.backend;
-    let root = &procfs.root;
+    let root = &procfs.root.get_priv::<Node>();
 
     let NodeKind::Dir(root) = &root.kind else {
         unreachable!();

+ 138 - 149
src/fs/tmpfs/dir.rs

@@ -1,72 +1,51 @@
-use core::{any::Any, future::Future};
+use alloc::sync::Arc;
+use alloc::vec;
+use alloc::vec::Vec;
 
-use alloc::{boxed::Box, sync::Arc, vec, vec::Vec};
 use eonix_log::println_warn;
-use eonix_sync::{LazyLock, RwLock, Spin};
-
-use crate::{
-    kernel::{
-        constants::{EEXIST, EINVAL, EISDIR, ENOENT, ENOSYS, ENOTDIR},
-        mem::PageCache,
-        timer::Instant,
-        vfs::{
-            dentry::{dcache, Dentry},
-            inode::{
-                Ino, Inode, InodeDirOps, InodeFileOps, InodeInfo, InodeOps, InodeUse, RenameData,
-            },
-            types::{DeviceId, Format, Mode, Permission},
-            SbRef,
-        },
-    },
-    prelude::KResult,
-};
-
-use super::{
-    file::{DeviceInode, FileInode, SymlinkInode},
-    TmpFs,
-};
+use eonix_sync::{LazyLock, RwLock};
+
+use super::file::{DeviceInode, FileInode, SymlinkInode};
+use super::TmpFs;
+use crate::kernel::constants::{EEXIST, EINVAL, EISDIR, ENOENT, ENOSYS, ENOTDIR};
+use crate::kernel::timer::Instant;
+use crate::kernel::vfs::dentry::{dcache, Dentry};
+use crate::kernel::vfs::inode::{Ino, InodeInfo, InodeOps, InodeUse, RenameData};
+use crate::kernel::vfs::types::{DeviceId, Format, Mode, Permission};
+use crate::kernel::vfs::{SbRef, SbUse};
+use crate::prelude::KResult;
 
 pub struct DirectoryInode {
-    sb: SbRef<TmpFs>,
-    ino: Ino,
-    info: Spin<InodeInfo>,
     entries: RwLock<Vec<(Arc<[u8]>, Ino)>>,
 }
 
-impl InodeOps for DirectoryInode {
-    type SuperBlock = TmpFs;
-
-    fn ino(&self) -> Ino {
-        self.ino
-    }
+fn link(dir: &InodeUse, entries: &mut Vec<(Arc<[u8]>, Ino)>, name: Arc<[u8]>, file: &InodeUse) {
+    let mut dir_info = dir.info.lock();
+    let mut file_info = file.info.lock();
 
-    fn format(&self) -> Format {
-        Format::DIR
-    }
+    let now = Instant::now();
 
-    fn info(&self) -> &Spin<InodeInfo> {
-        &self.info
-    }
+    file_info.nlink += 1;
+    file_info.ctime = now;
 
-    fn super_block(&self) -> &SbRef<Self::SuperBlock> {
-        &self.sb
-    }
+    dir_info.size += 1;
+    dir_info.mtime = now;
+    dir_info.ctime = now;
 
-    fn page_cache(&self) -> Option<&PageCache> {
-        None
-    }
+    entries.push((name, file.ino));
 }
 
 impl DirectoryInode {
-    pub fn new(ino: Ino, sb: SbRef<TmpFs>, perm: Permission) -> InodeUse<Self> {
+    pub fn new(ino: Ino, sb: SbRef<TmpFs>, perm: Permission) -> InodeUse {
         static DOT: LazyLock<Arc<[u8]>> = LazyLock::new(|| Arc::from(b".".as_slice()));
 
         let now = Instant::now();
 
-        InodeUse::new(Self {
+        InodeUse::new(
             sb,
             ino,
-            info: Spin::new(InodeInfo {
+            Format::DIR,
+            InodeInfo {
                 size: 1,
                 nlink: 1, // link from `.` to itself
                 perm,
@@ -75,35 +54,16 @@ impl DirectoryInode {
                 atime: now,
                 uid: 0,
                 gid: 0,
-            }),
-            entries: RwLock::new(vec![(DOT.clone(), ino)]),
-        })
-    }
-
-    fn link(
-        &self,
-        entries: &mut Vec<(Arc<[u8]>, Ino)>,
-        name: Arc<[u8]>,
-        file: &InodeUse<dyn Inode>,
-    ) {
-        let mut self_info = self.info.lock();
-        let mut file_info = file.info().lock();
-
-        let now = Instant::now();
-
-        file_info.nlink += 1;
-        file_info.ctime = now;
-
-        self_info.size += 1;
-        self_info.mtime = now;
-        self_info.ctime = now;
-
-        entries.push((name, file.ino()));
+            },
+            Self {
+                entries: RwLock::new(vec![(DOT.clone(), ino)]),
+            },
+        )
     }
 
     fn do_unlink(
         &self,
-        file: &InodeUse<dyn Inode>,
+        file: &InodeUse,
         filename: &[u8],
         entries: &mut Vec<(Arc<[u8]>, Ino)>,
         now: Instant,
@@ -112,11 +72,11 @@ impl DirectoryInode {
         file_info: &mut InodeInfo,
     ) -> KResult<()> {
         // SAFETY: `file_lock` has done the synchronization
-        if file.format() == Format::DIR {
+        if file.format == Format::DIR {
             return Err(EISDIR);
         }
 
-        let file_ino = file.ino();
+        let file_ino = file.ino;
         entries.retain(|(name, ino)| *ino != file_ino || name.as_ref() != filename);
 
         if decrease_size {
@@ -138,87 +98,114 @@ impl DirectoryInode {
     }
 }
 
-impl InodeDirOps for DirectoryInode {
-    fn readdir<'r, 'a: 'r, 'b: 'r>(
-        &'a self,
+impl InodeOps for DirectoryInode {
+    type SuperBlock = TmpFs;
+
+    async fn readdir(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        _: &InodeUse,
         offset: usize,
-        for_each_entry: &'b mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
-    ) -> impl Future<Output = KResult<KResult<usize>>> + Send + 'r {
-        Box::pin(async move {
-            let _sb = self.sb.get()?;
-            let entries = self.entries.read().await;
-
-            let mut count = 0;
-            for entry in entries.iter().skip(offset) {
-                match for_each_entry(&entry.0, entry.1) {
-                    Err(err) => return Ok(Err(err)),
-                    Ok(false) => break,
-                    Ok(true) => count += 1,
-                }
+        for_each_entry: &mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
+    ) -> KResult<KResult<usize>> {
+        let _sb = sb;
+        let entries = self.entries.read().await;
+
+        let mut count = 0;
+        for entry in entries.iter().skip(offset) {
+            match for_each_entry(&entry.0, entry.1) {
+                Err(err) => return Ok(Err(err)),
+                Ok(false) => break,
+                Ok(true) => count += 1,
             }
+        }
 
-            Ok(Ok(count))
-        })
+        Ok(Ok(count))
     }
 
-    async fn create(&self, at: &Arc<Dentry>, perm: Permission) -> KResult<()> {
-        let sb = self.sb.get()?;
+    async fn create(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        at: &Arc<Dentry>,
+        perm: Permission,
+    ) -> KResult<()> {
         let mut entries = self.entries.write().await;
 
         let ino = sb.backend.assign_ino();
-        let file: InodeUse<dyn Inode> = FileInode::new(ino, self.sb.clone(), 0, perm);
+        let file = FileInode::new(ino, sb.get_ref(), 0, perm);
 
-        self.link(&mut entries, at.get_name(), &file);
+        link(inode, &mut entries, at.get_name(), &file);
         at.fill(file);
 
         Ok(())
     }
 
-    async fn mknod(&self, at: &Dentry, mode: Mode, dev: DeviceId) -> KResult<()> {
+    async fn mknod(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        at: &Dentry,
+        mode: Mode,
+        dev: DeviceId,
+    ) -> KResult<()> {
         if !mode.is_chr() && !mode.is_blk() {
             return Err(EINVAL);
         }
 
-        let sb = self.sb.get()?;
         let mut entries = self.entries.write().await;
 
         let ino = sb.backend.assign_ino();
-        let file: InodeUse<dyn Inode> = DeviceInode::new(ino, self.sb.clone(), mode, dev);
+        let file = DeviceInode::new(ino, sb.get_ref(), mode, dev);
 
-        self.link(&mut entries, at.get_name(), &file);
+        link(inode, &mut entries, at.get_name(), &file);
         at.fill(file);
 
         Ok(())
     }
 
-    async fn symlink(&self, at: &Arc<Dentry>, target: &[u8]) -> KResult<()> {
-        let sb = self.sb.get()?;
+    async fn symlink(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        at: &Arc<Dentry>,
+        target: &[u8],
+    ) -> KResult<()> {
         let mut entries = self.entries.write().await;
 
         let ino = sb.backend.assign_ino();
-        let file: InodeUse<dyn Inode> = SymlinkInode::new(ino, self.sb.clone(), target.into());
+        let file = SymlinkInode::new(ino, sb.get_ref(), target.into());
 
-        self.link(&mut entries, at.get_name(), &file);
+        link(inode, &mut entries, at.get_name(), &file);
         at.fill(file);
 
         Ok(())
     }
 
-    async fn mkdir(&self, at: &Dentry, perm: Permission) -> KResult<()> {
-        let sb = self.sb.get()?;
+    async fn mkdir(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        at: &Dentry,
+        perm: Permission,
+    ) -> KResult<()> {
         let mut entries = self.entries.write().await;
 
         let ino = sb.backend.assign_ino();
-        let new_dir: InodeUse<dyn Inode> = DirectoryInode::new(ino, self.sb.clone(), perm);
+        let new_dir = DirectoryInode::new(ino, sb.get_ref(), perm);
 
-        self.link(&mut entries, at.get_name(), &new_dir);
+        link(inode, &mut entries, at.get_name(), &new_dir);
         at.fill(new_dir);
 
         Ok(())
     }
 
-    async fn unlink(&self, at: &Arc<Dentry>) -> KResult<()> {
-        let _sb = self.sb.get()?;
+    async fn unlink(
+        &self,
+        _sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        at: &Arc<Dentry>,
+    ) -> KResult<()> {
         let mut entries = self.entries.write().await;
 
         let file = at.get_inode()?;
@@ -230,8 +217,8 @@ impl InodeDirOps for DirectoryInode {
             &mut entries,
             Instant::now(),
             true,
-            &mut self.info.lock(),
-            &mut file.info().lock(),
+            &mut inode.info.lock(),
+            &mut file.info.lock(),
         )?;
 
         // Remove the dentry from the dentry cache immediately
@@ -241,8 +228,12 @@ impl InodeDirOps for DirectoryInode {
         Ok(())
     }
 
-    async fn rename(&self, rename_data: RenameData<'_, '_>) -> KResult<()> {
-        let sb = self.sb.get()?;
+    async fn rename(
+        &self,
+        sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        rename_data: RenameData<'_, '_>,
+    ) -> KResult<()> {
         let _rename_lock = sb.backend.rename_lock.lock().await;
         let mut self_entries = self.entries.write().await;
 
@@ -266,11 +257,11 @@ impl InodeDirOps for DirectoryInode {
             return Err(EEXIST);
         }
 
-        if new_parent.as_raw() == &raw const *self {
+        if inode == &new_parent {
             // Same directory rename
             // Remove from old location and add to new location
-            let old_ino = old_file.ino();
-            let new_ino = new_file.as_ref().map(|f| f.ino());
+            let old_ino = old_file.ino;
+            let new_ino = new_file.as_ref().map(|f| f.ino);
             let old_name = old_dentry.get_name();
             let new_name = new_dentry.get_name();
 
@@ -299,7 +290,7 @@ impl InodeDirOps for DirectoryInode {
                 // Replace existing file (i.e. rename the old and unlink the new)
                 let new_file = new_file.unwrap();
 
-                match (new_file.format(), old_file.format()) {
+                match (new_file.format, old_file.format) {
                     (Format::DIR, _) => return Err(EISDIR),
                     (_, Format::DIR) => return Err(ENOTDIR),
                     _ => {}
@@ -307,12 +298,12 @@ impl InodeDirOps for DirectoryInode {
 
                 self_entries.remove(new_idx);
 
-                self.info.lock().size -= 1;
+                inode.info.lock().size -= 1;
 
                 // The last reference to the inode is held by some dentry
                 // and will be released when the dentry is released
 
-                let mut new_info = new_file.info().lock();
+                let mut new_info = new_file.info.lock();
 
                 new_info.nlink -= 1;
                 new_info.mtime = now;
@@ -322,24 +313,21 @@ impl InodeDirOps for DirectoryInode {
             let (name, _) = &mut self_entries[old_ent_idx];
             *name = new_dentry.get_name();
 
-            let mut self_info = self.info.lock();
+            let mut self_info = inode.info.lock();
             self_info.mtime = now;
             self_info.ctime = now;
         } else {
             // Cross-directory rename - handle similar to same directory case
 
             // Get new parent directory
-            let new_parent_inode = new_dentry.parent().get_inode()?;
-            assert_eq!(new_parent_inode.format(), Format::DIR);
-
-            let new_parent = (&new_parent_inode as &dyn Any)
-                .downcast_ref::<DirectoryInode>()
-                .expect("new parent must be a DirectoryInode");
+            let new_parent = new_dentry.parent().get_inode()?;
+            assert_eq!(new_parent.format, Format::DIR);
 
-            let mut new_entries = new_parent.entries.write().await;
+            let new_parent_priv = new_parent.get_priv::<DirectoryInode>();
+            let mut new_entries = new_parent_priv.entries.write().await;
 
-            let old_ino = old_file.ino();
-            let new_ino = new_file.as_ref().map(|f| f.ino());
+            let old_ino = old_file.ino;
+            let new_ino = new_file.as_ref().map(|f| f.ino);
             let old_name = old_dentry.get_name();
             let new_name = new_dentry.get_name();
 
@@ -361,26 +349,28 @@ impl InodeDirOps for DirectoryInode {
                 // Replace existing file (i.e. move the old and unlink the new)
                 let new_file = new_file.unwrap();
 
-                match (old_file.format(), new_file.format()) {
+                match (old_file.format, new_file.format) {
                     (Format::DIR, Format::DIR) => {}
                     (Format::DIR, _) => return Err(ENOTDIR),
                     (_, _) => {}
                 }
 
                 // Unlink the old file that was replaced
-                new_parent.do_unlink(
+                new_parent_priv.do_unlink(
                     &new_file,
                     &new_name,
                     &mut new_entries,
                     now,
                     false,
                     &mut new_parent.info.lock(),
-                    &mut new_file.info().lock(),
+                    &mut new_file.info.lock(),
                 )?;
             } else {
-                new_parent.info.lock().size += 1;
-                new_parent.info.lock().mtime = now;
-                new_parent.info.lock().ctime = now;
+                let mut info = new_parent.info.lock();
+
+                info.size += 1;
+                info.mtime = now;
+                info.ctime = now;
             }
 
             // Remove from old directory
@@ -389,7 +379,7 @@ impl InodeDirOps for DirectoryInode {
             // Add new entry
             new_entries.push((new_name, old_ino));
 
-            let mut self_info = self.info.lock();
+            let mut self_info = inode.info.lock();
             self_info.size -= 1;
             self_info.mtime = now;
             self_info.ctime = now;
@@ -398,17 +388,16 @@ impl InodeDirOps for DirectoryInode {
         dcache::d_exchange(old_dentry, new_dentry).await;
         Ok(())
     }
-}
-
-impl InodeFileOps for DirectoryInode {
-    async fn chmod(&self, perm: Permission) -> KResult<()> {
-        let _sb = self.sb.get()?;
 
-        {
-            let mut info = self.info.lock();
-            info.perm = perm;
-            info.ctime = Instant::now();
-        }
+    async fn chmod(
+        &self,
+        _sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        perm: Permission,
+    ) -> KResult<()> {
+        let mut info = inode.info.lock();
+        info.perm = perm;
+        info.ctime = Instant::now();
 
         Ok(())
     }

+ 152 - 179
src/fs/tmpfs/file.rs

@@ -1,39 +1,26 @@
+use alloc::collections::btree_map::BTreeMap;
 use alloc::sync::Arc;
-use eonix_mm::paging::PAGE_SIZE;
-use eonix_sync::{RwLock, Spin};
-
-use crate::{
-    io::{Buffer, Stream},
-    kernel::{
-        mem::{CachePage, CachePageStream, PageCache, PageCacheBackendOps},
-        timer::Instant,
-        vfs::{
-            inode::{Ino, InodeDirOps, InodeFileOps, InodeInfo, InodeOps, InodeUse, WriteOffset},
-            types::{DeviceId, Format, Mode, Permission},
-            SbRef,
-        },
-    },
-    prelude::KResult,
-};
 
 use super::TmpFs;
+use crate::io::{Buffer, Stream};
+use crate::kernel::mem::{CachePage, PageCache, PageOffset};
+use crate::kernel::timer::Instant;
+use crate::kernel::vfs::inode::{Ino, InodeInfo, InodeOps, InodeUse, WriteOffset};
+use crate::kernel::vfs::types::{DeviceId, Format, Mode, Permission};
+use crate::kernel::vfs::{SbRef, SbUse};
+use crate::prelude::KResult;
 
-pub struct FileInode {
-    sb: SbRef<TmpFs>,
-    ino: Ino,
-    info: Spin<InodeInfo>,
-    rwsem: RwLock<()>,
-    pages: PageCache,
-}
+pub struct FileInode;
 
 impl FileInode {
-    pub fn new(ino: Ino, sb: SbRef<TmpFs>, size: usize, perm: Permission) -> InodeUse<Self> {
+    pub fn new(ino: Ino, sb: SbRef<TmpFs>, size: usize, perm: Permission) -> InodeUse {
         let now = Instant::now();
 
-        InodeUse::new_cyclic(|weak| Self {
+        InodeUse::new(
             sb,
             ino,
-            info: Spin::new(InodeInfo {
+            Format::REG,
+            InodeInfo {
                 size: size as _,
                 nlink: 1,
                 uid: 0,
@@ -42,60 +29,34 @@ impl FileInode {
                 atime: now,
                 ctime: now,
                 mtime: now,
-            }),
-            rwsem: RwLock::new(()),
-            pages: PageCache::new(weak.clone() as _),
-        })
-    }
-}
-
-impl PageCacheBackendOps for FileInode {
-    async fn read_page(&self, _cache_page: &mut CachePage, _offset: usize) -> KResult<usize> {
-        Ok(PAGE_SIZE)
-    }
-
-    async fn write_page(&self, _page: &mut CachePageStream, _offset: usize) -> KResult<usize> {
-        Ok(PAGE_SIZE)
-    }
-
-    fn size(&self) -> usize {
-        self.info.lock().size as usize
+            },
+            Self,
+        )
     }
 }
 
 impl InodeOps for FileInode {
     type SuperBlock = TmpFs;
 
-    fn ino(&self) -> Ino {
-        self.ino
-    }
-
-    fn format(&self) -> Format {
-        Format::REG
-    }
-
-    fn info(&self) -> &Spin<InodeInfo> {
-        &self.info
-    }
-
-    fn super_block(&self) -> &SbRef<Self::SuperBlock> {
-        &self.sb
-    }
-
-    fn page_cache(&self) -> Option<&PageCache> {
-        Some(&self.pages)
-    }
-}
-
-impl InodeDirOps for FileInode {}
-impl InodeFileOps for FileInode {
-    async fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
-        let _lock = self.rwsem.read().await;
-        self.pages.read(buffer, offset).await
+    async fn read(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        buffer: &mut dyn Buffer,
+        offset: usize,
+    ) -> KResult<usize> {
+        let _lock = inode.rwsem.read().await;
+        inode.get_page_cache().read(buffer, offset).await
     }
 
-    async fn write(&self, stream: &mut dyn Stream, offset: WriteOffset<'_>) -> KResult<usize> {
-        let _lock = self.rwsem.write().await;
+    async fn write(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        stream: &mut dyn Stream,
+        offset: WriteOffset<'_>,
+    ) -> KResult<usize> {
+        let _lock = inode.rwsem.write().await;
 
         let mut store_new_end = None;
         let offset = match offset {
@@ -104,74 +65,131 @@ impl InodeFileOps for FileInode {
                 store_new_end = Some(end);
 
                 // `info.size` won't change since we are holding the write lock.
-                self.info.lock().size as usize
+                inode.info.lock().size as usize
             }
         };
 
-        let wrote = self.pages.write(stream, offset).await?;
+        let page_cache = inode.get_page_cache();
+
+        if Arc::strong_count(&page_cache) == 1 {
+            // XXX: A temporary workaround here. Change this ASAP...
+            // Prevent the page cache from being dropped during the write.
+            let _ = Arc::into_raw(page_cache.clone());
+        }
+
+        let wrote = page_cache.write(stream, offset).await?;
         let cursor_end = offset + wrote;
 
         if let Some(store_end) = store_new_end {
             *store_end = cursor_end;
         }
 
-        {
-            let now = Instant::now();
-            let mut info = self.info.lock();
-            info.mtime = now;
-            info.ctime = now;
-            info.size = info.size.max(cursor_end as u64);
-        }
-
         Ok(wrote)
     }
 
-    async fn truncate(&self, length: usize) -> KResult<()> {
-        let _lock = self.rwsem.write().await;
+    async fn truncate(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        length: usize,
+    ) -> KResult<()> {
+        let _lock = inode.rwsem.write().await;
 
-        self.pages.resize(length).await?;
+        let now = Instant::now();
+        let mut info = inode.info.lock();
+        info.mtime = now;
+        info.ctime = now;
+        info.size = length as u64;
 
-        {
-            let now = Instant::now();
-            let mut info = self.info.lock();
-            info.mtime = now;
-            info.ctime = now;
-            info.size = length as u64;
-        }
+        Ok(())
+    }
+
+    async fn chmod(
+        &self,
+        _sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        perm: Permission,
+    ) -> KResult<()> {
+        let mut info = inode.info.lock();
+
+        info.perm = perm;
+        info.ctime = Instant::now();
+
+        Ok(())
+    }
 
+    async fn read_page(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        _: &InodeUse,
+        page: &mut CachePage,
+        _: PageOffset,
+    ) -> KResult<()> {
+        page.as_bytes_mut().fill(0);
         Ok(())
     }
 
-    async fn chmod(&self, perm: Permission) -> KResult<()> {
-        let _sb = self.sb.get()?;
+    async fn write_page(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        _: &InodeUse,
+        _: &mut CachePage,
+        _: PageOffset,
+    ) -> KResult<()> {
+        // XXX: actually we should refuse to do the writeback.
+        //      think of a way to inform that of the page cache.
+        Ok(())
+    }
 
-        {
-            let mut info = self.info.lock();
+    async fn write_begin<'a>(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        _: &InodeUse,
+        page_cache: &PageCache,
+        pages: &'a mut BTreeMap<PageOffset, CachePage>,
+        offset: usize,
+        _: usize,
+    ) -> KResult<&'a mut CachePage> {
+        // TODO: Remove dependency on `page_cache`.
+        page_cache
+            .get_page_locked(pages, PageOffset::from_byte_floor(offset))
+            .await
+    }
 
-            info.perm = perm;
-            info.ctime = Instant::now();
-        }
+    async fn write_end(
+        &self,
+        _: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        _: &PageCache,
+        _: &mut BTreeMap<PageOffset, CachePage>,
+        offset: usize,
+        _: usize,
+        copied: usize,
+    ) -> KResult<()> {
+        let now = Instant::now();
+        let mut info = inode.info.lock();
+        info.mtime = now;
+        info.ctime = now;
+        info.size = info.size.max((offset + copied) as u64);
 
         Ok(())
     }
 }
 
 pub struct DeviceInode {
-    sb: SbRef<TmpFs>,
-    ino: Ino,
-    info: Spin<InodeInfo>,
     is_block: bool,
     devid: DeviceId,
 }
 
 impl DeviceInode {
-    pub fn new(ino: Ino, sb: SbRef<TmpFs>, mode: Mode, devid: DeviceId) -> InodeUse<Self> {
+    pub fn new(ino: Ino, sb: SbRef<TmpFs>, mode: Mode, devid: DeviceId) -> InodeUse {
         let now = Instant::now();
 
-        InodeUse::new(Self {
+        InodeUse::new(
             sb,
             ino,
-            info: Spin::new(InodeInfo {
+            mode.format(),
+            InodeInfo {
                 size: 0,
                 nlink: 1,
                 uid: 0,
@@ -180,76 +198,49 @@ impl DeviceInode {
                 atime: now,
                 ctime: now,
                 mtime: now,
-            }),
-            is_block: mode.format() == Format::BLK,
-            devid,
-        })
+            },
+            Self {
+                is_block: mode.format() == Format::BLK,
+                devid,
+            },
+        )
     }
 }
 
 impl InodeOps for DeviceInode {
     type SuperBlock = TmpFs;
 
-    fn ino(&self) -> Ino {
-        self.ino
-    }
-
-    fn format(&self) -> Format {
-        if self.is_block {
-            Format::BLK
-        } else {
-            Format::CHR
-        }
-    }
-
-    fn info(&self) -> &Spin<InodeInfo> {
-        &self.info
-    }
-
-    fn super_block(&self) -> &SbRef<Self::SuperBlock> {
-        &self.sb
-    }
-
-    fn page_cache(&self) -> Option<&PageCache> {
-        None
-    }
-}
-
-impl InodeDirOps for DeviceInode {}
-impl InodeFileOps for DeviceInode {
-    async fn chmod(&self, perm: Permission) -> KResult<()> {
-        let _sb = self.sb.get()?;
-
-        {
-            let mut info = self.info.lock();
-
-            info.perm = perm;
-            info.ctime = Instant::now();
-        }
+    async fn chmod(
+        &self,
+        _sb: SbUse<Self::SuperBlock>,
+        inode: &InodeUse,
+        perm: Permission,
+    ) -> KResult<()> {
+        let mut info = inode.info.lock();
+        info.perm = perm;
+        info.ctime = Instant::now();
 
         Ok(())
     }
 
-    fn devid(&self) -> KResult<DeviceId> {
+    fn devid(&self, _: SbUse<Self::SuperBlock>, _: &InodeUse) -> KResult<DeviceId> {
         Ok(self.devid)
     }
 }
 
 pub struct SymlinkInode {
-    sb: SbRef<TmpFs>,
-    ino: Ino,
-    info: Spin<InodeInfo>,
     target: Arc<[u8]>,
 }
 
 impl SymlinkInode {
-    pub fn new(ino: Ino, sb: SbRef<TmpFs>, target: Arc<[u8]>) -> InodeUse<Self> {
+    pub fn new(ino: Ino, sb: SbRef<TmpFs>, target: Arc<[u8]>) -> InodeUse {
         let now = Instant::now();
 
-        InodeUse::new(Self {
+        InodeUse::new(
             sb,
             ino,
-            info: Spin::new(InodeInfo {
+            Format::LNK,
+            InodeInfo {
                 size: target.len() as _,
                 nlink: 1,
                 uid: 0,
@@ -258,39 +249,21 @@ impl SymlinkInode {
                 atime: now,
                 ctime: now,
                 mtime: now,
-            }),
-            target,
-        })
+            },
+            Self { target },
+        )
     }
 }
 
-impl InodeDirOps for SymlinkInode {}
 impl InodeOps for SymlinkInode {
     type SuperBlock = TmpFs;
 
-    fn ino(&self) -> Ino {
-        self.ino
-    }
-
-    fn format(&self) -> Format {
-        Format::LNK
-    }
-
-    fn info(&self) -> &Spin<InodeInfo> {
-        &self.info
-    }
-
-    fn super_block(&self) -> &SbRef<Self::SuperBlock> {
-        &self.sb
-    }
-
-    fn page_cache(&self) -> Option<&PageCache> {
-        None
-    }
-}
-
-impl InodeFileOps for SymlinkInode {
-    async fn readlink(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
+    async fn readlink(
+        &self,
+        _sb: SbUse<Self::SuperBlock>,
+        _inode: &InodeUse,
+        buffer: &mut dyn Buffer,
+    ) -> KResult<usize> {
         buffer
             .fill(self.target.as_ref())
             .map(|result| result.allow_partial())

+ 10 - 13
src/fs/tmpfs/mod.rs

@@ -1,23 +1,20 @@
 mod dir;
 mod file;
 
-use crate::kernel::vfs::inode::{Ino, InodeUse};
-use crate::kernel::vfs::types::{DeviceId, Permission};
-use crate::kernel::vfs::{SbRef, SbUse, SuperBlock, SuperBlockInfo};
-use crate::{
-    kernel::vfs::{
-        dentry::Dentry,
-        mount::{register_filesystem, Mount, MountCreator},
-    },
-    prelude::*,
-};
 use alloc::sync::Arc;
+use core::sync::atomic::{AtomicU64, Ordering};
+
 use async_trait::async_trait;
-use core::sync::atomic::AtomicU64;
-use core::sync::atomic::Ordering;
 use dir::DirectoryInode;
 use eonix_sync::Mutex;
 
+use crate::kernel::vfs::dentry::Dentry;
+use crate::kernel::vfs::inode::{Ino, InodeUse};
+use crate::kernel::vfs::mount::{register_filesystem, Mount, MountCreator};
+use crate::kernel::vfs::types::{DeviceId, Permission};
+use crate::kernel::vfs::{SbRef, SbUse, SuperBlock, SuperBlockInfo};
+use crate::prelude::*;
+
 pub struct TmpFs {
     next_ino: AtomicU64,
     rename_lock: Mutex<()>,
@@ -30,7 +27,7 @@ impl TmpFs {
         Ino::new(self.next_ino.fetch_add(1, Ordering::Relaxed))
     }
 
-    fn create() -> KResult<(SbUse<TmpFs>, InodeUse<DirectoryInode>)> {
+    fn create() -> KResult<(SbUse<TmpFs>, InodeUse)> {
         let tmpfs = SbUse::new(
             SuperBlockInfo {
                 io_blksize: 4096,

+ 1 - 1
src/kernel/mem.rs

@@ -12,5 +12,5 @@ pub use access::PhysAccess;
 pub(self) use mm_area::MMArea;
 pub use mm_list::{handle_kernel_page_fault, FileMapping, MMList, Mapping, Permission};
 pub use page_alloc::{GlobalPageAlloc, RawPage};
-pub use page_cache::{CachePage, CachePageStream, PageCache, PageCacheBackendOps};
+pub use page_cache::{CachePage, PageCache, PageOffset};
 pub use paging::{Page, PageBuffer, PageExcl, PageExt};

+ 43 - 54
src/kernel/mem/mm_area.rs

@@ -4,12 +4,12 @@ use core::cmp;
 
 use eonix_mm::address::{AddrOps as _, VAddr, VRange};
 use eonix_mm::page_table::{PageAttribute, RawAttribute, PTE};
-use eonix_mm::paging::{PAGE_SIZE, PFN};
+use eonix_mm::paging::PFN;
 
 use super::mm_list::EMPTY_PAGE;
 use super::{Mapping, Page, Permission};
-use crate::kernel::constants::EINVAL;
-use crate::kernel::mem::{PageExcl, PageExt};
+use crate::kernel::mem::page_cache::PageOffset;
+use crate::kernel::mem::{CachePage, PageExcl, PageExt};
 use crate::prelude::KResult;
 
 #[derive(Debug)]
@@ -141,59 +141,48 @@ impl MMArea {
 
         assert!(offset < file_mapping.length, "Offset out of range");
 
-        let Some(page_cache) = file_mapping.file.page_cache() else {
-            panic!("Mapping file should have pagecache");
+        let file_offset = file_mapping.offset + offset;
+
+        let map_page = |page: &Page, cache_page: &CachePage| {
+            if !self.permission.write {
+                assert!(!write, "Write fault on read-only mapping");
+
+                *pfn = page.clone().into_raw();
+                return;
+            }
+
+            if self.is_shared {
+                // We don't process dirty flags in write faults.
+                // Simply assume that page will eventually be dirtied.
+                // So here we can set the dirty flag now.
+                cache_page.set_dirty(true);
+                attr.insert(PageAttribute::WRITE);
+                *pfn = page.clone().into_raw();
+                return;
+            }
+
+            if !write {
+                // Delay the copy-on-write until write fault happens.
+                attr.insert(PageAttribute::COPY_ON_WRITE);
+                *pfn = page.clone().into_raw();
+                return;
+            }
+
+            // XXX: Change this. Let's handle mapped pages before CoW pages.
+            // Nah, we are writing to a mapped private mapping...
+            let mut new_page = PageExcl::zeroed();
+            new_page
+                .as_bytes_mut()
+                .copy_from_slice(page.lock().as_bytes());
+
+            attr.insert(PageAttribute::WRITE);
+            *pfn = new_page.into_page().into_raw();
         };
 
-        let file_offset = file_mapping.offset + offset;
-        let cnt_to_read = (file_mapping.length - offset).min(0x1000);
-
-        page_cache
-            .with_page(file_offset, |page, cache_page| {
-                // Non-write faults: we find page in pagecache and do mapping
-                // Write fault: we need to care about shared or private mapping.
-                if !write {
-                    // Bss is embarrassing in pagecache!
-                    // We have to assume cnt_to_read < PAGE_SIZE all bss
-                    if cnt_to_read < PAGE_SIZE {
-                        let mut new_page = PageExcl::zeroed();
-
-                        new_page.as_bytes_mut()[..cnt_to_read]
-                            .copy_from_slice(&page.lock().as_bytes()[..cnt_to_read]);
-
-                        *pfn = new_page.into_page().into_raw();
-                    } else {
-                        *pfn = page.clone().into_raw();
-                    }
-
-                    if self.permission.write {
-                        if self.is_shared {
-                            // The page may will not be written,
-                            // But we simply assume page will be dirty
-                            cache_page.set_dirty();
-                            attr.insert(PageAttribute::WRITE);
-                        } else {
-                            attr.insert(PageAttribute::COPY_ON_WRITE);
-                        }
-                    }
-                } else {
-                    if self.is_shared {
-                        cache_page.set_dirty();
-                        *pfn = page.clone().into_raw();
-                    } else {
-                        let mut new_page = PageExcl::zeroed();
-
-                        new_page.as_bytes_mut()[..cnt_to_read]
-                            .copy_from_slice(&page.lock().as_bytes()[..cnt_to_read]);
-
-                        *pfn = new_page.into_page().into_raw();
-                    }
-
-                    attr.insert(PageAttribute::WRITE);
-                }
-            })
-            .await?
-            .ok_or(EINVAL)?;
+        file_mapping
+            .page_cache
+            .with_page(PageOffset::from_byte_floor(file_offset), map_page)
+            .await?;
 
         attr.insert(PageAttribute::PRESENT);
         attr.remove(PageAttribute::MAPPED);

+ 9 - 6
src/kernel/mem/mm_list/mapping.rs

@@ -1,9 +1,12 @@
-use crate::kernel::vfs::inode::{Inode, InodeUse};
+use alloc::sync::Arc;
+
 use eonix_mm::paging::PAGE_SIZE;
 
+use crate::kernel::mem::PageCache;
+
 #[derive(Debug, Clone)]
 pub struct FileMapping {
-    pub file: InodeUse<dyn Inode>,
+    pub page_cache: Arc<PageCache>,
     /// Offset in the file, aligned to 4KB boundary.
     pub offset: usize,
     /// Length of the mapping. Exceeding part will be zeroed.
@@ -19,10 +22,10 @@ pub enum Mapping {
 }
 
 impl FileMapping {
-    pub fn new(file: InodeUse<dyn Inode>, offset: usize, length: usize) -> Self {
+    pub fn new(page_cache: Arc<PageCache>, offset: usize, length: usize) -> Self {
         assert_eq!(offset & (PAGE_SIZE - 1), 0);
         Self {
-            file,
+            page_cache,
             offset,
             length,
         }
@@ -30,10 +33,10 @@ impl FileMapping {
 
     pub fn offset(&self, offset: usize) -> Self {
         if self.length <= offset {
-            Self::new(self.file.clone(), self.offset + self.length, 0)
+            Self::new(self.page_cache.clone(), self.offset + self.length, 0)
         } else {
             Self::new(
-                self.file.clone(),
+                self.page_cache.clone(),
                 self.offset + offset,
                 self.length - offset,
             )

+ 3 - 2
src/kernel/mem/mm_list/page_fault.rs

@@ -1,11 +1,12 @@
-use super::{MMList, VAddr};
-use crate::kernel::task::Thread;
 use eonix_hal::mm::flush_tlb;
 use eonix_hal::traits::fault::PageFaultErrorCode;
 use eonix_mm::address::{Addr as _, AddrOps as _, VRange};
 use eonix_mm::paging::PAGE_SIZE;
 use posix_types::signal::Signal;
 
+use super::{MMList, VAddr};
+use crate::kernel::task::Thread;
+
 #[repr(C)]
 struct FixEntry {
     start: u64,

+ 2 - 2
src/kernel/mem/page_alloc.rs

@@ -4,9 +4,9 @@ mod zones;
 use core::sync::atomic::Ordering;
 
 use buddy_allocator::BuddyAllocator;
-use eonix_mm::address::{AddrOps as _, PRange};
+use eonix_mm::address::PRange;
 use eonix_mm::paging::{
-    GlobalPageAlloc as GlobalPageAllocTrait, PageAlloc, PageList, PageListSized as _, PFN,
+    GlobalPageAlloc as GlobalPageAllocTrait, PageAlloc, PageList, PageListSized as _,
 };
 use eonix_preempt::PreemptGuard;
 use eonix_sync::{NoContext, Spin};

+ 6 - 23
src/kernel/mem/page_alloc/raw_page.rs

@@ -32,15 +32,9 @@ impl SlabPageData {
     }
 }
 
-#[derive(Clone, Copy)]
-struct PageCacheData {
-    valid_size: usize,
-}
-
 #[repr(C)]
 union PageData {
     slab: SlabPageData,
-    page_cache: PageCacheData,
 }
 
 pub struct RawPage {
@@ -245,27 +239,16 @@ impl SlabPage for RawPage {
 }
 
 impl PageCacheRawPage for RawPagePtr {
-    fn valid_size(&self) -> &mut usize {
-        unsafe {
-            // SAFETY: The caller ensures that the page is in some page cache.
-            &mut self.as_mut().shared_data.page_cache.valid_size
-        }
-    }
-
     fn is_dirty(&self) -> bool {
         self.flags().has(PageFlags::DIRTY)
     }
 
-    fn clear_dirty(&self) {
-        self.flags().clear(PageFlags::DIRTY);
-    }
-
-    fn set_dirty(&self) {
-        self.flags().set(PageFlags::DIRTY);
-    }
-
-    fn cache_init(&self) {
-        self.as_mut().shared_data.page_cache = PageCacheData { valid_size: 0 };
+    fn set_dirty(&self, dirty: bool) {
+        if dirty {
+            self.flags().set(PageFlags::DIRTY);
+        } else {
+            self.flags().clear(PageFlags::DIRTY);
+        }
     }
 }
 

+ 129 - 287
src/kernel/mem/page_cache.rs

@@ -1,26 +1,27 @@
-use alloc::boxed::Box;
-use alloc::collections::btree_map::BTreeMap;
-use alloc::sync::Weak;
+use alloc::collections::btree_map::{BTreeMap, Entry};
 use core::future::Future;
 use core::mem::ManuallyDrop;
 
-use align_ext::AlignExt;
-use async_trait::async_trait;
 use eonix_hal::mm::ArchPhysAccess;
 use eonix_mm::address::{PAddr, PhysAccess};
 use eonix_mm::paging::{PageAlloc, RawPage, PAGE_SIZE, PAGE_SIZE_BITS, PFN};
 use eonix_sync::Mutex;
 
-use super::paging::AllocZeroed;
 use super::Page;
-use crate::io::{Buffer, FillResult, Stream};
+use crate::io::{Buffer, Stream};
+use crate::kernel::constants::EINVAL;
 use crate::kernel::mem::page_alloc::RawPagePtr;
+use crate::kernel::vfs::inode::InodeUse;
 use crate::prelude::KResult;
 use crate::GlobalPageAlloc;
 
+#[repr(transparent)]
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+pub struct PageOffset(usize);
+
 pub struct PageCache {
-    pages: Mutex<BTreeMap<usize, CachePage>>,
-    backend: Weak<dyn PageCacheBackend>,
+    pages: Mutex<BTreeMap<PageOffset, CachePage>>,
+    inode: InodeUse,
 }
 
 unsafe impl Send for PageCache {}
@@ -30,70 +31,46 @@ unsafe impl Sync for PageCache {}
 pub struct CachePage(RawPagePtr);
 
 unsafe impl Send for CachePage {}
+unsafe impl Sync for CachePage {}
 
-impl Buffer for CachePage {
-    fn total(&self) -> usize {
-        PAGE_SIZE
+impl PageOffset {
+    pub const fn from_byte_floor(offset: usize) -> Self {
+        Self(offset >> PAGE_SIZE_BITS)
     }
 
-    fn wrote(&self) -> usize {
-        self.valid_size()
+    pub const fn from_byte_ceil(offset: usize) -> Self {
+        Self((offset + PAGE_SIZE - 1) >> PAGE_SIZE_BITS)
     }
 
-    fn fill(&mut self, data: &[u8]) -> KResult<FillResult> {
-        let valid_size = self.valid_size();
-        let available = &mut self.all_mut()[valid_size..];
-        if available.len() == 0 {
-            return Ok(FillResult::Full);
-        }
-
-        let len = core::cmp::min(data.len(), available.len());
-        available[..len].copy_from_slice(&data[..len]);
+    pub fn iter_till(self, end: PageOffset) -> impl Iterator<Item = PageOffset> {
+        (self.0..end.0).map(PageOffset)
+    }
 
-        *self.0.valid_size() += len;
+    pub fn page_count(self) -> usize {
+        self.0
+    }
 
-        if len < data.len() {
-            Ok(FillResult::Partial(len))
-        } else {
-            Ok(FillResult::Done(len))
-        }
+    pub fn byte_count(self) -> usize {
+        self.page_count() * PAGE_SIZE
     }
 }
 
 impl CachePage {
     pub fn new() -> Self {
-        let page = GlobalPageAlloc.alloc().unwrap();
-        page.cache_init();
-        Self(page)
-    }
-
-    pub fn new_zeroed() -> Self {
-        let page = Page::zeroed();
-        let raw_page_ptr = RawPagePtr::from(page.into_raw());
-
-        raw_page_ptr.cache_init();
-        Self(raw_page_ptr)
-    }
-
-    pub fn valid_size(&self) -> usize {
-        *self.0.valid_size()
+        Self(GlobalPageAlloc.alloc().unwrap())
     }
 
-    pub fn set_valid_size(&mut self, valid_size: usize) {
-        *self.0.valid_size() = valid_size;
-    }
-
-    pub fn all(&self) -> &[u8] {
+    pub fn as_bytes(&self) -> &[u8] {
         unsafe {
             core::slice::from_raw_parts(
-                // SAFETY: The page is exclusively owned by us, so we can safely access its data.
+                // SAFETY: The page is owned by us, so we can safely access its data.
                 ArchPhysAccess::as_ptr(PAddr::from(PFN::from(self.0))).as_ptr(),
                 PAGE_SIZE,
             )
         }
     }
 
-    pub fn all_mut(&mut self) -> &mut [u8] {
+    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
         unsafe {
             core::slice::from_raw_parts_mut(
                 // SAFETY: The page is exclusively owned by us, so we can safely access its data.
@@ -103,306 +80,171 @@ impl CachePage {
         }
     }
 
-    pub fn valid_data(&self) -> &[u8] {
-        &self.all()[..self.valid_size()]
-    }
-
     pub fn is_dirty(&self) -> bool {
         self.0.is_dirty()
     }
 
-    pub fn set_dirty(&self) {
-        self.0.set_dirty();
+    pub fn set_dirty(&self, dirty: bool) {
+        self.0.set_dirty(dirty);
     }
 
-    pub fn clear_dirty(&self) {
-        self.0.clear_dirty();
+    pub fn get_page(&self) -> Page {
+        unsafe { Page::with_raw(PFN::from(self.0), |page| page.clone()) }
     }
 }
 
 impl PageCache {
-    pub fn new(backend: Weak<dyn PageCacheBackend>) -> Self {
+    pub fn new(inode: InodeUse) -> Self {
         Self {
             pages: Mutex::new(BTreeMap::new()),
-            backend: backend,
+            inode,
         }
     }
 
-    pub async fn read(&self, buffer: &mut dyn Buffer, mut offset: usize) -> KResult<usize> {
-        let mut pages = self.pages.lock().await;
-        let size = self.backend.upgrade().unwrap().size();
-
-        loop {
-            if offset >= size {
-                break;
-            }
-            let page_id = offset >> PAGE_SIZE_BITS;
-            let page = pages.get(&page_id);
-
-            match page {
-                Some(page) => {
-                    let inner_offset = offset % PAGE_SIZE;
-                    let available_in_file = size.saturating_sub(offset);
-
-                    // TODO: still cause unnecessary IO if valid_size < PAGESIZE
-                    //       and fill result is Done
-                    let page_data = &page.valid_data()[inner_offset..];
-                    let read_size = page_data.len().min(available_in_file);
-
-                    if read_size == 0
-                        || buffer.fill(&page_data[..read_size])?.should_stop()
-                        || buffer.available() == 0
-                    {
-                        break;
-                    }
-                    offset += read_size;
-                }
-                None => {
+    pub fn get_page_locked<'a>(
+        &self,
+        pages: &'a mut BTreeMap<PageOffset, CachePage>,
+        pgoff: PageOffset,
+    ) -> impl Future<Output = KResult<&'a mut CachePage>> + Send + use<'_, 'a> {
+        async move {
+            match pages.entry(pgoff) {
+                Entry::Occupied(ent) => Ok(ent.into_mut()),
+                Entry::Vacant(vacant_entry) => {
                     let mut new_page = CachePage::new();
-                    self.backend
-                        .upgrade()
-                        .unwrap()
-                        .read_page(&mut new_page, offset.align_down(PAGE_SIZE))
-                        .await?;
-                    pages.insert(page_id, new_page);
+                    self.inode.read_page(&mut new_page, pgoff).await?;
+
+                    Ok(vacant_entry.insert(new_page))
                 }
             }
         }
+    }
 
-        Ok(buffer.wrote())
+    fn len(&self) -> usize {
+        self.inode.info.lock().size as usize
     }
 
-    pub async fn write(&self, stream: &mut dyn Stream, mut offset: usize) -> KResult<usize> {
+    // TODO: Remove this.
+    pub async fn with_page(
+        &self,
+        pgoff: PageOffset,
+        func: impl FnOnce(&Page, &CachePage),
+    ) -> KResult<()> {
         let mut pages = self.pages.lock().await;
-        let old_size = self.backend.upgrade().unwrap().size();
-        let mut wrote = 0;
-
-        loop {
-            let page_id = offset >> PAGE_SIZE_BITS;
-            let page = pages.get_mut(&page_id);
-
-            match page {
-                Some(page) => {
-                    let inner_offset = offset % PAGE_SIZE;
-                    let cursor_end = match stream.poll_data(&mut page.all_mut()[inner_offset..])? {
-                        Some(buf) => {
-                            wrote += buf.len();
-                            inner_offset + buf.len()
-                        }
-                        None => {
-                            break;
-                        }
-                    };
-
-                    if page.valid_size() < cursor_end {
-                        page.set_valid_size(cursor_end);
-                    }
-                    page.set_dirty();
-                    offset += PAGE_SIZE - inner_offset;
-                }
-                None => {
-                    let new_page = if (offset >> PAGE_SIZE_BITS) > (old_size >> PAGE_SIZE_BITS) {
-                        let new_page = CachePage::new_zeroed();
-                        new_page
-                    } else {
-                        let mut new_page = CachePage::new();
-                        self.backend
-                            .upgrade()
-                            .unwrap()
-                            .read_page(&mut new_page, offset.align_down(PAGE_SIZE))
-                            .await?;
-                        new_page
-                    };
-
-                    pages.insert(page_id, new_page);
-                }
-            }
+        if pgoff > PageOffset::from_byte_ceil(self.len()) {
+            return Err(EINVAL);
         }
 
-        Ok(wrote)
-    }
+        let cache_page = self.get_page_locked(&mut pages, pgoff).await?;
 
-    pub async fn fsync(&self) -> KResult<()> {
-        let pages = self.pages.lock().await;
-        for (page_id, page) in pages.iter() {
-            if page.is_dirty() {
-                self.backend
-                    .upgrade()
-                    .unwrap()
-                    .write_page(&mut CachePageStream::new(*page), page_id << PAGE_SIZE_BITS)
-                    .await?;
-                page.clear_dirty();
-            }
+        unsafe {
+            let page = ManuallyDrop::new(Page::from_raw_unchecked(PFN::from(cache_page.0)));
+
+            func(&page, cache_page);
         }
+
         Ok(())
     }
 
-    // This function is used for extend write or truncate
-    pub async fn resize(&self, new_size: usize) -> KResult<()> {
+    pub async fn read(&self, buffer: &mut dyn Buffer, mut offset: usize) -> KResult<usize> {
         let mut pages = self.pages.lock().await;
-        let old_size = self.backend.upgrade().unwrap().size();
+        let total_len = self.len();
 
-        if new_size < old_size {
-            let begin = new_size.align_down(PAGE_SIZE) >> PAGE_SIZE_BITS;
-            let end = old_size.align_up(PAGE_SIZE) >> PAGE_SIZE_BITS;
+        if offset >= total_len {
+            return Ok(0);
+        }
 
-            for page_id in begin..end {
-                pages.remove(&page_id);
-            }
-        } else if new_size > old_size {
-            let begin = old_size.align_down(PAGE_SIZE) >> PAGE_SIZE_BITS;
-            let end = new_size.align_up(PAGE_SIZE) >> PAGE_SIZE_BITS;
+        let pgoff_start = PageOffset::from_byte_floor(offset);
+        let pgoff_end = PageOffset::from_byte_ceil(total_len);
 
-            pages.remove(&begin);
+        for pgoff in pgoff_start.iter_till(pgoff_end) {
+            let page = self.get_page_locked(&mut pages, pgoff).await?;
 
-            for page_id in begin..end {
-                let mut new_page = CachePage::new_zeroed();
+            let end_offset = (offset + PAGE_SIZE) / PAGE_SIZE * PAGE_SIZE;
+            let real_end = end_offset.min(total_len);
 
-                if page_id != end - 1 {
-                    new_page.set_valid_size(PAGE_SIZE);
-                } else {
-                    new_page.set_valid_size(new_size % PAGE_SIZE);
-                }
-                new_page.set_dirty();
-                pages.insert(page_id, new_page);
+            let inner_offset = offset % PAGE_SIZE;
+            let data_len = real_end - offset;
+
+            if buffer
+                .fill(&page.as_bytes()[inner_offset..inner_offset + data_len])?
+                .should_stop()
+                || buffer.available() == 0
+            {
+                break;
             }
+
+            offset = real_end;
         }
 
-        Ok(())
+        Ok(buffer.wrote())
     }
 
-    pub async fn with_page<F, O>(&self, offset: usize, func: F) -> KResult<Option<O>>
-    where
-        F: FnOnce(&Page, &CachePage) -> O,
-    {
-        let offset_aligin = offset.align_down(PAGE_SIZE);
-        let page_id = offset_aligin >> PAGE_SIZE_BITS;
-        let size = self.backend.upgrade().unwrap().size();
-
-        if offset_aligin > size {
-            return Ok(None);
-        }
-
+    pub async fn write(&self, stream: &mut dyn Stream, mut offset: usize) -> KResult<usize> {
         let mut pages = self.pages.lock().await;
+        let mut total_written = 0;
 
-        let raw_page_ptr = match pages.get(&page_id) {
-            Some(CachePage(raw_page_ptr)) => *raw_page_ptr,
-            None => {
-                let mut new_page = CachePage::new();
-                self.backend
-                    .upgrade()
-                    .unwrap()
-                    .read_page(&mut new_page, offset_aligin)
-                    .await?;
-                pages.insert(page_id, new_page);
-                new_page.0
+        loop {
+            let end_offset = (offset + PAGE_SIZE) / PAGE_SIZE * PAGE_SIZE;
+            let len = end_offset - offset;
+
+            // TODO: Rewrite to return a write state object.
+            let page = self
+                .inode
+                .write_begin(self, &mut pages, offset, len)
+                .await?;
+
+            let inner_offset = offset % PAGE_SIZE;
+            let written = stream
+                .poll_data(&mut page.as_bytes_mut()[inner_offset..])?
+                .map(|b| b.len())
+                .unwrap_or(0);
+
+            page.set_dirty(true);
+            self.inode
+                .write_end(self, &mut pages, offset, len, written)
+                .await?;
+
+            if written == 0 {
+                break;
             }
-        };
-
-        unsafe {
-            let page = ManuallyDrop::new(Page::from_raw_unchecked(PFN::from(raw_page_ptr)));
 
-            Ok(Some(func(&page, &CachePage(raw_page_ptr))))
+            total_written += written;
+            offset += written;
         }
-    }
-}
 
-pub struct CachePageStream {
-    page: CachePage,
-    cur: usize,
-}
-
-impl CachePageStream {
-    pub fn new(page: CachePage) -> Self {
-        Self { page, cur: 0 }
+        Ok(total_written)
     }
-}
-
-impl Stream for CachePageStream {
-    fn poll_data<'a>(&mut self, buf: &'a mut [u8]) -> KResult<Option<&'a mut [u8]>> {
-        if self.cur >= self.page.valid_size() {
-            return Ok(None);
-        }
-
-        let page_data = &self.page.all()[self.cur..self.page.valid_size()];
-        let to_read = buf.len().min(page_data.len());
 
-        buf[..to_read].copy_from_slice(&page_data[..to_read]);
-        self.cur += to_read;
+    pub async fn fsync(&self) -> KResult<()> {
+        let mut pages = self.pages.lock().await;
 
-        Ok(Some(&mut buf[..to_read]))
-    }
+        for (&pgoff, page) in pages.iter_mut() {
+            if !page.is_dirty() {
+                continue;
+            }
 
-    fn ignore(&mut self, len: usize) -> KResult<Option<usize>> {
-        if self.cur >= self.page.valid_size() {
-            return Ok(None);
+            self.inode.write_page(page, pgoff).await?;
+            page.set_dirty(false);
         }
 
-        let to_ignore = len.min(self.page.valid_size() - self.cur);
-        self.cur += to_ignore;
-        Ok(Some(to_ignore))
+        Ok(())
     }
 }
 
-// with this trait, "page cache" and "block cache" are unified,
-// for fs, offset is file offset (floor algin to PAGE_SIZE)
-// for blkdev, offset is block idx (floor align to PAGE_SIZE / BLK_SIZE)
-// Oh no, this would make unnecessary cache
-pub trait PageCacheBackendOps: Sized {
-    fn read_page(
-        &self,
-        page: &mut CachePage,
-        offset: usize,
-    ) -> impl Future<Output = KResult<usize>> + Send;
-
-    fn write_page(
-        &self,
-        page: &mut CachePageStream,
-        offset: usize,
-    ) -> impl Future<Output = KResult<usize>> + Send;
-
-    fn size(&self) -> usize;
-}
-
-#[async_trait]
-pub trait PageCacheBackend: Send + Sync {
-    async fn read_page(&self, page: &mut CachePage, offset: usize) -> KResult<usize>;
-    async fn write_page(&self, page: &mut CachePageStream, offset: usize) -> KResult<usize>;
-    fn size(&self) -> usize;
-}
-
-#[async_trait]
-impl<T> PageCacheBackend for T
-where
-    T: PageCacheBackendOps + Send + Sync + 'static,
-{
-    async fn read_page(&self, page: &mut CachePage, offset: usize) -> KResult<usize> {
-        self.read_page(page, offset).await
-    }
-
-    async fn write_page(&self, page: &mut CachePageStream, offset: usize) -> KResult<usize> {
-        self.write_page(page, offset).await
-    }
-
-    fn size(&self) -> usize {
-        self.size()
+impl core::fmt::Debug for PageCache {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("PageCache").finish()
     }
 }
 
 pub trait PageCacheRawPage: RawPage {
-    fn valid_size(&self) -> &mut usize;
-
     fn is_dirty(&self) -> bool;
-
-    fn set_dirty(&self);
-
-    fn clear_dirty(&self);
-
-    fn cache_init(&self);
+    fn set_dirty(&self, dirty: bool);
 }
 
 impl Drop for PageCache {
     fn drop(&mut self) {
-        let _ = self.fsync();
+        // TODO: Write back dirty pages...
+        // let _ = self.fsync();
     }
 }

+ 8 - 13
src/kernel/syscall/mm.rs

@@ -1,20 +1,15 @@
-use super::FromSyscallArg;
-use crate::kernel::constants::{EBADF, EINVAL};
-use crate::kernel::mem::FileMapping;
-use crate::kernel::task::Thread;
-use crate::kernel::vfs::filearray::FD;
-use crate::{
-    kernel::{
-        constants::{UserMmapFlags, UserMmapProtocol},
-        mem::{Mapping, Permission},
-    },
-    prelude::*,
-};
 use align_ext::AlignExt;
 use eonix_mm::address::{Addr as _, AddrOps as _, VAddr};
 use eonix_mm::paging::PAGE_SIZE;
 use posix_types::syscall_no::*;
 
+use super::FromSyscallArg;
+use crate::kernel::constants::{UserMmapFlags, UserMmapProtocol, EBADF, EINVAL};
+use crate::kernel::mem::{FileMapping, Mapping, Permission};
+use crate::kernel::task::Thread;
+use crate::kernel::vfs::filearray::FD;
+use crate::prelude::*;
+
 impl FromSyscallArg for UserMmapProtocol {
     fn from_arg(value: usize) -> UserMmapProtocol {
         UserMmapProtocol::from_bits_truncate(value as u32)
@@ -74,7 +69,7 @@ async fn do_mmap2(
             .get_inode()?
             .ok_or(EBADF)?;
 
-        Mapping::File(FileMapping::new(file, pgoffset, len))
+        Mapping::File(FileMapping::new(file.get_page_cache(), pgoffset, len))
     };
 
     let permission = Permission {

+ 38 - 32
src/kernel/task/loader/elf.rs

@@ -1,27 +1,22 @@
+use alloc::ffi::CString;
+use alloc::sync::Arc;
+use alloc::vec::Vec;
+
+use align_ext::AlignExt;
+use eonix_mm::address::{Addr, AddrOps as _, VAddr};
+use eonix_mm::paging::PAGE_SIZE;
+use xmas_elf::header::{self, Class, HeaderPt1, Machine_};
+use xmas_elf::program::{self, ProgramHeader32, ProgramHeader64};
+
 use super::{LoadInfo, ELF_MAGIC};
-use crate::io::UninitBuffer;
+use crate::io::{ByteBuffer, UninitBuffer};
+use crate::kernel::constants::ENOEXEC;
+use crate::kernel::mem::{FileMapping, MMList, Mapping, Permission};
 use crate::kernel::task::loader::aux_vec::{AuxKey, AuxVec};
+use crate::kernel::vfs::dentry::Dentry;
+use crate::kernel::vfs::FsContext;
 use crate::path::Path;
-use crate::{
-    io::ByteBuffer,
-    kernel::{
-        constants::ENOEXEC,
-        mem::{FileMapping, MMList, Mapping, Permission},
-        vfs::{dentry::Dentry, FsContext},
-    },
-    prelude::*,
-};
-use align_ext::AlignExt;
-use alloc::vec::Vec;
-use alloc::{ffi::CString, sync::Arc};
-use eonix_mm::{
-    address::{Addr, AddrOps as _, VAddr},
-    paging::PAGE_SIZE,
-};
-use xmas_elf::{
-    header::{self, Class, HeaderPt1, Machine_},
-    program::{self, ProgramHeader32, ProgramHeader64},
-};
+use crate::prelude::*;
 
 const INIT_STACK_SIZE: usize = 0x80_0000;
 
@@ -366,7 +361,7 @@ impl<E: ElfArch> Elf<E> {
                     vmap_start,
                     file_len,
                     Mapping::File(FileMapping::new(
-                        self.file.get_inode()?,
+                        self.file.get_inode()?.get_page_cache(),
                         file_offset,
                         real_file_length,
                     )),
@@ -376,16 +371,27 @@ impl<E: ElfArch> Elf<E> {
                 .await?;
         }
 
-        if vmem_len > file_len {
-            mm_list
-                .mmap_fixed(
-                    vmap_start + file_len,
-                    vmem_len - file_len,
-                    Mapping::Anonymous,
-                    permission,
-                    false,
-                )
-                .await?;
+        if vmem_vaddr_end > load_vaddr_end {
+            if load_vaddr_end.page_offset() != 0 {
+                let mut zero_len = PAGE_SIZE - load_vaddr_end.page_offset();
+                zero_len = zero_len.min(vmem_vaddr_end - load_vaddr_end);
+
+                mm_list
+                    .access_mut(load_vaddr_end, zero_len, |_, data| data.fill(0))
+                    .await?;
+            }
+
+            if vmem_len - file_len > 0 {
+                mm_list
+                    .mmap_fixed(
+                        vmap_start + file_len,
+                        vmem_len - file_len,
+                        Mapping::Anonymous,
+                        permission,
+                        false,
+                    )
+                    .await?;
+            }
         }
 
         Ok(vmap_start + vmem_len)

+ 31 - 35
src/kernel/vfs/dentry.rs

@@ -1,35 +1,31 @@
 pub mod dcache;
 mod walk;
 
-use core::{
-    cell::UnsafeCell,
-    fmt,
-    hash::{BuildHasher, BuildHasherDefault, Hasher},
-    sync::atomic::{AtomicPtr, AtomicU64, AtomicU8, Ordering},
-};
-
 use alloc::sync::Arc;
+use core::cell::UnsafeCell;
+use core::fmt;
+use core::hash::{BuildHasher, BuildHasherDefault, Hasher};
+use core::sync::atomic::{AtomicPtr, AtomicU64, AtomicU8, Ordering};
+
 use arcref::AsArcRef;
 use eonix_sync::LazyLock;
 use pointers::BorrowedArc;
-use posix_types::{namei::RenameFlags, open::OpenFlags, result::PosixError, stat::StatX};
-
-use crate::{
-    hash::KernelHasher,
-    io::Buffer,
-    io::Stream,
-    kernel::constants::{EEXIST, EINVAL, EISDIR, ELOOP, ENOENT, EPERM, ERANGE},
-    kernel::{block::BlockDevice, CharDevice},
-    path::Path,
-    prelude::*,
-    rcu::{rcu_read_lock, RCUNode, RCUPointer, RCUReadGuard},
-};
-
-use super::{
-    inode::{Ino, Inode, InodeUse, RenameData, WriteOffset},
-    types::{DeviceId, Format, Mode, Permission},
-    FsContext,
-};
+use posix_types::namei::RenameFlags;
+use posix_types::open::OpenFlags;
+use posix_types::result::PosixError;
+use posix_types::stat::StatX;
+
+use super::inode::{Ino, InodeUse, RenameData, WriteOffset};
+use super::types::{DeviceId, Format, Mode, Permission};
+use super::FsContext;
+use crate::hash::KernelHasher;
+use crate::io::{Buffer, Stream};
+use crate::kernel::block::BlockDevice;
+use crate::kernel::constants::{EEXIST, EINVAL, EISDIR, ELOOP, ENOENT, EPERM, ERANGE};
+use crate::kernel::CharDevice;
+use crate::path::Path;
+use crate::prelude::*;
+use crate::rcu::{rcu_read_lock, RCUNode, RCUPointer, RCUReadGuard};
 
 const D_INVALID: u8 = 0;
 const D_REGULAR: u8 = 1;
@@ -56,7 +52,7 @@ enum DentryKind {
 /// [lookup()]: crate::kernel::vfs::inode::InodeDirOps::lookup
 struct AssociatedInode {
     kind: UnsafeCell<Option<DentryKind>>,
-    inode: UnsafeCell<Option<InodeUse<dyn Inode>>>,
+    inode: UnsafeCell<Option<InodeUse>>,
 }
 
 /// # Safety
@@ -181,15 +177,15 @@ impl Dentry {
             .map_or(core::ptr::null(), |parent| Arc::as_ptr(&parent))
     }
 
-    pub fn fill(&self, file: InodeUse<dyn Inode>) {
+    pub fn fill(&self, file: InodeUse) {
         self.inode.store(file);
     }
 
-    pub fn inode(&self) -> Option<InodeUse<dyn Inode>> {
+    pub fn inode(&self) -> Option<InodeUse> {
         self.inode.load().map(|(_, inode)| inode.clone())
     }
 
-    pub fn get_inode(&self) -> KResult<InodeUse<dyn Inode>> {
+    pub fn get_inode(&self) -> KResult<InodeUse> {
         self.inode().ok_or(ENOENT)
     }
 
@@ -291,7 +287,7 @@ impl Dentry {
         let inode = self.get_inode()?;
 
         // Safety: Changing mode alone will have no effect on the file's contents
-        match inode.format() {
+        match inode.format {
             Format::DIR => Err(EISDIR),
             Format::REG => inode.read(buffer, offset).await,
             Format::BLK => {
@@ -309,7 +305,7 @@ impl Dentry {
     pub async fn write(&self, stream: &mut dyn Stream, offset: WriteOffset<'_>) -> KResult<usize> {
         let inode = self.get_inode()?;
         // Safety: Changing mode alone will have no effect on the file's contents
-        match inode.format() {
+        match inode.format {
             Format::DIR => Err(EISDIR),
             Format::REG => inode.write(stream, offset).await,
             Format::BLK => Err(EINVAL), // TODO
@@ -375,7 +371,7 @@ impl Dentry {
     }
 
     pub async fn chmod(&self, mode: Mode) -> KResult<()> {
-        self.get_inode()?.chmod(mode).await
+        self.get_inode()?.chmod(mode.perm()).await
     }
 
     pub async fn chown(&self, uid: u32, gid: u32) -> KResult<()> {
@@ -438,8 +434,8 @@ impl AssociatedInode {
         }
     }
 
-    fn store(&self, inode: InodeUse<dyn Inode>) {
-        let kind = match inode.format() {
+    fn store(&self, inode: InodeUse) {
+        let kind = match inode.format {
             Format::REG | Format::BLK | Format::CHR => DentryKind::Regular,
             Format::DIR => DentryKind::Directory,
             Format::LNK => DentryKind::Symlink,
@@ -463,7 +459,7 @@ impl AssociatedInode {
         DentryKind::atomic_acq(&self.kind)
     }
 
-    fn load(&self) -> Option<(DentryKind, &InodeUse<dyn Inode>)> {
+    fn load(&self) -> Option<(DentryKind, &InodeUse)> {
         self.kind().map(|kind| unsafe {
             let inode = (&*self.inode.get())
                 .as_ref()

+ 21 - 31
src/kernel/vfs/dentry/walk.rs

@@ -1,33 +1,23 @@
-use core::{
-    future::Future,
-    hash::{BuildHasher, BuildHasherDefault, Hasher},
-    ops::Deref,
-    pin::Pin,
-};
-
-use alloc::{boxed::Box, sync::Arc};
+use alloc::boxed::Box;
+use alloc::sync::Arc;
+use core::future::Future;
+use core::hash::{BuildHasher, BuildHasherDefault, Hasher};
+use core::ops::Deref;
+use core::pin::Pin;
+
 use arcref::{ArcRef, AsArcRef};
 use posix_types::result::PosixError;
 
-use crate::{
-    hash::KernelHasher,
-    io::ByteBuffer,
-    kernel::{
-        constants::ELOOP,
-        vfs::{
-            inode::{Inode, InodeUse},
-            FsContext,
-        },
-    },
-    path::{Path, PathComponent, PathIterator},
-    prelude::KResult,
-    rcu::{rcu_read_lock, RCUReadLock},
-};
-
-use super::{
-    dcache::{self, DCacheItem},
-    Dentry, DentryKind,
-};
+use super::dcache::{self, DCacheItem};
+use super::{Dentry, DentryKind};
+use crate::hash::KernelHasher;
+use crate::io::ByteBuffer;
+use crate::kernel::constants::ELOOP;
+use crate::kernel::vfs::inode::InodeUse;
+use crate::kernel::vfs::FsContext;
+use crate::path::{Path, PathComponent, PathIterator};
+use crate::prelude::KResult;
+use crate::rcu::{rcu_read_lock, RCUReadLock};
 
 struct DentryFind<'a, 'b> {
     parent: &'a Dentry,
@@ -40,7 +30,7 @@ pub enum WalkResultRcu<'rcu, 'path> {
     Ok(ArcRef<'rcu, Dentry>),
     Symlink {
         symlink: ArcRef<'rcu, Dentry>,
-        inode: InodeUse<dyn Inode>,
+        inode: InodeUse,
     },
     Miss {
         parent: ArcRef<'rcu, Dentry>,
@@ -53,7 +43,7 @@ pub enum WalkResult {
     Ok(Arc<Dentry>),
     Symlink {
         symlink: Arc<Dentry>,
-        inode: InodeUse<dyn Inode>,
+        inode: InodeUse,
     },
 }
 
@@ -270,7 +260,7 @@ impl FsContext {
     pub async fn follow_symlink(
         &self,
         symlink: ArcRef<'_, Dentry>,
-        inode: &InodeUse<dyn Inode>,
+        inode: &InodeUse,
         nr_follows: u32,
     ) -> KResult<Arc<Dentry>> {
         let mut target = [0; 256];
@@ -288,7 +278,7 @@ impl FsContext {
     fn follow_symlink_boxed<'r, 'a: 'r, 'b: 'r, 'c: 'r>(
         &'a self,
         symlink: ArcRef<'b, Dentry>,
-        inode: &'c InodeUse<dyn Inode>,
+        inode: &'c InodeUse,
         nr_follows: u32,
     ) -> Pin<Box<dyn Future<Output = KResult<Arc<Dentry>>> + Send + 'r>> {
         Box::pin(self.follow_symlink(symlink, inode, nr_follows))

+ 15 - 21
src/kernel/vfs/file/inode_file.rs

@@ -1,23 +1,17 @@
-use super::{File, FileType, SeekOption};
-use crate::{
-    io::{Buffer, BufferFill, Stream},
-    kernel::{
-        constants::{EBADF, EFAULT, ENOTDIR, EOVERFLOW, ESPIPE},
-        vfs::{
-            dentry::Dentry,
-            inode::{Inode, InodeUse, WriteOffset},
-            types::Format,
-        },
-    },
-    prelude::KResult,
-};
 use alloc::sync::Arc;
+
 use eonix_sync::Mutex;
-use posix_types::{
-    getdent::{UserDirent, UserDirent64},
-    open::OpenFlags,
-    stat::StatX,
-};
+use posix_types::getdent::{UserDirent, UserDirent64};
+use posix_types::open::OpenFlags;
+use posix_types::stat::StatX;
+
+use super::{File, FileType, SeekOption};
+use crate::io::{Buffer, BufferFill, Stream};
+use crate::kernel::constants::{EBADF, EFAULT, ENOTDIR, EOVERFLOW, ESPIPE};
+use crate::kernel::vfs::dentry::Dentry;
+use crate::kernel::vfs::inode::{InodeUse, WriteOffset};
+use crate::kernel::vfs::types::Format;
+use crate::prelude::KResult;
 
 pub struct InodeFile {
     pub r: bool,
@@ -34,7 +28,7 @@ impl InodeFile {
     pub fn new(dentry: Arc<Dentry>, flags: OpenFlags) -> File {
         // SAFETY: `dentry` used to create `InodeFile` is valid.
         // SAFETY: `mode` should never change with respect to the `S_IFMT` fields.
-        let format = dentry.inode().expect("dentry should be invalid").format();
+        let format = dentry.inode().expect("dentry should be invalid").format;
 
         let (r, w, a) = flags.as_rwa();
 
@@ -98,7 +92,7 @@ impl InodeFile {
 }
 
 impl File {
-    pub fn get_inode(&self) -> KResult<Option<InodeUse<dyn Inode>>> {
+    pub fn get_inode(&self) -> KResult<Option<InodeUse>> {
         if let FileType::Inode(inode_file) = &**self {
             Ok(Some(inode_file.dentry.get_inode()?))
         } else {
@@ -191,7 +185,7 @@ impl File {
             SeekOption::Set(n) => n,
             SeekOption::End(off) => {
                 let inode = inode_file.dentry.get_inode()?;
-                let size = inode.info().lock().size as usize;
+                let size = inode.info.lock().size as usize;
                 size.checked_add_signed(off).ok_or(EOVERFLOW)?
             }
         };

+ 20 - 26
src/kernel/vfs/filearray.rs

@@ -1,28 +1,23 @@
-use super::{
-    file::{File, InodeFile, Pipe},
-    types::{Format, Permission},
-    Spin, TerminalFile,
-};
-use crate::kernel::{
-    constants::{
-        EBADF, EISDIR, ENOTDIR, F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL,
-    },
-    syscall::{FromSyscallArg, SyscallRetVal},
-};
-use crate::{
-    kernel::{console::get_console, constants::ENXIO, vfs::dentry::Dentry, CharDevice},
-    prelude::*,
-};
 use alloc::sync::Arc;
-use intrusive_collections::{
-    intrusive_adapter, rbtree::Entry, Bound, KeyAdapter, RBTree, RBTreeAtomicLink,
-};
-use itertools::{
-    FoldWhile::{Continue, Done},
-    Itertools,
-};
+
+use intrusive_collections::rbtree::Entry;
+use intrusive_collections::{intrusive_adapter, Bound, KeyAdapter, RBTree, RBTreeAtomicLink};
+use itertools::FoldWhile::{Continue, Done};
+use itertools::Itertools;
 use posix_types::open::{FDFlags, OpenFlags};
 
+use super::file::{File, InodeFile, Pipe};
+use super::types::{Format, Permission};
+use super::{Spin, TerminalFile};
+use crate::kernel::console::get_console;
+use crate::kernel::constants::{
+    EBADF, EISDIR, ENOTDIR, ENXIO, F_DUPFD, F_DUPFD_CLOEXEC, F_GETFD, F_GETFL, F_SETFD, F_SETFL,
+};
+use crate::kernel::syscall::{FromSyscallArg, SyscallRetVal};
+use crate::kernel::vfs::dentry::Dentry;
+use crate::kernel::CharDevice;
+use crate::prelude::*;
+
 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
 pub struct FD(u32);
 
@@ -291,20 +286,19 @@ impl FileArray {
         let fdflag = flags.as_fd_flags();
 
         let inode = dentry.get_inode()?;
-        let file_format = inode.format();
 
-        match (flags.directory(), file_format, flags.write()) {
+        match (flags.directory(), inode.format, flags.write()) {
             (true, Format::DIR, _) => {}
             (true, _, _) => return Err(ENOTDIR),
             (false, Format::DIR, true) => return Err(EISDIR),
             _ => {}
         }
 
-        if flags.truncate() && flags.write() && file_format == Format::REG {
+        if flags.truncate() && flags.write() && inode.format == Format::REG {
             inode.truncate(0).await?;
         }
 
-        let file = if file_format == Format::CHR {
+        let file = if inode.format == Format::CHR {
             let device = CharDevice::get(inode.devid()?).ok_or(ENXIO)?;
             device.open(flags)?
         } else {

+ 255 - 276
src/kernel/vfs/inode/inode.rs

@@ -1,52 +1,149 @@
 use alloc::boxed::Box;
-use core::{
-    any::Any,
-    future::Future,
-    marker::Unsize,
-    ops::{CoerceUnsized, Deref},
-    pin::Pin,
-};
-use eonix_sync::Spin;
-
+use alloc::collections::btree_map::BTreeMap;
 use alloc::sync::{Arc, Weak};
-use async_trait::async_trait;
+use core::any::Any;
+use core::future::Future;
+use core::ops::Deref;
 
-use crate::{
-    io::{Buffer, Stream},
-    kernel::{
-        constants::{EINVAL, EPERM},
-        mem::PageCache,
-        timer::Instant,
-        vfs::{
-            dentry::Dentry,
-            types::{DeviceId, Format, Mode, Permission},
-            SbRef, SbUse, SuperBlock,
-        },
-    },
-    prelude::KResult,
-};
+use async_trait::async_trait;
+use eonix_sync::{RwLock, Spin};
 
 use super::{Ino, RenameData, WriteOffset};
+use crate::io::{Buffer, Stream};
+use crate::kernel::constants::{EINVAL, EPERM};
+use crate::kernel::mem::{CachePage, PageCache, PageOffset};
+use crate::kernel::timer::Instant;
+use crate::kernel::vfs::dentry::Dentry;
+use crate::kernel::vfs::types::{DeviceId, Format, Mode, Permission};
+use crate::kernel::vfs::{SbRef, SbUse, SuperBlock};
+use crate::prelude::KResult;
+
+pub struct Inode {
+    pub ino: Ino,
+    pub format: Format,
+    pub info: Spin<InodeInfo>,
+    pub rwsem: RwLock<()>,
+    page_cache: Spin<Weak<PageCache>>,
+    sb: SbRef<dyn SuperBlock>,
+    ops: Box<dyn InodeOpsErased>,
+}
 
-pub trait InodeOps: Sized + Send + Sync + 'static {
-    type SuperBlock: SuperBlock + Sized;
-
-    fn ino(&self) -> Ino;
-    fn format(&self) -> Format;
-    fn info(&self) -> &Spin<InodeInfo>;
-
-    fn super_block(&self) -> &SbRef<Self::SuperBlock>;
+macro_rules! return_type {
+    ($type:ty) => {
+        $type
+    };
+    () => {
+        ()
+    };
+}
 
-    fn page_cache(&self) -> Option<&PageCache>;
+macro_rules! define_inode_ops {
+    {
+        $(
+            $(#[$attr:meta])*
+            async fn $method:ident $(<$($lt:lifetime),+>)? (&self $(,)? $($name:ident : $type:ty $(,)?)*) $(-> $ret:ty)?
+                $body:block
+        )*
+
+        ---
+
+        $(
+            $(#[$attr1:meta])*
+            fn $method1:ident $(<$($lt1:lifetime),+>)? (&self $(,)? $($name1:ident : $type1:ty $(,)?)*) $(-> $ret1:ty)?
+                $body1:block
+        )*
+    } => {
+        #[allow(unused_variables)]
+        pub trait InodeOps: Sized + Send + Sync + 'static {
+            type SuperBlock: SuperBlock + Sized;
+
+            $(
+                $(#[$attr])*
+                fn $method $(<$($lt),+>)? (
+                &self,
+                sb: SbUse<Self::SuperBlock>,
+                inode: &InodeUse,
+                $($name : $type),*
+            ) -> impl Future<Output = return_type!($($ret)?)> + Send {
+                async { $body }
+            })*
+
+            $(
+                $(#[$attr1])*
+                fn $method1 $(<$($lt1),+>)? (
+                &self,
+                sb: SbUse<Self::SuperBlock>,
+                inode: &InodeUse,
+                $($name1 : $type1),*
+            ) -> return_type!($($ret1)?) {
+                $body1
+            })*
+        }
+
+        #[async_trait]
+        trait InodeOpsErased: Any + Send + Sync + 'static {
+            $(async fn $method $(<$($lt),+>)? (
+                &self,
+                sb: SbUse<dyn SuperBlock>,
+                inode: &InodeUse,
+                $($name : $type),*
+            ) -> return_type!($($ret)?);)*
+
+            $(fn $method1 $(<$($lt1),+>)? (
+                &self,
+                sb: SbUse<dyn SuperBlock>,
+                inode: &InodeUse,
+                $($name1 : $type1),*
+            ) -> return_type!($($ret1)?);)*
+        }
+
+        #[async_trait]
+        impl<T> InodeOpsErased for T
+        where
+            T: InodeOps,
+        {
+            $(async fn $method $(<$($lt),+>)? (
+                &self,
+                sb: SbUse<dyn SuperBlock>,
+                inode: &InodeUse,
+                $($name : $type),*
+            ) -> return_type!($($ret)?) {
+                self.$method(sb.downcast(), inode, $($name),*).await
+            })*
+
+            $(fn $method1 $(<$($lt1),+>)? (
+                &self,
+                sb: SbUse<dyn SuperBlock>,
+                inode: &InodeUse,
+                $($name1 : $type1),*
+            ) -> return_type!($($ret1)?) {
+                self.$method1(sb.downcast(), inode, $($name1),*)
+            })*
+        }
+
+        impl InodeUse {
+            $(pub async fn $method $(<$($lt),+>)? (
+                &self,
+                $($name : $type),*
+            ) -> return_type!($($ret)?) {
+                self.ops.$method(self.sbget()?, self, $($name),*).await
+            })*
+
+            $(pub fn $method1 $(<$($lt1),+>)? (
+                &self,
+                $($name1 : $type1),*
+            ) -> return_type!($($ret1)?) {
+                self.ops.$method1(self.sbget()?, self, $($name1),*)
+            })*
+        }
+    };
 }
 
-#[allow(unused_variables)]
-pub trait InodeDirOps: InodeOps {
-    fn lookup(
-        &self,
-        dentry: &Arc<Dentry>,
-    ) -> impl Future<Output = KResult<Option<InodeUse<dyn Inode>>>> + Send {
-        async { Err(EPERM) }
+define_inode_ops! {
+    // DIRECTORY OPERATIONS
+
+    async fn lookup(&self, dentry: &Arc<Dentry>) -> KResult<Option<InodeUse>> {
+        Err(EPERM)
     }
 
     /// Read directory entries and call the given closure for each entry.
@@ -55,255 +152,114 @@ pub trait InodeDirOps: InodeOps {
     /// - Ok(count): The number of entries read.
     /// - Ok(Err(err)): Some error occurred while calling the given closure.
     /// - Err(err): An error occurred while reading the directory.
-    fn readdir<'r, 'a: 'r, 'b: 'r>(
-        &'a self,
+    async fn readdir(
+        &self,
         offset: usize,
-        for_each_entry: &'b mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
-    ) -> impl Future<Output = KResult<KResult<usize>>> + Send + 'r {
-        async { Err(EPERM) }
+        for_each_entry: &mut (dyn (for<'a> FnMut(&'a [u8], Ino) -> KResult<bool>) + Send),
+    ) -> KResult<KResult<usize>> {
+        Err(EPERM)
     }
 
-    fn create(
-        &self,
-        at: &Arc<Dentry>,
-        mode: Permission,
-    ) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
+    async fn create(&self, at: &Arc<Dentry>, mode: Permission) -> KResult<()> {
+        Err(EPERM)
     }
 
-    fn mkdir(&self, at: &Dentry, mode: Permission) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
+    async fn mkdir(&self, at: &Dentry, mode: Permission) -> KResult<()> {
+        Err(EPERM)
     }
 
-    fn mknod(
-        &self,
-        at: &Dentry,
-        mode: Mode,
-        dev: DeviceId,
-    ) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
+    async fn mknod(&self, at: &Dentry, mode: Mode, dev: DeviceId) -> KResult<()> {
+        Err(EPERM)
     }
 
-    fn unlink(&self, at: &Arc<Dentry>) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
+    async fn unlink(&self, at: &Arc<Dentry>) -> KResult<()> {
+        Err(EPERM)
     }
 
-    fn symlink(&self, at: &Arc<Dentry>, target: &[u8]) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
+    async fn symlink(&self, at: &Arc<Dentry>, target: &[u8]) -> KResult<()> {
+        Err(EPERM)
     }
 
-    fn rename(&self, rename_data: RenameData<'_, '_>) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
+    async fn rename(&self, rename_data: RenameData<'_, '_>) -> KResult<()> {
+        Err(EPERM)
     }
-}
 
-#[allow(unused_variables)]
-pub trait InodeFileOps: InodeOps {
-    fn read(
-        &self,
-        buffer: &mut dyn Buffer,
-        offset: usize,
-    ) -> impl Future<Output = KResult<usize>> + Send {
-        async { Err(EINVAL) }
+    // FILE OPERATIONS
+
+    async fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
+        Err(EINVAL)
     }
 
-    fn read_direct(
-        &self,
-        buffer: &mut dyn Buffer,
-        offset: usize,
-    ) -> impl Future<Output = KResult<usize>> + Send {
-        async { Err(EINVAL) }
+    async fn read_direct(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
+        Err(EINVAL)
     }
 
-    fn write(
+    async fn write(
         &self,
         stream: &mut dyn Stream,
-        offset: WriteOffset<'_>,
-    ) -> impl Future<Output = KResult<usize>> + Send {
-        async { Err(EINVAL) }
+        offset: WriteOffset<'_>
+    ) -> KResult<usize> {
+        Err(EINVAL)
     }
 
-    fn write_direct(
+    async fn write_direct(
         &self,
         stream: &mut dyn Stream,
         offset: usize,
-    ) -> impl Future<Output = KResult<usize>> + Send {
-        async { Err(EINVAL) }
-    }
-
-    fn devid(&self) -> KResult<DeviceId> {
+    ) -> KResult<usize> {
         Err(EINVAL)
     }
 
-    fn readlink(&self, buffer: &mut dyn Buffer) -> impl Future<Output = KResult<usize>> + Send {
-        async { Err(EINVAL) }
-    }
-
-    fn truncate(&self, length: usize) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
-    }
-
-    fn chmod(&self, perm: Permission) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
-    }
-
-    fn chown(&self, uid: u32, gid: u32) -> impl Future<Output = KResult<()>> + Send {
-        async { Err(EPERM) }
-    }
-}
-
-#[async_trait]
-pub trait InodeDir {
-    async fn lookup(&self, dentry: &Arc<Dentry>) -> KResult<Option<InodeUse<dyn Inode>>>;
-    async fn create(&self, at: &Arc<Dentry>, perm: Permission) -> KResult<()>;
-    async fn mkdir(&self, at: &Dentry, perm: Permission) -> KResult<()>;
-    async fn mknod(&self, at: &Dentry, mode: Mode, dev: DeviceId) -> KResult<()>;
-    async fn unlink(&self, at: &Arc<Dentry>) -> KResult<()>;
-    async fn symlink(&self, at: &Arc<Dentry>, target: &[u8]) -> KResult<()>;
-    async fn rename(&self, rename_data: RenameData<'_, '_>) -> KResult<()>;
-
-    fn readdir<'r, 'a: 'r, 'b: 'r>(
-        &'a self,
-        offset: usize,
-        callback: &'b mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
-    ) -> Pin<Box<dyn Future<Output = KResult<KResult<usize>>> + Send + 'r>>;
-}
-
-#[async_trait]
-pub trait InodeFile {
-    async fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize>;
-    async fn read_direct(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize>;
-    async fn write(&self, stream: &mut dyn Stream, offset: WriteOffset<'_>) -> KResult<usize>;
-    async fn write_direct(&self, stream: &mut dyn Stream, offset: usize) -> KResult<usize>;
-    fn devid(&self) -> KResult<DeviceId>;
-    async fn readlink(&self, buffer: &mut dyn Buffer) -> KResult<usize>;
-    async fn truncate(&self, length: usize) -> KResult<()>;
-    async fn chmod(&self, mode: Mode) -> KResult<()>;
-    async fn chown(&self, uid: u32, gid: u32) -> KResult<()>;
-}
-
-pub trait Inode: InodeFile + InodeDir + Any + Send + Sync + 'static {
-    fn ino(&self) -> Ino;
-    fn format(&self) -> Format;
-    fn info(&self) -> &Spin<InodeInfo>;
-
-    // TODO: This might should be removed... Temporary workaround for now.
-    fn page_cache(&self) -> Option<&PageCache>;
-
-    fn sbref(&self) -> SbRef<dyn SuperBlock>;
-    fn sbget(&self) -> KResult<SbUse<dyn SuperBlock>>;
-}
-
-#[async_trait]
-impl<T> InodeFile for T
-where
-    T: InodeFileOps,
-{
-    async fn read(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
-        self.read(buffer, offset).await
-    }
-
-    async fn read_direct(&self, buffer: &mut dyn Buffer, offset: usize) -> KResult<usize> {
-        self.read_direct(buffer, offset).await
-    }
-
-    async fn write(&self, stream: &mut dyn Stream, offset: WriteOffset<'_>) -> KResult<usize> {
-        self.write(stream, offset).await
-    }
-
-    async fn write_direct(&self, stream: &mut dyn Stream, offset: usize) -> KResult<usize> {
-        self.write_direct(stream, offset).await
-    }
-
-    fn devid(&self) -> KResult<DeviceId> {
-        self.devid()
-    }
-
     async fn readlink(&self, buffer: &mut dyn Buffer) -> KResult<usize> {
-        self.readlink(buffer).await
+        Err(EINVAL)
     }
 
     async fn truncate(&self, length: usize) -> KResult<()> {
-        self.truncate(length).await
+        Err(EPERM)
     }
 
-    async fn chmod(&self, mode: Mode) -> KResult<()> {
-        self.chmod(Permission::new(mode.non_format_bits())).await
+    async fn chmod(&self, perm: Permission) -> KResult<()> {
+        Err(EPERM)
     }
 
     async fn chown(&self, uid: u32, gid: u32) -> KResult<()> {
-        self.chown(uid, gid).await
-    }
-}
-
-#[async_trait]
-impl<T> InodeDir for T
-where
-    T: InodeDirOps,
-{
-    async fn lookup(&self, dentry: &Arc<Dentry>) -> KResult<Option<InodeUse<dyn Inode>>> {
-        self.lookup(dentry).await
-    }
-
-    async fn create(&self, at: &Arc<Dentry>, perm: Permission) -> KResult<()> {
-        self.create(at, perm).await
-    }
-
-    async fn mkdir(&self, at: &Dentry, perm: Permission) -> KResult<()> {
-        self.mkdir(at, perm).await
+        Err(EPERM)
     }
 
-    async fn mknod(&self, at: &Dentry, mode: Mode, dev: DeviceId) -> KResult<()> {
-        self.mknod(at, mode, dev).await
-    }
-
-    async fn unlink(&self, at: &Arc<Dentry>) -> KResult<()> {
-        self.unlink(at).await
-    }
-
-    async fn symlink(&self, at: &Arc<Dentry>, target: &[u8]) -> KResult<()> {
-        self.symlink(at, target).await
+    // PAGE CACHE OPERATIONS
+    async fn read_page(&self, page: &mut CachePage, offset: PageOffset) -> KResult<()> {
+        Err(EINVAL)
     }
 
-    async fn rename(&self, rename_data: RenameData<'_, '_>) -> KResult<()> {
-        self.rename(rename_data).await
+    async fn write_page(&self, page: &mut CachePage, offset: PageOffset) -> KResult<()> {
+        Err(EINVAL)
     }
 
-    fn readdir<'r, 'a: 'r, 'b: 'r>(
-        &'a self,
+    async fn write_begin<'a>(
+        &self,
+        page_cache: &PageCache,
+        pages: &'a mut BTreeMap<PageOffset, CachePage>,
         offset: usize,
-        callback: &'b mut (dyn FnMut(&[u8], Ino) -> KResult<bool> + Send),
-    ) -> Pin<Box<dyn Future<Output = KResult<KResult<usize>>> + Send + 'r>> {
-        Box::pin(self.readdir(offset, callback))
-    }
-}
-
-impl<T> Inode for T
-where
-    T: InodeOps + InodeFile + InodeDir,
-{
-    fn ino(&self) -> Ino {
-        self.ino()
-    }
-
-    fn format(&self) -> Format {
-        self.format()
-    }
-
-    fn info(&self) -> &Spin<InodeInfo> {
-        self.info()
+        len: usize,
+    ) -> KResult<&'a mut CachePage> {
+        Err(EINVAL)
     }
 
-    fn page_cache(&self) -> Option<&PageCache> {
-        self.page_cache()
+    async fn write_end(
+        &self,
+        page_cache: &PageCache,
+        pages: &mut BTreeMap<PageOffset, CachePage>,
+        offset: usize,
+        len: usize,
+        copied: usize
+    ) -> KResult<()> {
+        Err(EINVAL)
     }
 
-    fn sbref(&self) -> SbRef<dyn SuperBlock> {
-        self.super_block().clone()
-    }
+    ---
 
-    fn sbget(&self) -> KResult<SbUse<dyn SuperBlock>> {
-        self.super_block().get().map(|sb| sb as _)
+    fn devid(&self) -> KResult<DeviceId> {
+        Err(EINVAL)
     }
 }
 
@@ -321,64 +277,87 @@ pub struct InodeInfo {
     pub mtime: Instant,
 }
 
-pub struct InodeUse<I>(Arc<I>)
-where
-    I: Inode + ?Sized;
+#[repr(transparent)]
+pub struct InodeUse(Arc<Inode>);
+
+impl InodeUse {
+    pub fn new(
+        sb: SbRef<dyn SuperBlock>,
+        ino: Ino,
+        format: Format,
+        info: InodeInfo,
+        ops: impl InodeOps,
+    ) -> Self {
+        let inode = Inode {
+            sb,
+            ino,
+            format,
+            info: Spin::new(info),
+            rwsem: RwLock::new(()),
+            page_cache: Spin::new(Weak::new()),
+            ops: Box::new(ops),
+        };
 
-impl<I> InodeUse<I>
-where
-    I: Inode,
-{
-    pub fn new(inode: I) -> Self {
         Self(Arc::new(inode))
     }
 
-    pub fn new_cyclic(inode_func: impl FnOnce(&Weak<I>) -> I) -> Self {
-        Self(Arc::new_cyclic(inode_func))
+    pub fn sbref(&self) -> SbRef<dyn SuperBlock> {
+        self.sb.clone()
     }
-}
 
-impl<I> InodeUse<I>
-where
-    I: Inode + ?Sized,
-{
-    pub fn as_raw(&self) -> *const I {
-        Arc::as_ptr(&self.0)
+    pub fn sbget(&self) -> KResult<SbUse<dyn SuperBlock>> {
+        self.sb.get().map(|sb| sb as _)
+    }
+
+    pub fn get_priv<I>(&self) -> &I
+    where
+        I: InodeOps,
+    {
+        let ops = (&*self.ops) as &dyn Any;
+
+        ops.downcast_ref()
+            .expect("InodeUse::private: InodeOps type mismatch")
     }
-}
 
-impl<T, U> CoerceUnsized<InodeUse<U>> for InodeUse<T>
-where
-    T: Inode + Unsize<U> + ?Sized,
-    U: Inode + ?Sized,
-{
+    pub fn get_page_cache(&self) -> Arc<PageCache> {
+        if let Some(cache) = self.page_cache.lock().upgrade() {
+            return cache;
+        }
+
+        // Slow path...
+        let cache = Arc::new(PageCache::new(self.clone()));
+        let mut page_cache = self.page_cache.lock();
+        if let Some(cache) = page_cache.upgrade() {
+            return cache;
+        }
+
+        *page_cache = Arc::downgrade(&cache);
+        cache
+    }
 }
 
-impl<I> Clone for InodeUse<I>
-where
-    I: Inode + ?Sized,
-{
+impl Clone for InodeUse {
     fn clone(&self) -> Self {
         Self(self.0.clone())
     }
 }
 
-impl<I> core::fmt::Debug for InodeUse<I>
-where
-    I: Inode + ?Sized,
-{
+impl core::fmt::Debug for InodeUse {
     fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-        write!(f, "InodeUse(ino={})", self.ino())
+        write!(f, "InodeUse(ino={})", self.ino)
     }
 }
 
-impl<I> Deref for InodeUse<I>
-where
-    I: Inode + ?Sized,
-{
-    type Target = I;
+impl Deref for InodeUse {
+    type Target = Inode;
 
     fn deref(&self) -> &Self::Target {
         self.0.deref()
     }
 }
+
+impl PartialEq for InodeUse {
+    fn eq(&self, other: &Self) -> bool {
+        Arc::ptr_eq(&self.0, &other.0)
+    }
+}

+ 1 - 1
src/kernel/vfs/inode/mod.rs

@@ -4,5 +4,5 @@ mod ops;
 mod statx;
 
 pub use ino::Ino;
-pub use inode::{Inode, InodeDirOps, InodeFileOps, InodeInfo, InodeOps, InodeUse};
+pub use inode::{Inode, InodeInfo, InodeOps, InodeUse};
 pub use ops::{RenameData, WriteOffset};

+ 2 - 3
src/kernel/vfs/inode/ops.rs

@@ -1,9 +1,8 @@
 use alloc::sync::Arc;
 
+use super::inode::InodeUse;
 use crate::kernel::vfs::dentry::Dentry;
 
-use super::{inode::InodeUse, Inode};
-
 pub enum WriteOffset<'end> {
     Position(usize),
     End(&'end mut usize),
@@ -12,7 +11,7 @@ pub enum WriteOffset<'end> {
 pub struct RenameData<'a, 'b> {
     pub old_dentry: &'a Arc<Dentry>,
     pub new_dentry: &'b Arc<Dentry>,
-    pub new_parent: InodeUse<dyn Inode>,
+    pub new_parent: InodeUse,
     pub is_exchange: bool,
     pub no_replace: bool,
 }

+ 11 - 21
src/kernel/vfs/inode/statx.rs

@@ -1,25 +1,17 @@
 use posix_types::stat::StatX;
 
-use crate::{
-    kernel::{
-        constants::{
-            STATX_ATIME, STATX_BLOCKS, STATX_CTIME, STATX_GID, STATX_INO, STATX_MODE, STATX_MTIME,
-            STATX_NLINK, STATX_SIZE, STATX_TYPE, STATX_UID,
-        },
-        vfs::types::Format,
-    },
-    prelude::KResult,
+use super::inode::InodeUse;
+use crate::kernel::constants::{
+    STATX_ATIME, STATX_BLOCKS, STATX_CTIME, STATX_GID, STATX_INO, STATX_MODE, STATX_MTIME,
+    STATX_NLINK, STATX_SIZE, STATX_TYPE, STATX_UID,
 };
+use crate::kernel::vfs::types::Format;
+use crate::prelude::KResult;
 
-use super::{inode::InodeUse, Inode};
-
-impl<I> InodeUse<I>
-where
-    I: Inode + ?Sized,
-{
+impl InodeUse {
     pub fn statx(&self, stat: &mut StatX, mask: u32) -> KResult<()> {
         let sb = self.sbget()?;
-        let info = self.info().lock();
+        let info = self.info.lock();
 
         if mask & STATX_NLINK != 0 {
             stat.stx_nlink = info.nlink as _;
@@ -53,10 +45,8 @@ where
         }
 
         if mask & STATX_TYPE != 0 {
-            let format = self.format();
-
-            stat.stx_mode |= format.as_raw() as u16;
-            if let Format::BLK | Format::CHR = format {
+            stat.stx_mode |= self.format.as_raw() as u16;
+            if let Format::BLK | Format::CHR = self.format {
                 let devid = self.devid()?;
                 stat.stx_rdev_major = devid.major as _;
                 stat.stx_rdev_minor = devid.minor as _;
@@ -65,7 +55,7 @@ where
         }
 
         if mask & STATX_INO != 0 {
-            stat.stx_ino = self.ino().as_raw();
+            stat.stx_ino = self.ino.as_raw();
             stat.stx_mask |= STATX_INO;
         }
 

+ 12 - 16
src/kernel/vfs/mount.rs

@@ -1,17 +1,17 @@
-use super::{
-    dentry::{dcache, Dentry, DROOT},
-    inode::{Inode, InodeUse},
-    SbUse, SuperBlock,
-};
-use crate::kernel::{
-    constants::{EEXIST, ENODEV, ENOTDIR},
-    task::block_on,
-};
-use crate::prelude::*;
-use alloc::{collections::btree_map::BTreeMap, string::ToString as _, sync::Arc};
+use alloc::collections::btree_map::BTreeMap;
+use alloc::string::ToString as _;
+use alloc::sync::Arc;
+
 use async_trait::async_trait;
 use eonix_sync::LazyLock;
 
+use super::dentry::{dcache, Dentry, DROOT};
+use super::inode::InodeUse;
+use super::{SbUse, SuperBlock};
+use crate::kernel::constants::{EEXIST, ENODEV, ENOTDIR};
+use crate::kernel::task::block_on;
+use crate::prelude::*;
+
 pub const MS_RDONLY: u64 = 1 << 0;
 pub const MS_NOSUID: u64 = 1 << 1;
 pub const MS_NODEV: u64 = 1 << 2;
@@ -39,11 +39,7 @@ pub struct Mount {
 }
 
 impl Mount {
-    pub fn new(
-        mp: &Dentry,
-        sb: SbUse<dyn SuperBlock>,
-        root_inode: InodeUse<dyn Inode>,
-    ) -> KResult<Self> {
+    pub fn new(mp: &Dentry, sb: SbUse<dyn SuperBlock>, root_inode: InodeUse) -> KResult<Self> {
         let root_dentry = Dentry::create(mp.parent().clone(), &mp.get_name());
         root_dentry.fill(root_inode);
 

+ 37 - 8
src/kernel/vfs/superblock.rs

@@ -1,16 +1,15 @@
-use core::{
-    marker::Unsize,
-    ops::{CoerceUnsized, Deref},
-};
-
 use alloc::sync::{Arc, Weak};
-use eonix_sync::RwLock;
+use core::any::{Any, TypeId};
+use core::marker::Unsize;
+use core::ops::{CoerceUnsized, Deref};
 
-use crate::{kernel::constants::EIO, prelude::KResult};
+use eonix_sync::RwLock;
 
 use super::types::DeviceId;
+use crate::kernel::constants::EIO;
+use crate::prelude::KResult;
 
-pub trait SuperBlock: Send + Sync + 'static {}
+pub trait SuperBlock: Any + Send + Sync + 'static {}
 
 #[derive(Debug, Clone)]
 pub struct SuperBlockInfo {
@@ -83,6 +82,36 @@ where
     }
 }
 
+impl<S> SbUse<S>
+where
+    S: SuperBlock + ?Sized,
+{
+    pub fn get_ref(&self) -> SbRef<S> {
+        SbRef(Arc::downgrade(&self.0))
+    }
+}
+
+impl SbUse<dyn SuperBlock> {
+    /// Downcast the superblock to a specific type.
+    ///
+    /// # Panics
+    /// Panics if the downcast fails.
+    pub fn downcast<U: SuperBlock>(self) -> SbUse<U> {
+        let Self(sb_complex) = self;
+        if (&sb_complex.backend as &dyn Any).type_id() != TypeId::of::<U>() {
+            panic!("Downcast failed: type mismatch");
+        }
+
+        unsafe {
+            // SAFETY: We have checked the type above and unsized coercion says
+            //         that Arc<T> has the same layout as Arc<U> if T: Unsize<U>.
+            SbUse(Arc::from_raw(
+                Arc::into_raw(sb_complex) as *const SuperBlockComplex<U>
+            ))
+        }
+    }
+}
+
 impl<S> Clone for SbRef<S>
 where
     S: SuperBlock + ?Sized,

+ 0 - 1
src/lib.rs

@@ -3,7 +3,6 @@
 #![feature(allocator_api)]
 #![feature(c_size_t)]
 #![feature(coerce_unsized)]
-#![feature(concat_idents)]
 #![feature(arbitrary_self_types)]
 #![feature(get_mut_unchecked)]
 #![feature(macro_metavar_expr)]