Files
asterinas/services/libs/aster-std/src/fs/ext2/dir.rs
2024-01-18 08:22:58 +08:00

324 lines
10 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-License-Identifier: MPL-2.0
use super::inode::{FileType, MAX_FNAME_LEN};
use super::prelude::*;
use core::iter::Iterator;
/// The data structure in a directory's data block. It is stored in a linked list.
///
/// Each entry contains the name of the entry, the inode number, the file type,
/// and the distance within the directory file to the next entry.
#[derive(Clone, Debug)]
pub struct DirEntry {
/// The header part.
header: DirEntryHeader,
/// Name of the entry, up to 255 bytes (excluding the null terminator).
name: CStr256,
}
impl DirEntry {
/// Constructs a new `DirEntry` object with the specified inode (`ino`),
/// name (`name`), and file type (`file_type`).
pub(super) fn new(ino: u32, name: &str, file_type: FileType) -> Self {
debug_assert!(name.len() <= MAX_FNAME_LEN);
let record_len = (Self::header_len() + name.len()).align_up(4) as u16;
Self {
header: DirEntryHeader {
ino,
record_len,
name_len: name.len() as u8,
file_type: DirEntryFileType::from(file_type) as _,
},
name: CStr256::from(name),
}
}
/// Constructs a `DirEntry` with the name "." and `self_ino` as its inode.
pub(super) fn self_entry(self_ino: u32) -> Self {
Self::new(self_ino, ".", FileType::Dir)
}
/// Constructs a `DirEntry` with the name ".." and `parent_ino` as its inode.
pub(super) fn parent_entry(parent_ino: u32) -> Self {
Self::new(parent_ino, "..", FileType::Dir)
}
/// Returns a reference to the header.
fn header(&self) -> &DirEntryHeader {
&self.header
}
/// Returns the length of the header.
fn header_len() -> usize {
core::mem::size_of::<DirEntryHeader>()
}
/// Returns the inode number.
pub fn ino(&self) -> u32 {
self.header.ino
}
/// Modifies the inode number.
pub fn set_ino(&mut self, ino: u32) {
self.header.ino = ino;
}
/// Returns the name.
pub fn name(&self) -> &str {
self.name.as_str().unwrap()
}
/// Returns the type.
pub fn type_(&self) -> FileType {
FileType::from(DirEntryFileType::try_from(self.header.file_type).unwrap())
}
/// Returns the distance to the next entry.
pub fn record_len(&self) -> usize {
self.header.record_len as _
}
/// Modifies the distance to the next entry.
pub(super) fn set_record_len(&mut self, record_len: usize) {
debug_assert!(record_len >= self.actual_len());
self.header.record_len = record_len as _;
}
/// Returns the actual length of the current entry.
pub(super) fn actual_len(&self) -> usize {
(Self::header_len() + self.name.len()).align_up(4)
}
/// Returns the length of the gap between the current entry and the next entry.
pub(super) fn gap_len(&self) -> usize {
self.record_len() - self.actual_len()
}
}
/// The header of `DirEntry`.
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod)]
struct DirEntryHeader {
/// Inode number
ino: u32,
/// Directory entry length
record_len: u16,
/// Name Length
name_len: u8,
/// Type indicator
file_type: u8,
}
/// The type indicator in the `DirEntry`.
#[repr(u8)]
#[derive(Copy, Clone, Debug, Eq, PartialEq, TryFromInt)]
enum DirEntryFileType {
Unknown = 0,
File = 1,
Dir = 2,
Char = 3,
Block = 4,
Fifo = 5,
Socket = 6,
Symlink = 7,
}
impl From<FileType> for DirEntryFileType {
fn from(file_type: FileType) -> Self {
match file_type {
FileType::Fifo => Self::Fifo,
FileType::Char => Self::Char,
FileType::Dir => Self::Dir,
FileType::Block => Self::Block,
FileType::File => Self::File,
FileType::Symlink => Self::Symlink,
FileType::Socket => Self::Socket,
}
}
}
impl From<DirEntryFileType> for FileType {
fn from(file_type: DirEntryFileType) -> Self {
match file_type {
DirEntryFileType::Fifo => Self::Fifo,
DirEntryFileType::Char => Self::Char,
DirEntryFileType::Dir => Self::Dir,
DirEntryFileType::Block => Self::Block,
DirEntryFileType::File => Self::File,
DirEntryFileType::Symlink => Self::Symlink,
DirEntryFileType::Socket => Self::Socket,
DirEntryFileType::Unknown => panic!("unknown file type"),
}
}
}
/// A reader for reading `DirEntry` from the page cache.
pub struct DirEntryReader<'a> {
page_cache: &'a PageCache,
offset: usize,
}
impl<'a> DirEntryReader<'a> {
/// Constructs a reader with the given page cache and offset.
pub(super) fn new(page_cache: &'a PageCache, from_offset: usize) -> Self {
Self {
page_cache,
offset: from_offset,
}
}
/// Reads one `DirEntry` from the current offset.
pub fn read_entry(&mut self) -> Result<DirEntry> {
let header = self
.page_cache
.pages()
.read_val::<DirEntryHeader>(self.offset)?;
if header.ino == 0 {
return_errno!(Errno::ENOENT);
}
let mut name = vec![0u8; header.name_len as _];
self.page_cache
.pages()
.read_bytes(self.offset + DirEntry::header_len(), &mut name)?;
let entry = DirEntry {
header,
name: CStr256::from(name.as_slice()),
};
self.offset += entry.record_len();
Ok(entry)
}
}
impl<'a> Iterator for DirEntryReader<'a> {
type Item = (usize, DirEntry);
fn next(&mut self) -> Option<Self::Item> {
let offset = self.offset;
let entry = match self.read_entry() {
Ok(entry) => entry,
Err(_) => {
return None;
}
};
Some((offset, entry))
}
}
/// A writer for modifying `DirEntry` of the page cache.
pub struct DirEntryWriter<'a> {
page_cache: &'a PageCache,
offset: usize,
}
impl<'a> DirEntryWriter<'a> {
/// Constructs a writer with the given page cache and offset.
pub(super) fn new(page_cache: &'a PageCache, from_offset: usize) -> Self {
Self {
page_cache,
offset: from_offset,
}
}
/// Writes a `DirEntry` at the current offset.
pub fn write_entry(&mut self, entry: &DirEntry) -> Result<()> {
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(),
)?;
self.offset += entry.record_len();
Ok(())
}
/// 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 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((offset, mut entry)) = DirEntryReader::new(self.page_cache, self.offset)
.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.pages().resize(new_size)?;
new_entry.set_record_len(BLOCK_SIZE);
self.offset = old_size;
self.write_entry(&new_entry)?;
return Ok(());
};
// Write in the gap between existing entries.
new_entry.set_record_len(entry.gap_len());
entry.set_record_len(entry.actual_len());
self.offset = offset;
self.write_entry(&entry)?;
self.write_entry(&new_entry)?;
Ok(())
}
/// 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);
let next_reader = DirEntryReader::new(self.page_cache, self_entry_record_len);
let Some(((pre_offset, mut pre_entry), (offset, entry))) = reader
.zip(next_reader)
.find(|((offset, _), (_, dir_entry))| dir_entry.name() == name)
else {
return_errno!(Errno::ENOENT);
};
if DirEntryReader::new(self.page_cache, offset)
.next()
.is_none()
&& Bid::from_offset(pre_offset) != Bid::from_offset(offset)
{
// Shrink the size.
let new_size = pre_offset.align_up(BLOCK_SIZE);
self.page_cache.pages().resize(new_size)?;
pre_entry.set_record_len(new_size - pre_offset);
self.offset = pre_offset;
self.write_entry(&pre_entry)?;
} else {
// Update the previous entry.
pre_entry.set_record_len(pre_entry.record_len() + entry.record_len());
self.offset = pre_offset;
self.write_entry(&pre_entry)?;
}
Ok(entry)
}
/// Renames the `DirEntry` from `old_name` to the `new_name` from the current offset.
///
/// It will moves the `DirEntry` to another position,
/// if the record length is not big enough.
pub fn rename_entry(&mut self, old_name: &str, new_name: &str) -> Result<()> {
let (offset, entry) = DirEntryReader::new(self.page_cache, self.offset)
.find(|(offset, entry)| entry.name() == old_name)
.ok_or(Error::new(Errno::ENOENT))?;
let mut new_entry = DirEntry::new(entry.ino(), new_name, entry.type_());
if new_entry.record_len() <= entry.record_len() {
// Just rename the entry.
new_entry.set_record_len(entry.record_len());
self.offset = offset;
self.write_entry(&new_entry)?;
} else {
// Move to another position.
self.remove_entry(old_name)?;
self.offset = 0;
self.append_entry(new_entry)?;
}
Ok(())
}
}