From 192df58a69c651eb899b593f06c6b31232ee76f9 Mon Sep 17 00:00:00 2001 From: lsig Date: Mon, 24 Nov 2025 16:45:46 -0500 Subject: [PATCH 1/9] [Read SB] add mutex init --- fs/rustezfs/ezfs.rs | 25 +++++++++++++++++-------- fs/rustezfs/sb.rs | 30 ++++++++++++++++++++++++++++-- rust/kernel/sb.rs | 15 ++++++++++++++- 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index 2726319ac48a1a..ee7fda28fafeb0 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -6,7 +6,7 @@ mod defs; mod dir; mod inode; mod sb; -use crate::sb::EzfsSuperblockDisk; +use crate::sb::{EzfsSuperblock, EzfsSuperblockDisk}; use defs::*; use kernel::dentry; use kernel::fs::{FileSystem, Registration}; @@ -63,8 +63,7 @@ impl RustEzFs { } impl FileSystem for RustEzFs { - // type Data = KBox; - type Data = (); + type Data = Pin>; type INodeData = (); const NAME: &'static CStr = c_str!("rustezfs"); const SUPER_TYPE: SuperType = SuperType::BlockDev; @@ -78,14 +77,24 @@ impl FileSystem for RustEzFs { }; let offset = 0; - let mapped_sb = mapper.mapped_folio(offset.try_into()?)?; + let disk_sb = { + let mapped_sb = mapper.mapped_folio(offset.try_into()?)?; + EzfsSuperblockDisk::from_bytes_copy(&mapped_sb).ok_or(EIO)? + }; + + if disk_sb.magic() != EZFS_MAGIC_NUMBER.try_into()? { + return Err(EINVAL); + } + + pr_info!("sb disk magic: {:?}", disk_sb.magic()); + + let ezfs_sb = KBox::pin_init(EzfsSuperblock::new(disk_sb, mapper), GFP_KERNEL)?; - let sb_disk = EzfsSuperblockDisk::from_bytes(&mapped_sb).ok_or(EIO)?; + pr_info!("sb in-memory magic: {:?}", ezfs_sb.magic()); - pr_info!("sb disk magic: {:?}", sb_disk.magic()); + sb.set_magic(EZFS_MAGIC_NUMBER); - // sb.set_magic(EZFS_MAGIC_NUMBER); - Ok(()) + Ok(ezfs_sb) } fn init_root(sb: &SuperBlock) -> Result> { diff --git a/fs/rustezfs/sb.rs b/fs/rustezfs/sb.rs index 645750a51be1a5..58f749e25a0c50 100644 --- a/fs/rustezfs/sb.rs +++ b/fs/rustezfs/sb.rs @@ -1,5 +1,10 @@ use crate::defs::{EZFS_BLOCK_SIZE, EZFS_MAX_DATA_BLKS, EZFS_MAX_INODES}; +use crate::RustEzFs; use core::mem::size_of; +use kernel::fs::FileSystem; +use kernel::inode; +use kernel::new_mutex; +use kernel::prelude::*; use kernel::sync::Mutex; use kernel::transmute::FromBytes; @@ -29,13 +34,34 @@ impl EzfsSuperblockDisk { // which accept any bit pattern. The struct is #[repr(C)] for consistent layout. unsafe impl FromBytes for EzfsSuperblockDisk {} -// TODO: pin data because of mutexes -// in-memory representation of sb +#[pin_data] pub(crate) struct EzfsSuperblock { version: u64, magic: u64, disk_blocks: u64, + #[pin] free_inodes: Mutex<[u32; (EZFS_MAX_INODES / 32) + 1]>, + #[pin] free_data_blocks: Mutex<[u32; (EZFS_MAX_DATA_BLKS / 32) + 1]>, + #[pin] zero_data_blocks: Mutex<[u8; (EZFS_MAX_DATA_BLKS / 32) + 1]>, + mapper: inode::Mapper, +} + +impl EzfsSuperblock { + pub fn new(disk_sb: EzfsSuperblockDisk, mapper: inode::Mapper) -> impl PinInit { + pin_init!(Self { + version: disk_sb.data.version, + magic: disk_sb.data.magic, + disk_blocks: disk_sb.data.disk_blocks, + free_inodes <- new_mutex!(disk_sb.data.free_inodes), + free_data_blocks <- new_mutex!(disk_sb.data.free_data_blocks), + zero_data_blocks <- new_mutex!(disk_sb.data.zero_data_blocks), + mapper, + }) + } + + pub fn magic(&self) -> u64 { + self.magic + } } diff --git a/rust/kernel/sb.rs b/rust/kernel/sb.rs index 764acd66acfbfa..4594e134b15572 100644 --- a/rust/kernel/sb.rs +++ b/rust/kernel/sb.rs @@ -1,7 +1,7 @@ use core::{marker::PhantomData, ptr}; use crate::error::{code::*, Result}; -use crate::types::ARef; +use crate::types::{ARef, ForeignOwnable}; use crate::{block, inode}; use crate::{ build_error, @@ -96,6 +96,19 @@ impl SuperBlock { } impl SuperBlock { + /// Returns the data associated with the superblock. + pub fn data(&self) -> ::Borrowed<'_> { + // TODO: Add UnspecifiedFS + // if T::IS_UNSPECIFIED { + // crate::build_error!("super block data type is unspecified"); + // } + + // SAFETY: This method is only available if the typestate implements `DataInited`, whose + // safety requirements include `s_fs_info` being properly initialised. + let ptr = unsafe { (*self.0.get()).s_fs_info }; + unsafe { T::Data::borrow(ptr) } + } + /// Tries to get an existing inode or create a new one if it doesn't exist yet. /// /// This method is not callable from a superblock where data isn't inited yet because it would From 1328f6e5351290143b0e85df0906be5b83b48b23 Mon Sep 17 00:00:00 2001 From: lsig Date: Mon, 24 Nov 2025 17:23:33 -0500 Subject: [PATCH 2/9] [Read Inode store] checkpoint --- fs/rustezfs/ezfs.rs | 17 +++++++++++------ fs/rustezfs/inode.rs | 19 +++++++++++++++++++ fs/rustezfs/sb.rs | 10 +++++++++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index ee7fda28fafeb0..52b58c0ed52eaa 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -6,6 +6,7 @@ mod defs; mod dir; mod inode; mod sb; +use crate::inode::InodeStore; use crate::sb::{EzfsSuperblock, EzfsSuperblockDisk}; use defs::*; use kernel::dentry; @@ -76,8 +77,8 @@ impl FileSystem for RustEzFs { return Err(EINVAL); }; - let offset = 0; let disk_sb = { + let offset = EZFS_SUPERBLOCK_DATABLOCK_NUMBER * EZFS_BLOCK_SIZE; let mapped_sb = mapper.mapped_folio(offset.try_into()?)?; EzfsSuperblockDisk::from_bytes_copy(&mapped_sb).ok_or(EIO)? }; @@ -86,12 +87,16 @@ impl FileSystem for RustEzFs { return Err(EINVAL); } - pr_info!("sb disk magic: {:?}", disk_sb.magic()); - - let ezfs_sb = KBox::pin_init(EzfsSuperblock::new(disk_sb, mapper), GFP_KERNEL)?; - - pr_info!("sb in-memory magic: {:?}", ezfs_sb.magic()); + let inode_store = { + let offset = EZFS_INODE_STORE_DATABLOCK_NUMBER * EZFS_BLOCK_SIZE; + let mapped_inode_store = mapper.mapped_folio(offset.try_into()?)?; + InodeStore::from_bytes_copy(&mapped_inode_store).ok_or(EIO)? + }; + let ezfs_sb = KBox::pin_init( + EzfsSuperblock::new(disk_sb, inode_store, mapper), + GFP_KERNEL, + )?; sb.set_magic(EZFS_MAGIC_NUMBER); Ok(ezfs_sb) diff --git a/fs/rustezfs/inode.rs b/fs/rustezfs/inode.rs index 855533d6b55573..598a161f1978d3 100644 --- a/fs/rustezfs/inode.rs +++ b/fs/rustezfs/inode.rs @@ -1,3 +1,6 @@ +use crate::defs::*; +use core::ops::Deref; +use kernel::transmute::FromBytes; use kernel::uapi::{gid_t, mode_t, uid_t}; #[repr(C)] @@ -13,3 +16,19 @@ pub(crate) struct EzfsInode { file_size: u64, nblocks: u64, } + +#[repr(C)] +pub(crate) struct InodeStore { + inodes: [EzfsInode; EZFS_MAX_INODES], +} + +impl Deref for InodeStore { + type Target = [EzfsInode]; + + fn deref(&self) -> &Self::Target { + &self.inodes + } +} + +// SAFETY: EzfsInode is FromBytes, so array of them is too +unsafe impl FromBytes for InodeStore {} diff --git a/fs/rustezfs/sb.rs b/fs/rustezfs/sb.rs index 58f749e25a0c50..b52702d9a72d42 100644 --- a/fs/rustezfs/sb.rs +++ b/fs/rustezfs/sb.rs @@ -1,4 +1,5 @@ use crate::defs::{EZFS_BLOCK_SIZE, EZFS_MAX_DATA_BLKS, EZFS_MAX_INODES}; +use crate::inode::InodeStore; use crate::RustEzFs; use core::mem::size_of; use kernel::fs::FileSystem; @@ -8,6 +9,7 @@ use kernel::prelude::*; use kernel::sync::Mutex; use kernel::transmute::FromBytes; +#[repr(C)] pub(crate) struct EzfsSuperblockDiskRaw { version: u64, magic: u64, @@ -46,10 +48,15 @@ pub(crate) struct EzfsSuperblock { #[pin] zero_data_blocks: Mutex<[u8; (EZFS_MAX_DATA_BLKS / 32) + 1]>, mapper: inode::Mapper, + inode_store: InodeStore, } impl EzfsSuperblock { - pub fn new(disk_sb: EzfsSuperblockDisk, mapper: inode::Mapper) -> impl PinInit { + pub fn new( + disk_sb: EzfsSuperblockDisk, + inode_store: InodeStore, + mapper: inode::Mapper, + ) -> impl PinInit { pin_init!(Self { version: disk_sb.data.version, magic: disk_sb.data.magic, @@ -58,6 +65,7 @@ impl EzfsSuperblock { free_data_blocks <- new_mutex!(disk_sb.data.free_data_blocks), zero_data_blocks <- new_mutex!(disk_sb.data.zero_data_blocks), mapper, + inode_store, }) } From fc2579fb074a514a8ec803f39691e07298b4444f Mon Sep 17 00:00:00 2001 From: lsig Date: Mon, 24 Nov 2025 17:36:20 -0500 Subject: [PATCH 3/9] [Read Inode store] lets go --- fs/rustezfs/ezfs.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index 52b58c0ed52eaa..bafa1d21ccd925 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -20,6 +20,7 @@ use kernel::types::ARef; use kernel::{c_str, fs, str::CStr}; use core::marker::{PhantomData, Send, Sync}; +use core::mem::size_of; use pin_init::{pin_data, PinInit, PinnedDrop}; struct RustEzFs; @@ -90,13 +91,15 @@ impl FileSystem for RustEzFs { let inode_store = { let offset = EZFS_INODE_STORE_DATABLOCK_NUMBER * EZFS_BLOCK_SIZE; let mapped_inode_store = mapper.mapped_folio(offset.try_into()?)?; - InodeStore::from_bytes_copy(&mapped_inode_store).ok_or(EIO)? + InodeStore::from_bytes_copy(&mapped_inode_store[..size_of::()]) + .ok_or(EIO)? }; let ezfs_sb = KBox::pin_init( EzfsSuperblock::new(disk_sb, inode_store, mapper), GFP_KERNEL, )?; + sb.set_magic(EZFS_MAGIC_NUMBER); Ok(ezfs_sb) From b1bfee08e6e45ee55fdf3747d187a066ce966d3c Mon Sep 17 00:00:00 2001 From: lsig Date: Mon, 24 Nov 2025 17:39:25 -0500 Subject: [PATCH 4/9] [Read Inode Store] Add comment --- fs/rustezfs/sb.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/rustezfs/sb.rs b/fs/rustezfs/sb.rs index b52702d9a72d42..8ad7b841a507cc 100644 --- a/fs/rustezfs/sb.rs +++ b/fs/rustezfs/sb.rs @@ -48,6 +48,7 @@ pub(crate) struct EzfsSuperblock { #[pin] zero_data_blocks: Mutex<[u8; (EZFS_MAX_DATA_BLKS / 32) + 1]>, mapper: inode::Mapper, + // TODO: Add mutex around inode store inode_store: InodeStore, } From 1670ed83385f79c2d21abde9c1499ed61d2b597c Mon Sep 17 00:00:00 2001 From: lsig Date: Tue, 25 Nov 2025 18:49:58 -0500 Subject: [PATCH 5/9] [ezfs] add lookup --- fs/rustezfs/dir.rs | 42 +++++++- fs/rustezfs/ezfs.rs | 102 ++++++++++++++----- fs/rustezfs/inode.rs | 51 +++++++++- fs/rustezfs/sb.rs | 22 ++-- rust/helpers/fs.c | 11 ++ rust/kernel/dentry.rs | 54 +++++++++- rust/kernel/fs.rs | 27 +++++ rust/kernel/fs/file.rs | 8 +- rust/kernel/inode.rs | 164 +++++++++++++++++++++++++++++- rust/kernel/types.rs | 95 +++++++++++++++++ scripts/generate_rust_analyzer.py | 2 +- 11 files changed, 528 insertions(+), 50 deletions(-) diff --git a/fs/rustezfs/dir.rs b/fs/rustezfs/dir.rs index 82f057ea26d29f..ae31a23a624e04 100644 --- a/fs/rustezfs/dir.rs +++ b/fs/rustezfs/dir.rs @@ -1,8 +1,46 @@ -use crate::defs::EZFS_FILENAME_BUF_SIZE; +use crate::defs::{EZFS_FILENAME_BUF_SIZE, EZFS_MAX_CHILDREN}; +use core::ops::Deref; +use kernel::transmute::FromBytes; #[repr(C)] pub(crate) struct EzfsDirEntry { inode_no: u64, active: u8, - filename: [char; EZFS_FILENAME_BUF_SIZE], + filename: [u8; EZFS_FILENAME_BUF_SIZE], } + +impl EzfsDirEntry { + pub(crate) fn inode_no(&self) -> u64 { + self.inode_no + } + + pub(crate) fn is_active(&self) -> bool { + self.active != 0 + } + + pub(crate) fn filename(&self) -> &[u8] { + let len = self + .filename + .iter() + .position(|&b| b == 0) + .unwrap_or(self.filename.len()); + + &self.filename[..len] + } +} + +#[repr(C)] +pub(crate) struct DirEntryStore { + dir_entries: [EzfsDirEntry; EZFS_MAX_CHILDREN], +} + +impl Deref for DirEntryStore { + type Target = [EzfsDirEntry]; + + fn deref(&self) -> &Self::Target { + &self.dir_entries + } +} + +// TODO: Add Safety +unsafe impl FromBytes for DirEntryStore {} diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index bafa1d21ccd925..390198e3f12f24 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -6,7 +6,8 @@ mod defs; mod dir; mod inode; mod sb; -use crate::inode::InodeStore; +use crate::dir::DirEntryStore; +use crate::inode::{EzfsInode, InodeStore}; use crate::sb::{EzfsSuperblock, EzfsSuperblockDisk}; use defs::*; use kernel::dentry; @@ -16,7 +17,7 @@ use kernel::prelude::*; use kernel::sb::{New, SuperBlock, Type as SuperType}; use kernel::time::UNIX_EPOCH; use kernel::transmute::FromBytes; -use kernel::types::ARef; +use kernel::types::{ARef, Locked}; use kernel::{c_str, fs, str::CStr}; use core::marker::{PhantomData, Send, Sync}; @@ -48,25 +49,53 @@ impl RustEzFs { INodeState::Uninitilized(new_inode) => new_inode, }; + let h = &*sb.data(); + let inode_store = { + let offset = EZFS_INODE_STORE_DATABLOCK_NUMBER * EZFS_BLOCK_SIZE; + let mapped_inode_store = h.mapper.mapped_folio(offset.try_into()?)?; + InodeStore::from_bytes_copy(&mapped_inode_store[..size_of::()]) + .ok_or(EIO)? + }; + + let ezfs_inode = inode_store[ino]; + let mode = ezfs_inode.mode(); + + const DIR_IOPS: kernel::inode::Ops = kernel::inode::Ops::new::(); + + let typ = match mode & fs::mode::S_IFMT { + fs::mode::S_IFREG => { + // inode + // .set_fops(file::Ops::generic_ro_file()) + // .set_aops(FILE_AOPS); + Type::Reg + } + fs::mode::S_IFDIR => { + inode.set_iops(DIR_IOPS); + // inode.set_iops(DIR_IOPS).set_fops(DIR_FOPS); + Type::Dir + } + _ => return Err(ENOENT), + }; + inode.init(Params { - typ: Type::Dir, - mode: 0o755, - size: 0, - blocks: 0, - nlink: 2, - uid: 0, - gid: 0, - ctime: UNIX_EPOCH, - mtime: UNIX_EPOCH, - atime: UNIX_EPOCH, - value: (), + typ, + mode: ezfs_inode.mode().try_into()?, + size: ezfs_inode.file_size().try_into()?, + blocks: ezfs_inode.nblocks(), + nlink: ezfs_inode.nlink(), + uid: ezfs_inode.uid(), + gid: ezfs_inode.gid(), + ctime: ezfs_inode.ctime()?, + mtime: ezfs_inode.mtime()?, + atime: ezfs_inode.atime()?, + value: ezfs_inode, }) } } impl FileSystem for RustEzFs { type Data = Pin>; - type INodeData = (); + type INodeData = EzfsInode; const NAME: &'static CStr = c_str!("rustezfs"); const SUPER_TYPE: SuperType = SuperType::BlockDev; @@ -88,17 +117,7 @@ impl FileSystem for RustEzFs { return Err(EINVAL); } - let inode_store = { - let offset = EZFS_INODE_STORE_DATABLOCK_NUMBER * EZFS_BLOCK_SIZE; - let mapped_inode_store = mapper.mapped_folio(offset.try_into()?)?; - InodeStore::from_bytes_copy(&mapped_inode_store[..size_of::()]) - .ok_or(EIO)? - }; - - let ezfs_sb = KBox::pin_init( - EzfsSuperblock::new(disk_sb, inode_store, mapper), - GFP_KERNEL, - )?; + let ezfs_sb = KBox::pin_init(EzfsSuperblock::new(disk_sb, mapper), GFP_KERNEL)?; sb.set_magic(EZFS_MAGIC_NUMBER); @@ -111,6 +130,39 @@ impl FileSystem for RustEzFs { } } +#[vtable] +impl kernel::inode::Operations for RustEzFs { + type FileSystem = Self; + + fn lookup( + parent: &Locked<&INode, kernel::inode::ReadSem>, + dentry: dentry::Unhashed<'_, Self::FileSystem>, + ) -> Result>>> { + let sb = &*parent.super_block(); + let h = sb.data(); + let name = dentry.name(); + let ezfs_dir_inode = parent.data(); + + let dir_entries = { + let offset = ezfs_dir_inode.data_blk_num() * EZFS_BLOCK_SIZE as u64; + let mapped = h.mapper.mapped_folio(offset.try_into()?)?; + DirEntryStore::from_bytes_copy(&mapped[..size_of::()]).ok_or(EIO)? + }; + + let dir_entry = dir_entries + .iter() + .find(|x| x.filename() == name && x.is_active()); + + let inode = if let Some(entry) = dir_entry { + Some(Self::iget(sb, entry.inode_no().try_into()?)?) + } else { + None + }; + + dentry.splice_alias(inode) + } +} + type FsModule = RustEzFsModule; module! { diff --git a/fs/rustezfs/inode.rs b/fs/rustezfs/inode.rs index 598a161f1978d3..642448e1a72922 100644 --- a/fs/rustezfs/inode.rs +++ b/fs/rustezfs/inode.rs @@ -1,22 +1,67 @@ use crate::defs::*; use core::ops::Deref; +use kernel::error::Result; +use kernel::time::Timespec; use kernel::transmute::FromBytes; use kernel::uapi::{gid_t, mode_t, uid_t}; #[repr(C)] +#[derive(Copy, Clone)] pub(crate) struct EzfsInode { mode: mode_t, uid: uid_t, gid: gid_t, - i_attime: i64, /* access time */ - i_mtime: i64, /* modified time */ - i_ctime: i64, /* change time */ + i_atime: i64, /* access time */ + i_mtime: i64, /* modified time */ + i_ctime: i64, /* change time */ nlink: u32, data_blk_num: u64, file_size: u64, nblocks: u64, } +impl EzfsInode { + pub(crate) fn mode(&self) -> mode_t { + self.mode + } + + pub(crate) fn uid(&self) -> uid_t { + self.uid + } + + pub(crate) fn gid(&self) -> gid_t { + self.gid + } + + pub(crate) fn atime(&self) -> Result { + Timespec::new(self.i_atime.try_into()?, 0) + } + + pub(crate) fn mtime(&self) -> Result { + Timespec::new(self.i_mtime.try_into()?, 0) + } + + pub(crate) fn ctime(&self) -> Result { + Timespec::new(self.i_ctime.try_into()?, 0) + } + + pub(crate) fn nlink(&self) -> u32 { + self.nlink + } + + pub(crate) fn data_blk_num(&self) -> u64 { + self.data_blk_num + } + + pub(crate) fn file_size(&self) -> u64 { + self.file_size + } + + pub(crate) fn nblocks(&self) -> u64 { + self.nblocks + } +} + #[repr(C)] pub(crate) struct InodeStore { inodes: [EzfsInode; EZFS_MAX_INODES], diff --git a/fs/rustezfs/sb.rs b/fs/rustezfs/sb.rs index 8ad7b841a507cc..8eea85de484ea3 100644 --- a/fs/rustezfs/sb.rs +++ b/fs/rustezfs/sb.rs @@ -38,24 +38,21 @@ unsafe impl FromBytes for EzfsSuperblockDisk {} #[pin_data] pub(crate) struct EzfsSuperblock { - version: u64, - magic: u64, - disk_blocks: u64, + pub(crate) version: u64, + pub(crate) magic: u64, + pub(crate) disk_blocks: u64, #[pin] - free_inodes: Mutex<[u32; (EZFS_MAX_INODES / 32) + 1]>, + pub(crate) free_inodes: Mutex<[u32; (EZFS_MAX_INODES / 32) + 1]>, #[pin] - free_data_blocks: Mutex<[u32; (EZFS_MAX_DATA_BLKS / 32) + 1]>, + pub(crate) free_data_blocks: Mutex<[u32; (EZFS_MAX_DATA_BLKS / 32) + 1]>, #[pin] - zero_data_blocks: Mutex<[u8; (EZFS_MAX_DATA_BLKS / 32) + 1]>, - mapper: inode::Mapper, - // TODO: Add mutex around inode store - inode_store: InodeStore, + pub(crate) zero_data_blocks: Mutex<[u8; (EZFS_MAX_DATA_BLKS / 32) + 1]>, + pub(crate) mapper: inode::Mapper, } impl EzfsSuperblock { - pub fn new( + pub(crate) fn new( disk_sb: EzfsSuperblockDisk, - inode_store: InodeStore, mapper: inode::Mapper, ) -> impl PinInit { pin_init!(Self { @@ -66,11 +63,10 @@ impl EzfsSuperblock { free_data_blocks <- new_mutex!(disk_sb.data.free_data_blocks), zero_data_blocks <- new_mutex!(disk_sb.data.zero_data_blocks), mapper, - inode_store, }) } - pub fn magic(&self) -> u64 { + pub(crate) fn magic(&self) -> u64 { self.magic } } diff --git a/rust/helpers/fs.c b/rust/helpers/fs.c index 1dec5a12959e32..0b8776aad1e616 100644 --- a/rust/helpers/fs.c +++ b/rust/helpers/fs.c @@ -20,3 +20,14 @@ void rust_helper_i_gid_write(struct inode *inode, gid_t gid) { i_gid_write(inode, gid); } + + +void rust_helper_inode_lock_shared(struct inode *inode) +{ + inode_lock_shared(inode); +} + +void rust_helper_inode_unlock_shared(struct inode *inode) +{ + inode_unlock_shared(inode); +} diff --git a/rust/kernel/dentry.rs b/rust/kernel/dentry.rs index 14941f56b5e97f..4ac78913e3f16a 100644 --- a/rust/kernel/dentry.rs +++ b/rust/kernel/dentry.rs @@ -2,7 +2,7 @@ use core::{marker::PhantomData, mem::ManuallyDrop, ops::Deref, ptr}; use crate::{ bindings, - error::Result, + error::{from_err_ptr, Result}, fs::FileSystem, inode::INode, prelude::*, @@ -45,6 +45,58 @@ impl DEntry { } } +pub struct Unhashed<'a, T: FileSystem + ?Sized>(pub(crate) &'a DEntry); + +impl Unhashed<'_, T> { + /// Splices a disconnected dentry into the tree if one exists. + pub fn splice_alias(self, inode: Option>>) -> Result>>> { + let inode_ptr = if let Some(i) = inode { + // Reject inode if it belongs to a different superblock. + if !ptr::eq(i.super_block(), self.0.super_block()) { + return Err(EINVAL); + } + + ManuallyDrop::new(i).0.get() + } else { + ptr::null_mut() + }; + + // SAFETY: Both inode and dentry are known to be valid. + let ptr = from_err_ptr(unsafe { bindings::d_splice_alias(inode_ptr, self.0 .0.get()) })?; + + // SAFETY: The C API guarantees that if a dentry is returned, the refcount has been + // incremented. + Ok(ptr::NonNull::new(ptr).map(|v| unsafe { ARef::from_raw(v.cast::>()) })) + } + + /// Returns the name of the dentry. + /// + /// Being unhashed guarantees that the name won't change. + pub fn name(&self) -> &[u8] { + // SAFETY: The name is immutable, so it is ok to read it. + let name = unsafe { &*ptr::addr_of!((*self.0 .0.get()).__bindgen_anon_1.d_name) }; + + // This ensures that a `u32` is representable in `usize`. If it isn't, we'll get a build + // break. + const _: usize = 0xffffffff; + + // SAFETY: The union is just allow an easy way to get the `hash` and `len` at once. `len` + // is always valid. + let len = unsafe { name.__bindgen_anon_1.__bindgen_anon_1.len } as usize; + + // SAFETY: The name is immutable, so it is ok to read it. + unsafe { core::slice::from_raw_parts(name.name, len) } + } +} + +impl Deref for Unhashed<'_, T> { + type Target = DEntry; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + pub struct Root(ARef>); impl Root { diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index cbe892708e9ff5..100683adcd2cf7 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -40,6 +40,33 @@ pub type Offset = i64; /// This is C's `pgoff_t`. pub type PageOffset = usize; +/// Contains constants related to Linux file modes. +pub mod mode { + /// A bitmask used to the file type from a mode value. + pub const S_IFMT: u32 = bindings::S_IFMT; + + /// File type constant for block devices. + pub const S_IFBLK: u32 = bindings::S_IFBLK; + + /// File type constant for char devices. + pub const S_IFCHR: u32 = bindings::S_IFCHR; + + /// File type constant for directories. + pub const S_IFDIR: u32 = bindings::S_IFDIR; + + /// File type constant for pipes. + pub const S_IFIFO: u32 = bindings::S_IFIFO; + + /// File type constant for symbolic links. + pub const S_IFLNK: u32 = bindings::S_IFLNK; + + /// File type constant for regular files. + pub const S_IFREG: u32 = bindings::S_IFREG; + + /// File type constant for sockets. + pub const S_IFSOCK: u32 = bindings::S_IFSOCK; +} + /// A file system type. pub trait FileSystem { /// Data associated with each file system instance (super-block). diff --git a/rust/kernel/fs/file.rs b/rust/kernel/fs/file.rs index cf06e73a6da041..a3864cbd66c881 100644 --- a/rust/kernel/fs/file.rs +++ b/rust/kernel/fs/file.rs @@ -10,12 +10,14 @@ use crate::{ bindings, cred::Credential, - error::{code::*, to_result, Error, Result}, + error::{code::*, from_result, to_result, Error, Result}, fmt, + fs::{FileSystem, Offset}, + inode::{self, INode}, sync::aref::{ARef, AlwaysRefCounted}, - types::{NotThreadSafe, Opaque}, + types::{Locked, NotThreadSafe, Opaque}, }; -use core::ptr; +use core::{marker::PhantomData, mem::ManuallyDrop, ptr}; /// Flags associated with a [`File`]. pub mod flags { diff --git a/rust/kernel/inode.rs b/rust/kernel/inode.rs index c5317483143728..0abecb44f6e386 100644 --- a/rust/kernel/inode.rs +++ b/rust/kernel/inode.rs @@ -1,13 +1,17 @@ use core::marker::PhantomData; use core::mem::{ManuallyDrop, MaybeUninit}; +use macros::vtable; + +use crate::dentry::{self, DEntry}; use crate::error::{from_err_ptr, Result}; use crate::folio::{self, Folio}; use crate::fs::PageOffset; -use crate::prelude::{EIO, ERANGE}; +use crate::prelude::{EIO, ENOTSUPP, ERANGE}; +use crate::sb::SuperBlock; use crate::str::CString; use crate::time::Timespec; -use crate::types::AlwaysRefCounted; +use crate::types::{AlwaysRefCounted, Lockable, Locked}; use crate::{block, container_of}; use crate::{ fs::{FileSystem, Offset}, @@ -23,6 +27,21 @@ pub enum INodeState { Uninitilized(New), } +/// Operations implemented by inodes. +#[vtable] +pub trait Operations { + /// File system that these operations are compatible with. + type FileSystem: FileSystem + ?Sized; + + /// Returns the inode corresponding to the directory entry with the given name + fn lookup( + _parent: &Locked<&INode, ReadSem>, + _dentry: dentry::Unhashed<'_, Self::FileSystem>, + ) -> Result>>> { + Err(ENOTSUPP) + } +} + /// A node (inode) in the file index. /// /// Wraps the kernel's `struct inode`. @@ -49,6 +68,34 @@ impl INode { unsafe { &*ptr.cast::() } } + /// Returns the number of the inode. + pub fn ino(&self) -> Ino { + // SAFETY: `i_ino` is immutable, and `self` is guaranteed to be valid by the existence of a + // shared reference (&self) to it. + unsafe { (*self.0.get()).i_ino } + } + + /// Returns the super-block that owns the inode. + pub fn super_block(&self) -> &SuperBlock { + // SAFETY: `i_sb` is immutable, and `self` is guaranteed to be valid by the existence of a + // shared reference (&self) to it. + unsafe { SuperBlock::from_raw((*self.0.get()).i_sb) } + } + + /// Returns the data associated with the inode. + pub fn data(&self) -> &T::INodeData { + // TODO: add UnspecifiedFS + // if T::IS_UNSPECIFIED { + // crate::build_error!("inode data type is unspecified"); + // } + // TODO: Add safety + let outerp = unsafe { container_of!(self.0.get(), WithData, inode) }; + // SAFETY: `self` is guaranteed to be valid by the existence of a shared reference + // (`&self`) to it. Additionally, we know `T::INodeData` is always initialised in an + // `INode`. + unsafe { &*(*outerp).data.as_ptr() } + } + /// Returns a mapper for this inode. /// /// # Safety @@ -116,6 +163,22 @@ unsafe impl AlwaysRefCounted for INode { } } +/// Indicates that the an inode's rw semapahore is locked in read (shared) mode. +pub struct ReadSem; + +unsafe impl Lockable for INode { + fn raw_lock(&self) { + // SAFETY: Since there's a reference to the inode, it must be valid. + unsafe { bindings::inode_lock_shared(self.0.get()) } + } + + unsafe fn unlock(&self) { + // SAFETY: Since there's a reference to the inode, it must be valid. Additionally, the + // safety requirements of this functino require that the inode be locked in read mode. + unsafe { bindings::inode_unlock_shared(self.0.get()) } + } +} + /// Allows mapping the contents of the inode. /// /// # Invariants @@ -259,6 +322,13 @@ impl New { // being called with the `ManuallyDrop` instance created above. Ok(unsafe { ARef::from_raw(manual.0.cast::>()) }) } + + pub fn set_iops(&mut self, iops: Ops) -> &mut Self { + let inode = unsafe { self.0.as_mut() }; + inode.i_op = iops.0; + + self + } } impl Drop for New { @@ -335,3 +405,93 @@ pub struct Params { /// Value to attach to this node. pub value: T, } + +/// Represents inode operations. +pub struct Ops(*const bindings::inode_operations, PhantomData); + +impl Ops { + /// Returns inode operations for symbolic links that are stored in a single page. + pub fn page_symlink_inode() -> Self { + // SAFETY: This is a constant in C, it never changes. + Self( + unsafe { &bindings::page_symlink_inode_operations }, + PhantomData, + ) + } + + /// Returns inode operations for symbolic links that are stored in the `i_lnk` field. + pub fn simple_symlink_inode() -> Self { + // SAFETY: This is a constant in C, it never changes. + Self( + unsafe { &bindings::simple_symlink_inode_operations }, + PhantomData, + ) + } + + /// Creates the inode operations from a type that implements the [`Operations`] trait. + pub const fn new + ?Sized>() -> Self { + struct Table(PhantomData); + impl Table { + const TABLE: bindings::inode_operations = bindings::inode_operations { + lookup: if T::HAS_LOOKUP { + Some(Self::lookup_callback) + } else { + None + }, + get_link: None, + // get_link: if T::HAS_GET_LINK { + // Some(Self::get_link_callback) + // } else { + // None + // }, + permission: None, + get_inode_acl: None, + readlink: None, + create: None, + link: None, + unlink: None, + symlink: None, + mkdir: None, + rmdir: None, + mknod: None, + rename: None, + setattr: None, + getattr: None, + listxattr: None, + fiemap: None, + update_time: None, + atomic_open: None, + tmpfile: None, + get_acl: None, + set_acl: None, + fileattr_set: None, + fileattr_get: None, + get_offset_ctx: None, + }; + + extern "C" fn lookup_callback( + parent_ptr: *mut bindings::inode, + dentry_ptr: *mut bindings::dentry, + _flags: u32, + ) -> *mut bindings::dentry { + // SAFETY: The C API guarantees that `parent_ptr` is a valid inode. + let parent = unsafe { INode::from_raw(parent_ptr) }; + + // SAFETY: The C API guarantees that `dentry_ptr` is a valid dentry. + let dentry = unsafe { DEntry::from_raw(dentry_ptr) }; + + // SAFETY: The C API guarantees that the inode's rw semaphore is locked at least in + // read mode. It does not expect callees to unlock it, so we make the locked object + // manually dropped to avoid unlocking it. + let locked = ManuallyDrop::new(unsafe { Locked::new(parent) }); + + match T::lookup(&locked, dentry::Unhashed(dentry)) { + Err(e) => e.to_ptr(), + Ok(None) => ptr::null_mut(), + Ok(Some(ret)) => ManuallyDrop::new(ret).0.get(), + } + } + } + Self(&Table::::TABLE, PhantomData) + } +} diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs index dc0a02f5c3cfc5..85845eb7d92ab0 100644 --- a/rust/kernel/types.rs +++ b/rust/kernel/types.rs @@ -443,3 +443,98 @@ pub type NotThreadSafe = PhantomData<*mut ()>; /// [`NotThreadSafe`]: type@NotThreadSafe #[allow(non_upper_case_globals)] pub const NotThreadSafe: NotThreadSafe = PhantomData; + +/// A lockable object +/// +/// Implementers must implement [`Lockable::raw_lock`] and [`Lockable::unlock`], and they'll get a +/// `lock` method that returns a guard that can be used to access the locked object and unlocks it +/// automatically when it goes out of scope. +/// +/// `M` is tag that may be used to specify which mode to lock/unlock when an object may be locked +/// may be locked in multiple ways. For example, inodes have `i_lock` and `i_rwsem` so they can be +/// locked in 3 different modes. If an implementer can be locked in only one way, the default unit +/// type can be omitted for brevity. +/// +/// # Safety +/// +/// The [`Lockable::raw_lock`] function must lock the object, otherwise we run into UB if multiple +/// instances believe they have serialised access. +pub unsafe trait Lockable { + /// Locks the object + /// + /// The returned guard will automatically unlock when it goes out of scope. + fn lock(&self) -> Locked<&Self, M> { + self.raw_lock(); + + // SAFETY: the object was locked above, so responsibility to unlock is transferred to the + // `Locked` instance. + unsafe { Locked::new(self) } + } + + /// Locks the object + fn raw_lock(&self); + + /// Unlocks the object + /// + /// # Safety + /// + /// This object must be locked and it must not be used as a `Locked` object before another call + /// to [`Self::raw_lock`]. + unsafe fn unlock(&self); +} + +/// A locked version of an existing type +/// +/// # Invariants +/// +/// The object is locked and the responsibility to unlock belongs to the [`Locked`] instance. +#[repr(transparent)] +pub struct Locked(T, PhantomData) +where + T::Target: Lockable; + +impl Locked +where + T::Target: Lockable, +{ + /// Creates a new instance of [`Locked`]. + /// + /// # Safety + /// + /// The instance `T` must be locked and the responsibility to unlock is transferred to the + /// returned instance of [`Locked`]. + pub unsafe fn new(v: T) -> Self { + Self(v, PhantomData) + } +} + +impl Deref for Locked +where + T::Target: Lockable, +{ + type Target = T::Target; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl Drop for Locked +where + T::Target: Lockable, +{ + fn drop(&mut self) { + // SAFETY: The type invariants guarantee that the object is locked + unsafe { self.0.unlock() } + } +} + +impl + AlwaysRefCounted, M> From> for Locked, M> { + fn from(value: Locked<&T, M>) -> Self { + let aref = ARef::::from(value.deref()); + core::mem::forget(value); + + // SAFETY: We forgot the locked value above, so responsibility is transferred to the new + // instance of [`Locked`]. + unsafe { Locked::new(aref) } + } +} diff --git a/scripts/generate_rust_analyzer.py b/scripts/generate_rust_analyzer.py index fc27f0cca752d3..3ccf27e8180115 100755 --- a/scripts/generate_rust_analyzer.py +++ b/scripts/generate_rust_analyzer.py @@ -152,7 +152,7 @@ def is_root_crate(build_file, target): # Then, the rest outside of `rust/`. # # We explicitly mention the top-level folders we want to cover. - extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers")) + extra_dirs = map(lambda dir: srctree / dir, ("samples", "drivers", "fs")) if external_src is not None: extra_dirs = [external_src] for folder in extra_dirs: From 43048acf77bf0ba24f4acc563ea8ee113aba2f87 Mon Sep 17 00:00:00 2001 From: lsig Date: Tue, 25 Nov 2025 19:38:21 -0500 Subject: [PATCH 6/9] [ezfs] add lookup for ezfs --- fs/rustezfs/ezfs.rs | 47 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index 390198e3f12f24..cc1d83a07c78f1 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -50,14 +50,13 @@ impl RustEzFs { }; let h = &*sb.data(); - let inode_store = { - let offset = EZFS_INODE_STORE_DATABLOCK_NUMBER * EZFS_BLOCK_SIZE; - let mapped_inode_store = h.mapper.mapped_folio(offset.try_into()?)?; - InodeStore::from_bytes_copy(&mapped_inode_store[..size_of::()]) - .ok_or(EIO)? - }; - let ezfs_inode = inode_store[ino]; + let offset = EZFS_INODE_STORE_DATABLOCK_NUMBER * EZFS_BLOCK_SIZE; + let mapped_inode_store = h.mapper.mapped_folio(offset.try_into()?)?; + let inode_store = + InodeStore::from_bytes(&mapped_inode_store[..size_of::()]).ok_or(EIO)?; + + let ezfs_inode = inode_store[ino - 1]; let mode = ezfs_inode.mode(); const DIR_IOPS: kernel::inode::Ops = kernel::inode::Ops::new::(); @@ -142,16 +141,30 @@ impl kernel::inode::Operations for RustEzFs { let h = sb.data(); let name = dentry.name(); let ezfs_dir_inode = parent.data(); - - let dir_entries = { - let offset = ezfs_dir_inode.data_blk_num() * EZFS_BLOCK_SIZE as u64; - let mapped = h.mapper.mapped_folio(offset.try_into()?)?; - DirEntryStore::from_bytes_copy(&mapped[..size_of::()]).ok_or(EIO)? - }; - - let dir_entry = dir_entries - .iter() - .find(|x| x.filename() == name && x.is_active()); + pr_info!("data_blk_num: {:?}", ezfs_dir_inode.data_blk_num()); + + let offset = ezfs_dir_inode + .data_blk_num() + .checked_mul(EZFS_BLOCK_SIZE as u64) + .ok_or(EIO)?; + let mapped = h.mapper.mapped_folio(offset.try_into()?)?; + let dir_entries = + DirEntryStore::from_bytes(&mapped[..size_of::()]).ok_or(EIO)?; + + let dir_entry = dir_entries.iter().find(|x| { + pr_info!( + "filename: {:?} = {}\n", + x.filename(), + core::str::from_utf8(x.filename()).unwrap_or("") + ); + + pr_info!( + "dname: {:?} = {}\n", + name, + core::str::from_utf8(name).unwrap_or("") + ); + x.filename() == name && x.is_active() + }); let inode = if let Some(entry) = dir_entry { Some(Self::iget(sb, entry.inode_no().try_into()?)?) From 58e06411ae6f635db3806c6e758b82726230d90d Mon Sep 17 00:00:00 2001 From: lsig Date: Wed, 26 Nov 2025 14:43:38 -0500 Subject: [PATCH 7/9] [ezfs] ls works??? (checkpoint) --- fs/rustezfs/ezfs.rs | 72 +++++++- rust/kernel/fs.rs | 25 +++ rust/kernel/fs/file.rs | 393 +++++++++++++++++++++++++++++++++++++++-- rust/kernel/inode.rs | 13 +- rust/kernel/lib.rs | 1 + rust/kernel/user.rs | 40 +++++ 6 files changed, 515 insertions(+), 29 deletions(-) create mode 100644 rust/kernel/user.rs diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index cc1d83a07c78f1..8e78e723fa0c7b 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -6,12 +6,12 @@ mod defs; mod dir; mod inode; mod sb; -use crate::dir::DirEntryStore; +use crate::dir::{DirEntryStore, EzfsDirEntry}; use crate::inode::{EzfsInode, InodeStore}; use crate::sb::{EzfsSuperblock, EzfsSuperblockDisk}; use defs::*; use kernel::dentry; -use kernel::fs::{FileSystem, Registration}; +use kernel::fs::{file, File, FileSystem, Registration}; use kernel::inode::{INode, INodeState, Mapper, Params, Type}; use kernel::prelude::*; use kernel::sb::{New, SuperBlock, Type as SuperType}; @@ -59,18 +59,17 @@ impl RustEzFs { let ezfs_inode = inode_store[ino - 1]; let mode = ezfs_inode.mode(); + const DIR_FOPS: file::Ops = file::Ops::new::(); const DIR_IOPS: kernel::inode::Ops = kernel::inode::Ops::new::(); let typ = match mode & fs::mode::S_IFMT { fs::mode::S_IFREG => { - // inode - // .set_fops(file::Ops::generic_ro_file()) - // .set_aops(FILE_AOPS); + inode.set_fops(file::Ops::generic_ro_file()); + // .set_aops(FILE_AOPS); Type::Reg } fs::mode::S_IFDIR => { - inode.set_iops(DIR_IOPS); - // inode.set_iops(DIR_IOPS).set_fops(DIR_FOPS); + inode.set_iops(DIR_IOPS).set_fops(DIR_FOPS); Type::Dir } _ => return Err(ENOENT), @@ -176,6 +175,65 @@ impl kernel::inode::Operations for RustEzFs { } } +#[vtable] +impl file::Operations for RustEzFs { + type FileSystem = Self; + + fn read_dir( + _file: &File, + inode: &Locked<&INode, kernel::inode::ReadSem>, + emitter: &mut file::DirEmitter, + ) -> Result { + let sb = &*inode.super_block(); + let h = sb.data(); + + let index = { + let pos: usize = emitter.pos().try_into().map_err(|_| ENOENT)?; + pr_info!("emitter position: {:?}", pos); + + if pos % size_of::() != 0 { + return Err(ENOENT); + } + + pos / size_of::() + }; + + pr_info!("emitter index: {:?}", index); + + if index >= EZFS_MAX_CHILDREN { + return Ok(()); + } + + let ezfs_dir_inode = inode.data(); + let offset = ezfs_dir_inode + .data_blk_num() + .checked_mul(EZFS_BLOCK_SIZE as u64) + .ok_or(EIO)?; + + let mapped = h.mapper.mapped_folio(offset.try_into()?)?; + let dir_entries = + DirEntryStore::from_bytes(&mapped[..size_of::()]).ok_or(EIO)?; + + let active_entries = dir_entries + .iter() + .skip(index) + .filter(|&entry| entry.is_active()); + + for entry in active_entries { + if !emitter.emit( + size_of::() as i64, + entry.filename(), + entry.inode_no(), + file::DirEntryType::Unknown, + ) { + return Ok(()); + } + } + + Ok(()) + } +} + type FsModule = RustEzFsModule; module! { diff --git a/rust/kernel/fs.rs b/rust/kernel/fs.rs index 100683adcd2cf7..7e23179196268a 100644 --- a/rust/kernel/fs.rs +++ b/rust/kernel/fs.rs @@ -81,6 +81,12 @@ pub trait FileSystem { /// Determines how superblocks for this file system type are keyed. const SUPER_TYPE: sb::Type = sb::Type::Independent; + /// Determines if an implementation doesn't specify the required types. + /// + /// This is meant for internal use only. + #[doc(hidden)] + const IS_UNSPECIFIED: bool = false; + fn fill_super( sb: &mut SuperBlock, mapper: Option>, //TODO: Default type parameter should be UnspecifiedFS @@ -93,6 +99,25 @@ pub trait FileSystem { fn init_root(sb: &SuperBlock) -> Result>; } +/// A file system that is unspecified. +/// +/// Attempting to get super-block or inode data from it will result in a build error. +pub struct UnspecifiedFS; + +impl FileSystem for UnspecifiedFS { + type Data = (); + type INodeData = (); + const NAME: &'static CStr = crate::c_str!("unspecified"); + const IS_UNSPECIFIED: bool = true; + fn fill_super(_: &mut SuperBlock, _: Option) -> Result { + Err(ENOTSUPP) + } + + fn init_root(_: &SuperBlock) -> Result> { + Err(ENOTSUPP) + } +} + /// A file system registration. #[pin_data(PinnedDrop)] pub struct Registration { diff --git a/rust/kernel/fs/file.rs b/rust/kernel/fs/file.rs index a3864cbd66c881..fbdd468b238b10 100644 --- a/rust/kernel/fs/file.rs +++ b/rust/kernel/fs/file.rs @@ -7,15 +7,18 @@ //! C headers: [`include/linux/fs.h`](srctree/include/linux/fs.h) and //! [`include/linux/file.h`](srctree/include/linux/file.h) +use macros::vtable; + use crate::{ bindings, cred::Credential, error::{code::*, from_result, to_result, Error, Result}, fmt, - fs::{FileSystem, Offset}, - inode::{self, INode}, + fs::{FileSystem, Offset, UnspecifiedFS}, + inode::{self, INode, Ino}, sync::aref::{ARef, AlwaysRefCounted}, types::{Locked, NotThreadSafe, Opaque}, + user, }; use core::{marker::PhantomData, mem::ManuallyDrop, ptr}; @@ -180,21 +183,22 @@ pub mod flags { /// * There must not be any active calls to `fdget_pos` on this file that did not take the /// `f_pos_lock` mutex. #[repr(transparent)] -pub struct File { +pub struct File { inner: Opaque, + _p: PhantomData, } // SAFETY: This file is known to not have any active `fdget_pos` calls that did not take the // `f_pos_lock` mutex, so it is safe to transfer it between threads. -unsafe impl Send for File {} +unsafe impl Send for File {} // SAFETY: This file is known to not have any active `fdget_pos` calls that did not take the // `f_pos_lock` mutex, so it is safe to access its methods from several threads in parallel. -unsafe impl Sync for File {} +unsafe impl Sync for File {} // SAFETY: The type invariants guarantee that `File` is always ref-counted. This implementation // makes `ARef` own a normal refcount. -unsafe impl AlwaysRefCounted for File { +unsafe impl AlwaysRefCounted for File { #[inline] fn inc_ref(&self) { // SAFETY: The existence of a shared reference means that the refcount is nonzero. @@ -202,7 +206,7 @@ unsafe impl AlwaysRefCounted for File { } #[inline] - unsafe fn dec_ref(obj: ptr::NonNull) { + unsafe fn dec_ref(obj: ptr::NonNull>) { // SAFETY: To call this method, the caller passes us ownership of a normal refcount, so we // may drop it. The cast is okay since `File` has the same representation as `struct file`. unsafe { bindings::fput(obj.cast().as_ptr()) } @@ -224,13 +228,14 @@ unsafe impl AlwaysRefCounted for File { /// /// [`assume_no_fdget_pos`]: LocalFile::assume_no_fdget_pos #[repr(transparent)] -pub struct LocalFile { +pub struct LocalFile { inner: Opaque, + _p: PhantomData, } // SAFETY: The type invariants guarantee that `LocalFile` is always ref-counted. This implementation // makes `ARef` own a normal refcount. -unsafe impl AlwaysRefCounted for LocalFile { +unsafe impl AlwaysRefCounted for LocalFile { #[inline] fn inc_ref(&self) { // SAFETY: The existence of a shared reference means that the refcount is nonzero. @@ -238,7 +243,7 @@ unsafe impl AlwaysRefCounted for LocalFile { } #[inline] - unsafe fn dec_ref(obj: ptr::NonNull) { + unsafe fn dec_ref(obj: ptr::NonNull>) { // SAFETY: To call this method, the caller passes us ownership of a normal refcount, so we // may drop it. The cast is okay since `LocalFile` has the same representation as // `struct file`. @@ -246,7 +251,7 @@ unsafe impl AlwaysRefCounted for LocalFile { } } -impl LocalFile { +impl LocalFile { /// Constructs a new `struct file` wrapper from a file descriptor. /// /// The file descriptor belongs to the current process, and there might be active local calls @@ -256,7 +261,7 @@ impl LocalFile { /// /// [`assume_no_fdget_pos`]: LocalFile::assume_no_fdget_pos #[inline] - pub fn fget(fd: u32) -> Result, BadFdError> { + pub fn fget(fd: u32) -> Result>, BadFdError> { // SAFETY: FFI call, there are no requirements on `fd`. let ptr = ptr::NonNull::new(unsafe { bindings::fget(fd) }).ok_or(BadFdError)?; @@ -277,7 +282,7 @@ impl LocalFile { /// * The caller must ensure that if there is an active call to `fdget_pos` that did not take /// the `f_pos_lock` mutex, then that call is on the current thread. #[inline] - pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a LocalFile { + pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a LocalFile { // SAFETY: The caller guarantees that the pointer is not dangling and stays valid for the // duration of `'a`. The cast is okay because `LocalFile` is `repr(transparent)`. // @@ -300,7 +305,7 @@ impl LocalFile { /// /// There must not be any active `fdget_pos` calls on the current thread. #[inline] - pub unsafe fn assume_no_fdget_pos(me: ARef) -> ARef { + pub unsafe fn assume_no_fdget_pos(me: ARef>) -> ARef> { // INVARIANT: There are no `fdget_pos` calls on the current thread, and by the type // invariants, if there is a `fdget_pos` call on another thread, then it took the // `f_pos_lock` mutex. @@ -339,9 +344,15 @@ impl LocalFile { // FIXME(read_once): Replace with `read_once` when available on the Rust side. unsafe { core::ptr::addr_of!((*self.as_ptr()).f_flags).read_volatile() } } + + /// Returns the inode associated with the file. + pub fn inode(&self) -> &INode { + // SAFETY: `f_inode` is an immutable field, so it's safe to read it. + unsafe { INode::from_raw((*self.inner.get()).f_inode) } + } } -impl File { +impl File { /// Creates a reference to a [`File`] from a valid pointer. /// /// # Safety @@ -351,7 +362,7 @@ impl File { /// * The caller must ensure that if there are active `fdget_pos` calls on this file, then they /// took the `f_pos_lock` mutex. #[inline] - pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a File { + pub unsafe fn from_raw_file<'a>(ptr: *const bindings::file) -> &'a File { // SAFETY: The caller guarantees that the pointer is not dangling and stays valid for the // duration of `'a`. The cast is okay because `File` is `repr(transparent)`. // @@ -361,10 +372,10 @@ impl File { } // Make LocalFile methods available on File. -impl core::ops::Deref for File { - type Target = LocalFile; +impl core::ops::Deref for File { + type Target = LocalFile; #[inline] - fn deref(&self) -> &LocalFile { + fn deref(&self) -> &LocalFile { // SAFETY: The caller provides a `&File`, and since it is a reference, it must point at a // valid file for the desired duration. // @@ -468,3 +479,347 @@ impl fmt::Debug for BadFdError { f.pad("EBADF") } } + +/// Indicates how to interpret the `offset` argument in [`Operations::seek`]. +#[repr(u32)] +pub enum Whence { + /// `offset` bytes from the start of the file. + Set = bindings::SEEK_SET, + + /// `offset` bytes from the end of the file. + End = bindings::SEEK_END, + + /// `offset` bytes from the current location. + Cur = bindings::SEEK_CUR, + + /// The next location greater than or equal to `offset` that contains data. + Data = bindings::SEEK_DATA, + + /// The next location greater than or equal to `offset` that contains a hole. + Hole = bindings::SEEK_HOLE, +} + +impl TryFrom for Whence { + type Error = crate::error::Error; + + fn try_from(v: i32) -> Result { + match v { + v if v == Self::Set as i32 => Ok(Self::Set), + v if v == Self::End as i32 => Ok(Self::End), + v if v == Self::Cur as i32 => Ok(Self::Cur), + v if v == Self::Data as i32 => Ok(Self::Data), + v if v == Self::Hole as i32 => Ok(Self::Hole), + _ => Err(EDOM), + } + } +} + +/// Generic implementation of [`Operations::seek`]. +pub fn generic_seek( + file: &File, + offset: Offset, + whence: Whence, +) -> Result { + let n = unsafe { bindings::generic_file_llseek(file.inner.get(), offset, whence as i32) }; + if n < 0 { + Err(Error::from_errno(n.try_into()?)) + } else { + Ok(n) + } +} + +/// Operations implemented by files +#[vtable] +pub trait Operations { + /// File system that these operations are compatible with. + type FileSystem: FileSystem + ?Sized; + + /// Reads data from this file into caller's buffer. + fn read( + _file: &File, + _buffer: &mut user::Writer, + _offset: &mut Offset, + ) -> Result { + Err(EINVAL) + } + + /// Seeks the file to the given offset. + fn seek(_file: &File, _offset: Offset, _whence: Whence) -> Result { + Err(EINVAL) + } + + /// Reads directory entries from directory files. + /// + /// [`DirEmitter::pos`] holds the current position of the directory reader. + fn read_dir( + _file: &File, + _inode: &Locked<&INode, inode::ReadSem>, + _emitter: &mut DirEmitter, + ) -> Result { + Err(EINVAL) + } +} + +/// Represents file operations +pub struct Ops { + pub(crate) inner: *const bindings::file_operations, + _p: PhantomData, +} + +impl Ops { + /// Returns file operations for page-cache-based ro files. + pub fn generic_ro_file() -> Self { + // SAFETY: This is a constant in C, it never changes. + Self { + inner: unsafe { &bindings::generic_ro_fops }, + _p: PhantomData, + } + } + /// Creates file operations from a type that implements the [`Operations`] trait. + pub const fn new + ?Sized>() -> Self { + struct Table(PhantomData); + impl Table { + const TABLE: bindings::file_operations = bindings::file_operations { + owner: ptr::null_mut(), + llseek: if T::HAS_SEEK { + Some(Self::seek_callback) + } else { + None + }, + read: if T::HAS_READ { + Some(Self::read_callback) + } else { + None + }, + write: None, + read_iter: None, + write_iter: None, + iopoll: None, + iterate_shared: if T::HAS_READ_DIR { + Some(Self::read_dir_callback) + } else { + None + }, + poll: None, + unlocked_ioctl: None, + fop_flags: 0, + compat_ioctl: None, + mmap: None, + mmap_prepare: None, + open: None, + flush: None, + release: None, + fsync: None, + fasync: None, + lock: None, + get_unmapped_area: None, + check_flags: None, + flock: None, + splice_write: None, + splice_read: None, + splice_eof: None, + setlease: None, + fallocate: None, + show_fdinfo: None, + copy_file_range: None, + remap_file_range: None, + fadvise: None, + uring_cmd: None, + uring_cmd_iopoll: None, + }; + + unsafe extern "C" fn seek_callback( + file_ptr: *mut bindings::file, + offset: bindings::loff_t, + whence: i32, + ) -> bindings::loff_t { + from_result(|| { + // SAFETY: The C API guarantees that `file` is valid for the duration of the + // callback. Since this callback is specifically for filesystem T, we know `T` + // is the right filesystem. + let file = unsafe { File::from_raw_file(file_ptr) }; + T::seek(file, offset, whence.try_into()?) + }) + } + + unsafe extern "C" fn read_callback( + file_ptr: *mut bindings::file, + ptr: *mut core::ffi::c_char, + len: usize, + offset: *mut bindings::loff_t, + ) -> isize { + from_result(|| { + // SAFETY: The C API guarantees that `file` is valid for the duration of the + // callback. Since this callback is specifically for filesystem T, we know `T` + // is the right filesystem. + let file = unsafe { File::from_raw_file(file_ptr) }; + let mut writer = user::Writer::new(ptr, len); + + // SAFETY: The C API guarantees that `offset` is valid for read and write. + let read = T::read(file, &mut writer, unsafe { &mut *offset })?; + Ok(isize::try_from(read)?) + }) + } + + unsafe extern "C" fn read_dir_callback( + file_ptr: *mut bindings::file, + ctx_ptr: *mut bindings::dir_context, + ) -> core::ffi::c_int { + from_result(|| { + // SAFETY: The C API guarantees that `file` is valid for the duration of the + // callback. Since this callback is specifically for filesystem T, we know `T` + // is the right filesystem. + let file = unsafe { File::from_raw_file(file_ptr) }; + + // SAFETY: The C API guarantees that this is the only reference to the + // `dir_context` instance. + let emitter = unsafe { &mut *ctx_ptr.cast::() }; + let orig_pos = emitter.pos(); + + // SAFETY: The C API guarantees that the inode's rw semaphore is locked in read + // mode. It does not expect callees to unlock it, so we make the locked object + // manually dropped to avoid unlocking it. + let locked = ManuallyDrop::new(unsafe { Locked::new(file.inode()) }); + + // Call the module implementation. We ignore errors if directory entries have + // been succesfully emitted: this is because we want users to see them before + // the error. + match T::read_dir(file, &locked, emitter) { + Ok(_) => Ok(0), + Err(e) => { + if emitter.pos() == orig_pos { + Err(e) + } else { + Ok(0) + } + } + } + }) + } + } + Self { + inner: &Table::::TABLE, + _p: PhantomData, + } + } +} + +/// The types of directory entries reported by [`Operations::read_dir`]. +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum DirEntryType { + /// Unknown type. + Unknown = bindings::DT_UNKNOWN, + + /// Named pipe (first-in, first-out) type. + Fifo = bindings::DT_FIFO, + + /// Character device type. + Chr = bindings::DT_CHR, + + /// Directory type. + Dir = bindings::DT_DIR, + + /// Block device type. + Blk = bindings::DT_BLK, + + /// Regular file type. + Reg = bindings::DT_REG, + + /// Symbolic link type. + Lnk = bindings::DT_LNK, + + /// Named unix-domain socket type. + Sock = bindings::DT_SOCK, + + /// White-out type. + Wht = bindings::DT_WHT, +} + +impl From<&inode::Type> for DirEntryType { + fn from(value: &inode::Type) -> Self { + match value { + inode::Type::Fifo => DirEntryType::Fifo, + inode::Type::Chr(_, _) => DirEntryType::Chr, + inode::Type::Dir => DirEntryType::Dir, + inode::Type::Blk(_, _) => DirEntryType::Blk, + inode::Type::Reg => DirEntryType::Reg, + inode::Type::Lnk(_) => DirEntryType::Lnk, + inode::Type::Sock => DirEntryType::Sock, + } + } +} + +impl TryFrom for DirEntryType { + type Error = crate::error::Error; + + fn try_from(v: u32) -> Result { + match v { + v if v == Self::Unknown as u32 => Ok(Self::Unknown), + v if v == Self::Fifo as u32 => Ok(Self::Fifo), + v if v == Self::Chr as u32 => Ok(Self::Chr), + v if v == Self::Dir as u32 => Ok(Self::Dir), + v if v == Self::Blk as u32 => Ok(Self::Blk), + v if v == Self::Reg as u32 => Ok(Self::Reg), + v if v == Self::Lnk as u32 => Ok(Self::Lnk), + v if v == Self::Sock as u32 => Ok(Self::Sock), + v if v == Self::Wht as u32 => Ok(Self::Wht), + _ => Err(EDOM), + } + } +} + +/// Directory entry emitter. +/// +/// This is used in [`Operations::read_dir`] implementations to report the directory entry. +#[repr(transparent)] +pub struct DirEmitter(bindings::dir_context); + +impl DirEmitter { + /// Returns the current position of the emitter. + pub fn pos(&self) -> Offset { + self.0.pos + } + + /// Emits a directory entry. + /// + /// `pos_inc` is the number with which to increment the current position on success. + /// + /// `name` is the name of the entry. + /// + /// `ino` is the inode number of the entry. + /// + /// `etype` is the type of the entry. + /// + /// Returns `false` when the entry could not be emitted, possibly because the user-provided + /// buffer is full. + pub fn emit(&mut self, pos_inc: Offset, name: &[u8], ino: u64, etype: DirEntryType) -> bool { + let Ok(name_len) = i32::try_from(name.len()) else { + return false; + }; + + let Some(actor) = self.0.actor else { + return false; + }; + + let Some(new_pos) = self.0.pos.checked_add(pos_inc) else { + return false; + }; + + // SAFETY: `name` is valid at least for the duration of the `actor` call. + let ret = unsafe { + actor( + &mut self.0, + name.as_ptr(), + name_len, + self.0.pos, + ino, + etype as _, + ) + }; + if ret { + self.0.pos = new_pos; + } + ret + } +} diff --git a/rust/kernel/inode.rs b/rust/kernel/inode.rs index 0abecb44f6e386..f199aa25055753 100644 --- a/rust/kernel/inode.rs +++ b/rust/kernel/inode.rs @@ -6,7 +6,7 @@ use macros::vtable; use crate::dentry::{self, DEntry}; use crate::error::{from_err_ptr, Result}; use crate::folio::{self, Folio}; -use crate::fs::PageOffset; +use crate::fs::{file, PageOffset, UnspecifiedFS}; use crate::prelude::{EIO, ENOTSUPP, ERANGE}; use crate::sb::SuperBlock; use crate::str::CString; @@ -184,8 +184,7 @@ unsafe impl Lockable for INode { /// # Invariants /// /// Mappers are unique per range per inode. -// TODO: should be default UnspecifiedFS -pub struct Mapper { +pub struct Mapper { inode: ARef>, begin: Offset, end: Offset, @@ -329,6 +328,14 @@ impl New { self } + + /// Sets the file operations on this new inode. + pub fn set_fops(&mut self, fops: file::Ops) -> &mut Self { + // SAFETY: By the type invariants, it's ok to modify the inode. + let inode = unsafe { self.0.as_mut() }; + inode.__bindgen_anon_3.i_fop = fops.inner; + self + } } impl Drop for New { diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index cb49fe45e4d66b..3e839d3dd85b9e 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -143,6 +143,7 @@ pub mod tracepoint; pub mod transmute; pub mod types; pub mod uaccess; +pub mod user; pub mod workqueue; pub mod xarray; diff --git a/rust/kernel/user.rs b/rust/kernel/user.rs new file mode 100644 index 00000000000000..38326d2fa7aed0 --- /dev/null +++ b/rust/kernel/user.rs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! User-space related functions. +use crate::error::{code::*, Result}; + +/// A writer to userspace memory. +pub struct Writer { + ptr: *mut u8, + len: usize, +} + +impl Writer { + pub(crate) fn new(ptr: *mut u8, len: usize) -> Self { + Self { ptr, len } + } + + /// Writes all of `data` into user memory. + pub fn write_all(&mut self, data: &[u8]) -> Result { + let len = data.len(); + if len > self.len { + return Err(EFAULT); + } + + let pending = unsafe { + bindings::copy_to_user( + self.ptr.cast::(), + data.as_ptr().cast::(), + data.len().try_into()?, + ) + }; + if pending != 0 { + return Err(EFAULT); + } + + self.ptr = self.ptr.wrapping_add(len); + self.len -= len; + + Ok(()) + } +} From debd5c923ccb15d195f139c5022bdc1d2f7cb75a Mon Sep 17 00:00:00 2001 From: lsig Date: Wed, 26 Nov 2025 15:24:33 -0500 Subject: [PATCH 8/9] [ezfs] move ops def into static fs scope --- fs/rustezfs/ezfs.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index 8e78e723fa0c7b..861900acceb728 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -43,6 +43,9 @@ impl kernel::InPlaceModule for RustEzFsModule { } impl RustEzFs { + const DIR_FOPS: file::Ops = file::Ops::new::(); + const DIR_IOPS: kernel::inode::Ops = kernel::inode::Ops::new::(); + fn iget(sb: &SuperBlock, ino: usize) -> Result>> { let mut inode = match sb.get_or_create_inode(ino)? { INodeState::Existing(inode) => return Ok(inode), @@ -59,9 +62,6 @@ impl RustEzFs { let ezfs_inode = inode_store[ino - 1]; let mode = ezfs_inode.mode(); - const DIR_FOPS: file::Ops = file::Ops::new::(); - const DIR_IOPS: kernel::inode::Ops = kernel::inode::Ops::new::(); - let typ = match mode & fs::mode::S_IFMT { fs::mode::S_IFREG => { inode.set_fops(file::Ops::generic_ro_file()); @@ -69,7 +69,7 @@ impl RustEzFs { Type::Reg } fs::mode::S_IFDIR => { - inode.set_iops(DIR_IOPS).set_fops(DIR_FOPS); + inode.set_iops(Self::DIR_IOPS).set_fops(Self::DIR_FOPS); Type::Dir } _ => return Err(ENOENT), From 857e65466598918c416dc7b54b355168e69e6d1d Mon Sep 17 00:00:00 2001 From: lsig Date: Wed, 26 Nov 2025 16:35:19 -0500 Subject: [PATCH 9/9] [ezfs] fix kernel crash? --- fs/rustezfs/ezfs.rs | 4 +++- rust/kernel/inode.rs | 2 +- rust/kernel/sb.rs | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/fs/rustezfs/ezfs.rs b/fs/rustezfs/ezfs.rs index 861900acceb728..6a56992f559763 100644 --- a/fs/rustezfs/ezfs.rs +++ b/fs/rustezfs/ezfs.rs @@ -64,7 +64,9 @@ impl RustEzFs { let typ = match mode & fs::mode::S_IFMT { fs::mode::S_IFREG => { - inode.set_fops(file::Ops::generic_ro_file()); + inode + .set_iops(Self::DIR_IOPS) + .set_fops(file::Ops::generic_ro_file()); // .set_aops(FILE_AOPS); Type::Reg } diff --git a/rust/kernel/inode.rs b/rust/kernel/inode.rs index f199aa25055753..31920abfaf6ded 100644 --- a/rust/kernel/inode.rs +++ b/rust/kernel/inode.rs @@ -258,6 +258,7 @@ impl New { unsafe { bindings::inode_nohighmem(inode) }; } } + // TODO: Look into this // if let Some(s) = str { // inode.__bindgen_anon_5.i_link = s.into_foreign().cast::().cast_mut(); // } @@ -325,7 +326,6 @@ impl New { pub fn set_iops(&mut self, iops: Ops) -> &mut Self { let inode = unsafe { self.0.as_mut() }; inode.i_op = iops.0; - self } diff --git a/rust/kernel/sb.rs b/rust/kernel/sb.rs index 4594e134b15572..51eb3fc33a3996 100644 --- a/rust/kernel/sb.rs +++ b/rust/kernel/sb.rs @@ -98,10 +98,9 @@ impl SuperBlock { impl SuperBlock { /// Returns the data associated with the superblock. pub fn data(&self) -> ::Borrowed<'_> { - // TODO: Add UnspecifiedFS - // if T::IS_UNSPECIFIED { - // crate::build_error!("super block data type is unspecified"); - // } + if T::IS_UNSPECIFIED { + crate::build_error!("super block data type is unspecified"); + } // SAFETY: This method is only available if the typestate implements `DataInited`, whose // safety requirements include `s_fs_info` being properly initialised.