use alloc::{vec, vec::Vec};
use crate::{
block_read::BlockRead,
dir,
error::{Error, Result},
file, format,
inode::Dinode,
path::Path,
resolve,
superblock::Superblock,
};
pub const MAX_FILE_BYTES: u64 = 256 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
Regular,
Directory,
Symlink,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Inode {
pub ino: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Metadata {
size: u64,
mode: u32,
kind: EntryKind,
}
impl Metadata {
pub fn size(&self) -> u64 {
self.size
}
pub fn mode(&self) -> u32 {
self.mode
}
pub fn is_file(&self) -> bool {
self.kind == EntryKind::Regular
}
pub fn is_dir(&self) -> bool {
self.kind == EntryKind::Directory
}
pub fn is_symlink(&self) -> bool {
self.kind == EntryKind::Symlink
}
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: Vec<u8>,
pub kind: EntryKind,
pub inode_number: u64,
}
pub struct Xfs<R: BlockRead> {
reader: R,
sb: Superblock,
}
impl<R: BlockRead> Xfs<R> {
pub fn open(mut reader: R, _device_size_bytes: u64) -> Result<Self> {
let mut buf = vec![0u8; 4096];
reader.read_at(0, &mut buf).map_err(|_| Error::Io {
token: "io_sb",
offset: 0,
})?;
let sb = Superblock::parse(&buf)?;
Ok(Xfs { reader, sb })
}
pub fn uuid(&self) -> [u8; 16] {
self.sb.uuid
}
pub fn label(&self) -> Option<&str> {
self.sb.label()
}
pub fn resolve(&mut self, path: Path<'_>) -> Result<Inode> {
resolve::resolve(&mut self.reader, &self.sb, path).map(|ino| Inode { ino })
}
pub fn metadata(&mut self, inode: &Inode) -> Result<Metadata> {
let di = Dinode::read(&mut self.reader, &self.sb, inode.ino)?;
Ok(meta_of(&di))
}
pub fn read_file(&mut self, path: Path<'_>) -> Result<Vec<u8>> {
let ino = resolve::resolve(&mut self.reader, &self.sb, path)?;
let di = Dinode::read(&mut self.reader, &self.sb, ino)?;
if !di.is_reg() {
return Err(Error::NotARegularFile);
}
if di.size > MAX_FILE_BYTES {
return Err(Error::FileTooLarge {
size: di.size,
max: MAX_FILE_BYTES,
});
}
let cap = usize::try_from(di.size).map_err(|_| Error::FileTooLarge {
size: di.size,
max: MAX_FILE_BYTES,
})?;
let extents = file::extents_of(&mut self.reader, &self.sb, &di)?;
let mut out = vec![0u8; cap];
let n = file::read_into_with_extents(
&mut self.reader,
&self.sb,
&extents,
di.size,
0,
&mut out,
)?;
out.truncate(n);
Ok(out)
}
pub fn read_file_at(&mut self, inode: &Inode, offset: u64, buf: &mut [u8]) -> Result<usize> {
let di = Dinode::read(&mut self.reader, &self.sb, inode.ino)?;
if !di.is_reg() {
return Err(Error::NotARegularFile);
}
file::read_into(&mut self.reader, &self.sb, &di, offset, buf)
}
pub fn read_link(&mut self, path: Path<'_>) -> Result<Vec<u8>> {
let ino = resolve::resolve(&mut self.reader, &self.sb, path)?;
let di = Dinode::read(&mut self.reader, &self.sb, ino)?;
crate::symlink::read_link(&mut self.reader, &self.sb, &di, MAX_FILE_BYTES)
}
pub fn read_dir(&mut self, path: Path<'_>) -> Result<Vec<DirEntry>> {
let ino = resolve::resolve(&mut self.reader, &self.sb, path)?;
let di = Dinode::read(&mut self.reader, &self.sb, ino)?;
let raws = dir::read_dir(&mut self.reader, &self.sb, &di)?;
let mut out = Vec::with_capacity(raws.len());
for e in raws {
let kind = if self.sb.ftype {
kind_from_ftype(e.ftype)
} else {
Dinode::read(&mut self.reader, &self.sb, e.inumber)
.map_or(EntryKind::Other, |d| meta_of(&d).kind)
};
out.push(DirEntry {
name: e.name,
kind,
inode_number: e.inumber,
});
}
Ok(out)
}
}
fn meta_of(di: &Dinode) -> Metadata {
let kind = if di.is_reg() {
EntryKind::Regular
} else if di.is_dir() {
EntryKind::Directory
} else if di.is_symlink() {
EntryKind::Symlink
} else {
EntryKind::Other
};
Metadata {
size: di.size,
mode: u32::from(di.mode),
kind,
}
}
fn kind_from_ftype(ft: u8) -> EntryKind {
match ft {
1 => EntryKind::Regular,
2 => EntryKind::Directory,
7 => EntryKind::Symlink,
_ => EntryKind::Other,
}
}
const _: u8 = format::DINODE_FMT_DEV;