ext2: better directory handling

This commit is contained in:
Mark Poliakov 2024-12-31 15:17:06 +02:00
parent 3aea206cad
commit 77b6403c68
5 changed files with 744 additions and 650 deletions

View File

@ -265,6 +265,15 @@ impl InodeMode {
} }
} }
pub fn dirent_indicator(&self) -> Option<u8> {
match self.0 & 0xF000 {
0x4000 => Some(2),
0x8000 => Some(1),
0xA000 => Some(7),
_ => None,
}
}
pub fn default_for_type(ty: FileType) -> Self { pub fn default_for_type(ty: FileType) -> Self {
match ty { match ty {
FileType::File => Self(0o644 | 0x8000), FileType::File => Self(0o644 | 0x8000),

View File

@ -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<Ext2Fs>,
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::<Dirent>()])
}
pub fn dirent_mut(&mut self) -> &mut Dirent {
bytemuck::from_bytes_mut(&mut self.data[..size_of::<Dirent>()])
}
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::<Dirent>()..size_of::<Dirent>() + 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::<Dirent>()
// }
// pub fn record_size(&self) -> usize {
// (self.dirent().ent_size as usize).max(size_of::<Dirent>())
// }
// 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<Record> {
if self.offset + size_of::<Dirent>() > self.block.len() {
return None;
}
let dirent_struct_end = self.offset + size_of::<Dirent>();
let dirent: &Dirent = bytemuck::from_bytes(&self.block[self.offset..dirent_struct_end]);
let record_size = (dirent.ent_size as usize).max(size_of::<Dirent>());
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<u32> {
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::<Dirent>() + 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::<Dirent>();
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::<Dirent>() {
// 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::<Dirent>()]
.copy_from_slice(bytemuck::bytes_of(&new_dirent));
self.block[self.offset + size_of::<Dirent>()..self.offset + new_total_size]
.copy_from_slice(name);
if extra_space >= size_of::<Dirent>() {
// 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::<Dirent>()]
.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<Self::Item> {
loop {
if self.offset + size_of::<Dirent>() >= self.block.len() {
return None;
}
let entry_end = self.offset + size_of::<Dirent>();
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::<Dirent>());
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<Ext2Fs>, 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<Ext2Fs>,
inode: InodeAccess,
parent_ino: Option<u32>,
) -> Result<NodeRef, Error> {
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::<Dirent>() + 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::<Dirent>()]
.copy_from_slice(bytemuck::bytes_of(&dirent));
block[size_of::<Dirent>()..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::<Dirent>()]
.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::<DirectoryNode>() => {
// Check that the directory is not empty
dir.prepare_for_removal().await?;
dir.inode.ino()
}
data if let Some(file) = data.downcast_ref::<RegularNode>() => 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<NodeRef, Error> {
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<DirectoryEntry>],
) -> 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<u64, Error> {
block!(self.inode.size().await)?
}
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
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<DirectoryOpenPosition, Error> {
Ok(DirectoryOpenPosition::FromPhysical(0))
}
fn len(&self, _node: &NodeRef) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
fn lookup(&self, _node: &NodeRef, search_name: &str) -> Result<NodeRef, Error> {
block!(self.lookup_entry(search_name).await)?
}
fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
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::<DirectoryNode>() => dir.inode.ino(),
data if let Some(file) = data.downcast_ref::<RegularNode>() => 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<DirectoryEntry>],
) -> Result<(usize, u64), Error> {
block!(self.read_entries(pos, entries).await)?
}
}

View File

@ -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<Ext2Fs>,
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<Self, Self::Error> {
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<Ext2Fs>, 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<Ext2Fs>,
inode: InodeAccess,
parent_ino: Option<u32>,
) -> Result<NodeRef, Error> {
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<Ext2Fs>,
name: DirentName<'_>,
ino: u32,
ty: u8,
) -> Result<bool, Error> {
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<Ext2Fs>,
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::<Dirent>()].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::<DirectoryNode>() => {
// Check that the directory is not empty
dir.prepare_for_removal().await?;
dir.inode.ino()
}
data if let Some(file) = data.downcast_ref::<RegularNode>() => 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<NodeRef, Error> {
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<DirectoryEntry>],
) -> 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<u64, Error> {
block!(self.inode.size().await)?
}
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
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<DirectoryOpenPosition, Error> {
Ok(DirectoryOpenPosition::FromPhysical(0))
}
fn len(&self, _node: &NodeRef) -> Result<usize, Error> {
Err(Error::NotImplemented)
}
fn lookup(&self, _node: &NodeRef, name: &str) -> Result<NodeRef, Error> {
let name = DirentName::try_from(name)?;
block!(self.lookup_entry(name).await)?
}
fn create_node(&self, _parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
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::<DirectoryNode>() => dir.inode.ino(),
data if let Some(file) = data.downcast_ref::<RegularNode>() => 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<DirectoryEntry>],
) -> Result<(usize, u64), Error> {
block!(self.read_entries(pos, entries).await)?
}
}

View File

@ -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<Target = [u8]> + 'a> Record<'a, D> {
pub fn dirent(&self) -> &Dirent {
bytemuck::from_bytes(&self.data[..size_of::<Dirent>()])
}
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::<Dirent>() + 3) & !3
}
pub fn record_size(&self) -> usize {
(self.dirent().ent_size as usize).max(size_of::<Dirent>())
}
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::<Dirent>()..size_of::<Dirent>() + self.name_len()]
} else {
b""
}
}
fn name(&self) -> Option<&str> {
core::str::from_utf8(self.name_bytes()).ok()
}
fn type_indicator(&self) -> Option<FileType> {
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<Target = [u8]> + 'a> Record<'a, D> {
pub fn dirent_mut(&mut self) -> &mut Dirent {
bytemuck::from_bytes_mut(&mut self.data[..size_of::<Dirent>()])
}
}
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<Record<&[u8]>> {
if self.offset + size_of::<Dirent>() > self.block.len() {
return None;
}
let dirent_struct_end = self.offset + size_of::<Dirent>();
let dirent: &Dirent = bytemuck::from_bytes(&self.block[self.offset..dirent_struct_end]);
let record_size = (dirent.ent_size as usize).max(size_of::<Dirent>());
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<DirectoryEntry>]) -> 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<u32> {
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<Record<&mut [u8]>> {
if self.offset + size_of::<Dirent>() > self.block.len() {
return None;
}
let dirent_struct_end = self.offset + size_of::<Dirent>();
let dirent: &Dirent = bytemuck::from_bytes(&self.block[self.offset..dirent_struct_end]);
let record_size = (dirent.ent_size as usize).max(size_of::<Dirent>());
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<u32> {
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::<Dirent>() + 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::<Dirent>();
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
}
}

View File

@ -132,6 +132,56 @@ impl InodeAccess {
result result
} }
pub async fn map_blocks<T, F: Fn(&Inode, usize, &[u8]) -> Result<Option<T>, Error>>(
&self,
mapper: F,
) -> Result<Option<T>, 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<T, F: Fn(&Inode, usize, &mut [u8]) -> Result<Option<T>, Error>>(
&self,
mapper: F,
) -> Result<Option<T>, 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<Metadata, Error> { pub async fn metadata(&self) -> Result<Metadata, Error> {
self.map(|inode| Ok(inode.metadata(&self.inode_cache.fs, self.ino))) self.map(|inode| Ok(inode.metadata(&self.inode_cache.fs, self.ino)))
.await .await