use alloc::format;
use alloc::vec::Vec;
use typed_path::Component;
use bytes::Buf;
use typed_path::{UnixComponent, UnixPath};
use super::file::File;
use super::walkdir::WalkDir;
use crate::backend::AsyncImage;
use crate::dirent;
use crate::filesystem::{BlockPlan, EroFSCore};
use crate::types::*;
use crate::{Error, Result};
#[derive(Debug, Clone)]
pub struct EroFS<I: AsyncImage> {
image: I,
core: EroFSCore,
}
impl<I: AsyncImage> EroFS<I> {
pub async fn new(image: I) -> Result<Self> {
let mut super_block = vec![0u8; SuperBlock::size()];
image
.read_exact_at(&mut super_block, SUPER_BLOCK_OFFSET)
.await?;
let core = EroFSCore::new(&super_block)?;
Ok(Self { image, core })
}
pub async fn walk_dir(&self, root: impl AsRef<UnixPath>) -> Result<WalkDir<'_, I>> {
WalkDir::new(self, root.as_ref()).await
}
pub async fn read_dir(&self, path: impl AsRef<UnixPath>) -> Result<WalkDir<'_, I>> {
Ok(WalkDir::new(self, path.as_ref()).await?.max_depth(1))
}
pub async fn open(&self, path: impl AsRef<UnixPath>) -> Result<File<'_, I>> {
let inode = self
.get_path_inode(path.as_ref())
.await?
.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 async fn get_inode(&self, nid: u64) -> Result<Inode> {
let offset = self.core.get_inode_offset(nid) as usize;
let mut buf = vec![0u8; InodeExtended::size()];
self.image.read_exact_at(&mut buf, offset).await?;
self.core.parse_inode(&buf, nid)
}
pub(crate) async fn read_inode_block(&self, inode: &Inode, offset: usize) -> Result<Vec<u8>> {
match self.core.plan_inode_block_read(inode, offset)? {
BlockPlan::Direct { offset, size } => {
if size > self.core.block_size {
return Err(Error::CorruptedData(format!(
"invalid direct block size {} at offset {}",
size, offset
)));
}
let mut buf = vec![0u8; size];
self.image.read_exact_at(&mut buf, offset).await?;
Ok(buf)
}
BlockPlan::Chunked {
addr_offset,
chunk_fixed,
chunk_size,
data_size,
chunk_index,
} => {
let mut addr_buf = vec![0u8; 4];
self.image.read_exact_at(&mut addr_buf, addr_offset).await?;
let chunk_addr = (&addr_buf[..]).get_i32_le();
let (offset, size) = self.core.resolve_chunk_read(
chunk_addr,
chunk_fixed,
chunk_size,
data_size,
chunk_index,
)?;
let mut buf = vec![0u8; size];
self.image.read_exact_at(&mut buf, offset).await?;
Ok(buf)
}
}
}
pub(crate) async fn get_path_inode(&self, path: &UnixPath) -> Result<Option<Inode>> {
let mut nid = self.core.super_block.root_nid as u64;
let path = path.normalize();
'outer: for part in path.components() {
if part == UnixComponent::RootDir {
continue;
}
let inode = self.get_inode(nid).await?;
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
.read_inode_block(&inode, i * self.core.block_size)
.await?;
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).await?;
Ok(Some(inode))
}
}