diff --git a/kernel/driver/fs/ext2/src/data.rs b/kernel/driver/fs/ext2/src/data.rs index f007371a..b0b0d6dc 100644 --- a/kernel/driver/fs/ext2/src/data.rs +++ b/kernel/driver/fs/ext2/src/data.rs @@ -265,6 +265,15 @@ impl InodeMode { } } + pub fn dirent_indicator(&self) -> Option { + match self.0 & 0xF000 { + 0x4000 => Some(2), + 0x8000 => Some(1), + 0xA000 => Some(7), + _ => None, + } + } + pub fn default_for_type(ty: FileType) -> Self { match ty { FileType::File => Self(0o644 | 0x8000), diff --git a/kernel/driver/fs/ext2/src/dir.rs b/kernel/driver/fs/ext2/src/dir.rs deleted file mode 100644 index 35f68333..00000000 --- a/kernel/driver/fs/ext2/src/dir.rs +++ /dev/null @@ -1,650 +0,0 @@ -// TODO: dedupe the code for iterating the directory records -use core::{ - any::Any, - mem::{self, MaybeUninit}, - str::FromStr, - sync::atomic::{AtomicBool, Ordering}, -}; - -use alloc::sync::Arc; -use libk::{ - block, - error::Error, - time::real_time, - vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef}, -}; -use yggdrasil_abi::{ - io::{DirectoryEntry, FileMode, FileType}, - util::FixedString, -}; - -use crate::{data::FsRequiredFeatures, file::RegularNode, inode::InodeAccess, Dirent, Ext2Fs}; - -pub struct DirectoryNode { - fs: Arc, - locked: AtomicBool, - pub(crate) inode: InodeAccess, -} - -struct DirentIter<'a> { - fs: &'a Ext2Fs, - block: &'a [u8], - offset: usize, -} - -struct DirentIterMut<'a> { - fs: &'a Ext2Fs, - block: &'a mut [u8], - offset: usize, -} - -struct Record<'a> { - fs: &'a Ext2Fs, - data: &'a mut [u8], - #[allow(unused)] - offset: usize, -} - -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; - - let name = name.as_bytes(); - - let new_total_size = size_of::() + name.len(); - let new_aligned_size = (new_total_size + 3) & !3; - - loop { - if self.offset + new_aligned_size >= self.block.len() { - return false; - } - - let entry_end = self.offset + size_of::(); - let dirent: &Dirent = bytemuck::from_bytes(&self.block[self.offset..entry_end]); - - if dirent.ino != 0 || (dirent.ent_size as usize) < new_aligned_size { - self.offset += dirent.ent_size as usize; - continue; - } - - let extra_space = dirent.ent_size as usize - new_aligned_size; - - let aligned_size = if extra_space >= size_of::() { - // Fit another dummy entry after this one - new_aligned_size - } else { - // Fill the whole entry - dirent.ent_size as usize - }; - - // TODO if fs has dirent type attr, set it instead of extra name len - let new_dirent = Dirent { - ent_size: aligned_size as _, - name_length_low: name.len() as _, - type_indicator: 0, // TODO - ino, - }; - self.block[self.offset..self.offset + size_of::()] - .copy_from_slice(bytemuck::bytes_of(&new_dirent)); - self.block[self.offset + size_of::()..self.offset + new_total_size] - .copy_from_slice(name); - - if extra_space >= size_of::() { - // Fit an extra dummy dirent - let dummy = Dirent { - ent_size: extra_space as _, - name_length_low: 0, - type_indicator: 0, - ino: 0, - }; - self.block[self.offset + new_aligned_size - ..self.offset + new_aligned_size + size_of::()] - .copy_from_slice(bytemuck::bytes_of(&dummy)); - } - - return true; - } - } -} - -impl<'a> DirentIter<'a> { - pub fn new(fs: &'a Ext2Fs, block: &'a [u8], offset: usize) -> Self { - Self { fs, block, offset } - } -} - -impl<'a> Iterator for DirentIter<'a> { - type Item = (&'a Dirent, &'a [u8], usize); - - fn next(&mut self) -> Option { - loop { - if self.offset + size_of::() >= self.block.len() { - return None; - } - - let entry_end = self.offset + size_of::(); - let dirent: &Dirent = bytemuck::from_bytes(&self.block[self.offset..entry_end]); - - if dirent.ino == 0 { - self.offset += dirent.ent_size as usize; - continue; - } - - let name_len = dirent.name_length_low as usize; - - if !self - .fs - .required_features - .contains(FsRequiredFeatures::DIRENT_TYPE_FIELD) - { - todo!() - } - - // TODO handle broken dirent fields? - assert!(entry_end + name_len <= self.block.len()); - assert!(dirent.ent_size as usize >= size_of::()); - - let name = &self.block[entry_end..entry_end + name_len]; - - let offset = self.offset; - self.offset += dirent.ent_size as usize; - - return Some((dirent, name, offset)); - } - } -} - -impl DirectoryNode { - pub fn new(fs: Arc, inode: InodeAccess) -> NodeRef { - Node::directory( - Self { - fs: fs.clone(), - locked: AtomicBool::new(false), - inode, - }, - NodeFlags::empty(), - None, - Some(fs), - ) - } - - pub async fn create( - fs: Arc, - inode: InodeAccess, - parent_ino: Option, - ) -> Result { - let ino = inode.ino(); - let this = Self { - fs: fs.clone(), - locked: AtomicBool::new(false), - inode, - }; - - // fsck wants . as first entry, .. as second - this.create_entry(".", ino).await?; - if let Some(parent) = parent_ino { - this.create_entry("..", parent).await?; - } - - Ok(Node::directory(this, NodeFlags::empty(), None, Some(fs))) - } - - async fn create_entry(&self, name: &str, ino: u32) -> Result<(), Error> { - assert!(name.len() < 255); - - // This directory is in the process of being unlinked and cannot be written to - if self.locked.load(Ordering::Acquire) { - return Err(Error::DoesNotExist); - } - - let child = InodeAccess::new(self.inode.cache().clone(), ino); - child - .map_mut(|inode| { - inode.inc_hard_count(); - inode.dtime = 0; - Ok(()) - }) - .await?; - - self.inode - .amap_mut(async |inode| { - let mut fit = false; - let n = inode.blocks(&self.fs) as u32; - for i in 0..n { - let fit_block = self - .fs - .with_inode_block_mut(inode, i, 0, |block| { - let mut iter = DirentIterMut::new(&self.fs, &mut block[..], 0); - if iter.try_fit(name, ino) { - Ok(true) - } else { - Ok(false) - } - }) - .await?; - - if fit_block { - fit = true; - break; - } - } - - if !fit { - // Allocate a new block - let block_index = inode.blocks(&self.fs) as u32; - - // Grow the storage - inode - .reserve( - &self.fs, - (block_index as u64 + 1) * self.fs.block_size as u64, - ) - .await?; - - self.fs - .with_inode_block_mut(inode, block_index, self.fs.block_size, |block| { - block.fill(0); - - // Place dirent - let total_len = size_of::() + name.len(); - let aligned_len = (total_len + 3) & !3; - let dirent = Dirent { - ino, - ent_size: aligned_len as _, - type_indicator: 0, // TODO - name_length_low: name.len() as u8, - }; - block[..size_of::()] - .copy_from_slice(bytemuck::bytes_of(&dirent)); - block[size_of::()..total_len].copy_from_slice(name.as_bytes()); - - // Fill the rest with empty blocks - let dummy = Dirent { - ino: 0, - ent_size: (self.fs.block_size - aligned_len).try_into().unwrap(), - type_indicator: 0, - name_length_low: 0, - }; - block[aligned_len..aligned_len + size_of::()] - .copy_from_slice(bytemuck::bytes_of(&dummy)); - - Ok(()) - }) - .await?; - } - Ok(()) - }) - .await - } - - async fn prepare_for_removal(&self) -> Result<(), Error> { - // Check that the directory doesn't have any children besides . and .. and set the locked - // flag to prevent anyone from adding entries while the directory is being unlinked - // TODO find a better way to do this - self.inode - .amap_mut(async |inode| { - let n = inode.blocks(&self.fs); - - for i in 0..n { - self.fs - .with_inode_block(inode, i as _, |block| { - let iter = DirentIter::new(&self.fs, block, 0); - - for (_, name, _) in iter { - if name != b"." && name != b".." { - return Err(Error::IsADirectory); - } - } - - Ok(()) - }) - .await?; - } - - self.locked.store(true, Ordering::Release); - Ok(()) - }) - .await - } - - async fn remove_entry(&self, child: &Node, name: &str) -> Result<(), Error> { - if name.len() >= 255 || name.contains('/') || name.contains('\0') { - return Err(Error::InvalidArgument); - } - assert_eq!( - Arc::as_ptr(&child.filesystem().unwrap()).addr(), - Arc::as_ptr(&self.fs).addr() - ); - - let child_ino = match child.data_as_any() { - data if let Some(dir) = data.downcast_ref::() => { - // Check that the directory is not empty - dir.prepare_for_removal().await?; - dir.inode.ino() - } - data if let Some(file) = data.downcast_ref::() => file.inode.ino(), - _ => return Err(Error::InvalidOperation), - }; - - let ino = self - .inode - .amap_mut(async |inode| { - let mut result = None; - 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; - } - } - - result.ok_or(Error::DoesNotExist) - }) - .await?; - - assert_eq!(ino, child_ino); - log::info!("ext2: unlinked ino #{ino}"); - - let child = InodeAccess::new(self.inode.cache().clone(), ino); - // TODO put the inode into the "free list" to be freed on cache flush/when no one is - // holding a ref to it any longer - child - .map_mut(|inode| { - inode.hard_links = inode.hard_links.saturating_sub(1); - inode.dtime = real_time().seconds as u32; - Ok(()) - }) - .await?; - - Ok(()) - } - - async fn lookup_entry(&self, search_name: &str) -> Result { - assert!(search_name.len() < 255); - - self.inode - .amap(async |inode| { - let n = inode.blocks(&self.fs) as u32; - - for i in 0..n { - let ino = self - .fs - .with_inode_block(inode, i, |block| { - let iter = DirentIter::new(&self.fs, block, 0); - - for (dirent, name, _) in iter { - let Ok(name) = core::str::from_utf8(name) else { - continue; - }; - - if search_name == name { - return Ok(Some(dirent.ino)); - } - } - - Ok(None) - }) - .await?; - - if let Some(ino) = ino { - return self.fs.load_node(ino).await; - } - } - - Err(Error::DoesNotExist) - }) - .await - } - - async fn read_entries( - &self, - mut pos: u64, - entries: &mut [MaybeUninit], - ) -> Result<(usize, u64), Error> { - self.inode - .amap(async |inode| { - let size = inode.size(&self.fs); - if pos >= inode.size(&self.fs) || entries.is_empty() { - return Ok((0, pos)); - } - - loop { - if pos >= size { - return Ok((0, pos)); - } - - let index = pos / self.fs.block_size as u64; - let offset = (pos % self.fs.block_size as u64) as usize; - - let (entry_count, new_pos) = self - .fs - .with_inode_block(inode, index as u32, |block| { - let mut pos = pos; - let mut entry_count = 0; - - let iter = DirentIter::new(&self.fs, block, offset); - - for (dirent, name, entry_offset) in iter { - pos = (index * self.fs.block_size as u64) + entry_offset as u64; - - if entry_count >= entries.len() { - break; - } - - pos += dirent.ent_size as u64; - - if let Ok(name) = core::str::from_utf8(name) { - entries[entry_count].write(DirectoryEntry { - ty: None, - name: FixedString::from_str(name)?, - }); - - entry_count += 1; - } - } - - Ok((entry_count, pos)) - }) - .await?; - - pos = new_pos; - - // If read any entries from the block, return - if entry_count != 0 { - return Ok((entry_count, pos)); - } - - // Otherwise go to next block - pos = (index + 1) * self.fs.block_size as u64; - } - }) - .await - } -} - -impl CommonImpl for DirectoryNode { - fn as_any(&self) -> &dyn Any { - self - } - - fn size(&self, _node: &NodeRef) -> Result { - block!(self.inode.size().await)? - } - - fn metadata(&self, _node: &NodeRef) -> Result { - block!(self.inode.metadata().await)? - } - - fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> { - block!(self.inode.update_metadata(metadata).await)? - } -} - -impl DirectoryImpl for DirectoryNode { - fn open(&self, _node: &NodeRef) -> Result { - Ok(DirectoryOpenPosition::FromPhysical(0)) - } - - fn len(&self, _node: &NodeRef) -> Result { - Err(Error::NotImplemented) - } - - fn lookup(&self, _node: &NodeRef, search_name: &str) -> Result { - block!(self.lookup_entry(search_name).await)? - } - - fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result { - if self.fs.force_readonly { - return Err(Error::ReadOnly); - } - let mode = match ty { - FileType::Directory | FileType::Symlink => FileMode::default_dir(), - _ => FileMode::default_file(), - }; - block!(InodeAccess::allocate(&self.fs, ty, mode, Some(self.inode.ino())).await)? - } - - fn attach_node(&self, _parent: &NodeRef, child: &NodeRef, name: &str) -> Result<(), Error> { - if self.fs.force_readonly { - return Err(Error::ReadOnly); - } - // Check that child is ext2 - let child_ino = match child.data_as_any() { - data if let Some(dir) = data.downcast_ref::() => dir.inode.ino(), - data if let Some(file) = data.downcast_ref::() => file.inode.ino(), - _ => return Err(Error::InvalidOperation), - }; - block!(self.create_entry(name, child_ino).await)??; - - Ok(()) - } - - fn unlink_node(&self, _parent: &NodeRef, child: &NodeRef, name: &str) -> Result<(), Error> { - if self.fs.force_readonly { - return Err(Error::ReadOnly); - } - block!(self.remove_entry(child, name).await)? - } - - fn read_entries( - &self, - _node: &NodeRef, - pos: u64, - entries: &mut [MaybeUninit], - ) -> Result<(usize, u64), Error> { - block!(self.read_entries(pos, entries).await)? - } -} diff --git a/kernel/driver/fs/ext2/src/dir/mod.rs b/kernel/driver/fs/ext2/src/dir/mod.rs new file mode 100644 index 00000000..f33366b7 --- /dev/null +++ b/kernel/driver/fs/ext2/src/dir/mod.rs @@ -0,0 +1,410 @@ +// TODO: dedupe the code for iterating the directory records +use core::{ + any::Any, + mem::MaybeUninit, + ops::Deref, + sync::atomic::{AtomicBool, Ordering}, +}; + +use alloc::sync::Arc; +use libk::{ + block, + error::Error, + time::real_time, + vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef}, +}; +use walk::{DirentIter, DirentIterMut}; +use yggdrasil_abi::io::{DirectoryEntry, FileMode, FileType}; + +use crate::{file::RegularNode, inode::InodeAccess, Dirent, Ext2Fs, Inode}; + +mod walk; + +pub struct DirectoryNode { + fs: Arc, + locked: AtomicBool, + pub(crate) inode: InodeAccess, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Hash)] +pub struct DirentName<'a>(&'a str); + +impl DirentName<'_> { + pub const DOT: DirentName<'static> = DirentName("."); + pub const DOT_DOT: DirentName<'static> = DirentName(".."); + + pub fn is_dot_or_dotdot(&self) -> bool { + self.0 == Self::DOT.0 || self.0 == Self::DOT_DOT.0 + } +} + +impl<'a> TryFrom<&'a str> for DirentName<'a> { + type Error = Error; + + fn try_from(value: &'a str) -> Result { + if value.len() >= 255 || value.is_empty() || value.contains('/') { + return Err(Error::InvalidArgument); + } + Ok(Self(value)) + } +} + +impl Deref for DirentName<'_> { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl DirectoryNode { + pub fn new(fs: Arc, inode: InodeAccess) -> NodeRef { + Node::directory( + Self { + fs: fs.clone(), + locked: AtomicBool::new(false), + inode, + }, + NodeFlags::empty(), + None, + Some(fs), + ) + } + + pub async fn create( + fs: Arc, + inode: InodeAccess, + parent_ino: Option, + ) -> Result { + let ino = inode.ino(); + let this = Self { + fs: fs.clone(), + locked: AtomicBool::new(false), + inode, + }; + + // fsck wants . as first entry, .. as second + this.create_entry(DirentName::DOT, ino).await?; + if let Some(parent) = parent_ino { + this.create_entry(DirentName::DOT_DOT, parent).await?; + } + + Ok(Node::directory(this, NodeFlags::empty(), None, Some(fs))) + } + + async fn create_entry(&self, name: DirentName<'_>, ino: u32) -> Result<(), Error> { + async fn try_fit( + inode: &mut Inode, + fs: &Arc, + name: DirentName<'_>, + ino: u32, + ty: u8, + ) -> Result { + let n = inode.blocks(fs) as u32; + + for i in 0..n { + let fit_block = fs + .with_inode_block_mut(inode, i, 0, |block| { + let mut iter = DirentIterMut::new(fs, &mut block[..], 0); + if iter.try_fit(name, ino, ty) { + Ok(true) + } else { + Ok(false) + } + }) + .await?; + + if fit_block { + return Ok(true); + } + } + + Ok(false) + } + + async fn extend( + inode: &mut Inode, + fs: &Arc, + name: DirentName<'_>, + ino: u32, + ty: u8, + ) -> Result<(), Error> { + // Allocate a new block + let block_index = inode.blocks(fs) as u32; + + // Grow the storage + inode + .reserve(fs, (block_index as u64 + 1) * fs.block_size as u64) + .await?; + + fs.with_inode_block_mut(inode, block_index, fs.block_size, |block| { + // Place a dummy dirent spanning the whole block + block.fill(0); + block[..size_of::()].copy_from_slice(bytemuck::bytes_of(&Dirent { + ino: 0, + ent_size: fs.block_size as _, + type_indicator: 0, + name_length_low: 0, + })); + + // Insert the entry there + let mut iter = DirentIterMut::new(fs, &mut block[..], 0); + if iter.try_fit(name, ino, ty) { + Ok(()) + } else { + unreachable!( + "Possible driver bug: cannot fit a record into a just-allocated block" + ) + } + }) + .await + } + + // This directory is in the process of being unlinked and cannot be written to + if self.locked.load(Ordering::Acquire) { + return Err(Error::DoesNotExist); + } + + let child = InodeAccess::new(self.inode.cache().clone(), ino); + + let ty = child + .amap_mut(async |child_inode| { + // Get child inode type indicator which may or may not be used in the dirent + let ty = child_inode + .mode + .dirent_indicator() + .ok_or(Error::InvalidArgument)?; + + child_inode.inc_hard_count(); + child_inode.dtime = 0; + + Ok(ty) + }) + .await?; + + // Insert the entry + self.inode + .amap_mut(async |inode| { + // Try fitting the entry in existing blocks + if try_fit(inode, &self.fs, name, ino, ty).await? { + return Ok(()); + } + // Otherwise, allocate a new block and extend the directory + extend(inode, &self.fs, name, ino, ty).await + }) + .await + } + + async fn prepare_for_removal(&self) -> Result<(), Error> { + // Check that the directory doesn't have any children besides . and .. and set the locked + // flag to prevent anyone from adding entries while the directory is being unlinked + // TODO find a better way to do this + + self.locked.store(true, Ordering::Release); + + let result = self + .inode + .map_blocks(|_, _, block| { + let mut iter = DirentIter::new(&self.fs, block, 0); + while let Some(record) = iter.next_record() { + if !record.is_empty() + && record.name_bytes() != b"." + && record.name_bytes() != b".." + { + return Err(Error::IsADirectory); + } + } + + Ok(Option::<()>::None) + }) + .await; + + match result { + Ok(_) => Ok(()), + Err(error) => { + self.locked.store(false, Ordering::Release); + Err(error) + } + } + } + + async fn remove_entry(&self, child: &Node, name: DirentName<'_>) -> Result<(), Error> { + assert_eq!( + Arc::as_ptr(&child.filesystem().unwrap()).addr(), + Arc::as_ptr(&self.fs).addr() + ); + let child_ino = match child.data_as_any() { + data if let Some(dir) = data.downcast_ref::() => { + // Check that the directory is not empty + dir.prepare_for_removal().await?; + dir.inode.ino() + } + data if let Some(file) = data.downcast_ref::() => file.inode.ino(), + _ => return Err(Error::InvalidOperation), + }; + + let ino = self + .inode + .map_blocks_mut(|_inode, _index, block| { + let mut iter = DirentIterMut::new(&self.fs, block, 0); + Ok(iter.remove(name)) + }) + .await?; + + let ino = ino.ok_or(Error::DoesNotExist)?; + + assert_eq!(ino, child_ino); + log::info!("ext2: unlinked ino #{ino}"); + + let child = InodeAccess::new(self.inode.cache().clone(), ino); + // TODO put the inode into the "free list" to be freed on cache flush/when no one is + // holding a ref to it any longer + child + .map_mut(|inode| { + inode.hard_links = inode.hard_links.saturating_sub(1); + inode.dtime = real_time().seconds as u32; + Ok(()) + }) + .await?; + + Ok(()) + } + + async fn lookup_entry(&self, name: DirentName<'_>) -> Result { + let entry = self + .inode + .map_blocks(|_inode, _index, block| { + let mut iter = DirentIter::new(&self.fs, block, 0); + Ok(iter.find_ino(name)) + }) + .await?; + + let ino = entry.ok_or(Error::DoesNotExist)?; + // Shouldn't happen, but just in case + if ino == 0 { + return Err(Error::DoesNotExist); + } + + self.fs.load_node(ino).await + } + + async fn read_entries( + &self, + mut pos: u64, + entries: &mut [MaybeUninit], + ) -> Result<(usize, u64), Error> { + self.inode + .amap(async |inode| { + let size = inode.size(&self.fs); + if pos >= inode.size(&self.fs) || entries.is_empty() { + return Ok((0, pos)); + } + + loop { + if pos >= size { + return Ok((0, pos)); + } + + let index = pos / self.fs.block_size as u64; + let offset = (pos % self.fs.block_size as u64) as usize; + + let (entry_count, new_pos) = self + .fs + .with_inode_block(inode, index as u32, |block| { + let mut iter = DirentIter::new(&self.fs, block, offset); + let entry_count = iter.list_entries(entries); + Ok((entry_count, pos + iter.offset() as u64)) + }) + .await?; + + pos = new_pos; + + // If read any entries from the block, return + if entry_count != 0 { + return Ok((entry_count, pos)); + } + + // Otherwise go to next block + pos = (index + 1) * self.fs.block_size as u64; + } + }) + .await + } +} + +impl CommonImpl for DirectoryNode { + fn as_any(&self) -> &dyn Any { + self + } + + fn size(&self, _node: &NodeRef) -> Result { + block!(self.inode.size().await)? + } + + fn metadata(&self, _node: &NodeRef) -> Result { + block!(self.inode.metadata().await)? + } + + fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> { + block!(self.inode.update_metadata(metadata).await)? + } +} + +impl DirectoryImpl for DirectoryNode { + fn open(&self, _node: &NodeRef) -> Result { + Ok(DirectoryOpenPosition::FromPhysical(0)) + } + + fn len(&self, _node: &NodeRef) -> Result { + Err(Error::NotImplemented) + } + + fn lookup(&self, _node: &NodeRef, name: &str) -> Result { + let name = DirentName::try_from(name)?; + block!(self.lookup_entry(name).await)? + } + + fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result { + if self.fs.force_readonly { + return Err(Error::ReadOnly); + } + let mode = match ty { + FileType::Directory | FileType::Symlink => FileMode::default_dir(), + _ => FileMode::default_file(), + }; + block!(InodeAccess::allocate(&self.fs, ty, mode, Some(self.inode.ino())).await)? + } + + fn attach_node(&self, _parent: &NodeRef, child: &NodeRef, name: &str) -> Result<(), Error> { + if self.fs.force_readonly { + return Err(Error::ReadOnly); + } + let name = DirentName::try_from(name)?; + // Check that child is ext2 + let child_ino = match child.data_as_any() { + data if let Some(dir) = data.downcast_ref::() => dir.inode.ino(), + data if let Some(file) = data.downcast_ref::() => file.inode.ino(), + _ => return Err(Error::InvalidOperation), + }; + block!(self.create_entry(name, child_ino).await)??; + + Ok(()) + } + + fn unlink_node(&self, _parent: &NodeRef, child: &NodeRef, name: &str) -> Result<(), Error> { + if self.fs.force_readonly { + return Err(Error::ReadOnly); + } + let name = DirentName::try_from(name)?; + block!(self.remove_entry(child, name).await)? + } + + fn read_entries( + &self, + _node: &NodeRef, + pos: u64, + entries: &mut [MaybeUninit], + ) -> Result<(usize, u64), Error> { + block!(self.read_entries(pos, entries).await)? + } +} diff --git a/kernel/driver/fs/ext2/src/dir/walk.rs b/kernel/driver/fs/ext2/src/dir/walk.rs new file mode 100644 index 00000000..12c6af7c --- /dev/null +++ b/kernel/driver/fs/ext2/src/dir/walk.rs @@ -0,0 +1,275 @@ +use core::{ + mem::{self, MaybeUninit}, + ops::{Deref, DerefMut}, + str::FromStr, +}; + +use yggdrasil_abi::{ + io::{DirectoryEntry, FileType}, + util::FixedString, +}; + +use crate::{data::FsRequiredFeatures, Dirent, Ext2Fs}; + +use super::DirentName; + +pub(super) struct DirentIter<'a> { + fs: &'a Ext2Fs, + block: &'a [u8], + offset: usize, +} + +pub(super) struct DirentIterMut<'a> { + fs: &'a Ext2Fs, + block: &'a mut [u8], + offset: usize, +} + +pub(super) struct Record<'a, D: 'a> { + fs: &'a Ext2Fs, + data: D, + offset: usize, +} + +impl<'a, D: Deref + 'a> Record<'a, D> { + pub fn dirent(&self) -> &Dirent { + bytemuck::from_bytes(&self.data[..size_of::()]) + } + + pub fn is_empty(&self) -> bool { + self.dirent().ino == 0 + } + + pub fn ino(&self) -> u32 { + self.dirent().ino + } + + pub fn data_size(&self) -> usize { + // Whole dirent is free space + if self.is_empty() { + return 0; + } + + (self.name_len() + size_of::() + 3) & !3 + } + + 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()) + } + + 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 + } + + pub(super) 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() + } + + fn type_indicator(&self) -> Option { + if self + .fs + .required_features + .contains(FsRequiredFeatures::DIRENT_TYPE_FIELD) + { + match self.dirent().type_indicator { + 1 => Some(FileType::File), + 2 => Some(FileType::Directory), + 7 => Some(FileType::Symlink), + _ => None, + } + } else { + None + } + } +} + +impl<'a, D: DerefMut + 'a> Record<'a, D> { + pub fn dirent_mut(&mut self) -> &mut Dirent { + bytemuck::from_bytes_mut(&mut self.data[..size_of::()]) + } +} + +impl<'a> DirentIter<'a> { + pub fn new(fs: &'a Ext2Fs, block: &'a [u8], offset: usize) -> Self { + Self { fs, block, offset } + } + + pub fn offset(&self) -> usize { + self.offset + } + + pub 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: &self.block[self.offset..self.offset + record_size], + fs: self.fs, + offset: self.offset, + }; + + self.offset += record_size; + Some(record) + } + + pub fn list_entries(&mut self, entries: &mut [MaybeUninit]) -> usize { + let mut entry_count = 0; + while let Some(record) = self.next_record() { + if entry_count >= entries.len() { + break; + } + + if let Some(name) = record + .name() + .and_then(|name| FixedString::from_str(name).ok()) + { + let ty = record.type_indicator(); + entries[entry_count].write(DirectoryEntry { ty, name }); + entry_count += 1; + } + } + entry_count + } + + pub fn find_ino(&mut self, name: DirentName<'_>) -> Option { + while let Some(record) = self.next_record() { + if record.name().map(|n| n == &*name).unwrap_or(false) { + return Some(record.ino()); + } + } + None + } +} + +impl<'a> DirentIterMut<'a> { + pub fn new(fs: &'a Ext2Fs, block: &'a mut [u8], offset: usize) -> Self { + Self { fs, block, offset } + } + + pub 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: DirentName<'_>) -> Option { + 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: DirentName<'_>, ino: u32, ty: u8) -> bool { + let name = name.as_bytes(); + + let entry_size = size_of::() + name.len(); + let aligned_entry_size = (entry_size + 3) & !3; + + let name_length_low = name.len() as u8; + let type_indicator = if self + .fs + .required_features + .contains(FsRequiredFeatures::DIRENT_TYPE_FIELD) + { + ty + } else { + todo!() + }; + + while let Some(mut record) = self.next_record() { + if record.free_space() < aligned_entry_size { + continue; + } + + let free_space = record.free_space(); + + let offset = if record.is_empty() { + // ino = 0, offset = 0, basically just overwrite the old record + log::info!( + "Fitting #{ino} into an empty record of size {free_space} ({})", + record.record_size() + ); + 0 + } else { + // ino != 0, offset = end of the record, keep it + let size = record.data_size(); + log::info!( + "Fitting #{ino} into a non-empty record of size {size}+{free_space} ({})", + record.record_size() + ); + + // Trim the record + record.dirent_mut().ent_size = size as u16; + + size + }; + let offset = offset + record.offset; + + log::info!("Will place #{ino} at {offset}, size {entry_size} ({free_space})"); + + // Insert the new dirent, make it occupy all the free space + let new_dirent = Dirent { + ino, + type_indicator, + name_length_low, + ent_size: free_space as u16, + }; + let new_dirent_end = offset + size_of::(); + + self.block[offset..new_dirent_end].copy_from_slice(bytemuck::bytes_of(&new_dirent)); + self.block[new_dirent_end..offset + entry_size].copy_from_slice(name); + + return true; + } + + false + } +} diff --git a/kernel/driver/fs/ext2/src/inode.rs b/kernel/driver/fs/ext2/src/inode.rs index a6e1dc8b..98f8ae7e 100644 --- a/kernel/driver/fs/ext2/src/inode.rs +++ b/kernel/driver/fs/ext2/src/inode.rs @@ -132,6 +132,56 @@ impl InodeAccess { 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