diff --git a/kernel/src/fs/ext2/dir.rs b/kernel/src/fs/ext2/dir.rs index 7c8308d3a..fccaf6bc2 100644 --- a/kernel/src/fs/ext2/dir.rs +++ b/kernel/src/fs/ext2/dir.rs @@ -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::(); + 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::() - } - /// 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 { 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 { - 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 { + 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) diff --git a/kernel/src/fs/ext2/inode.rs b/kernel/src/fs/ext2/inode.rs index 2d5ed3872..ffe24ad96 100644 --- a/kernel/src/fs/ext2/inode.rs +++ b/kernel/src/fs/ext2/inode.rs @@ -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(())