From 7c392540571aa7a42364627a815de4232b691272 Mon Sep 17 00:00:00 2001 From: Dirinkbottle <2909128143@qq.com> Date: Thu, 18 Dec 2025 12:16:02 +0800 Subject: [PATCH] feat(rsext4): add pure rust ext4 backend support --- api/axfeat/Cargo.toml | 1 + modules/axfs/Cargo.toml | 2 + modules/axfs/src/fs/mod.rs | 7 +- modules/axfs/src/fs/rs_ext4/fs.rs | 182 +++++++ modules/axfs/src/fs/rs_ext4/inode.rs | 685 +++++++++++++++++++++++++++ modules/axfs/src/fs/rs_ext4/mod.rs | 9 + modules/axfs/src/fs/rs_ext4/util.rs | 45 ++ 7 files changed, 930 insertions(+), 1 deletion(-) create mode 100644 modules/axfs/src/fs/rs_ext4/fs.rs create mode 100644 modules/axfs/src/fs/rs_ext4/inode.rs create mode 100644 modules/axfs/src/fs/rs_ext4/mod.rs create mode 100644 modules/axfs/src/fs/rs_ext4/util.rs diff --git a/api/axfeat/Cargo.toml b/api/axfeat/Cargo.toml index 875d302d0a..49dabe7803 100644 --- a/api/axfeat/Cargo.toml +++ b/api/axfeat/Cargo.toml @@ -62,6 +62,7 @@ fs = [ "axruntime/fs", ] # TODO: try to remove "paging" fs-ext4 = ["fs", "axfs/ext4"] +fs-rsext4 = ["fs","axfs/rsext4"] fs-fat = ["fs", "axfs/fat"] fs-times = ["fs", "axfs/times"] diff --git a/modules/axfs/Cargo.toml b/modules/axfs/Cargo.toml index f6faa12093..287d72cc47 100644 --- a/modules/axfs/Cargo.toml +++ b/modules/axfs/Cargo.toml @@ -12,10 +12,12 @@ default = [] use-ramdisk = [] # TODO: init ramdisk fat = ["dep:fatfs"] ext4 = ["dep:lwext4_rust"] +rsext4 = ["dep:rsext4"] times = [] std = ["lwext4_rust?/std"] [dependencies] +rsext4 = { git = "https://github.com/Dirinkbottle/rsext4.git", optional = true } axalloc = { workspace = true } axdriver = { workspace = true, features = ["block"] } axerrno = { workspace = true } diff --git a/modules/axfs/src/fs/mod.rs b/modules/axfs/src/fs/mod.rs index 5eb69733da..08f62fc4e5 100644 --- a/modules/axfs/src/fs/mod.rs +++ b/modules/axfs/src/fs/mod.rs @@ -4,13 +4,18 @@ mod fat; #[cfg(feature = "ext4")] mod ext4; +#[cfg(feature = "rsext4")] +mod rs_ext4; + use axdriver::AxBlockDevice; use axfs_ng_vfs::{Filesystem, VfsResult}; use cfg_if::cfg_if; pub fn new_default(dev: AxBlockDevice) -> VfsResult { cfg_if! { - if #[cfg(feature = "ext4")] { + if #[cfg(feature = "rsext4")]{ + rs_ext4::Rsext4Filesystem::new(dev) } + else if #[cfg(feature = "ext4")] { ext4::Ext4Filesystem::new(dev) } else if #[cfg(feature = "fat")] { Ok(fat::FatFilesystem::new(dev)) diff --git a/modules/axfs/src/fs/rs_ext4/fs.rs b/modules/axfs/src/fs/rs_ext4/fs.rs new file mode 100644 index 0000000000..b6d4d7e05b --- /dev/null +++ b/modules/axfs/src/fs/rs_ext4/fs.rs @@ -0,0 +1,182 @@ +#![cfg(feature = "rsext4")] + +use alloc::{sync::Arc}; +use core::cell::OnceCell; +use axdriver::prelude::BlockDriverOps; +use alloc::vec; +use axdriver::AxBlockDevice; +use axfs_ng_vfs::{ + path::MAX_NAME_LEN, DirEntry, DirNode, Filesystem, FilesystemOps, Reference, StatFs, + VfsResult, +}; +use kspin::{SpinNoPreempt as Mutex, SpinNoPreemptGuard as MutexGuard}; + +use rsext4::ext4_backend::ext4::Ext4FileSystem; +use rsext4::{BlockDevice as RsBlockDevice, BlockDevError, BlockDevResult, Jbd2Dev}; + +use super::{inode::Rsext4Node, util::{into_vfs_err, into_vfs_fs_err}}; + +const EXT4_ROOT_INO: u32 = 2; + +pub struct Rsext4Disk(pub AxBlockDevice); + +impl RsBlockDevice for Rsext4Disk { + fn write(&mut self, buffer: &[u8], block_id: u32, count: u32) -> BlockDevResult<()> { + let dev_bs = self.0.block_size(); + let fs_bs = rsext4::BLOCK_SIZE; + if fs_bs % dev_bs != 0 { + return Err(BlockDevError::InvalidBlockSize { + size: dev_bs, + expected: fs_bs, + }); + } + let ratio = (fs_bs / dev_bs) as u64; + let total_bytes = (count as usize) + .saturating_mul(fs_bs) + .min(buffer.len()); + + for (i, fs_block) in buffer[..total_bytes].chunks(fs_bs).enumerate() { + for (j, dev_chunk) in fs_block.chunks(dev_bs).enumerate() { + let mut blk = vec![0u8; dev_bs]; + blk[..dev_chunk.len()].copy_from_slice(dev_chunk); + let dev_block_id = (block_id as u64 + i as u64) * ratio + j as u64; + self.0 + .write_block(dev_block_id, &blk) + .map_err(|_| BlockDevError::WriteError)?; + } + } + Ok(()) + } + + fn read(&mut self, buffer: &mut [u8], block_id: u32, count: u32) -> BlockDevResult<()> { + let dev_bs = self.0.block_size(); + let fs_bs = rsext4::BLOCK_SIZE; + if fs_bs % dev_bs != 0 { + return Err(BlockDevError::InvalidBlockSize { + size: dev_bs, + expected: fs_bs, + }); + } + let ratio = (fs_bs / dev_bs) as u64; + let total_bytes = (count as usize) + .saturating_mul(fs_bs) + .min(buffer.len()); + + for (i, fs_block) in buffer[..total_bytes].chunks_mut(fs_bs).enumerate() { + for (j, dev_chunk) in fs_block.chunks_mut(dev_bs).enumerate() { + let mut blk = vec![0u8; dev_bs]; + let dev_block_id = (block_id as u64 + i as u64) * ratio + j as u64; + self.0 + .read_block(dev_block_id, &mut blk) + .map_err(|_| BlockDevError::ReadError)?; + dev_chunk.copy_from_slice(&blk[..dev_chunk.len()]); + } + } + Ok(()) + } + + fn open(&mut self) -> BlockDevResult<()> { + Ok(()) + } + + fn close(&mut self) -> BlockDevResult<()> { + Ok(()) + } + + fn total_blocks(&self) -> u64 { + let dev_bs = self.0.block_size(); + let fs_bs = rsext4::BLOCK_SIZE; + if fs_bs % dev_bs != 0 { + return 0; + } + let ratio = (fs_bs / dev_bs) as u64; + self.0.num_blocks() / ratio + } + + fn block_size(&self) -> u32 { + rsext4::BLOCK_SIZE as u32 + } + + fn flush(&mut self) -> BlockDevResult<()> { + self.0.flush().map_err(|_| BlockDevError::WriteError) + } +} + +pub struct Rsext4FilesystemInner { + pub fs: Ext4FileSystem, + pub dev: Jbd2Dev, +} + +pub struct Rsext4Filesystem { + inner: Mutex, + root_dir: OnceCell, +} + +impl Rsext4Filesystem { + pub fn new(dev: AxBlockDevice) -> VfsResult { + let mut jbd = Jbd2Dev::initial_jbd2dev(0, Rsext4Disk(dev), true); + let fs = Ext4FileSystem::mount(&mut jbd).map_err(into_vfs_fs_err)?; + + let fs = Arc::new(Self { + inner: Mutex::new(Rsext4FilesystemInner { fs, dev: jbd }), + root_dir: OnceCell::new(), + }); + + let _ = fs.root_dir.set(DirEntry::new_dir( + |this| DirNode::new(Rsext4Node::new(fs.clone(), "/", EXT4_ROOT_INO, Some(this))), + Reference::root(), + )); + + Ok(Filesystem::new(fs)) + } + + pub(crate) fn lock<'a>(&'a self) -> MutexGuard<'a,Rsext4FilesystemInner> { + self.inner.lock() + } +} + +unsafe impl Send for Rsext4Filesystem {} +unsafe impl Sync for Rsext4Filesystem {} + +impl FilesystemOps for Rsext4Filesystem { + fn name(&self) -> &str { + "ext4" + } + + fn root_dir(&self) -> DirEntry { + self.root_dir.get().unwrap().clone() + } + + fn stat(&self) -> VfsResult { + let inner = self.lock(); + let sb = &inner.fs.superblock; + Ok(StatFs { + fs_type: 0xef53, + block_size: rsext4::BLOCK_SIZE as _, + blocks: sb.blocks_count() as _, + blocks_free: sb.free_blocks_count() as _, + blocks_available: sb.free_blocks_count() as _, + file_count: sb.s_inodes_count as _, + free_file_count: sb.s_free_inodes_count as _, + name_length: MAX_NAME_LEN as _, + fragment_size: 0, + mount_flags: 0, + }) + } + + fn flush(&self) -> VfsResult<()> { + let fs = &mut self.lock().fs; + let block_dev = &mut self.lock().dev; + + fs.bitmap_cache.flush_all(block_dev).map_err(into_vfs_err)?; + fs.inodetable_cahce.flush_all(block_dev).map_err(into_vfs_err)?; + fs.datablock_cache.flush_all(block_dev).map_err(into_vfs_err)?; + + // 4. Update superblock + fs.sync_superblock(block_dev).map_err(into_vfs_err)?; + + // Write back group descriptors + fs.sync_group_descriptors(block_dev).map_err(into_vfs_err) + } +} + diff --git a/modules/axfs/src/fs/rs_ext4/inode.rs b/modules/axfs/src/fs/rs_ext4/inode.rs new file mode 100644 index 0000000000..a1514242f9 --- /dev/null +++ b/modules/axfs/src/fs/rs_ext4/inode.rs @@ -0,0 +1,685 @@ +#![cfg(feature = "rsext4")] + +use alloc::{borrow::ToOwned, string::String, sync::Arc, vec::Vec}; +use core::{any::Any, task::Context, time::Duration}; + +use axfs_ng_vfs::{ + DirEntry, DirEntrySink, DirNode, DirNodeOps, FileNode, FileNodeOps, FilesystemOps, Metadata, + MetadataUpdate, NodeFlags, NodeOps, NodePermission, NodeType, Reference, VfsError, VfsResult, + WeakDirEntry, DeviceId, +}; +use axpoll::{IoEvents, Pollable}; + +use rsext4::ext4_backend::entries::{Ext4DirEntry2, classic_dir}; +use rsext4::ext4_backend::disknode::{Ext4Extent, Ext4Inode}; + +use super::fs::{Rsext4Disk, Rsext4Filesystem}; +use super::util::{into_vfs_err, into_vfs_type}; + +fn read_symlink_target( + fs: &mut rsext4::Ext4FileSystem, + dev: &mut rsext4::Jbd2Dev, + inode: &mut Ext4Inode, +) -> VfsResult> { + let size = inode.size() as usize; + if size == 0 { + return Ok(Vec::new()); + } + if size <= 60 { + let mut raw = [0u8; 60]; + for (i, word) in inode.i_block.iter().take(15).enumerate() { + raw[i * 4..i * 4 + 4].copy_from_slice(&word.to_le_bytes()); + } + return Ok(raw[..size].to_vec()); + } + + let block_bytes = rsext4::BLOCK_SIZE; + let total_blocks = size.div_ceil(block_bytes); + let mut buf = Vec::with_capacity(size); + if inode.have_extend_header_and_use_extend() { + let blocks = rsext4::ext4_backend::loopfile::resolve_inode_block_allextend(fs, dev, inode) + .map_err(into_vfs_err)?; + for phys in blocks { + let cached = fs + .datablock_cache + .get_or_load(dev, phys.1) + .map_err(into_vfs_err)?; + buf.extend_from_slice(&cached.data[..block_bytes]); + if buf.len() >= size { + break; + } + } + } else { + for lbn in 0..total_blocks { + let phys = match rsext4::ext4_backend::loopfile::resolve_inode_block(dev, inode, lbn as u32) + .map_err(into_vfs_err)? + { + Some(b) => b, + None => break, + }; + let cached = fs + .datablock_cache + .get_or_load(dev, phys as u64) + .map_err(into_vfs_err)?; + buf.extend_from_slice(&cached.data[..block_bytes]); + } + } + buf.truncate(size); + Ok(buf) +} + +pub struct Rsext4Node { + pub fs: Arc, + pub path: String, + pub ino: u32, + pub this: Option, +} + +impl Rsext4Node { + pub(crate) fn new( + fs: Arc, + path: impl Into, + ino: u32, + this: Option, + ) -> Arc { + Arc::new(Self { + fs, + path: path.into(), + ino, + this, + }) + } + + fn child_path(&self, name: &str) -> String { + if self.path == "/" { + let mut p = String::from("/"); + p.push_str(name); + p + } else { + let mut p = self.path.clone(); + p.push('/'); + p.push_str(name); + p + } + } + + fn create_entry(&self, name: &str, child_path: String, child_ino: u32) -> VfsResult { + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + let inode = fs.get_inode_by_num(dev, child_ino).map_err(into_vfs_err)?; + let node_type = into_vfs_type(&inode); + + let reference = Reference::new( + self.this.as_ref().and_then(WeakDirEntry::upgrade), + name.to_owned(), + ); + + Ok(if node_type == NodeType::Directory { + DirEntry::new_dir( + |this| DirNode::new(Rsext4Node::new(self.fs.clone(), child_path, child_ino, Some(this))), + reference, + ) + } else { + DirEntry::new_file( + FileNode::new(Rsext4Node::new(self.fs.clone(), child_path, child_ino, None)), + node_type, + reference, + ) + }) + } + + fn lookup_locked(&self, name: &str) -> VfsResult { + let child_path = self.child_path(name); + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + let res = rsext4::get_inode_with_num(fs, dev, &child_path) + .map_err(into_vfs_err)? + .ok_or(VfsError::NotFound)?; + let _ = inner; + self.create_entry(name, child_path, res.0) + } +} + +impl NodeOps for Rsext4Node { + fn inode(&self) -> u64 { + self.ino as _ + } + + fn metadata(&self) -> VfsResult { + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + let inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?; + let node_type = into_vfs_type(&inode); + + Ok(Metadata { + inode: self.ino as _, + device: 0, + nlink: inode.i_links_count as _, + mode: NodePermission::from_bits_truncate((inode.i_mode & 0o777) as u16), + node_type, + uid: inode.i_uid as _, + gid: inode.i_gid as _, + size: inode.size(), + block_size: rsext4::BLOCK_SIZE as _, + blocks: ((inode.blocks_count() as u64) * 512) / (rsext4::BLOCK_SIZE as u64), + rdev: DeviceId::default(), + atime: Duration::from_secs(inode.i_atime as _), + mtime: Duration::from_secs(inode.i_mtime as _), + ctime: Duration::from_secs(inode.i_ctime as _), + }) + } + + fn update_metadata(&self, _update: MetadataUpdate) -> VfsResult<()> { + // rsext4-ext4 backend does not fully support updating inode timestamps/mode/owner yet. + // Return success to avoid breaking common userspace tools (e.g. apk) that treat + // utimensat/chmod/chown failures as fatal. + Ok(()) + } + + fn len(&self) -> VfsResult { + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + let inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?; + Ok(inode.size()) + } + + fn filesystem(&self) -> &dyn FilesystemOps { + &*self.fs + } + + fn sync(&self, _data_only: bool) -> VfsResult<()> { + self.fs.flush() + } + + fn into_any(self: Arc) -> Arc { + self + } + + fn flags(&self) -> NodeFlags { + NodeFlags::BLOCKING | NodeFlags::NON_CACHEABLE + } +} + +impl FileNodeOps for Rsext4Node { + fn read_at(&self, buf: &mut [u8], offset: u64) -> VfsResult { + + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + let mut inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?; + let file_size = inode.size() as usize; + + let off = offset as usize; + if off >= file_size { + return Ok(0); + } + let n = core::cmp::min(buf.len(), file_size - off); + + if inode.is_symlink() { + let data = read_symlink_target(fs, dev, &mut inode)?; + let n2 = core::cmp::min(n, data.len().saturating_sub(off)); + if n2 == 0 { + return Ok(0); + } + buf[..n2].copy_from_slice(&data[off..off + n2]); + + + return Ok(n2); + } + + let block_bytes = rsext4::BLOCK_SIZE; + let start_lbn = off / block_bytes; + let end_lbn = (off + n - 1) / block_bytes; + + let mut copied = 0usize; + let mut inode_for_map = inode.clone(); + for lbn in start_lbn..=end_lbn { + let phys = match rsext4::ext4_backend::loopfile::resolve_inode_block( + dev, + &mut inode_for_map, + lbn as u32, + ) + .map_err(into_vfs_err)? + { + Some(b) => b as u64, + None => { + // sparse/hole block: fill zeros + let dst_off = lbn * block_bytes; + let dst_start = off.max(dst_off) - off; + let dst_end = (off + n).min(dst_off + block_bytes) - off; + buf[dst_start..dst_end].fill(0); + continue; + } + }; + + let cached = fs + .datablock_cache + .get_or_load(dev, phys) + .map_err(into_vfs_err)?; + + let blk = &cached.data[..block_bytes]; + let src_off = if lbn == start_lbn { off % block_bytes } else { 0 }; + let to_copy = core::cmp::min(block_bytes - src_off, n - copied); + buf[copied..copied + to_copy].copy_from_slice(&blk[src_off..src_off + to_copy]); + copied += to_copy; + if copied >= n { + break; + } + } + + + Ok(copied) + } + + fn write_at(&self, buf: &[u8], offset: u64) -> VfsResult { + + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + + rsext4::write_file_with_ino(dev, fs, self.ino, offset, buf) + .map_err(into_vfs_err)?; + + Ok(buf.len()) + } + + fn append(&self, buf: &[u8]) -> VfsResult<(usize, u64)> { + + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + let inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?; + let offset = inode.size(); + rsext4::write_file_with_ino(dev, fs, self.ino, offset, buf) + .map_err(into_vfs_err)?; + + + Ok((buf.len(), offset + buf.len() as u64)) + } + + fn set_len(&self, len: u64) -> VfsResult<()> { + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + rsext4::truncate_with_ino(dev, fs, self.ino, len).map_err(into_vfs_err) + } + + fn set_symlink(&self, target: &str) -> VfsResult<()> { + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + + let mut inode = fs.get_inode_by_num(dev, self.ino).map_err(into_vfs_err)?; + // If the inode currently has blocks (e.g. created as regular file), free them first. + // This avoids leaks and keeps metadata consistent. + if inode.blocks_count() != 0 { + if inode.have_extend_header_and_use_extend() { + let used_blocks = rsext4::ext4_backend::loopfile::resolve_inode_block_allextend( + fs, + dev, + &mut inode, + ) + .map_err(into_vfs_err)?; + for blk in used_blocks { + fs.free_block(dev, blk.1).map_err(into_vfs_err)?; + } + } else { + // Only direct pointers are supported by this backend. + for w in inode.i_block.iter_mut().take(12) { + let phys = *w as u64; + if phys != 0 { + fs.free_block(dev, phys).map_err(into_vfs_err)?; + } + *w = 0; + } + } + } + + let target_bytes = target.as_bytes(); + let target_len = target_bytes.len() as u64; + + // Convert inode to symlink + inode.i_mode = Ext4Inode::S_IFLNK | 0o777; + inode.i_flags &= !Ext4Inode::EXT4_EXTENTS_FL;//symbol链接不允许存在extent标志,但是可以使用extendheader + inode.i_size_lo = (target_len & 0xffff_ffff) as u32; + inode.i_size_high = (target_len >> 32) as u32; + inode.i_blocks_lo = 0; + inode.l_i_blocks_high = 0; + inode.i_block = [0; 15]; + + if target_len == 0 { + // empty + } else if target_len as usize <= 60 { + // fast symlink stored inline in i_block + let mut raw = [0u8; 60]; + raw[..target_len as usize].copy_from_slice(target_bytes); + for (i, word) in inode.i_block.iter_mut().take(15).enumerate() { + *word = u32::from_le_bytes([ + raw[i * 4], + raw[i * 4 + 1], + raw[i * 4 + 2], + raw[i * 4 + 3], + ]); + } + } else { + // slow symlink stored in data blocks + // Prefer extent mode when available. + if fs.superblock.has_extents() { + inode.i_flags |= Ext4Inode::EXT4_EXTENTS_FL; + inode.write_extend_header(); + + let block_bytes = rsext4::BLOCK_SIZE as u64; + let total_blocks = target_len.div_ceil(block_bytes); + let mut new_blocks_map: Vec<(u32, u64)> = Vec::new(); + + for lbn in 0..(total_blocks as u32) { + let phys = fs.alloc_block(dev).map_err(into_vfs_err)?; + let start = (lbn as usize) * rsext4::BLOCK_SIZE; + let end = core::cmp::min(start + rsext4::BLOCK_SIZE, target_bytes.len()); + fs.datablock_cache.modify_new(phys, |data| { + data.fill(0); + data[..(end - start)].copy_from_slice(&target_bytes[start..end]); + }); + new_blocks_map.push((lbn, phys)); + } + + let mut tree = rsext4::ext4_backend::extents_tree::ExtentTree::new(&mut inode); + let mut idx = 0usize; + while idx < new_blocks_map.len() { + let (start_lbn, start_phys) = new_blocks_map[idx]; + let mut run_len: u32 = 1; + let mut last_lbn = start_lbn; + let mut last_phys = start_phys; + idx += 1; + while idx < new_blocks_map.len() { + let (cur_lbn, cur_phys) = new_blocks_map[idx]; + if cur_lbn == last_lbn + 1 && cur_phys == last_phys + 1 { + run_len = run_len.saturating_add(1); + last_lbn = cur_lbn; + last_phys = cur_phys; + idx += 1; + } else { + break; + } + } + let ext = Ext4Extent::new( + start_lbn, + start_phys, + run_len as u16, + ); + tree.insert_extent(fs, ext, dev).map_err(into_vfs_err)?; + } + + // ExtentTree insertion may allocate extra metadata blocks (index/leaf nodes) and + // already accounts them into inode.i_blocks*. Do not overwrite it. + let cur = inode.blocks_count(); + let data_sectors = total_blocks.saturating_mul((rsext4::BLOCK_SIZE / 512) as u64); + let newv = cur.saturating_add(data_sectors); + inode.i_blocks_lo = (newv & 0xffff_ffff) as u32; + inode.l_i_blocks_high = ((newv >> 32) & 0xffff) as u16; + } else { + // Fallback: direct blocks only (max 12) + let block_bytes = rsext4::BLOCK_SIZE as u64; + let total_blocks = target_len.div_ceil(block_bytes); + if total_blocks > 12 { + return Err(VfsError::Unsupported); + } + for lbn in 0..(total_blocks as u32) { + let phys = fs.alloc_block(dev).map_err(into_vfs_err)?; + let start = (lbn as usize) * rsext4::BLOCK_SIZE; + let end = core::cmp::min(start + rsext4::BLOCK_SIZE, target_bytes.len()); + fs.datablock_cache.modify_new(phys, |data| { + data.fill(0); + data[..(end - start)].copy_from_slice(&target_bytes[start..end]); + }); + inode.i_block[lbn as usize] = phys as u32; + } + let iblocks_used = total_blocks.saturating_mul((rsext4::BLOCK_SIZE / 512) as u64); + inode.i_blocks_lo = (iblocks_used & 0xffff_ffff) as u32; + inode.l_i_blocks_high = ((iblocks_used >> 32) & 0xffff) as u16; + } + } + + fs.modify_inode(dev, self.ino, |td| { + *td = inode; + }) + .map_err(into_vfs_err)?; + + Ok(()) + } +} + +impl Pollable for Rsext4Node { + fn poll(&self) -> IoEvents { + IoEvents::IN | IoEvents::OUT + } + + fn register(&self, _context: &mut Context<'_>, _events: IoEvents) {} +} + +impl DirNodeOps for Rsext4Node { + fn read_dir(&self, offset: u64, sink: &mut dyn DirEntrySink) -> VfsResult { + + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + let dir_inode = fs + .get_inode_by_num(dev, self.ino) + .map_err(into_vfs_err)?; + if !dir_inode.is_dir() { + return Err(VfsError::NotADirectory); + } + + let total_size = dir_inode.size() as usize; + let block_bytes = rsext4::BLOCK_SIZE; + let total_blocks = if total_size == 0 { + 0 + } else { + total_size.div_ceil(block_bytes) + }; + + let mut entries: alloc::vec::Vec<(String, u32)> = alloc::vec::Vec::new(); + fn scan_block( + fs: &mut rsext4::Ext4FileSystem, + dev: &mut rsext4::Jbd2Dev, + block_bytes: usize, + phys_u64: u64, + entries: &mut alloc::vec::Vec<(String, u32)>, + ) -> VfsResult<()> { + let cached = fs.datablock_cache.get_or_load(dev, phys_u64).map_err(into_vfs_err)?; + let block_data = &cached.data[..block_bytes]; + for info in classic_dir::list_entries(block_data) { + let name = core::str::from_utf8(info.name) + .map_err(|_| VfsError::InvalidData)? + .to_owned(); + if name == "." || name == ".." { + continue; + } + entries.push((name, info.inode as u32)); + } + Ok(()) + } + + if dir_inode.have_extend_header_and_use_extend() { + let blocks = rsext4::ext4_backend::loopfile::resolve_inode_block_allextend( + fs, + dev, + &mut dir_inode.clone(), + ) + .map_err(into_vfs_err)?; + for phys in blocks { + scan_block(fs, dev, block_bytes, phys.1, &mut entries)?; + } + } else { + for lbn in 0..total_blocks { + let phys = match rsext4::ext4_backend::loopfile::resolve_inode_block( + + dev, + &mut dir_inode.clone(), + lbn as u32, + ) + .map_err(into_vfs_err)? { + Some(b) => b, + None => continue, + }; + scan_block(fs, dev, block_bytes, phys as u64, &mut entries)?; + } + } + + let start = offset as usize; + if start >= entries.len() { + return Ok(0); + } + + let mut count = 0; + for (idx, (name, ino)) in entries.into_iter().enumerate().skip(start) { + let inode = fs.get_inode_by_num(dev, ino).map_err(into_vfs_err)?; + let node_type = into_vfs_type(&inode); + let next_off = (idx + 1) as u64; + if !sink.accept(&name, ino as u64, node_type, next_off) { + break; + } + count += 1; + } + + + Ok(count) + } + + fn lookup(&self, name: &str) -> VfsResult { + self.lookup_locked(name) + } + + fn create( + &self, + name: &str, + node_type: NodeType, + _permission: NodePermission, + ) -> VfsResult { + let child_path = self.child_path(name); + let mut inner = self.fs.lock(); + + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + + if rsext4::get_inode_with_num(fs, dev, &child_path) + .map_err(into_vfs_err)? + .is_some() + { + return Err(VfsError::AlreadyExists); + } + + match node_type { + NodeType::Directory => { + let Some((ino, _inode)) = rsext4::mkdir_with_ino(dev, fs, &child_path) else { + error!("rsext4 create mkdir failed path={}", child_path); + return Err(VfsError::Io); + }; + let _ = inner; + return self.create_entry(name, child_path, ino); + } + NodeType::RegularFile => { + let Some((ino, _inode)) = rsext4::mkfile_with_ino(dev, fs, &child_path, None,None) else { + error!("rsext4 create mkfile failed path={}", child_path); + return Err(VfsError::Io); + }; + let _ = inner; + return self.create_entry(name, child_path, ino); + } + NodeType::Symlink => { + // Create an empty inode first, then FileNodeOps::set_symlink will convert it + // into a symlink and store the target. + let Some((ino, _inode)) = rsext4::mkfile_with_ino(dev, fs, &child_path, None,Some(Ext4DirEntry2::EXT4_FT_SYMLINK)) else { + error!("rsext4 create symlink(mkfile) failed path={}", child_path); + return Err(VfsError::Io); + }; + let _ = inner; + return self.create_entry(name, child_path, ino); + } + _ => return Err(VfsError::Unsupported), + } + + + } + + fn link(&self, _name: &str, _node: &DirEntry) -> VfsResult { + let target = _node.downcast::()?; + if !Arc::ptr_eq(&self.fs, &target.fs) { + return Err(VfsError::InvalidInput); + } + + let link_path = self.child_path(_name); + let linked_path = target.path.clone(); + + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + + if rsext4::get_inode_with_num(fs, dev, &link_path) + .map_err(into_vfs_err)? + .is_some() + { + return Err(VfsError::AlreadyExists); + } + + // rsext4 hardlink: create a new dir entry at link_path pointing to linked_path's inode. + rsext4::link(fs, dev, &link_path, &linked_path); + + let _ = inner; + self.lookup_locked(_name) + } + + fn unlink(&self, _name: &str) -> VfsResult<()> { + let child_path = self.child_path(_name); + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + + if rsext4::get_inode_with_num(fs, dev, &child_path) + .map_err(into_vfs_err)? + .is_none() + { + return Err(VfsError::NotFound); + } + + rsext4::unlink(fs, dev, &child_path); + + if rsext4::get_inode_with_num(fs, dev, &child_path) + .map_err(into_vfs_err)? + .is_some() + { + return Err(VfsError::Io); + } + + Ok(()) + } + + fn rename(&self, _src_name: &str, _dst_dir: &DirNode, _dst_name: &str) -> VfsResult<()> { + let dst_node = _dst_dir.downcast::()?; + if !Arc::ptr_eq(&self.fs, &dst_node.fs) { + return Err(VfsError::InvalidInput); + } + + let src_path = self.child_path(_src_name); + let dst_path = dst_node.child_path(_dst_name); + + let mut inner = self.fs.lock(); + let inner = &mut *inner; + let (fs, dev) = (&mut inner.fs, &mut inner.dev); + + if let Err(e) = rsext4::rename(dev, fs, &src_path, &dst_path).map_err(into_vfs_err) { + error!( + "rsext4 rename failed: src={} dst={} err={:?}", + src_path, + dst_path, + e + ); + return Err(e); + } + Ok(()) + } +} diff --git a/modules/axfs/src/fs/rs_ext4/mod.rs b/modules/axfs/src/fs/rs_ext4/mod.rs new file mode 100644 index 0000000000..077ee7a021 --- /dev/null +++ b/modules/axfs/src/fs/rs_ext4/mod.rs @@ -0,0 +1,9 @@ +#![deny(dead_code)] +#![deny(unused)] +#![deny(warnings)] + +mod fs; +mod inode; +mod util; + +pub use fs::Rsext4Filesystem; \ No newline at end of file diff --git a/modules/axfs/src/fs/rs_ext4/util.rs b/modules/axfs/src/fs/rs_ext4/util.rs new file mode 100644 index 0000000000..842f7ad0f0 --- /dev/null +++ b/modules/axfs/src/fs/rs_ext4/util.rs @@ -0,0 +1,45 @@ + +use axfs_ng_vfs::{NodeType, VfsError}; + +use rsext4::ext4_backend::disknode::Ext4Inode; +use rsext4::BlockDevError; +use rsext4::RSEXT4Error; + +pub fn into_vfs_err(err: BlockDevError) -> VfsError { + use BlockDevError::*; + match err { + InvalidInput | BufferTooSmall { .. } | AlignmentError { .. } => VfsError::InvalidInput, + BlockOutOfRange { .. } | InvalidBlockSize { .. } => VfsError::InvalidData, + DeviceNotOpen | DeviceClosed | DeviceBusy | Timeout => VfsError::Io, + ReadOnly => VfsError::PermissionDenied, + NoSpace => VfsError::StorageFull, + Unsupported => VfsError::Unsupported, + PermissionDenied => VfsError::PermissionDenied, + Corrupted | ChecksumError => VfsError::InvalidData, + ReadError | WriteError | IoError | Unknown => VfsError::Io, + } +} + +pub fn into_vfs_fs_err(err: RSEXT4Error) -> VfsError { + use RSEXT4Error::*; + match err { + IoError => VfsError::Io, + InvalidMagic | InvalidSuperblock => VfsError::InvalidData, + FilesystemHasErrors => VfsError::InvalidData, + UnsupportedFeature => VfsError::Unsupported, + AlreadyMounted => VfsError::AlreadyExists, + } +} + +pub fn into_vfs_type(inode: &Ext4Inode) -> NodeType { + if inode.is_dir() { + NodeType::Directory + } else if inode.is_symlink() { + NodeType::Symlink + } else if inode.is_file() { + NodeType::RegularFile + } else { + NodeType::Unknown + } +} +