ext2: use the same access method for icache as for bcache

This commit is contained in:
Mark Poliakov 2024-12-31 11:23:33 +02:00
parent 5f2c99f5c7
commit fd9ea77adb
9 changed files with 646 additions and 495 deletions

View File

@ -113,6 +113,12 @@ impl BlockDevice for NvmeNamespace {
debug_assert_eq!(buffer_address.into_u64() % self.block_size() as u64, 0);
let lba_count = buffer.len() / self.block_size();
// TODO ArchitectureImpl::flush_data_cache()
#[cfg(target_arch = "x86_64")]
unsafe {
core::arch::asm!("wbinvd");
}
let result = self
.controller
.perform_io(

View File

@ -0,0 +1,118 @@
use libk::error::Error;
use crate::{BlockGroupDescriptor, Ext2Fs, ExtendedSuperblock};
impl Ext2Fs {
async fn allocate<
F: Fn(&mut BlockGroupDescriptor) -> Option<(u32, u32)>,
G: Fn(&mut ExtendedSuperblock),
>(
&self,
descriptor_mapper: F,
superblock_mapper: G,
) -> Result<u32, Error> {
let bit_per_block = self.block_size * 8;
for group_index in 0..self.bgdt.entry_count as u32 {
let bitmap = self
.with_bgdt_entry_mut(group_index, |descriptor| {
match descriptor_mapper(descriptor) {
Some(val) => Ok(Some(val)),
_ => Ok(None),
}
})
.await?;
if let Some((bitmap, group_item_count)) = bitmap {
let no = self
.with_block_mut(bitmap, 0, |bitmap| {
for i in 0..bit_per_block.min(group_item_count as usize) {
let index = i / 8;
let bit = 1u8 << (i % 8);
if bitmap[index] & bit == 0 {
bitmap[index] |= bit;
return Ok(Some(i as u32));
}
}
Ok(None)
})
.await?
.expect("TODO: bgdt says there're things, but bitmap says there aren't");
{
let mut state = self.state.write();
superblock_mapper(&mut state.superblock);
state.dirty = true;
}
if !self.cached {
self.flush_superblock().await?;
}
return Ok(group_index * group_item_count + no);
}
}
todo!("ENOSPC")
}
pub(crate) async fn allocate_inode(&self, directory: bool) -> Result<u32, Error> {
let ino = self
.allocate(
|descriptor| {
if descriptor.unallocated_inodes == 0
|| (directory && descriptor.directories == u16::MAX)
{
None
} else {
if directory {
descriptor.directories += 1;
}
descriptor.unallocated_inodes -= 1;
log::info!("bg unallocated_inodes = {}", descriptor.unallocated_inodes);
Some((descriptor.inode_usage_bitmap, self.block_group_inode_count))
}
},
|superblock| {
superblock.total_unallocated_inodes -= 1;
log::info!(
"total_unallocated_inodes = {}",
superblock.total_unallocated_inodes
);
},
)
.await?;
let ino = ino + 1;
log::debug!("ext2: allocated inode #{ino}");
Ok(ino)
}
pub(crate) async fn allocate_block(&self) -> Result<u32, Error> {
let no = self
.allocate(
|descriptor| {
if descriptor.unallocated_blocks == 0 {
None
} else {
descriptor.unallocated_blocks -= 1;
log::info!("bg unallocated_blocks = {}", descriptor.unallocated_blocks);
Some((descriptor.block_usage_bitmap, self.block_group_block_count))
}
},
|superblock| {
superblock.total_unallocated_blocks -= 1;
log::info!(
"total_unallocated_blocks = {}",
superblock.total_unallocated_blocks
);
},
)
.await?;
log::debug!("ext2: allocated block #{no}");
Ok(no)
}
}

View File

@ -14,7 +14,7 @@ use libk::{
vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef},
};
use yggdrasil_abi::{
io::{DirectoryEntry, FileType},
io::{DirectoryEntry, FileMode, FileType},
util::FixedString,
};
@ -40,8 +40,9 @@ struct DirentIterMut<'a> {
struct Record<'a> {
fs: &'a Ext2Fs,
offset: usize,
data: &'a mut [u8],
#[allow(unused)]
offset: usize,
}
impl Record<'_> {
@ -57,9 +58,9 @@ impl Record<'_> {
self.dirent().ino == 0
}
pub fn ino(&self) -> u32 {
self.dirent().ino
}
// pub fn ino(&self) -> u32 {
// self.dirent().ino
// }
fn name_len(&self) -> usize {
let len = self.dirent().name_length_low as usize;
@ -81,26 +82,26 @@ impl Record<'_> {
}
}
fn name(&self) -> Option<&str> {
core::str::from_utf8(self.name_bytes()).ok()
}
// 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;
}
// pub fn data_size(&self) -> usize {
// // Whole dirent is free space
// if self.is_empty() {
// return 0;
// }
self.name_len() + size_of::<Dirent>()
}
// self.name_len() + size_of::<Dirent>()
// }
pub fn record_size(&self) -> usize {
(self.dirent().ent_size as usize).max(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())
}
// pub fn free_space(&self) -> usize {
// self.data.len().saturating_sub(self.data_size())
// }
}
impl<'a> DirentIterMut<'a> {
@ -300,120 +301,114 @@ impl DirectoryNode {
return Err(Error::DoesNotExist);
}
{
let mut holder = self.inode.cache().get_mut(ino).await?;
{
let mut child = holder.write();
let child = InodeAccess::new(self.inode.cache().clone(), ino);
child
.map_mut(|inode| {
inode.inc_hard_count();
inode.dtime = 0;
Ok(())
})
.await?;
child.inc_hard_count();
child.dtime = 0;
}
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?;
holder.put().await?;
}
let mut holder = self.inode.get_mut().await?;
{
let mut inode = holder.write();
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_block {
fit = true;
break;
}
}
}
if !fit {
// Allocate a new block
let block_index = inode.blocks(&self.fs) as u32;
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?;
// 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);
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());
// 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));
// 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?;
}
}
holder.put().await?;
Ok(())
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
let mut holder = self.inode.get_mut().await?;
{
let inode = holder.write();
let n = inode.blocks(&self.fs);
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 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);
for (_, name, _) in iter {
if name != b"." && name != b".." {
return Err(Error::IsADirectory);
}
}
}
Ok(())
})
.await?;
}
Ok(())
})
.await?;
}
self.locked.store(true, Ordering::Release);
}
holder.put().await?;
Ok(())
self.locked.store(true, Ordering::Release);
Ok(())
})
.await
}
async fn remove_entry(&self, child: &Node, name: &str) -> Result<(), Error> {
@ -435,12 +430,10 @@ impl DirectoryNode {
_ => return Err(Error::InvalidOperation),
};
let ino = {
let mut holder = self.inode.get_mut().await?;
let mut result = None;
{
let inode = holder.write();
let ino = self
.inode
.amap_mut(async |inode| {
let mut result = None;
let n = inode.blocks(&self.fs);
for i in 0..n {
@ -457,26 +450,24 @@ impl DirectoryNode {
break;
}
}
}
holder.put().await?;
result
};
result.ok_or(Error::DoesNotExist)
})
.await?;
let ino = ino.ok_or(Error::DoesNotExist)?;
assert_eq!(ino, child_ino);
log::info!("ext2: unlinked ino #{ino}");
// Decrement the refcount on the inode
let mut holder = self.inode.cache().get_mut(ino).await?;
{
// 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
let mut inode = holder.write();
inode.hard_links = inode.hard_links.saturating_sub(1);
inode.dtime = real_time().seconds as _;
}
holder.put().await?;
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(())
}
@ -484,36 +475,38 @@ impl DirectoryNode {
async fn lookup_entry(&self, search_name: &str) -> Result<NodeRef, Error> {
assert!(search_name.len() < 255);
let inode = self.inode.get().await?;
let inode = inode.read();
let n = inode.blocks(&self.fs) as u32;
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 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;
};
for (dirent, name, _) in iter {
let Ok(name) = core::str::from_utf8(name) else {
continue;
};
if search_name == name {
return Ok(Some(dirent.ino));
}
if search_name == name {
return Ok(Some(dirent.ino));
}
}
Ok(None)
})
.await?;
if let Some(ino) = ino {
return self.fs.load_node(ino).await;
}
}
Ok(None)
})
.await?;
if let Some(ino) = ino {
return self.fs.load_node(ino).await;
}
}
Err(Error::DoesNotExist)
Err(Error::DoesNotExist)
})
.await
}
async fn read_entries(
@ -521,63 +514,64 @@ impl DirectoryNode {
mut pos: u64,
entries: &mut [MaybeUninit<DirectoryEntry>],
) -> Result<(usize, u64), Error> {
let inode = self.inode.get().await?;
let inode = inode.read();
self.inode
.amap(async |inode| {
let size = inode.size(&self.fs);
if pos >= inode.size(&self.fs) || entries.is_empty() {
return Ok((0, pos));
}
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;
}
loop {
if pos >= size {
return Ok((0, pos));
}
Ok((entry_count, pos))
})
.await?;
let index = pos / self.fs.block_size as u64;
let offset = (pos % self.fs.block_size as u64) as usize;
pos = new_pos;
let (entry_count, new_pos) = self
.fs
.with_inode_block(&inode, index as u32, |block| {
let mut pos = pos;
let mut entry_count = 0;
// If read any entries from the block, return
if entry_count != 0 {
return Ok((entry_count, pos));
}
let iter = DirentIter::new(&self.fs, block, offset);
// Otherwise go to next block
pos = (index + 1) * self.fs.block_size as u64;
}
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
}
}
@ -587,18 +581,14 @@ impl CommonImpl for DirectoryNode {
}
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
let inode = block!(self.inode.get().await)??;
let inode = inode.read();
Ok(inode.size(&self.fs))
block!(self.inode.size().await)?
}
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
let inode = block!(self.inode.get().await)??;
let inode = inode.read();
Ok(inode.metadata(&self.fs, self.inode.ino()))
block!(self.inode.metadata().await)?
}
fn set_metadata(&self, node: &NodeRef, metadata: &Metadata) -> Result<(), Error> {
fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> {
block!(self.inode.update_metadata(metadata).await)?
}
}
@ -620,7 +610,11 @@ impl DirectoryImpl for DirectoryNode {
if self.fs.force_readonly {
return Err(Error::ReadOnly);
}
block!(self.fs.create_node(Some(self.inode.ino()), ty).await)?
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> {

View File

@ -29,61 +29,55 @@ impl RegularNode {
}
async fn resize(&self, new_size: u64) -> Result<(), Error> {
let mut holder = self.inode.get_mut().await?;
let mut inode = holder.write();
inode.resize(&self.fs, new_size).await?;
drop(inode);
holder.put().await?;
Ok(())
self.inode
.amap_mut(async |inode| {
inode.resize(&self.fs, new_size).await?;
Ok(())
})
.await
}
async fn read(&self, pos: u64, buffer: &mut [u8]) -> Result<usize, Error> {
let holder = self.inode.get().await?;
let inode = holder.read();
self.fs.read_inode_data(&inode, pos, buffer).await
self.inode
.amap(async |inode| self.fs.read_inode_data(inode, pos, buffer).await)
.await
}
async fn write(&self, mut pos: u64, buffer: &[u8]) -> Result<usize, Error> {
let mut holder = self.inode.get_mut().await?;
let mut inode = holder.write();
self.inode
.amap_mut(async |inode| {
let need_size = pos + buffer.len() as u64;
inode.reserve(&self.fs, need_size).await?;
let need_size = pos + buffer.len() as u64;
inode.reserve(&self.fs, need_size).await?;
let mut offset = 0;
let mut remaining = buffer.len();
let mut offset = 0;
let mut remaining = buffer.len();
while remaining != 0 {
let block_index = pos / self.fs.block_size as u64;
let block_offset = (pos % self.fs.block_size as u64) as usize;
let amount = remaining.min(self.fs.block_size - block_offset);
while remaining != 0 {
let block_index = pos / self.fs.block_size as u64;
let block_offset = (pos % self.fs.block_size as u64) as usize;
let amount = remaining.min(self.fs.block_size - block_offset);
self.fs
.with_inode_block_mut(&inode, block_index as u32, amount, |block| {
block[block_offset..block_offset + amount]
.copy_from_slice(&buffer[offset..offset + amount]);
Ok(())
})
.await?;
self.fs
.with_inode_block_mut(&inode, block_index as u32, amount, |block| {
block[block_offset..block_offset + amount]
.copy_from_slice(&buffer[offset..offset + amount]);
Ok(())
})
.await?;
pos += amount as u64;
offset += amount;
remaining -= amount;
}
drop(inode);
holder.put().await?;
Ok(offset)
pos += amount as u64;
offset += amount;
remaining -= amount;
}
Ok(offset)
})
.await
}
}
impl CommonImpl for RegularNode {
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
let inode = block!(self.inode.get().await)??;
let inode = inode.read();
Ok(inode.metadata(&self.fs, self.inode.ino()))
block!(self.inode.metadata().await)?
}
fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> {
@ -95,9 +89,7 @@ impl CommonImpl for RegularNode {
}
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
let inode = block!(self.inode.get().await)??;
let inode = inode.read();
Ok(inode.size(&self.fs))
block!(self.inode.size().await)?
}
}

View File

@ -1,18 +1,27 @@
use core::{
cmp::Ordering,
ops::{Deref, DerefMut},
ops::{AsyncFnOnce, Deref, DerefMut},
};
use alloc::sync::Arc;
use libk::{block, error::Error, task::sync::AsyncMutex, vfs::Metadata};
use bytemuck::Zeroable;
use libk::{
block,
error::Error,
task::sync::AsyncMutex,
time::real_time,
vfs::{Metadata, NodeRef},
};
use libk_util::{
lru_hash_table::LruCache,
sync::spin_rwlock::{IrqSafeRwLock, IrqSafeRwLockReadGuard, IrqSafeRwLockWriteGuard},
};
use yggdrasil_abi::io::FileType;
use yggdrasil_abi::io::{FileMode, FileType};
use crate::{
data::{FsReadonlyFeatures, InodeMode, DIRECT_BLOCK_COUNT},
dir::DirectoryNode,
file::RegularNode,
Ext2Fs, Inode,
};
@ -73,12 +82,63 @@ impl InodeAccess {
&self.inode_cache
}
pub async fn get(&self) -> Result<InodeRef, Error> {
self.inode_cache.get(self.ino).await
pub async fn map<T, F: FnOnce(&Inode) -> Result<T, Error>>(
&self,
mapper: F,
) -> Result<T, Error> {
let inode = self.inode_cache.get(self.ino).await?;
let result = {
let lock = inode.read();
mapper(&*lock)
};
result
}
pub async fn get_mut(&self) -> Result<InodeMut, Error> {
self.inode_cache.get_mut(self.ino).await
pub async fn map_mut<T, F: FnOnce(&mut Inode) -> Result<T, Error>>(
&self,
mapper: F,
) -> Result<T, Error> {
let mut inode = self.inode_cache.get_mut(self.ino).await?;
let result = {
let mut lock = inode.write();
mapper(&mut *lock)
};
inode.put().await?;
result
}
pub async fn amap<T, F: AsyncFnOnce(&Inode) -> Result<T, Error>>(
&self,
mapper: F,
) -> Result<T, Error> {
let inode = self.inode_cache.get(self.ino).await?;
let result = {
let lock = inode.read();
mapper(&*lock).await
};
result
}
pub async fn amap_mut<T, F: AsyncFnOnce(&mut Inode) -> Result<T, Error>>(
&self,
mapper: F,
) -> Result<T, Error> {
let mut inode = self.inode_cache.get_mut(self.ino).await?;
let result = {
let mut lock = inode.write();
mapper(&mut *lock).await
};
inode.put().await?;
result
}
pub async fn metadata(&self) -> Result<Metadata, Error> {
self.map(|inode| Ok(inode.metadata(&self.inode_cache.fs, self.ino)))
.await
}
pub async fn size(&self) -> Result<u64, Error> {
self.map(|inode| Ok(inode.size(&self.inode_cache.fs))).await
}
pub async fn update_metadata(&self, metadata: &Metadata) -> Result<(), Error> {
@ -93,10 +153,7 @@ impl InodeAccess {
.try_into()
.map_err(|_| Error::InvalidArgument)?;
let mut holder = self.get_mut().await?;
{
let mut inode = holder.write();
self.map_mut(|inode| {
inode.mtime = metadata.mtime as _;
inode.atime = metadata.mtime as _;
inode.ctime = metadata.ctime as _;
@ -104,8 +161,75 @@ impl InodeAccess {
inode.mode.update_permissions(metadata.mode);
inode.uid = uid;
inode.gid = gid;
Ok(())
})
.await
}
async fn populate_inode(
fs: &Arc<Ext2Fs>,
ty: FileType,
mode: FileMode,
parent_ino: Option<u32>,
ino: u32,
) -> Result<NodeRef, Error> {
log::info!("ext2: allocated inode #{ino}");
let now = real_time().seconds as u32;
let mut imode = InodeMode::default_for_type(ty);
imode.update_permissions(mode);
fs.write_inode(
ino,
&Inode {
ctime: now,
mtime: now,
atime: now,
mode: imode,
..Inode::zeroed()
},
)
.await?;
let this = InodeAccess::new(fs.inode_cache.get().clone(), ino);
let fs = fs.clone();
let node = match ty {
FileType::Directory => DirectoryNode::create(fs, this, parent_ino).await?,
FileType::File => RegularNode::new(fs, this),
FileType::Symlink => todo!(),
_ => return Err(Error::NotImplemented),
};
Ok(node)
}
pub async fn allocate(
fs: &Arc<Ext2Fs>,
ty: FileType,
mode: FileMode,
parent_ino: Option<u32>,
) -> Result<NodeRef, Error> {
if parent_ino.is_none() && ty != FileType::Directory {
log::warn!("ext2: cannot allocate non-directory inode without a parent");
return Err(Error::InvalidOperation);
}
if !matches!(ty, FileType::Symlink | FileType::Directory | FileType::File) {
log::warn!("ext2: cannot allocate inode of type {ty:?}");
return Err(Error::InvalidOperation);
}
let ino = fs.allocate_inode(ty == FileType::Directory).await?;
match Self::populate_inode(fs, ty, mode, parent_ino, ino).await {
Ok(node) => Ok(node),
Err(error) => {
log::warn!("ext2: couldn't set up inode #{ino}: {error:?}");
// TODO free the inode and flush it from the cache
return Err(error);
}
}
holder.put().await
}
}
@ -166,7 +290,7 @@ impl InodeCache {
Ok(value)
}
pub async fn get(&self, ino: u32) -> Result<InodeRef, Error> {
async fn get(&self, ino: u32) -> Result<InodeRef, Error> {
if self.cache.is_some() {
let entry = self.entry(ino).await?;
let inode = CachedInodeRef { entry };
@ -184,7 +308,7 @@ impl InodeCache {
}
}
pub async fn get_mut(self: &Arc<Self>, ino: u32) -> Result<InodeMut, Error> {
async fn get_mut(self: &Arc<Self>, ino: u32) -> Result<InodeMut, Error> {
if self.cache.is_some() {
let entry = self.entry(ino).await?;
let inode = CachedInodeMut { entry };

View File

@ -7,22 +7,23 @@ extern crate alloc;
use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use bytemuck::Zeroable;
use data::{FsReadonlyFeatures, FsRequiredFeatures, InodeMode};
use data::{FsReadonlyFeatures, FsRequiredFeatures};
use dir::DirectoryNode;
use file::RegularNode;
use inode::{InodeAccess, InodeCache};
use libk::{
device::block::{cache::DeviceMapper, BlockDevice},
error::Error,
time::real_time,
vfs::{Filesystem, FilesystemMountOption, NodeRef},
};
use libk_util::{sync::spin_rwlock::IrqSafeRwLock, OneTimeInit};
mod allocation;
mod data;
pub mod inode;
pub mod dir;
pub mod file;
pub mod inode;
pub mod symlink;
pub use data::{BlockGroupDescriptor, Dirent, ExtendedSuperblock, Inode};
@ -245,11 +246,21 @@ impl Ext2Fs {
let block_group_block_count = superblock.block_group_block_count;
log::info!("Inode size: {}", superblock.inode_size());
log::info!("Total free inodes: {}", superblock.total_unallocated_inodes);
log::info!("Total free blocks: {}", superblock.total_unallocated_blocks);
let mapper = match cached {
false => DeviceMapper::uncached(device, block_size),
false => DeviceMapper::uncached(device, block_size, "ext2")?,
true => {
DeviceMapper::cached_with_capacity(device, block_size, block_size * 16, 128, 64)
let segment_size = (block_size * 16).min(65536);
DeviceMapper::cached_with_capacity(
device,
block_size,
segment_size,
128,
64,
"ext2",
)?
}
};
@ -284,8 +295,8 @@ impl Ext2Fs {
pub async fn load_node(self: &Arc<Self>, ino: u32) -> Result<NodeRef, Error> {
let cache = self.inode_cache.get();
let mode = cache.get(ino).await?.read().mode;
let inode = InodeAccess::new(cache.clone(), ino);
let mode = inode.map(|inode| Ok(inode.mode)).await?;
match mode.node_type() {
Some(FileType::Directory) => Ok(DirectoryNode::new(self.clone(), inode)),
Some(FileType::File) => Ok(RegularNode::new(self.clone(), inode)),
@ -484,6 +495,11 @@ impl Ext2Fs {
let state = self.state.read();
if state.dirty {
log::info!("Flushing superblock");
log::info!(
"inodes {} blocks {}",
state.superblock.total_unallocated_inodes,
state.superblock.total_unallocated_blocks
);
self.mapper
.device()
.write_exact(
@ -495,156 +511,6 @@ impl Ext2Fs {
Ok(())
}
async fn allocate<
F: Fn(&mut BlockGroupDescriptor) -> Option<(u32, u32)>,
G: Fn(&mut ExtendedSuperblock),
>(
&self,
descriptor_mapper: F,
superblock_mapper: G,
) -> Result<u32, Error> {
let bit_per_block = self.block_size * 8;
for group_index in 0..self.bgdt.entry_count as u32 {
let bitmap = self
.with_bgdt_entry_mut(group_index, |descriptor| {
match descriptor_mapper(descriptor) {
Some(val) => Ok(Some(val)),
_ => Ok(None),
}
})
.await?;
if let Some((bitmap, group_item_count)) = bitmap {
let no = self
.with_block_mut(bitmap, 0, |bitmap| {
for i in 0..bit_per_block.min(group_item_count as usize) {
let index = i / 8;
let bit = 1u8 << (i % 8);
if bitmap[index] & bit == 0 {
bitmap[index] |= bit;
return Ok(Some(i as u32));
}
}
Ok(None)
})
.await?
.expect("TODO: bgdt says there're things, but bitmap says there aren't");
{
let mut state = self.state.write();
superblock_mapper(&mut state.superblock);
state.dirty = true;
}
if !self.cached {
self.flush_superblock().await?;
}
return Ok(group_index * group_item_count + no);
}
}
todo!("ENOSPC")
}
async fn allocate_inode(&self, directory: bool) -> Result<u32, Error> {
let ino = self
.allocate(
|descriptor| {
if descriptor.unallocated_inodes == 0
|| (directory && descriptor.directories == u16::MAX)
{
None
} else {
if directory {
descriptor.directories += 1;
}
descriptor.unallocated_inodes -= 1;
Some((descriptor.inode_usage_bitmap, self.block_group_inode_count))
}
},
|superblock| {
superblock.total_unallocated_inodes -= 1;
},
)
.await?;
let ino = ino + 1;
log::debug!("ext2: allocated inode #{ino}");
Ok(ino)
}
async fn allocate_block(&self) -> Result<u32, Error> {
let no = self
.allocate(
|descriptor| {
if descriptor.unallocated_blocks == 0 {
None
} else {
descriptor.unallocated_blocks -= 1;
Some((descriptor.block_usage_bitmap, self.block_group_block_count))
}
},
|superblock| {
superblock.total_unallocated_blocks -= 1;
},
)
.await?;
log::debug!("ext2: allocated block #{no}");
Ok(no)
}
async fn create_node(
self: &Arc<Self>,
parent: Option<u32>,
ty: FileType,
) -> Result<NodeRef, Error> {
let inode_cache = self.inode_cache.get();
let ino = self.allocate_inode(ty == FileType::Directory).await?;
log::info!("Allocated inode #{ino}");
let access = InodeAccess::new(inode_cache.clone(), ino);
let now = real_time().seconds as u32;
{
// Write initial inode
let mut inode = access.get_mut().await?;
{
let mut data = inode.write();
// Create new inode struct
let mut value = Inode::zeroed();
value.mode = InodeMode::default_for_type(ty);
value.ctime = now;
value.mtime = now;
value.atime = now;
**data = value;
}
inode.put().await?;
}
// TODO dealloc ino if failed
// self.write_inode(ino, &inode).await.unwrap();
let node = match ty {
FileType::Directory => DirectoryNode::create(self.clone(), access, parent)
.await
.unwrap(),
FileType::File => RegularNode::new(self.clone(), access),
_ => todo!(),
};
Ok(node)
}
async fn read_index(&self, block_index: u32, index: usize) -> Result<u32, Error> {
self.with_block(block_index, |block| {
let indirect: &[u32] = unsafe {

View File

@ -31,38 +31,39 @@ impl SymlinkNode {
}
async fn read(&self, buf: &mut [u8]) -> Result<usize, Error> {
let inode = self.inode.get().await?;
let inode = inode.read();
self.inode
.amap(async |inode| {
let len = inode.size(&self.fs) as usize;
if len > self.fs.block_size {
log::warn!("ext2: symlink size > block size");
return Err(Error::InvalidFile);
}
if buf.len() < len {
return Err(Error::BufferTooSmall);
}
let len = inode.size(&self.fs) as usize;
if len > self.fs.block_size {
log::warn!("ext2: symlink size > block size");
return Err(Error::InvalidFile);
}
if buf.len() < len {
return Err(Error::BufferTooSmall);
}
let mut write = self.cache.write();
write.clear();
let mut write = self.cache.write();
write.clear();
// If length of symlink is lower than 60, data is stored directly in "block address"
// section of the inode
if len < 60 {
let bytes = unsafe { Self::link_from_inode_blocks(&inode, len) };
write.extend_from_slice(bytes);
buf[..len].copy_from_slice(bytes);
} else {
self.fs
.with_inode_block(&inode, 0, |block| {
write.extend_from_slice(&block[..len]);
buf[..len].copy_from_slice(&block[..len]);
Ok(())
})
.await?;
}
// If length of symlink is lower than 60, data is stored directly in "block address"
// section of the inode
if len < 60 {
let bytes = unsafe { Self::link_from_inode_blocks(&inode, len) };
write.extend_from_slice(bytes);
buf[..len].copy_from_slice(bytes);
} else {
self.fs
.with_inode_block(&inode, 0, |block| {
write.extend_from_slice(&block[..len]);
buf[..len].copy_from_slice(&block[..len]);
Ok(())
})
.await?;
}
Ok(len)
Ok(len)
})
.await
}
unsafe fn link_from_inode_blocks(inode: &Inode, len: usize) -> &[u8] {
@ -73,9 +74,7 @@ impl SymlinkNode {
impl CommonImpl for SymlinkNode {
fn size(&self, _node: &NodeRef) -> Result<u64, Error> {
let inode = block!(self.inode.get().await)??;
let inode = inode.read();
Ok(inode.size(&self.fs))
block!(self.inode.size().await)?
}
fn as_any(&self) -> &dyn Any {
@ -83,9 +82,7 @@ impl CommonImpl for SymlinkNode {
}
fn metadata(&self, _node: &NodeRef) -> Result<Metadata, Error> {
let inode = block!(self.inode.get().await)??;
let inode = inode.read();
Ok(inode.metadata(&self.fs, self.inode.ino()))
block!(self.inode.metadata().await)?
}
fn set_metadata(&self, _node: &NodeRef, metadata: &Metadata) -> Result<(), Error> {

View File

@ -52,18 +52,24 @@ impl DeviceMapper {
segment_size: usize,
bucket_capacity: usize,
bucket_count: usize,
) -> DeviceMapper {
filesystem: &str,
) -> Result<DeviceMapper, Error> {
DeviceMapper::cached_with_capacity_in(
device,
block_size,
segment_size,
bucket_capacity,
bucket_count,
filesystem,
)
}
pub fn uncached(device: Arc<dyn BlockDevice>, block_size: usize) -> DeviceMapper {
DeviceMapper::uncached_in(device, block_size)
pub fn uncached(
device: Arc<dyn BlockDevice>,
block_size: usize,
filesystem: &str,
) -> Result<DeviceMapper, Error> {
DeviceMapper::uncached_in(device, block_size, filesystem)
}
}
@ -74,27 +80,37 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> DeviceMapper<A> {
segment_size: usize,
bucket_capacity: usize,
bucket_count: usize,
) -> DeviceMapper<A> {
let cache = BlockCache::<A>::with_capacity_in(
filesystem: &str,
) -> Result<DeviceMapper<A>, Error> {
BlockCache::<A>::with_capacity_in(
device,
block_size,
segment_size,
bucket_capacity,
bucket_count,
);
DeviceMapper::<A>::Cached(cache)
filesystem,
)
.map(DeviceMapper::<A>::Cached)
}
pub fn uncached_in(device: Arc<dyn BlockDevice>, block_size: usize) -> DeviceMapper<A> {
pub fn uncached_in(
device: Arc<dyn BlockDevice>,
block_size: usize,
filesystem: &str,
) -> Result<DeviceMapper<A>, Error> {
if block_size % device.block_size() != 0 {
panic!("Cache block size is not multiple of device block size");
log::error!(
"Couldn't create block mapper for {filesystem}: \
cache block size is not a multiple of device block size"
);
return Err(Error::InvalidArgument);
}
let uncache = UncachedCache::<A> {
device,
block_size,
_pd: PhantomData,
};
Self::Uncached(uncache)
Ok(Self::Uncached(uncache))
}
pub fn device(&self) -> &Arc<dyn BlockDevice> {
@ -179,21 +195,29 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> BlockCache<A> {
segment_size: usize,
bucket_capacity: usize,
bucket_count: usize,
) -> BlockCache<A> {
assert_eq!(
block_size % device.block_size(),
0,
"Block size is not a multiple of device block size"
);
assert_eq!(
segment_size % block_size,
0,
"Segment size is not a multiple of block size"
);
assert!(
segment_size >= block_size,
"Segment size is less than block size"
);
filesystem: &str,
) -> Result<BlockCache<A>, Error> {
if block_size % device.block_size() != 0 {
log::error!(
"Couldn't create block cache for {filesystem}: \
cache block size is not a multiple of device block size"
);
return Err(Error::InvalidArgument);
}
if segment_size % block_size != 0 {
log::error!(
"Couldn't create block cache for {filesystem}: \
segment size is not a multiple of block size"
);
return Err(Error::InvalidArgument);
}
if segment_size < block_size {
log::error!(
"Couldn't create block cache for {filesystem}: \
segment size is smaller than block size"
);
return Err(Error::InvalidArgument);
}
log::info!("New block cache: block: {block_size}B, segment: {segment_size}B, geometry: {bucket_capacity}x{bucket_count}");
log::info!(
@ -201,12 +225,12 @@ impl<A: PhysicalMemoryAllocator<Address = PhysicalAddress>> BlockCache<A> {
(segment_size * bucket_capacity * bucket_count) / 1024
);
BlockCache {
Ok(BlockCache {
device,
block_size,
segment_size,
cache: AsyncMutex::new(LruCache::with_capacity(bucket_capacity, bucket_count)),
}
})
}
pub fn device(&self) -> &Arc<dyn BlockDevice> {

View File

@ -44,11 +44,15 @@ pub trait RegularImpl: CommonImpl {
node: &NodeRef,
opts: OpenOptions,
) -> Result<(u64, Option<InstanceData>), Error> {
let _ = node;
let _ = opts;
Err(Error::NotImplemented)
}
/// Closes a file
fn close(&self, node: &NodeRef, instance: Option<&InstanceData>) -> Result<(), Error> {
let _ = node;
let _ = instance;
Ok(())
}
@ -60,6 +64,10 @@ pub trait RegularImpl: CommonImpl {
pos: u64,
buf: &mut [u8],
) -> Result<usize, Error> {
let _ = node;
let _ = instance;
let _ = pos;
let _ = buf;
Err(Error::NotImplemented)
}
@ -71,11 +79,17 @@ pub trait RegularImpl: CommonImpl {
pos: u64,
buf: &[u8],
) -> Result<usize, Error> {
let _ = node;
let _ = instance;
let _ = pos;
let _ = buf;
Err(Error::NotImplemented)
}
/// Resizes the file to requested size
fn truncate(&self, node: &NodeRef, new_size: u64) -> Result<(), Error> {
let _ = node;
let _ = new_size;
Err(Error::NotImplemented)
}
}
@ -85,6 +99,7 @@ pub trait DirectoryImpl: CommonImpl {
/// Opens a directory for reading its entries. Returns [DirectoryOpenPosition] to specify the
/// starting position.
fn open(&self, node: &NodeRef) -> Result<DirectoryOpenPosition, Error> {
let _ = node;
Err(Error::NotImplemented)
}
@ -95,31 +110,45 @@ pub trait DirectoryImpl: CommonImpl {
pos: u64,
entries: &mut [MaybeUninit<DirectoryEntry>],
) -> Result<(usize, u64), Error> {
let _ = node;
let _ = pos;
let _ = entries;
Err(Error::NotImplemented)
}
/// Creates a child node, but does not associate it with the directory yet
fn create_node(&self, parent: &NodeRef, ty: FileType) -> Result<NodeRef, Error> {
let _ = parent;
let _ = ty;
Err(Error::ReadOnly)
}
/// Associates the given node with the directory, creating an entry for it inside
fn attach_node(&self, parent: &NodeRef, child: &NodeRef, name: &str) -> Result<(), Error> {
let _ = parent;
let _ = child;
let _ = name;
Err(Error::NotImplemented)
}
/// Removes an entry of the directory with given name
fn unlink_node(&self, parent: &NodeRef, child: &NodeRef, name: &str) -> Result<(), Error> {
let _ = parent;
let _ = child;
let _ = name;
Err(Error::NotImplemented)
}
/// Fetches the child of the directory with given name
fn lookup(&self, node: &NodeRef, name: &str) -> Result<NodeRef, Error> {
let _ = node;
let _ = name;
Err(Error::NotImplemented)
}
/// Returns the "length" of the directory in entries
fn len(&self, node: &NodeRef) -> Result<usize, Error> {
let _ = node;
Err(Error::NotImplemented)
}
}
@ -144,6 +173,7 @@ pub trait SymlinkImpl: CommonImpl {
/// Fetches the contents of the symlink into a buffer
fn read_link(&self, buf: &mut [u8]) -> Result<usize, Error> {
let _ = buf;
Err(Error::NotImplemented)
}
}