ext2: better directory handling
This commit is contained in:
parent
3aea206cad
commit
77b6403c68
@ -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),
|
||||||
|
@ -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)?
|
|
||||||
}
|
|
||||||
}
|
|
410
kernel/driver/fs/ext2/src/dir/mod.rs
Normal file
410
kernel/driver/fs/ext2/src/dir/mod.rs
Normal 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)?
|
||||||
|
}
|
||||||
|
}
|
275
kernel/driver/fs/ext2/src/dir/walk.rs
Normal file
275
kernel/driver/fs/ext2/src/dir/walk.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user