mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-24 09:53:24 +00:00
Fix and revise the directory entry handling in ext2
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
d71771e49a
commit
328ba47ccd
@ -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)
|
||||
|
@ -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(())
|
||||
|
Reference in New Issue
Block a user