Fix and revise the directory entry handling in ext2

This commit is contained in:
Shaowei Song
2025-01-16 06:40:56 +00:00
committed by Tate, Hongliang Tian
parent d71771e49a
commit 328ba47ccd
2 changed files with 161 additions and 109 deletions

View File

@ -1,5 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
#![expect(dead_code)]
use super::{inode::MAX_FNAME_LEN, prelude::*};
/// The data structure in a directory's data block. It is stored in a linked list.
@ -15,19 +17,16 @@ pub struct DirEntry {
}
impl DirEntry {
const ALIGN: usize = 4;
const HEADER_LEN: usize = core::mem::size_of::<DirEntryHeader>();
const PARENT_OFFSET: usize = Self::HEADER_LEN + Self::ALIGN;
/// Constructs a new `DirEntry` object with the specified inode (`ino`),
/// name (`name`), and file type (`inode_type`).
pub(super) fn new(ino: u32, name: &str, inode_type: InodeType) -> Self {
debug_assert!(name.len() <= MAX_FNAME_LEN);
let record_len = (Self::header_len() + name.len()).align_up(4) as u16;
let header = DirEntryHeader::new(ino, inode_type, name.len());
Self {
header: DirEntryHeader {
ino,
record_len,
name_len: name.len() as u8,
inode_type: DirEntryFileType::from(inode_type) as _,
},
header,
name: CStr256::from(name),
}
}
@ -47,11 +46,6 @@ impl DirEntry {
&self.header
}
/// Returns the length of the header.
const fn header_len() -> usize {
core::mem::size_of::<DirEntryHeader>()
}
/// Returns the inode number.
pub fn ino(&self) -> u32 {
self.header.ino
@ -80,7 +74,7 @@ impl DirEntry {
/// Returns the actual length of the current entry.
pub(super) fn actual_len(&self) -> usize {
(Self::header_len() + self.header.name_len as usize).align_up(4)
(Self::HEADER_LEN + self.header.name_len as usize).align_up(Self::ALIGN)
}
}
@ -98,6 +92,20 @@ pub(super) struct DirEntryHeader {
inode_type: u8,
}
impl DirEntryHeader {
pub(super) fn new(ino: u32, inode_type: InodeType, name_len: usize) -> Self {
debug_assert!(name_len <= MAX_FNAME_LEN);
let record_len = (DirEntry::HEADER_LEN + name_len).align_up(DirEntry::ALIGN) as u16;
Self {
ino,
record_len,
name_len: name_len as u8,
inode_type: DirEntryFileType::from(inode_type) as _,
}
}
}
/// The type indicator in the `DirEntry`.
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
@ -202,12 +210,15 @@ impl<'a> DirEntryReader<'a> {
/// Returns the target entry with the given name.
pub fn find_entry_item(&mut self, name: &str) -> Option<DirEntryItem> {
let mut iter = self.iter();
let name_len = name.len();
let name_bytes = name.as_bytes();
iter.find(|entry_item| {
if entry_item.name_len() != name.len() {
if entry_item.name_len() != name_len {
return false;
}
match self.read_name(entry_item) {
Ok(name_buf) => name_buf == name.as_bytes(),
Ok(name_buf) => name_buf == name_bytes,
Err(_) => false,
}
})
@ -227,7 +238,7 @@ impl<'a> DirEntryReader<'a> {
let name_len = entry_item.name_len();
let name_buf = &mut self.name_buf.as_mut().unwrap()[..name_len];
let offset = entry_item.offset + DirEntry::header_len();
let offset = entry_item.offset + DirEntry::HEADER_LEN;
self.page_cache.pages().read_bytes(offset, name_buf)?;
Ok(name_buf)
}
@ -275,7 +286,7 @@ impl Iterator for DirEntryIter<'_> {
/// A directory entry item describes the basic information of a `DirEntry`,
/// including the entry header and the entry's offset. The entry name is not
/// present and will be retrieved from the page cache when needed.
#[derive(Debug)]
#[derive(Clone, Copy, Debug)]
pub(super) struct DirEntryItem {
header: DirEntryHeader,
offset: usize,
@ -325,21 +336,13 @@ impl DirEntryItem {
/// Returns the actual length of the current entry.
pub fn actual_len(&self) -> usize {
(DirEntry::header_len() + self.name_len()).align_up(4)
(DirEntry::HEADER_LEN + self.name_len()).align_up(DirEntry::ALIGN)
}
/// Returns the length of the gap between the current entry and the next entry.
pub fn gap_len(&self) -> usize {
self.record_len() - self.actual_len()
}
/// Converts to a `DirEntry` given the name.
pub fn to_entry_with_name(&self, name: &str) -> DirEntry {
DirEntry {
header: self.header,
name: CStr256::from(name),
}
}
}
/// A writer for modifying `DirEntry` of the page cache.
@ -359,77 +362,128 @@ impl<'a> DirEntryWriter<'a> {
}
}
/// Writes a `DirEntry` at the current offset.
pub fn write_entry(&mut self, entry: &DirEntry) -> Result<()> {
/// Writes a `DirEntry` at the current offset. The name is written after the header.
pub fn write_entry(&mut self, header: &DirEntryHeader, name: &str) -> Result<()> {
self.page_cache.pages().write_val(self.offset, header)?;
self.page_cache
.pages()
.write_val(self.offset, entry.header())?;
self.page_cache.pages().write_bytes(
self.offset + DirEntry::header_len(),
entry.name().as_bytes(),
)?;
.write_bytes(self.offset + DirEntry::HEADER_LEN, name.as_bytes())?;
self.offset += entry.record_len();
self.offset += header.record_len as usize;
Ok(())
}
/// Writes the header of a `DirEntry` at the current offset.
pub(super) fn write_header_only(&mut self, header: &DirEntryHeader) -> Result<()> {
pub fn write_header_only(&mut self, header: &DirEntryHeader) -> Result<()> {
self.page_cache.pages().write_val(self.offset, header)?;
self.offset += header.record_len as usize;
Ok(())
}
/// Initializes two special `DirEntry`s ("." and "..") with the given inode numbers.
pub fn init_dir(&mut self, self_ino: u32, parent_ino: u32) -> Result<()> {
debug_assert!(self.page_cache.pages().size() == 0 && self.offset == 0);
self.page_cache.pages().resize(BLOCK_SIZE)?;
let self_header = DirEntryHeader::new(self_ino, InodeType::Dir, 1);
self.write_entry(&self_header, ".")?;
debug_assert_eq!(self.offset, DirEntry::PARENT_OFFSET);
let mut parent_header = DirEntryHeader::new(parent_ino, InodeType::Dir, 2);
parent_header.record_len = (BLOCK_SIZE - self.offset) as _;
self.write_entry(&parent_header, "..")
}
/// Appends a new `DirEntry` starting from the current offset.
///
/// If there is a gap between existing entries, inserts the new entry into the gap
/// If there is a gap between existing entries, inserts the new entry into the gap;
/// If there is no available space, expands the size and appends the new entry at the end.
pub fn append_entry(&mut self, mut new_entry: DirEntry) -> Result<()> {
let Some(mut entry_item) = DirEntryReader::new(self.page_cache, self.offset)
.iter()
.find(|entry| entry.gap_len() >= new_entry.record_len())
else {
// Resize and append it at the new block.
let old_size = self.page_cache.pages().size();
let new_size = old_size + BLOCK_SIZE;
self.page_cache.resize(new_size)?;
new_entry.set_record_len(BLOCK_SIZE);
self.offset = old_size;
self.write_entry(&new_entry)?;
return Ok(());
};
/// `check_existence` is determined to check if the new entry already exists.
pub fn append_new_entry(
&mut self,
header: DirEntryHeader,
name: &str,
check_existence: bool,
) -> Result<()> {
let name_len = name.len();
debug_assert_eq!(header.name_len as usize, name_len);
let name_bytes = name.as_bytes();
let mut entry_item_with_enough_gap = None;
for entry_item in DirEntryReader::new(self.page_cache, self.offset).iter() {
if entry_item_with_enough_gap.is_none()
&& entry_item.gap_len() >= header.record_len as usize
{
entry_item_with_enough_gap = Some(entry_item);
if !check_existence {
break;
}
}
// Write in the gap between existing entries.
new_entry.set_record_len(entry_item.gap_len());
entry_item.set_record_len(entry_item.actual_len());
self.offset = entry_item.offset;
self.write_header_only(&entry_item.header)?;
self.write_entry(&new_entry)?;
if check_existence
&& entry_item.name_len() == name_len
&& self.read_name(&entry_item)? == name_bytes
{
return_errno!(Errno::EEXIST);
}
}
if let Some(entry_item) = entry_item_with_enough_gap {
self.append_entry_in_the_gap(entry_item, header, name)?;
} else {
self.append_entry_in_the_end(header, name)?;
}
Ok(())
}
fn append_entry_in_the_gap(
&mut self,
mut entry_with_enough_gap: DirEntryItem,
mut header: DirEntryHeader,
name: &str,
) -> Result<()> {
// Write in the gap between existing entries.
header.record_len = entry_with_enough_gap.gap_len() as u16;
entry_with_enough_gap.set_record_len(entry_with_enough_gap.actual_len());
self.offset = entry_with_enough_gap.offset;
self.write_header_only(&entry_with_enough_gap.header)?;
self.write_entry(&header, name)
}
fn append_entry_in_the_end(&mut self, mut header: DirEntryHeader, name: &str) -> Result<()> {
// Resize and append it at the new block.
let old_size = self.page_cache.pages().size();
let new_size = old_size + BLOCK_SIZE;
self.page_cache.resize(new_size)?;
header.record_len = BLOCK_SIZE as _;
self.offset = old_size;
self.write_entry(&header, name)
}
/// Removes and returns an existing `DirEntry` indicated by `name`.
pub fn remove_entry(&mut self, name: &str) -> Result<DirEntry> {
let self_entry_record_len = DirEntry::self_entry(0).record_len();
let reader = DirEntryReader::new(self.page_cache, 0).iter();
let next_reader = DirEntryReader::new(self.page_cache, self_entry_record_len).iter();
let Some((mut pre_entry_item, entry_item)) =
reader.zip(next_reader).find(|(_, entry_item)| {
entry_item.name_len() == name.len()
&& self.read_name(entry_item).unwrap() == name.as_bytes()
})
else {
pub fn remove_entry(&mut self, name: &str) -> Result<DirEntryItem> {
let mut pre_entry_item = None;
let name_len = name.len();
let name_bytes = name.as_bytes();
let mut iter = DirEntryReader::new(self.page_cache, DirEntry::PARENT_OFFSET).iter();
let Some(target_entry_item) = iter.find(|entry| {
if entry.offset < self.offset {
pre_entry_item = Some(*entry);
}
entry.offset == self.offset
&& entry.name_len() == name_len
&& self.read_name(entry).unwrap() == name_bytes
}) else {
return_errno!(Errno::ENOENT);
};
let is_last_entry = iter.next().is_none();
let mut pre_entry_item = pre_entry_item.unwrap();
let pre_offset = pre_entry_item.offset;
let offset = entry_item.offset;
if Bid::from_offset(pre_offset) != Bid::from_offset(offset)
&& DirEntryReader::new(self.page_cache, entry_item.offset)
.iter()
.next()
.is_none()
{
if is_last_entry {
// Shrink the size.
let new_size = pre_offset.align_up(BLOCK_SIZE);
self.page_cache.resize(new_size)?;
@ -438,12 +492,13 @@ impl<'a> DirEntryWriter<'a> {
self.write_header_only(&pre_entry_item.header)?;
} else {
// Update the previous entry.
pre_entry_item.set_record_len(pre_entry_item.record_len() + entry_item.record_len());
pre_entry_item
.set_record_len(pre_entry_item.record_len() + target_entry_item.record_len());
self.offset = pre_offset;
self.write_header_only(&pre_entry_item.header)?;
}
Ok(entry_item.to_entry_with_name(name))
Ok(target_entry_item)
}
/// Renames the `DirEntry` from `old_name` to the `new_name` from the current offset.
@ -454,18 +509,21 @@ impl<'a> DirEntryWriter<'a> {
let entry_item = DirEntryReader::new(self.page_cache, self.offset)
.find_entry_item(old_name)
.ok_or(Error::new(Errno::ENOENT))?;
let old_record_len = entry_item.record_len();
let mut new_entry = DirEntry::new(entry_item.ino(), new_name, entry_item.type_());
if new_entry.record_len() <= entry_item.record_len() {
let mut new_entry_header =
DirEntryHeader::new(entry_item.ino(), entry_item.type_(), new_name.len());
if new_entry_header.record_len as usize <= old_record_len {
// Just rename the entry.
new_entry.set_record_len(entry_item.record_len());
new_entry_header.record_len = old_record_len as _;
self.offset = entry_item.offset;
self.write_entry(&new_entry)?;
self.write_entry(&new_entry_header, new_name)?;
} else {
// Move to another position.
self.offset = entry_item.offset;
self.remove_entry(old_name)?;
self.offset = 0;
self.append_entry(new_entry)?;
self.offset = DirEntry::PARENT_OFFSET;
self.append_new_entry(new_entry_header, new_name, false)?;
}
Ok(())
@ -480,7 +538,7 @@ impl<'a> DirEntryWriter<'a> {
let name_len = item.name_len();
let name_buf = &mut self.name_buf.as_mut().unwrap()[..name_len];
let offset = item.offset + DirEntry::header_len();
let offset = item.offset + DirEntry::HEADER_LEN;
self.page_cache.pages().read_bytes(offset, name_buf)?;
Ok(name_buf)

View File

@ -10,7 +10,7 @@ use inherit_methods_macro::inherit_methods;
use super::{
block_ptr::{BidPath, BlockPtrs, Ext2Bid, BID_SIZE, MAX_BLOCK_PTRS},
dir::{DirEntry, DirEntryItem, DirEntryReader, DirEntryWriter},
dir::{DirEntryHeader, DirEntryItem, DirEntryReader, DirEntryWriter},
fs::Ext2,
indirect_block_cache::{IndirectBlock, IndirectBlockCache},
prelude::*,
@ -134,9 +134,6 @@ impl Inode {
if inner.hard_links() == 0 {
return_errno_with_message!(Errno::ENOENT, "dir removed");
}
if inner.contains_entry(name) {
return_errno!(Errno::EEXIST);
}
let inode = self
.fs()
@ -146,13 +143,13 @@ impl Inode {
self.fs().free_inode(inode.ino, is_dir).unwrap();
return Err(e);
}
let new_entry = DirEntry::new(inode.ino, name, inode_type);
let mut inner = inner.upgrade();
if let Err(e) = inner.append_entry(new_entry, inode_type, name) {
if let Err(e) = inner.append_new_entry(inode.ino, inode_type, name, true) {
self.fs().free_inode(inode.ino, is_dir).unwrap();
return Err(e);
}
let now = now();
inner.set_mtime(now);
inner.set_ctime(now);
@ -211,13 +208,8 @@ impl Inode {
return_errno!(Errno::EPERM);
}
if inner.contains_entry(name) {
return_errno!(Errno::EEXIST);
}
let new_entry = DirEntry::new(inode.ino, name, inode_type);
let mut inner = inner.upgrade();
inner.append_entry(new_entry, inode_type, name)?;
inner.append_new_entry(inode.ino, inode_type, name, true)?;
let now = now();
inner.set_mtime(now);
inner.set_ctime(now);
@ -490,8 +482,7 @@ impl Inode {
}
self_inner.remove_entry_at(old_name, src_offset)?;
let new_entry = DirEntry::new(src_inode.ino, new_name, src_inode_typ);
target_inner.append_entry(new_entry, src_inode_typ, new_name)?;
target_inner.append_new_entry(src_inode.ino, src_inode_typ, new_name, false)?;
let now = now();
self_inner.set_mtime(now);
self_inner.set_ctime(now);
@ -578,8 +569,7 @@ impl Inode {
self_inner.remove_entry_at(old_name, src_offset)?;
target_inner.remove_entry_at(new_name, dst_offset)?;
let new_entry = DirEntry::new(src_inode.ino, new_name, src_inode_typ);
target_inner.append_entry(new_entry, src_inode_typ, new_name)?;
target_inner.append_new_entry(src_inode.ino, src_inode_typ, new_name, false)?;
dst_inner.dec_hard_links();
let now = now();
self_inner.set_mtime(now);
@ -1032,8 +1022,8 @@ impl InodeInner {
fn init_dir(&mut self, self_ino: u32, parent_ino: u32) -> Result<()> {
debug_assert_eq!(self.inode_type(), InodeType::Dir);
self.append_entry(DirEntry::self_entry(self_ino), InodeType::Dir, ".")?;
self.append_entry(DirEntry::parent_entry(parent_ino), InodeType::Dir, "..")?;
DirEntryWriter::new(&self.page_cache, 0).init_dir(self_ino, parent_ino)?;
self.inc_hard_links(); // for ".."
Ok(())
}
@ -1049,15 +1039,20 @@ impl InodeInner {
DirEntryReader::new(&self.page_cache, 0).entry_count()
}
pub fn append_entry(
pub fn append_new_entry(
&mut self,
entry: DirEntry,
ino: u32,
inode_type: InodeType,
name: &str,
check_existence: bool,
) -> Result<()> {
debug_assert!(inode_type == entry.type_() && entry.name() == name);
let entry_header = DirEntryHeader::new(ino, inode_type, name.len());
DirEntryWriter::new(&self.page_cache, 0).append_new_entry(
entry_header,
name,
check_existence,
)?;
DirEntryWriter::new(&self.page_cache, 0).append_entry(entry)?;
let file_size = self.file_size();
let page_cache_size = self.page_cache.pages().size();
if page_cache_size > file_size {
@ -1073,14 +1068,13 @@ impl InodeInner {
}
pub fn remove_entry_at(&mut self, name: &str, offset: usize) -> Result<()> {
let entry = DirEntryWriter::new(&self.page_cache, offset).remove_entry(name)?;
let is_dir = entry.type_() == InodeType::Dir;
let removed_entry = DirEntryWriter::new(&self.page_cache, offset).remove_entry(name)?;
let file_size = self.file_size();
let page_cache_size = self.page_cache.pages().size();
if page_cache_size < file_size {
self.inode_impl.resize(page_cache_size)?;
}
if is_dir {
if removed_entry.type_() == InodeType::Dir {
self.dec_hard_links(); // for ".."
}
Ok(())