diff --git a/kernel/driver/fs/ext2/src/dir.rs b/kernel/driver/fs/ext2/src/dir.rs index a6458f97..eb4e6cf9 100644 --- a/kernel/driver/fs/ext2/src/dir.rs +++ b/kernel/driver/fs/ext2/src/dir.rs @@ -1,9 +1,15 @@ -use core::{any::Any, mem::MaybeUninit, str::FromStr}; +// TODO: dedupe the code for iterating the directory records +use core::{ + any::Any, + mem::{self, MaybeUninit}, + str::FromStr, +}; use alloc::sync::Arc; use libk::{ block, error::Error, + time::real_time, vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef}, }; use yggdrasil_abi::{ @@ -30,11 +36,117 @@ struct DirentIterMut<'a> { offset: usize, } +struct Record<'a> { + fs: &'a Ext2Fs, + offset: usize, + data: &'a mut [u8], +} + +impl Record<'_> { + pub fn dirent(&self) -> &Dirent { + bytemuck::from_bytes(&self.data[..size_of::()]) + } + + pub fn dirent_mut(&mut self) -> &mut Dirent { + bytemuck::from_bytes_mut(&mut self.data[..size_of::()]) + } + + pub fn is_empty(&self) -> bool { + self.dirent().ino == 0 + } + + pub fn ino(&self) -> u32 { + self.dirent().ino + } + + fn name_len(&self) -> usize { + let len = self.dirent().name_length_low as usize; + if !self + .fs + .required_features + .contains(FsRequiredFeatures::DIRENT_TYPE_FIELD) + { + todo!(); + } + len + } + + fn name_bytes(&self) -> &[u8] { + if !self.is_empty() { + &self.data[size_of::()..size_of::() + self.name_len()] + } else { + b"" + } + } + + fn name(&self) -> Option<&str> { + core::str::from_utf8(self.name_bytes()).ok() + } + + pub fn data_size(&self) -> usize { + // Whole dirent is free space + if self.is_empty() { + return 0; + } + + self.name_len() + size_of::() + } + + pub fn record_size(&self) -> usize { + (self.dirent().ent_size as usize).max(size_of::()) + } + + pub fn free_space(&self) -> usize { + self.data.len().saturating_sub(self.data_size()) + } +} + impl<'a> DirentIterMut<'a> { pub fn new(fs: &'a Ext2Fs, block: &'a mut [u8], offset: usize) -> Self { Self { fs, block, offset } } + fn next_record(&mut self) -> Option { + if self.offset + size_of::() > self.block.len() { + return None; + } + + let dirent_struct_end = self.offset + size_of::(); + let dirent: &Dirent = bytemuck::from_bytes(&self.block[self.offset..dirent_struct_end]); + let record_size = (dirent.ent_size as usize).max(size_of::()); + let record = Record { + data: &mut self.block[self.offset..self.offset + record_size], + fs: self.fs, + offset: self.offset, + }; + + self.offset += record_size; + Some(record) + } + + pub fn remove(&mut self, search: &str) -> Option { + if search.is_empty() || search == "." || search == ".." { + return None; + } + + let mut ino = None; + while let Some(mut record) = self.next_record() { + if record.name_bytes() != search.as_bytes() { + continue; + } + + let dirent = record.dirent_mut(); + ino = Some(mem::replace(&mut dirent.ino, 0)); + break; + } + + let ino = ino?; + + // TODO compact the block + + Some(ino) + } + pub fn try_fit(&mut self, name: &str, ino: u32) -> bool { let _ = self.fs; @@ -185,6 +297,7 @@ impl DirectoryNode { let mut child = holder.write(); child.inc_hard_count(); + child.dtime = 0; } holder.put().await?; @@ -263,6 +376,53 @@ impl DirectoryNode { Ok(()) } + async fn remove_entry(&self, name: &str) -> Result<(), Error> { + // TODO if unlinking a directory, check that it only contains . and .. + assert!(name.len() < 255); + + let ino = { + let mut holder = self.inode.get_mut().await?; + let mut result = None; + + { + let inode = holder.write(); + let n = inode.blocks(&self.fs); + + for i in 0..n { + let ino = self + .fs + .with_inode_block_mut(&inode, i as _, 0, |block| { + let mut iter = DirentIterMut::new(&self.fs, block, 0); + Ok(iter.remove(name)) + }) + .await?; + + if let Some(ino) = ino { + result = Some(ino); + break; + } + } + } + holder.put().await?; + + result + }; + + let ino = ino.ok_or(Error::DoesNotExist)?; + log::info!("ext2: unlinked ino #{ino}"); + + // Decrement the refcount on the inode + let mut holder = self.inode.cache().get_mut(ino).await?; + { + let mut inode = holder.write(); + inode.hard_links = inode.hard_links.saturating_sub(1); + inode.dtime = real_time().seconds as _; + } + holder.put().await?; + + Ok(()) + } + async fn lookup_entry(&self, search_name: &str) -> Result { assert!(search_name.len() < 255); @@ -416,12 +576,11 @@ impl DirectoryImpl for DirectoryNode { Ok(()) } - fn unlink_node(&self, _parent: &NodeRef, _name: &str) -> Result<(), Error> { + fn unlink_node(&self, _parent: &NodeRef, name: &str) -> Result<(), Error> { if self.fs.force_readonly { return Err(Error::ReadOnly); } - log::error!("ext2: unlink_node not implemented"); - Err(Error::NotImplemented) + block!(self.remove_entry(name).await)? } fn read_entries(