use alloc::{format, string::ToString, sync::Arc};
use bytes::Buf;
use typed_path::Component;
use typed_path::{UnixComponent, UnixPath};
use super::file::File;
use super::walkdir::WalkDir;
use crate::backend::Image;
use crate::dirent;
use crate::filesystem::{BlockPlan, EroFSCore};
use crate::types::*;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct EroFS<I: Image> {
image: Arc<I>,
core: EroFSCore,
}
impl<I: Image> EroFS<I> {
pub fn new(image: I) -> Result<Self> {
let sb_data = image
.get(SUPER_BLOCK_OFFSET..)
.ok_or_else(|| Error::InvalidSuperblock("failed to read super block".to_string()))?;
let core = EroFSCore::new(sb_data)?;
Ok(Self {
image: image.into(),
core,
})
}
pub fn walk_dir<P: AsRef<UnixPath>>(&self, root: P) -> Result<WalkDir<'_, I>> {
WalkDir::new(self, root)
}
pub fn read_dir<P: AsRef<UnixPath>>(&self, path: P) -> Result<WalkDir<'_, I>> {
Ok(WalkDir::new(self, path)?.max_depth(1))
}
pub fn open<P: AsRef<UnixPath>>(&self, path: P) -> Result<File<'_, I>> {
let inode = self
.get_path_inode(&path)?
.ok_or_else(|| Error::PathNotFound(path.as_ref().to_string_lossy().into_owned()))?;
self.open_inode_file(inode)
}
pub fn open_inode_file(&self, inode: Inode) -> Result<File<'_, I>> {
if !inode.is_file() {
return Err(Error::NotAFile(format!(
"inode {} is not a regular file",
inode.id()
)));
}
Ok(File::new(inode, self))
}
pub fn super_block(&self) -> &SuperBlock {
&self.core.super_block
}
pub(crate) fn block_size(&self) -> usize {
self.core.block_size
}
pub fn get_inode(&self, nid: u64) -> Result<Inode> {
let offset = self.core.get_inode_offset(nid) as usize;
let data = self
.image
.get(offset..)
.ok_or_else(|| Error::OutOfBounds("failed to read inode format".to_string()))?;
self.core.parse_inode(data, nid)
}
pub(crate) fn get_inode_block(&self, inode: &Inode, offset: usize) -> Result<&[u8]> {
match self.core.plan_inode_block_read(inode, offset)? {
BlockPlan::Direct { offset, size } => self
.image
.get(offset..offset + size)
.ok_or_else(|| Error::OutOfBounds("failed to get inode data".to_string())),
BlockPlan::Chunked {
addr_offset,
chunk_fixed,
chunk_size,
data_size,
chunk_index,
} => {
let chunk_addr = self
.image
.get(addr_offset..addr_offset + 4)
.ok_or_else(|| Error::OutOfBounds("failed to get chunk address".to_string()))?
.get_i32_le();
let (offset, size) = self.core.resolve_chunk_read(
chunk_addr,
chunk_fixed,
chunk_size,
data_size,
chunk_index,
)?;
self.image
.get(offset..offset + size)
.ok_or_else(|| Error::OutOfBounds("failed to get inode data".to_string()))
}
}
}
pub(crate) fn get_path_inode<P: AsRef<UnixPath>>(&self, path: P) -> Result<Option<Inode>> {
let mut nid = self.core.super_block.root_nid as u64;
let path = path.as_ref().normalize();
'outer: for part in path.components() {
if part == UnixComponent::RootDir {
continue;
}
let inode = self.get_inode(nid)?;
let block_count = inode.data_size().div_ceil(self.core.block_size);
if block_count == 0 {
return Ok(None);
}
for i in 0..block_count {
let block = self.get_inode_block(&inode, i * self.core.block_size)?;
if let Some(found_nid) = dirent::find_nodeid_by_name(part.as_bytes(), block)? {
nid = found_nid;
continue 'outer;
}
}
return Ok(None);
}
let inode = self.get_inode(nid)?;
Ok(Some(inode))
}
}