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 } }