ext2: free deleted inodes/blocks

This commit is contained in:
Mark Poliakov 2025-01-02 16:18:35 +02:00
parent 17e2fba8b7
commit 11f731bf0f
7 changed files with 299 additions and 48 deletions

View File

@ -1,6 +1,7 @@
use libk::error::Error;
use libk::{error::Error, time::real_time};
use yggdrasil_abi::io::FileType;
use crate::{BlockGroupDescriptor, Ext2Fs, ExtendedSuperblock};
use crate::{BlockGroupDescriptor, Ext2Fs, ExtendedSuperblock, Inode};
impl Ext2Fs {
async fn allocate<
@ -80,6 +81,57 @@ impl Ext2Fs {
Ok(ino)
}
pub(crate) async fn free_inode(&self, ino: u32, inode: &mut Inode) -> Result<(), Error> {
if ino == 0 {
log::warn!("Tried to free a NULL inode");
return Ok(());
}
let is_directory = inode
.mode
.node_type()
.map(|m| m == FileType::Directory)
.unwrap_or(false);
// Free inode blocks
inode.resize(self, 0).await?;
inode.dtime = real_time().seconds as u32;
self.write_inode(ino, &inode).await?;
let inode_index = ino - 1;
let group_index = inode_index / self.block_group_inode_count;
let bitmap = self
.with_bgdt_entry_mut(group_index, |descriptor| {
if is_directory {
descriptor.directories = descriptor.directories.saturating_sub(1);
}
descriptor.unallocated_inodes += 1;
Ok(descriptor.inode_usage_bitmap)
})
.await?;
self.with_block_mut(bitmap, size_of::<u32>(), |bitmap| {
let index = (inode_index / 8) as usize;
let bit = 1u8 << (inode_index % 8);
if bitmap[index] & bit == 0 {
log::warn!("Trying to free an unallocated inode #{ino}");
}
bitmap[index] &= !bit;
Ok(())
})
.await?;
self.with_superblock_mut(|sb| {
sb.total_unallocated_inodes += 1;
Ok(())
})
.await?;
Ok(())
}
pub(crate) async fn allocate_block(&self) -> Result<u32, Error> {
let no = self
.allocate(
@ -101,4 +153,38 @@ impl Ext2Fs {
Ok(no)
}
pub(crate) async fn free_block(&self, block_index: u32) -> Result<(), Error> {
if block_index == 0 {
log::warn!("Tried to free a NULL block");
return Ok(());
}
let group_index = block_index / self.block_group_block_count;
let bitmap = self
.with_bgdt_entry_mut(group_index, |descriptor| {
descriptor.unallocated_blocks += 1;
Ok(descriptor.block_usage_bitmap)
})
.await?;
self.with_block_mut(bitmap, size_of::<u32>(), |bitmap| {
let index = (block_index / 8) as usize;
let bit = 1u8 << (block_index % 8);
if bitmap[index] & bit == 0 {
log::warn!("Freeing a block #{block_index}, but bitmap says it's not allocated");
}
bitmap[index] &= !bit;
Ok(())
})
.await?;
self.with_superblock_mut(|sb| {
sb.total_unallocated_blocks += 1;
Ok(())
})
.await?;
Ok(())
}
}

View File

@ -215,7 +215,16 @@ impl DirectoryNode {
.await;
match result {
Ok(_) => Ok(()),
Ok(_) => {
// Remove "." link
self.inode
.map_mut(|inode| {
inode.hard_links -= 1;
Ok(())
})
.await?;
Ok(())
}
Err(error) => {
self.locked.store(false, Ordering::Release);
Err(error)
@ -228,16 +237,28 @@ impl DirectoryNode {
Arc::as_ptr(&child.filesystem().unwrap()).addr(),
Arc::as_ptr(&self.fs).addr()
);
let child_ino = match child.data_as_any() {
let (child_ino, is_directory) = 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()
(dir.inode.ino(), true)
}
data if let Some(file) = data.downcast_ref::<RegularNode>() => {
(file.inode.ino(), false)
}
data if let Some(file) = data.downcast_ref::<RegularNode>() => file.inode.ino(),
_ => return Err(Error::InvalidOperation),
};
if is_directory {
// child's .. points to this node
self.inode
.map_mut(|inode| {
inode.hard_links = inode.hard_links.saturating_sub(1);
Ok(())
})
.await?;
}
let ino = self
.inode
.map_blocks_mut(|_inode, _index, block| {
@ -257,7 +278,7 @@ impl DirectoryNode {
child
.map_mut(|inode| {
inode.hard_links = inode.hard_links.saturating_sub(1);
inode.dtime = real_time().seconds as u32;
log::info!("#{ino} hard links = {}", inode.hard_links);
Ok(())
})
.await?;

View File

@ -142,6 +142,9 @@ impl<'a> DirentIter<'a> {
if entry_count >= entries.len() {
break;
}
if record.is_empty() {
continue;
}
if let Some(name) = record
.name()

View File

@ -48,8 +48,12 @@ impl InodeAccess {
) -> Result<T, Error> {
let inode = self.inode_cache.get(self.ino).await?;
let lock = inode.read();
if lock.is_deleted() {
Err(Error::DoesNotExist)
} else {
mapper(&lock.inode)
}
}
pub async fn map_mut<T, F: FnOnce(&mut Inode) -> Result<T, Error>>(
&self,
@ -57,7 +61,11 @@ impl InodeAccess {
) -> Result<T, Error> {
let inode = self.inode_cache.get(self.ino).await?;
let mut lock = inode.write();
let result = mapper(&mut lock.inode);
let result = if lock.is_deleted() {
Err(Error::DoesNotExist)
} else {
mapper(&mut lock.inode)
};
self.inode_cache.put(self.ino, &mut lock).await?;
result
}
@ -68,8 +76,12 @@ impl InodeAccess {
) -> Result<T, Error> {
let inode = self.inode_cache.get(self.ino).await?;
let lock = inode.read();
if lock.is_deleted() {
Err(Error::DoesNotExist)
} else {
mapper(&lock.inode).await
}
}
pub async fn amap_mut<T, F: AsyncFnOnce(&mut Inode) -> Result<T, Error>>(
&self,
@ -77,7 +89,11 @@ impl InodeAccess {
) -> Result<T, Error> {
let inode = self.inode_cache.get(self.ino).await?;
let mut lock = inode.write();
let result = mapper(&mut lock.inode).await;
let result = if lock.is_deleted() {
Err(Error::DoesNotExist)
} else {
mapper(&mut lock.inode).await
};
self.inode_cache.put(self.ino, &mut lock).await?;
result
}
@ -257,11 +273,11 @@ impl InodeCache {
ino: u32,
inode: Arc<IrqSafeRwLock<InodeHolder>>,
) -> Result<(), Error> {
let inode = inode.read();
let mut inode = inode.write();
if inode.dirty {
assert!(!self.synchronous);
log::debug!("Flush dirty inode {ino}");
self.fs.write_inode(ino, &inode.inode).await?;
self.fs.write_or_free_inode(ino, &mut inode.inode).await?;
}
Ok(())
}
@ -314,7 +330,7 @@ impl InodeCache {
async fn put(&self, ino: u32, holder: &mut InodeHolder) -> Result<(), Error> {
if self.synchronous {
// Immediately write-back
self.fs.write_inode(ino, &holder.inode).await
self.fs.write_or_free_inode(ino, &mut holder.inode).await
} else {
// Mark dirty
holder.dirty = true;

View File

@ -12,18 +12,38 @@ pub mod cache;
pub use cache::{InodeAccess, InodeCache, InodeHolder};
impl Inode {
#[inline(always)]
fn l0_capacity(blocks: u64) -> u64 {
blocks.min(DIRECT_BLOCK_COUNT as u64)
}
#[inline(always)]
fn l1_capacity(fs: &Ext2Fs, blocks: u64) -> u64 {
blocks
.saturating_sub(DIRECT_BLOCK_COUNT as u64)
.min(fs.pointers_per_block as u64)
}
#[inline(always)]
fn l2_capacity(fs: &Ext2Fs, blocks: u64) -> (u64, u64) {
let l2_l1 = blocks
.saturating_sub((DIRECT_BLOCK_COUNT + fs.pointers_per_block) as u64)
.min((fs.pointers_per_block * fs.pointers_per_block) as u64);
let l2_l0 = l2_l1.div_ceil(fs.pointers_per_block as u64);
(l2_l0, l2_l1)
}
async fn grow_direct(
&mut self,
fs: &Ext2Fs,
old_capacity: u64,
new_capacity: u64,
) -> Result<(), Error> {
let old_l0_capacity = old_capacity.min(DIRECT_BLOCK_COUNT as u64);
let new_l0_capacity = new_capacity.min(DIRECT_BLOCK_COUNT as u64);
debug_assert!(old_l0_capacity <= new_l0_capacity);
let old_l0_capacity = Self::l0_capacity(old_capacity);
let new_l0_capacity = Self::l0_capacity(new_capacity);
log::debug!("Grow L0: {old_l0_capacity} -> {new_l0_capacity}");
debug_assert!(old_l0_capacity <= new_l0_capacity);
for i in old_l0_capacity..new_l0_capacity {
let i = i as usize;
@ -34,21 +54,37 @@ impl Inode {
Ok(())
}
async fn shrink_direct(
&mut self,
fs: &Ext2Fs,
old_capacity: u64,
new_capacity: u64,
) -> Result<(), Error> {
let old_l0_capacity = Self::l0_capacity(old_capacity);
let new_l0_capacity = Self::l0_capacity(new_capacity);
log::debug!("Shrink L0: {old_l0_capacity} -> {new_l0_capacity}");
debug_assert!(old_l0_capacity >= new_l0_capacity);
for i in new_l0_capacity..old_l0_capacity {
let i = i as usize;
fs.free_block(self.blocks.direct_blocks[i]).await?;
self.blocks.direct_blocks[i] = 0;
}
Ok(())
}
async fn grow_l1(
&mut self,
fs: &Ext2Fs,
old_capacity: u64,
new_capacity: u64,
) -> Result<(), Error> {
let old_l1_capacity = old_capacity
.saturating_sub(DIRECT_BLOCK_COUNT as u64)
.min(fs.pointers_per_block as u64);
let new_l1_capacity = new_capacity
.saturating_sub(DIRECT_BLOCK_COUNT as u64)
.min(fs.pointers_per_block as u64);
let old_l1_capacity = Self::l1_capacity(fs, old_capacity);
let new_l1_capacity = Self::l1_capacity(fs, new_capacity);
log::debug!("Grow L1: {old_l1_capacity} -> {new_l1_capacity}");
debug_assert!(old_l1_capacity <= new_l1_capacity);
if old_l1_capacity == 0 && new_l1_capacity != 0 {
@ -69,27 +105,48 @@ impl Inode {
Ok(())
}
async fn shrink_l1(
&mut self,
fs: &Ext2Fs,
old_capacity: u64,
new_capacity: u64,
) -> Result<(), Error> {
let old_l1_capacity = Self::l1_capacity(fs, old_capacity);
let new_l1_capacity = Self::l1_capacity(fs, new_capacity);
log::debug!("Shrink L1: {old_l1_capacity} -> {new_l1_capacity}");
debug_assert!(old_l1_capacity >= new_l1_capacity);
for i in new_l1_capacity..old_l1_capacity {
debug_assert_ne!(self.blocks.indirect_block_l1, 0);
let i = i as usize;
let index = fs.remove_index(self.blocks.indirect_block_l1, i).await?;
fs.free_block(index).await?;
}
if old_l1_capacity != 0 && new_l1_capacity == 0 {
// Free the L1 indirect block
fs.free_block(self.blocks.indirect_block_l1).await?;
self.blocks.indirect_block_l1 = 0;
}
Ok(())
}
async fn grow_l2(
&mut self,
fs: &Ext2Fs,
old_capacity: u64,
new_capacity: u64,
) -> Result<(), Error> {
let old_l2_capacity = (old_capacity as usize)
.saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block)
.min(fs.pointers_per_block * fs.pointers_per_block);
let new_l2_capacity = (new_capacity as usize)
.saturating_sub(DIRECT_BLOCK_COUNT + fs.pointers_per_block)
.min(fs.pointers_per_block * fs.pointers_per_block);
let (old_l2_l0, old_l2_l1) = Self::l2_capacity(fs, old_capacity);
let (new_l2_l0, new_l2_l1) = Self::l2_capacity(fs, new_capacity);
let old_l2_l0 = old_l2_capacity.div_ceil(fs.pointers_per_block);
let new_l2_l0 = new_l2_capacity.div_ceil(fs.pointers_per_block);
log::debug!("Grow L2: {old_l2_l1} ({old_l2_l0} L2-1) -> {new_l2_l1} ({new_l2_l0} L2-1)");
debug_assert!(old_l2_l1 <= new_l2_l1);
debug_assert!(old_l2_l0 <= new_l2_l0);
log::debug!(
"Grow L2: {old_l2_capacity} ({old_l2_l0} L2-1) -> {new_l2_capacity} ({new_l2_l0} L2-1)"
);
if old_l2_capacity == 0 && new_l2_capacity != 0 {
if old_l2_l1 == 0 && new_l2_l1 != 0 {
// Allocate L2 indirect block
let block = fs.allocate_block().await?;
self.blocks.indirect_block_l2 = block;
@ -98,14 +155,16 @@ impl Inode {
// Grow L2 direct-indirect block
for i in old_l2_l0..new_l2_l0 {
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
let i = i as usize;
let block = fs.allocate_block().await?;
fs.write_index(self.blocks.indirect_block_l2, i, block)
.await?;
}
// Grow L2 indirect-indirect blocks
for i in old_l2_capacity..new_l2_capacity {
for i in old_l2_l1..new_l2_l1 {
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
let i = i as usize;
let l1i = i / fs.pointers_per_block;
let l0i = i % fs.pointers_per_block;
let indirect = fs.read_index(self.blocks.indirect_block_l2, l1i).await?;
@ -117,6 +176,48 @@ impl Inode {
Ok(())
}
async fn shrink_l2(
&mut self,
fs: &Ext2Fs,
old_capacity: u64,
new_capacity: u64,
) -> Result<(), Error> {
let (old_l2_l0, old_l2_l1) = Self::l2_capacity(fs, old_capacity);
let (new_l2_l0, new_l2_l1) = Self::l2_capacity(fs, new_capacity);
log::debug!("Grow L2: {old_l2_l1} ({old_l2_l0} L2-1) -> {new_l2_l1} ({new_l2_l0} L2-1)");
debug_assert!(old_l2_l1 >= new_l2_l1);
debug_assert!(old_l2_l0 >= new_l2_l0);
// Shrink L2 indirect-indirect blocks
for i in new_l2_l1..old_l2_l1 {
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
let i = i as usize;
let l1i = i / fs.pointers_per_block;
let l0i = i % fs.pointers_per_block;
let indirect = fs.read_index(self.blocks.indirect_block_l2, l1i).await?;
debug_assert_ne!(indirect, 0);
let block = fs.remove_index(indirect, l0i).await?;
fs.free_block(block).await?;
}
// Shrink L2 direct-indirect block
for i in new_l2_l0..old_l2_l0 {
debug_assert_ne!(self.blocks.indirect_block_l2, 0);
let i = i as usize;
let block = fs.remove_index(self.blocks.indirect_block_l2, i).await?;
fs.free_block(block).await?;
}
if new_l2_l1 == 0 && old_l2_l1 != 0 {
// Free L2 indirect block
fs.free_block(self.blocks.indirect_block_l2).await?;
self.blocks.indirect_block_l2 = 0;
}
Ok(())
}
fn set_size(&mut self, fs: &Ext2Fs, size: u64) {
let block_count = size.div_ceil(fs.block_size as u64);
@ -164,7 +265,12 @@ impl Inode {
self.grow_l2(fs, old_blocks, new_blocks).await?;
}
// Shrink
Ordering::Greater => todo!(),
Ordering::Greater => {
log::debug!("Shrink inode: {old_blocks} -> {new_blocks} blocks");
self.shrink_l2(fs, old_blocks, new_blocks).await?;
self.shrink_l1(fs, old_blocks, new_blocks).await?;
self.shrink_direct(fs, old_blocks, new_blocks).await?;
}
// No change
Ordering::Equal => (),
}

View File

@ -4,6 +4,8 @@
extern crate alloc;
use core::mem;
use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use bytemuck::Zeroable;
@ -14,6 +16,7 @@ use inode::{InodeAccess, InodeCache};
use libk::{
device::block::{cache::DeviceMapper, BlockDevice},
error::Error,
time::real_time,
vfs::{Filesystem, FilesystemMountOption, NodeRef},
};
use libk_util::OneTimeInit;
@ -343,6 +346,14 @@ impl Ext2Fs {
Ok(())
}
pub async fn write_or_free_inode(&self, ino: u32, inode: &mut Inode) -> Result<(), Error> {
if inode.is_deleted() {
self.free_inode(ino, inode).await
} else {
self.write_inode(ino, inode).await
}
}
pub async fn read_inode_data(
&self,
inode: &Inode,
@ -426,6 +437,17 @@ impl Ext2Fs {
.await
}
async fn remove_index(&self, block_index: u32, index: usize) -> Result<u32, Error> {
self.with_block_mut(block_index, size_of::<u32>(), |block| {
let indirect: &mut [u32] = unsafe {
core::slice::from_raw_parts_mut(block.as_mut_ptr().cast(), self.pointers_per_block)
};
Ok(mem::replace(&mut indirect[index], 0))
})
.await
}
async fn inode_block_index(&self, inode: &Inode, index: u32) -> Result<u32, Error> {
let mut index = index as usize;
// L0

View File

@ -107,15 +107,12 @@ impl Node {
let directory = self.as_directory()?;
let node = directory.imp.create_node(self, info.ty)?;
// Fill out the node info
node.set_access(
Some(info.uid),
Some(info.gid),
Some(info.mode),
check.clone(),
)?;
self.create_node(node.clone(), info.name, check.clone())?;
self.create_node(node, info.name, check)
// Fill out the node info
node.set_access(Some(info.uid), Some(info.gid), Some(info.mode), check)?;
Ok(node)
}
/// Attaches a pre-created node to its parent