diff --git a/kernel/driver/fs/ext2/src/inode.rs b/kernel/driver/fs/ext2/src/inode.rs deleted file mode 100644 index 98f8ae7e..00000000 --- a/kernel/driver/fs/ext2/src/inode.rs +++ /dev/null @@ -1,648 +0,0 @@ -use core::{ - cmp::Ordering, - ops::{AsyncFnOnce, Deref, DerefMut}, -}; - -use alloc::sync::Arc; -use bytemuck::Zeroable; -use libk::{ - block, - error::Error, - task::sync::AsyncMutex, - time::real_time, - vfs::{Metadata, NodeRef}, -}; -use libk_util::{ - lru_hash_table::LruCache, - sync::spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard, IrqSafeRwLockWriteGuard}, -}; -use yggdrasil_abi::io::{FileMode, FileType}; - -use crate::{ - data::{FsReadonlyFeatures, InodeMode, DIRECT_BLOCK_COUNT}, - dir::DirectoryNode, - file::RegularNode, - Ext2Fs, Inode, -}; - -pub struct InodeHolder { - inode: Inode, - dirty: bool, -} - -pub struct CachedInodeRef { - entry: Arc>, -} - -pub struct UncachedInodeRef { - entry: IrqSafeRwLock, -} - -pub enum InodeRef { - Cached(CachedInodeRef), - Uncached(UncachedInodeRef), -} - -pub struct CachedInodeMut { - entry: Arc>, -} - -pub struct UncachedInodeMut { - ino: u32, - fs: Arc, - put: bool, - data: IrqSafeRwLock, -} - -pub enum InodeMut { - Cached(CachedInodeMut), - Uncached(UncachedInodeMut), -} - -pub struct InodeCache { - fs: Arc, - cache: Option>>>>, -} - -pub struct InodeAccess { - inode_cache: Arc, - ino: u32, -} - -impl InodeAccess { - pub fn new(inode_cache: Arc, ino: u32) -> Self { - Self { inode_cache, ino } - } - - pub fn ino(&self) -> u32 { - self.ino - } - - pub fn cache(&self) -> &Arc { - &self.inode_cache - } - - pub async fn map Result>( - &self, - mapper: F, - ) -> Result { - let inode = self.inode_cache.get(self.ino).await?; - let result = { - let lock = inode.read(); - mapper(&lock) - }; - result - } - - pub async fn map_mut Result>( - &self, - mapper: F, - ) -> Result { - let mut inode = self.inode_cache.get_mut(self.ino).await?; - let result = { - let mut lock = inode.write(); - mapper(&mut lock) - }; - inode.put().await?; - result - } - - pub async fn amap Result>( - &self, - mapper: F, - ) -> Result { - let inode = self.inode_cache.get(self.ino).await?; - let result = { - let lock = inode.read(); - mapper(&*lock).await - }; - result - } - - pub async fn amap_mut Result>( - &self, - mapper: F, - ) -> Result { - let mut inode = self.inode_cache.get_mut(self.ino).await?; - let result = { - let mut lock = inode.write(); - mapper(&mut *lock).await - }; - inode.put().await?; - result - } - - pub async fn map_blocks Result, Error>>( - &self, - mapper: F, - ) -> Result, Error> { - self.amap(async |inode| { - let block_count = inode.blocks(&self.inode_cache.fs); - - for index in 0..block_count { - let result = self - .inode_cache - .fs - .with_inode_block(inode, index as u32, |block| mapper(inode, index, block)) - .await?; - - if let Some(result) = result { - return Ok(Some(result)); - } - } - - Ok(None) - }) - .await - } - - pub async fn map_blocks_mut Result, Error>>( - &self, - mapper: F, - ) -> Result, Error> { - self.amap(async |inode| { - let block_count = inode.blocks(&self.inode_cache.fs); - - for index in 0..block_count { - let result = self - .inode_cache - .fs - .with_inode_block_mut(inode, index as u32, 0, |block| { - mapper(inode, index, block) - }) - .await?; - - if let Some(result) = result { - return Ok(Some(result)); - } - } - - Ok(None) - }) - .await - } - - pub async fn metadata(&self) -> Result { - self.map(|inode| Ok(inode.metadata(&self.inode_cache.fs, self.ino))) - .await - } - - pub async fn size(&self) -> Result { - self.map(|inode| Ok(inode.size(&self.inode_cache.fs))).await - } - - pub async fn update_metadata(&self, metadata: &Metadata) -> Result<(), Error> { - let uid = metadata - .uid - .bits() - .try_into() - .map_err(|_| Error::InvalidArgument)?; - let gid = metadata - .gid - .bits() - .try_into() - .map_err(|_| Error::InvalidArgument)?; - - self.map_mut(|inode| { - inode.mtime = metadata.mtime as _; - inode.atime = metadata.mtime as _; - inode.ctime = metadata.ctime as _; - - inode.mode.update_permissions(metadata.mode); - inode.uid = uid; - inode.gid = gid; - - Ok(()) - }) - .await - } - - async fn populate_inode( - fs: &Arc, - ty: FileType, - mode: FileMode, - parent_ino: Option, - ino: u32, - ) -> Result { - log::info!("ext2: allocated inode #{ino}"); - - let now = real_time().seconds as u32; - - let mut imode = InodeMode::default_for_type(ty); - imode.update_permissions(mode); - - fs.write_inode( - ino, - &Inode { - ctime: now, - mtime: now, - atime: now, - mode: imode, - ..Inode::zeroed() - }, - ) - .await?; - - let this = InodeAccess::new(fs.inode_cache.get().clone(), ino); - let fs = fs.clone(); - let node = match ty { - FileType::Directory => DirectoryNode::create(fs, this, parent_ino).await?, - FileType::File => RegularNode::new(fs, this), - FileType::Symlink => todo!(), - _ => return Err(Error::NotImplemented), - }; - - Ok(node) - } - - pub async fn allocate( - fs: &Arc, - ty: FileType, - mode: FileMode, - parent_ino: Option, - ) -> Result { - if parent_ino.is_none() && ty != FileType::Directory { - log::warn!("ext2: cannot allocate non-directory inode without a parent"); - return Err(Error::InvalidOperation); - } - if !matches!(ty, FileType::Symlink | FileType::Directory | FileType::File) { - log::warn!("ext2: cannot allocate inode of type {ty:?}"); - return Err(Error::InvalidOperation); - } - - let ino = fs.allocate_inode(ty == FileType::Directory).await?; - - match Self::populate_inode(fs, ty, mode, parent_ino, ino).await { - Ok(node) => Ok(node), - Err(error) => { - log::warn!("ext2: couldn't set up inode #{ino}: {error:?}"); - // TODO free the inode and flush it from the cache - Err(error) - } - } - } -} - -impl InodeCache { - pub fn with_capacity(fs: Arc, bucket_capacity: usize) -> Self { - Self { - fs, - cache: Some(AsyncMutex::new(LruCache::with_capacity(bucket_capacity, 4))), - } - } - - pub fn uncached(fs: Arc) -> Self { - Self { fs, cache: None } - } - - async fn evict_inode( - &self, - ino: u32, - inode: Arc>, - ) -> Result<(), Error> { - let inode = inode.read(); - if inode.dirty { - log::debug!("Flush dirty inode {ino}"); - self.fs.write_inode(ino, &inode.inode).await?; - } - Ok(()) - } - - async fn fetch_inode(&self, ino: u32) -> Result>, Error> { - let inode = self.fs.read_inode(ino).await?; - Ok(Arc::new(IrqSafeRwLock::new(InodeHolder { - inode, - dirty: false, - }))) - } - - async fn entry(&self, ino: u32) -> Result>, Error> { - let Some(cache) = self.cache.as_ref() else { - log::warn!("Cannot use InodeCache::entry with no cache"); - return Err(Error::InvalidOperation); - }; - if ino < 1 || ino > self.fs.total_inodes { - return Err(Error::InvalidFile); - } - - let mut lock = cache.lock().await; - let (value, evicted) = lock - .try_get_or_insert_with_async(ino, || self.fetch_inode(ino)) - .await?; - let value = value.clone(); - - if let Some((ino, holder)) = evicted { - if let Err(error) = self.evict_inode(ino, holder).await { - log::error!("ext2: inode flush error: ino={ino}, error={error:?}"); - } - } - - Ok(value) - } - - async fn get(&self, ino: u32) -> Result { - if self.cache.is_some() { - let entry = self.entry(ino).await?; - let inode = CachedInodeRef { entry }; - Ok(InodeRef::Cached(inode)) - } else { - let inode = self.fs.read_inode(ino).await?; - let data = InodeHolder { - inode, - dirty: false, - }; - let inode = UncachedInodeRef { - entry: IrqSafeRwLock::new(data), - }; - Ok(InodeRef::Uncached(inode)) - } - } - - async fn get_mut(self: &Arc, ino: u32) -> Result { - if self.cache.is_some() { - let entry = self.entry(ino).await?; - let inode = CachedInodeMut { entry }; - Ok(InodeMut::Cached(inode)) - } else { - let inode = self.fs.read_inode(ino).await?; - let data = InodeHolder { - inode, - dirty: false, - }; - let inode = UncachedInodeMut { - fs: self.fs.clone(), - ino, - put: false, - data: IrqSafeRwLock::new(data), - }; - Ok(InodeMut::Uncached(inode)) - } - } - - pub async fn flush(&self) -> Result<(), Error> { - if let Some(cache) = self.cache.as_ref() { - let mut last_error = None; - - let mut lock = cache.lock().await; - while let Some((ino, inode)) = lock.pop_entry() { - if let Err(error) = self.evict_inode(ino, inode).await { - log::error!("ext2: flush inode cache error: ino={ino}, error={error:?}"); - last_error = Some(error); - } - } - - match last_error { - None => Ok(()), - Some(error) => Err(error), - } - } else { - Ok(()) - } - } -} - -impl Inode { - async fn grow_direct( - &mut self, - fs: &Ext2Fs, - old_capacity: u64, - new_capacity: u64, - ) -> Result<(), Error> { - let old_l0_capacity = old_capacity.min(DIRECT_BLOCK_COUNT as u64); - let new_l0_capacity = new_capacity.min(DIRECT_BLOCK_COUNT as u64); - - debug_assert!(old_l0_capacity <= new_l0_capacity); - - log::debug!("Grow L0: {old_l0_capacity} -> {new_l0_capacity}"); - - for i in old_l0_capacity..new_l0_capacity { - let i = i as usize; - let block = fs.allocate_block().await?; - self.blocks.direct_blocks[i] = block; - } - - Ok(()) - } - - async fn grow_l1( - &mut self, - fs: &Ext2Fs, - old_capacity: u64, - new_capacity: u64, - ) -> Result<(), Error> { - let old_l1_capacity = old_capacity - .saturating_sub(DIRECT_BLOCK_COUNT as u64) - .min(fs.pointers_per_block as u64); - let new_l1_capacity = new_capacity - .saturating_sub(DIRECT_BLOCK_COUNT as u64) - .min(fs.pointers_per_block as u64); - - log::debug!("Grow L1: {old_l1_capacity} -> {new_l1_capacity}"); - - debug_assert!(old_l1_capacity <= new_l1_capacity); - - if old_l1_capacity == 0 && new_l1_capacity != 0 { - // Allocate an indirect block - let block = fs.allocate_block().await?; - self.blocks.indirect_block_l1 = block; - } - - for i in old_l1_capacity..new_l1_capacity { - // Allocate inner blocks - debug_assert_ne!(self.blocks.indirect_block_l1, 0); - let i = i as usize; - let block = fs.allocate_block().await?; - fs.write_index(self.blocks.indirect_block_l1, i, block) - .await?; - } - - Ok(()) - } - - async fn grow_l2( - &mut self, - fs: &Ext2Fs, - old_capacity: u64, - new_capacity: u64, - ) -> Result<(), Error> { - let old_l2_capacity = (old_capacity as usize) - .saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block) - .min(fs.pointers_per_block * fs.pointers_per_block); - let new_l2_capacity = (new_capacity as usize) - .saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block) - .min(fs.pointers_per_block * fs.pointers_per_block); - - let old_l2_l0 = old_l2_capacity.div_ceil(fs.pointers_per_block); - let new_l2_l0 = new_l2_capacity.div_ceil(fs.pointers_per_block); - - log::debug!( - "Grow L2: {old_l2_capacity} ({old_l2_l0} L2-1) -> {new_l2_capacity} ({new_l2_l0} L2-1)" - ); - - if old_l2_capacity == 0 && new_l2_capacity != 0 { - // Allocate L2 indirect block - let block = fs.allocate_block().await?; - self.blocks.indirect_block_l2 = block; - } - - // Grow L2 direct-indirect block - for i in old_l2_l0..new_l2_l0 { - debug_assert_ne!(self.blocks.indirect_block_l2, 0); - let block = fs.allocate_block().await?; - fs.write_index(self.blocks.indirect_block_l2, i, block) - .await?; - } - - // Grow L2 indirect-indirect blocks - for i in old_l2_capacity..new_l2_capacity { - debug_assert_ne!(self.blocks.indirect_block_l2, 0); - let l1i = i / fs.pointers_per_block; - let l0i = i % fs.pointers_per_block; - let indirect = fs.read_index(self.blocks.indirect_block_l2, l1i).await?; - debug_assert_ne!(indirect, 0); - let block = fs.allocate_block().await?; - fs.write_index(indirect, l0i, block).await?; - } - - Ok(()) - } - - fn set_size(&mut self, fs: &Ext2Fs, size: u64) { - let block_count = size.div_ceil(fs.block_size as u64); - - if fs - .write_features - .contains(FsReadonlyFeatures::FILE_SIZE_64_BIT) - { - self.size_upper = (size >> 32) as u32; - self.size_lower = size as u32; - } else { - if size > u32::MAX as u64 { - todo!("File too large") - } - self.size_lower = size as u32; - } - - self.sector_count = block_count as u32 * (fs.block_size / 512) as u32; - } - - pub async fn resize(&mut self, fs: &Ext2Fs, size: u64) -> Result { - if size == self.size(fs) { - return Ok(false); - } - - // TODO check max inode size - - let new_blocks = size.div_ceil(fs.block_size as u64); - let old_blocks = self.size(fs).div_ceil(fs.block_size as u64); - - if new_blocks as usize - > DIRECT_BLOCK_COUNT - + fs.pointers_per_block - + fs.pointers_per_block * fs.pointers_per_block - { - log::warn!("ext2: only L0/L1 are supported"); - return Err(Error::InvalidArgument); - } - - match old_blocks.cmp(&new_blocks) { - // Grow - Ordering::Less => { - log::debug!("Grow inode: {old_blocks} -> {new_blocks} blocks"); - self.grow_direct(fs, old_blocks, new_blocks).await?; - self.grow_l1(fs, old_blocks, new_blocks).await?; - self.grow_l2(fs, old_blocks, new_blocks).await?; - } - // Shrink - Ordering::Greater => todo!(), - // No change - Ordering::Equal => (), - } - - self.set_size(fs, size); - - Ok(true) - } - - pub fn inc_hard_count(&mut self) { - self.hard_links += 1; - } - - pub async fn reserve(&mut self, fs: &Ext2Fs, capacity: u64) -> Result { - if capacity > self.size(fs) { - self.resize(fs, capacity).await - } else { - Ok(false) - } - } -} - -impl Deref for InodeHolder { - type Target = Inode; - - fn deref(&self) -> &Self::Target { - &self.inode - } -} - -impl DerefMut for InodeHolder { - fn deref_mut(&mut self) -> &mut Self::Target { - self.dirty = true; - &mut self.inode - } -} - -impl InodeMut { - pub fn read(&self) -> IrqSafeRwLockReadGuard { - match self { - Self::Cached(inode) => inode.entry.read(), - Self::Uncached(inode) => inode.data.read(), - } - } - - pub fn write(&self) -> IrqSafeRwLockWriteGuard { - match self { - Self::Cached(inode) => inode.entry.write(), - Self::Uncached(inode) => inode.data.write(), - } - } - - pub async fn put(&mut self) -> Result<(), Error> { - match self { - Self::Cached(_) => (), - Self::Uncached(inode) => { - log::info!("Write inode #{} back", inode.ino); - inode.put = true; - inode.fs.write_inode(inode.ino, &inode.data.read()).await?; - } - } - Ok(()) - } -} - -impl InodeRef { - pub fn read(&self) -> IrqSafeRwLockReadGuard { - match self { - Self::Cached(inode) => inode.entry.read(), - Self::Uncached(inode) => inode.entry.read(), - } - } -} - -impl Drop for InodeMut { - fn drop(&mut self) { - match self { - Self::Uncached(inode) if !inode.put => { - // Do node writeback in background - let ino = inode.ino; - match block!(self.put().await) { - Err(error) | Ok(Err(error)) => { - log::error!("Drop for InodeMut (#{}) failed: {error:?}", ino); - } - Ok(Ok(())) => (), - } - } - _ => (), - } - } -} diff --git a/kernel/driver/fs/ext2/src/inode/cache.rs b/kernel/driver/fs/ext2/src/inode/cache.rs new file mode 100644 index 00000000..548558c0 --- /dev/null +++ b/kernel/driver/fs/ext2/src/inode/cache.rs @@ -0,0 +1,329 @@ +use core::ops::{AsyncFnOnce, Deref, DerefMut}; + +use alloc::sync::Arc; +use bytemuck::Zeroable; +use libk::{ + error::Error, + task::sync::AsyncMutex, + time::real_time, + vfs::{Metadata, NodeRef}, +}; +use libk_util::{lru_hash_table::LruCache, sync::spin_rwlock::IrqSafeRwLock}; +use yggdrasil_abi::io::{FileMode, FileType}; + +use crate::{data::InodeMode, dir::DirectoryNode, file::RegularNode, Ext2Fs, Inode}; + +pub struct InodeHolder { + inode: Inode, + dirty: bool, +} + +pub struct InodeCache { + fs: Arc, + synchronous: bool, + cache: AsyncMutex>>>, +} + +pub struct InodeAccess { + inode_cache: Arc, + ino: u32, +} + +impl InodeAccess { + pub fn new(inode_cache: Arc, ino: u32) -> Self { + Self { inode_cache, ino } + } + + pub fn ino(&self) -> u32 { + self.ino + } + + pub fn cache(&self) -> &Arc { + &self.inode_cache + } + + pub async fn map Result>( + &self, + mapper: F, + ) -> Result { + let inode = self.inode_cache.entry(self.ino).await?; + let lock = inode.read(); + mapper(&lock.inode) + } + + pub async fn map_mut Result>( + &self, + mapper: F, + ) -> Result { + let inode = self.inode_cache.entry(self.ino).await?; + let mut lock = inode.write(); + let result = mapper(&mut lock.inode); + self.inode_cache.put(self.ino, &mut lock).await?; + result + } + + pub async fn amap Result>( + &self, + mapper: F, + ) -> Result { + let inode = self.inode_cache.entry(self.ino).await?; + let lock = inode.read(); + mapper(&lock.inode).await + } + + pub async fn amap_mut Result>( + &self, + mapper: F, + ) -> Result { + let inode = self.inode_cache.entry(self.ino).await?; + let mut lock = inode.write(); + let result = mapper(&mut lock.inode).await; + self.inode_cache.put(self.ino, &mut lock).await?; + result + } + + pub async fn map_blocks Result, Error>>( + &self, + mapper: F, + ) -> Result, Error> { + self.amap(async |inode| { + let block_count = inode.blocks(&self.inode_cache.fs); + + for index in 0..block_count { + let result = self + .inode_cache + .fs + .with_inode_block(inode, index as u32, |block| mapper(inode, index, block)) + .await?; + + if let Some(result) = result { + return Ok(Some(result)); + } + } + + Ok(None) + }) + .await + } + + pub async fn map_blocks_mut Result, Error>>( + &self, + mapper: F, + ) -> Result, Error> { + self.amap(async |inode| { + let block_count = inode.blocks(&self.inode_cache.fs); + + for index in 0..block_count { + let result = self + .inode_cache + .fs + .with_inode_block_mut(inode, index as u32, 0, |block| { + mapper(inode, index, block) + }) + .await?; + + if let Some(result) = result { + return Ok(Some(result)); + } + } + + Ok(None) + }) + .await + } + + pub async fn metadata(&self) -> Result { + self.map(|inode| Ok(inode.metadata(&self.inode_cache.fs, self.ino))) + .await + } + + pub async fn size(&self) -> Result { + self.map(|inode| Ok(inode.size(&self.inode_cache.fs))).await + } + + pub async fn update_metadata(&self, metadata: &Metadata) -> Result<(), Error> { + let uid = metadata + .uid + .bits() + .try_into() + .map_err(|_| Error::InvalidArgument)?; + let gid = metadata + .gid + .bits() + .try_into() + .map_err(|_| Error::InvalidArgument)?; + + self.map_mut(|inode| { + inode.mtime = metadata.mtime as _; + inode.atime = metadata.mtime as _; + inode.ctime = metadata.ctime as _; + + inode.mode.update_permissions(metadata.mode); + inode.uid = uid; + inode.gid = gid; + + Ok(()) + }) + .await + } + + async fn populate_inode( + fs: &Arc, + ty: FileType, + mode: FileMode, + parent_ino: Option, + ino: u32, + ) -> Result { + log::info!("ext2: allocated inode #{ino}"); + + let now = real_time().seconds as u32; + + let mut imode = InodeMode::default_for_type(ty); + imode.update_permissions(mode); + + fs.write_inode( + ino, + &Inode { + ctime: now, + mtime: now, + atime: now, + mode: imode, + ..Inode::zeroed() + }, + ) + .await?; + + let this = InodeAccess::new(fs.inode_cache.get().clone(), ino); + let fs = fs.clone(); + let node = match ty { + FileType::Directory => DirectoryNode::create(fs, this, parent_ino).await?, + FileType::File => RegularNode::new(fs, this), + FileType::Symlink => todo!(), + _ => return Err(Error::NotImplemented), + }; + + Ok(node) + } + + pub async fn allocate( + fs: &Arc, + ty: FileType, + mode: FileMode, + parent_ino: Option, + ) -> Result { + if parent_ino.is_none() && ty != FileType::Directory { + log::warn!("ext2: cannot allocate non-directory inode without a parent"); + return Err(Error::InvalidOperation); + } + if !matches!(ty, FileType::Symlink | FileType::Directory | FileType::File) { + log::warn!("ext2: cannot allocate inode of type {ty:?}"); + return Err(Error::InvalidOperation); + } + + let ino = fs.allocate_inode(ty == FileType::Directory).await?; + + match Self::populate_inode(fs, ty, mode, parent_ino, ino).await { + Ok(node) => Ok(node), + Err(error) => { + log::warn!("ext2: couldn't set up inode #{ino}: {error:?}"); + // TODO free the inode and flush it from the cache + Err(error) + } + } + } +} + +impl InodeCache { + pub fn with_capacity(fs: Arc, bucket_capacity: usize, synchronous: bool) -> Self { + Self { + fs, + synchronous, + cache: AsyncMutex::new(LruCache::with_capacity(bucket_capacity, 4)), + } + } + + async fn evict_inode( + &self, + ino: u32, + inode: Arc>, + ) -> Result<(), Error> { + let inode = inode.read(); + if inode.dirty { + assert!(!self.synchronous); + log::debug!("Flush dirty inode {ino}"); + self.fs.write_inode(ino, &inode.inode).await?; + } + Ok(()) + } + + async fn fetch_inode(&self, ino: u32) -> Result>, Error> { + let inode = self.fs.read_inode(ino).await?; + Ok(Arc::new(IrqSafeRwLock::new(InodeHolder { + inode, + dirty: false, + }))) + } + + async fn entry(&self, ino: u32) -> Result>, Error> { + if ino < 1 || ino > self.fs.total_inodes { + return Err(Error::InvalidFile); + } + + let mut lock = self.cache.lock().await; + let (value, evicted) = lock + .try_get_or_insert_with_async(ino, || self.fetch_inode(ino)) + .await?; + let value = value.clone(); + + if let Some((ino, holder)) = evicted { + if let Err(error) = self.evict_inode(ino, holder).await { + log::error!("ext2: inode flush error: ino={ino}, error={error:?}"); + } + } + + Ok(value) + } + + async fn put(&self, ino: u32, holder: &mut InodeHolder) -> Result<(), Error> { + if self.synchronous { + // Immediately write-back + self.fs.write_inode(ino, &holder.inode).await + } else { + // Mark dirty + holder.dirty = true; + Ok(()) + } + } + + pub async fn flush(&self) -> Result<(), Error> { + let mut last_error = None; + + let mut lock = self.cache.lock().await; + while let Some((ino, inode)) = lock.pop_entry() { + if let Err(error) = self.evict_inode(ino, inode).await { + log::error!("ext2: flush inode #{ino} error: {error:?}"); + last_error = Some(error); + } + } + + match last_error { + None => Ok(()), + Some(error) => Err(error), + } + } +} + +impl Deref for InodeHolder { + type Target = Inode; + + fn deref(&self) -> &Self::Target { + &self.inode + } +} + +impl DerefMut for InodeHolder { + fn deref_mut(&mut self) -> &mut Self::Target { + self.dirty = true; + &mut self.inode + } +} diff --git a/kernel/driver/fs/ext2/src/inode/mod.rs b/kernel/driver/fs/ext2/src/inode/mod.rs new file mode 100644 index 00000000..c5df947a --- /dev/null +++ b/kernel/driver/fs/ext2/src/inode/mod.rs @@ -0,0 +1,188 @@ +use core::cmp::Ordering; + +use libk::error::Error; + +use crate::{ + data::{FsReadonlyFeatures, DIRECT_BLOCK_COUNT}, + Ext2Fs, Inode, +}; + +pub mod cache; + +pub use cache::{InodeAccess, InodeCache, InodeHolder}; + +impl Inode { + async fn grow_direct( + &mut self, + fs: &Ext2Fs, + old_capacity: u64, + new_capacity: u64, + ) -> Result<(), Error> { + let old_l0_capacity = old_capacity.min(DIRECT_BLOCK_COUNT as u64); + let new_l0_capacity = new_capacity.min(DIRECT_BLOCK_COUNT as u64); + + debug_assert!(old_l0_capacity <= new_l0_capacity); + + log::debug!("Grow L0: {old_l0_capacity} -> {new_l0_capacity}"); + + for i in old_l0_capacity..new_l0_capacity { + let i = i as usize; + let block = fs.allocate_block().await?; + self.blocks.direct_blocks[i] = block; + } + + Ok(()) + } + + async fn grow_l1( + &mut self, + fs: &Ext2Fs, + old_capacity: u64, + new_capacity: u64, + ) -> Result<(), Error> { + let old_l1_capacity = old_capacity + .saturating_sub(DIRECT_BLOCK_COUNT as u64) + .min(fs.pointers_per_block as u64); + let new_l1_capacity = new_capacity + .saturating_sub(DIRECT_BLOCK_COUNT as u64) + .min(fs.pointers_per_block as u64); + + log::debug!("Grow L1: {old_l1_capacity} -> {new_l1_capacity}"); + + debug_assert!(old_l1_capacity <= new_l1_capacity); + + if old_l1_capacity == 0 && new_l1_capacity != 0 { + // Allocate an indirect block + let block = fs.allocate_block().await?; + self.blocks.indirect_block_l1 = block; + } + + for i in old_l1_capacity..new_l1_capacity { + // Allocate inner blocks + debug_assert_ne!(self.blocks.indirect_block_l1, 0); + let i = i as usize; + let block = fs.allocate_block().await?; + fs.write_index(self.blocks.indirect_block_l1, i, block) + .await?; + } + + Ok(()) + } + + async fn grow_l2( + &mut self, + fs: &Ext2Fs, + old_capacity: u64, + new_capacity: u64, + ) -> Result<(), Error> { + let old_l2_capacity = (old_capacity as usize) + .saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block) + .min(fs.pointers_per_block * fs.pointers_per_block); + let new_l2_capacity = (new_capacity as usize) + .saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block) + .min(fs.pointers_per_block * fs.pointers_per_block); + + let old_l2_l0 = old_l2_capacity.div_ceil(fs.pointers_per_block); + let new_l2_l0 = new_l2_capacity.div_ceil(fs.pointers_per_block); + + log::debug!( + "Grow L2: {old_l2_capacity} ({old_l2_l0} L2-1) -> {new_l2_capacity} ({new_l2_l0} L2-1)" + ); + + if old_l2_capacity == 0 && new_l2_capacity != 0 { + // Allocate L2 indirect block + let block = fs.allocate_block().await?; + self.blocks.indirect_block_l2 = block; + } + + // Grow L2 direct-indirect block + for i in old_l2_l0..new_l2_l0 { + debug_assert_ne!(self.blocks.indirect_block_l2, 0); + let block = fs.allocate_block().await?; + fs.write_index(self.blocks.indirect_block_l2, i, block) + .await?; + } + + // Grow L2 indirect-indirect blocks + for i in old_l2_capacity..new_l2_capacity { + debug_assert_ne!(self.blocks.indirect_block_l2, 0); + let l1i = i / fs.pointers_per_block; + let l0i = i % fs.pointers_per_block; + let indirect = fs.read_index(self.blocks.indirect_block_l2, l1i).await?; + debug_assert_ne!(indirect, 0); + let block = fs.allocate_block().await?; + fs.write_index(indirect, l0i, block).await?; + } + + Ok(()) + } + + fn set_size(&mut self, fs: &Ext2Fs, size: u64) { + let block_count = size.div_ceil(fs.block_size as u64); + + if fs + .write_features + .contains(FsReadonlyFeatures::FILE_SIZE_64_BIT) + { + self.size_upper = (size >> 32) as u32; + self.size_lower = size as u32; + } else { + if size > u32::MAX as u64 { + todo!("File too large") + } + self.size_lower = size as u32; + } + + self.sector_count = block_count as u32 * (fs.block_size / 512) as u32; + } + + pub async fn resize(&mut self, fs: &Ext2Fs, size: u64) -> Result { + if size == self.size(fs) { + return Ok(false); + } + + // TODO check max inode size + + let new_blocks = size.div_ceil(fs.block_size as u64); + let old_blocks = self.size(fs).div_ceil(fs.block_size as u64); + + if new_blocks as usize + > DIRECT_BLOCK_COUNT + + fs.pointers_per_block + + fs.pointers_per_block * fs.pointers_per_block + { + log::warn!("ext2: only L0/L1 are supported"); + return Err(Error::InvalidArgument); + } + + match old_blocks.cmp(&new_blocks) { + // Grow + Ordering::Less => { + log::debug!("Grow inode: {old_blocks} -> {new_blocks} blocks"); + self.grow_direct(fs, old_blocks, new_blocks).await?; + self.grow_l1(fs, old_blocks, new_blocks).await?; + self.grow_l2(fs, old_blocks, new_blocks).await?; + } + // Shrink + Ordering::Greater => todo!(), + // No change + Ordering::Equal => (), + } + + self.set_size(fs, size); + + Ok(true) + } + + pub fn inc_hard_count(&mut self) { + self.hard_links += 1; + } + + pub async fn reserve(&mut self, fs: &Ext2Fs, capacity: u64) -> Result { + if capacity > self.size(fs) { + self.resize(fs, capacity).await + } else { + Ok(false) + } + } +} diff --git a/kernel/driver/fs/ext2/src/lib.rs b/kernel/driver/fs/ext2/src/lib.rs index 18b067db..ed50efcb 100644 --- a/kernel/driver/fs/ext2/src/lib.rs +++ b/kernel/driver/fs/ext2/src/lib.rs @@ -107,11 +107,7 @@ impl Ext2Fs { log::error!("Ext2 init error: {:?}", e); })?; let fs = Arc::new(fs); - // let inode_cache = InodeCache::uncached(fs.clone()); - let inode_cache = match cached { - false => InodeCache::uncached(fs.clone()), - true => InodeCache::with_capacity(fs.clone(), 64), - }; + let inode_cache = InodeCache::with_capacity(fs.clone(), 64, !cached); fs.inode_cache.init(inode_cache.into());