ext2: add a simple unlink()

This commit is contained in:
Mark Poliakov 2024-12-29 17:53:01 +02:00
parent 8c96a009ad
commit ce8600a782

View File

@ -1,9 +1,15 @@
use core::{any::Any, mem::MaybeUninit, str::FromStr}; // TODO: dedupe the code for iterating the directory records
use core::{
any::Any,
mem::{self, MaybeUninit},
str::FromStr,
};
use alloc::sync::Arc; use alloc::sync::Arc;
use libk::{ use libk::{
block, block,
error::Error, error::Error,
time::real_time,
vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef}, vfs::{CommonImpl, DirectoryImpl, DirectoryOpenPosition, Metadata, Node, NodeFlags, NodeRef},
}; };
use yggdrasil_abi::{ use yggdrasil_abi::{
@ -30,11 +36,117 @@ struct DirentIterMut<'a> {
offset: usize, offset: usize,
} }
struct Record<'a> {
fs: &'a Ext2Fs,
offset: usize,
data: &'a mut [u8],
}
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> { impl<'a> DirentIterMut<'a> {
pub fn new(fs: &'a Ext2Fs, block: &'a mut [u8], offset: usize) -> Self { pub fn new(fs: &'a Ext2Fs, block: &'a mut [u8], offset: usize) -> Self {
Self { fs, block, offset } 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 { pub fn try_fit(&mut self, name: &str, ino: u32) -> bool {
let _ = self.fs; let _ = self.fs;
@ -185,6 +297,7 @@ impl DirectoryNode {
let mut child = holder.write(); let mut child = holder.write();
child.inc_hard_count(); child.inc_hard_count();
child.dtime = 0;
} }
holder.put().await?; holder.put().await?;
@ -263,6 +376,53 @@ impl DirectoryNode {
Ok(()) Ok(())
} }
async fn remove_entry(&self, name: &str) -> Result<(), Error> {
// TODO if unlinking a directory, check that it only contains . and ..
assert!(name.len() < 255);
let ino = {
let mut holder = self.inode.get_mut().await?;
let mut result = None;
{
let inode = holder.write();
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;
}
}
}
holder.put().await?;
result
};
let ino = ino.ok_or(Error::DoesNotExist)?;
log::info!("ext2: unlinked ino #{ino}");
// Decrement the refcount on the inode
let mut holder = self.inode.cache().get_mut(ino).await?;
{
let mut inode = holder.write();
inode.hard_links = inode.hard_links.saturating_sub(1);
inode.dtime = real_time().seconds as _;
}
holder.put().await?;
Ok(())
}
async fn lookup_entry(&self, search_name: &str) -> Result<NodeRef, Error> { async fn lookup_entry(&self, search_name: &str) -> Result<NodeRef, Error> {
assert!(search_name.len() < 255); assert!(search_name.len() < 255);
@ -416,12 +576,11 @@ impl DirectoryImpl for DirectoryNode {
Ok(()) Ok(())
} }
fn unlink_node(&self, _parent: &NodeRef, _name: &str) -> Result<(), Error> { fn unlink_node(&self, _parent: &NodeRef, name: &str) -> Result<(), Error> {
if self.fs.force_readonly { if self.fs.force_readonly {
return Err(Error::ReadOnly); return Err(Error::ReadOnly);
} }
log::error!("ext2: unlink_node not implemented"); block!(self.remove_entry(name).await)?
Err(Error::NotImplemented)
} }
fn read_entries( fn read_entries(