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

View File

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