use alloc::{string::String, vec, vec::Vec};
use crate::{
block_read::{read_exact, BlockRead},
dir::{self, EntrySet},
error::{Error, Result},
file, name,
path::Path,
resolve,
upcase::Upcase,
vbr::{self, Geometry},
};
pub const MAX_FILE_BYTES: u64 = 256 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
Regular,
Directory,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Metadata {
size: u64,
kind: EntryKind,
}
impl Metadata {
pub fn size(&self) -> u64 {
self.size
}
pub fn is_file(&self) -> bool {
self.kind == EntryKind::Regular
}
pub fn is_dir(&self) -> bool {
self.kind == EntryKind::Directory
}
pub fn kind(&self) -> EntryKind {
self.kind
}
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub kind: EntryKind,
pub first_cluster: u32,
}
pub struct ExFat<R: BlockRead> {
reader: R,
geo: Geometry,
upcase: Upcase,
label: Option<String>,
}
impl<R: BlockRead> ExFat<R> {
pub fn open(mut reader: R, _device_size_bytes: u64) -> Result<Self> {
let mut sec0 = [0u8; 512];
read_exact(&mut reader, 0, &mut sec0, "io_vbr")?;
let geo = vbr::parse(&sec0)?;
let bps = geo.bytes_per_sector as usize;
let mut region = vec![0u8; bps * 12];
read_exact(&mut reader, 0, &mut region, "io_vbr")?;
vbr::verify_boot_checksum(®ion, bps)?;
let meta = dir::scan_root_meta(&mut reader, &geo)?;
let mut upcase = Upcase::ascii();
if let Some(cluster) = meta.upcase_cluster {
upcase.load(&mut reader, &geo, cluster)?;
}
let label = meta
.label
.as_deref()
.map(name::decode_lossy)
.filter(|s| !s.is_empty());
Ok(Self {
reader,
geo,
upcase,
label,
})
}
pub fn volume_serial(&self) -> u32 {
self.geo.volume_serial
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn metadata(&mut self, path: Path<'_>) -> Result<Metadata> {
match resolve::resolve(&mut self.reader, &self.geo, &self.upcase, path)? {
None => Ok(Metadata {
size: 0,
kind: EntryKind::Directory,
}),
Some(es) => Ok(Metadata {
size: es.data_length,
kind: kind_of(&es),
}),
}
}
pub fn exists(&mut self, path: Path<'_>) -> Result<bool> {
match resolve::resolve(&mut self.reader, &self.geo, &self.upcase, path) {
Ok(_) => Ok(true),
Err(Error::NotFound { .. }) => Ok(false),
Err(e) => Err(e),
}
}
pub fn read(&mut self, path: Path<'_>) -> Result<Vec<u8>> {
let es = self.regular_entry(path)?;
if es.data_length > MAX_FILE_BYTES {
return Err(Error::FileTooLarge {
size: es.data_length,
max: MAX_FILE_BYTES,
});
}
let cap = usize::try_from(es.data_length).map_err(|_| Error::FileTooLarge {
size: es.data_length,
max: MAX_FILE_BYTES,
})?;
let mut out = vec![0u8; cap];
let n = file::read_at(&mut self.reader, &self.geo, &es, 0, &mut out)?;
out.truncate(n);
Ok(out)
}
pub fn read_at(&mut self, path: Path<'_>, offset: u64, buf: &mut [u8]) -> Result<usize> {
let es = self.regular_entry(path)?;
file::read_at(&mut self.reader, &self.geo, &es, offset, buf)
}
pub fn read_dir(&mut self, path: Path<'_>) -> Result<Vec<DirEntry>> {
let (cluster, contiguous) =
match resolve::resolve(&mut self.reader, &self.geo, &self.upcase, path)? {
None => (self.geo.root_cluster, false),
Some(es) if es.is_dir => (es.first_cluster, es.contiguous),
Some(_) => {
return Err(Error::NotFound {
component: "not a directory",
})
}
};
let entries = dir::read_entries(&mut self.reader, &self.geo, cluster, contiguous)?;
let mut out = Vec::with_capacity(entries.len());
for es in entries {
out.push(DirEntry {
name: name::decode_lossy(&es.name),
kind: kind_of(&es),
first_cluster: es.first_cluster,
});
}
Ok(out)
}
fn regular_entry(&mut self, path: Path<'_>) -> Result<EntrySet> {
let es = resolve::resolve(&mut self.reader, &self.geo, &self.upcase, path)?
.ok_or(Error::NotARegularFile)?;
if es.is_dir {
return Err(Error::NotARegularFile);
}
Ok(es)
}
}
fn kind_of(es: &EntrySet) -> EntryKind {
if es.is_dir {
EntryKind::Directory
} else {
EntryKind::Regular
}
}