use crate::io::{Read, Seek, SeekFrom, Write};
use alloc::boxed::Box;
use alloc::format;
use alloc::vec;
use alloc::vec::Vec;
use crate::Result;
use crate::block::BlockDevice;
use crate::fs::{FileHandle, FileReadHandle};
use super::dir::{self, ENTRY_SIZE, FileEntrySet, SECFLAG_ALLOC_POSSIBLE, SECFLAG_NO_FAT_CHAIN};
use super::fat;
use super::{Exfat, set_bitmap_bit, split_path};
pub struct ExfatFileHandle<'a> {
pub(super) fs: &'a mut Exfat,
pub(super) dev: &'a mut dyn BlockDevice,
pub(super) parent_cluster: u32,
pub(super) entry_pos: u64,
pub(super) entry_total: usize,
pub(super) entry_bytes: Vec<u8>,
pub(super) chain: Vec<u32>,
pub(super) no_fat_chain: bool,
pub(super) len: u64,
pub(super) valid_len: u64,
pub(super) pos: u64,
pub(super) entry_dirty: bool,
}
impl<'a> ExfatFileHandle<'a> {
fn cluster_size(&self) -> u64 {
self.fs.boot.bytes_per_cluster() as u64
}
fn cluster_disk_offset(&self, cluster: u32) -> u64 {
self.fs.boot.cluster_byte_offset(cluster)
}
fn refresh_entry_bytes(&mut self) {
let stream_off = ENTRY_SIZE;
let mut flags = self.entry_bytes[stream_off + 1];
if self.chain.is_empty() {
flags &= !SECFLAG_ALLOC_POSSIBLE;
flags &= !SECFLAG_NO_FAT_CHAIN;
} else {
flags |= SECFLAG_ALLOC_POSSIBLE;
if self.no_fat_chain {
flags |= SECFLAG_NO_FAT_CHAIN;
} else {
flags &= !SECFLAG_NO_FAT_CHAIN;
}
}
self.entry_bytes[stream_off + 1] = flags;
self.entry_bytes[stream_off + 8..stream_off + 16]
.copy_from_slice(&self.valid_len.to_le_bytes());
let first_cluster = if self.chain.is_empty() {
0
} else {
self.chain[0]
};
self.entry_bytes[stream_off + 20..stream_off + 24]
.copy_from_slice(&first_cluster.to_le_bytes());
debug_assert!(self.valid_len <= self.len);
self.entry_bytes[stream_off + 24..stream_off + 32].copy_from_slice(&self.len.to_le_bytes());
let csum = dir::set_checksum(&self.entry_bytes);
self.entry_bytes[2..4].copy_from_slice(&csum.to_le_bytes());
self.entry_dirty = true;
}
fn write_entry_set(&mut self) -> Result<()> {
if !self.entry_dirty {
return Ok(());
}
let cb = self.cluster_size();
let chain = self.fs.dir_chain(self.parent_cluster)?;
let n_slots = self.entry_total / ENTRY_SIZE;
for k in 0..n_slots {
let p = self.entry_pos + (k as u64) * ENTRY_SIZE as u64;
let cluster_idx = (p / cb) as usize;
let cluster_off = p % cb;
if cluster_idx >= chain.len() {
return Err(crate::Error::InvalidImage(
"exfat: entry-set position past parent directory chain".into(),
));
}
let cluster = chain[cluster_idx];
let disk_off = self.cluster_disk_offset(cluster) + cluster_off;
let src_off = k * ENTRY_SIZE;
self.dev
.write_at(disk_off, &self.entry_bytes[src_off..src_off + ENTRY_SIZE])?;
}
self.entry_dirty = false;
Ok(())
}
fn materialise_fat_chain(&mut self) {
if !self.no_fat_chain {
return;
}
for i in 0..self.chain.len() {
let cur = self.chain[i];
let next = if i + 1 < self.chain.len() {
self.chain[i + 1]
} else {
fat::EOC
};
self.fs.fat.set_raw(cur, next);
}
self.fs.fat_dirty = true;
self.no_fat_chain = false;
}
fn grow_chain(&mut self, n: u32) -> Result<()> {
if n == 0 {
return Ok(());
}
self.materialise_fat_chain();
let prev_tail = self.chain.last().copied();
for i in 0..n {
let c = self.fs.alloc_cluster()?;
if i == 0 {
if let Some(prev) = prev_tail {
self.fs.fat.set_raw(prev, c);
}
} else {
let prev = *self.chain.last().unwrap();
self.fs.fat.set_raw(prev, c);
}
self.chain.push(c);
}
self.fs.fat_dirty = true;
Ok(())
}
fn shrink_chain(&mut self, keep: usize) -> Result<()> {
if keep >= self.chain.len() {
return Ok(());
}
for &c in &self.chain[keep..] {
if c >= 2 {
self.fs.fat.set_raw(c, fat::FREE);
set_bitmap_bit(&mut self.fs.bitmap, c, false);
if self.fs.next_free_hint > c {
self.fs.next_free_hint = c;
}
}
}
self.fs.fat_dirty = true;
self.fs.bitmap_dirty = true;
self.chain.truncate(keep);
if !self.chain.is_empty() && !self.no_fat_chain {
let tail = *self.chain.last().unwrap();
self.fs.fat.set_raw(tail, fat::EOC);
}
Ok(())
}
fn ensure_capacity(&mut self, target_len: u64) -> Result<()> {
let cb = self.cluster_size();
let have = self.chain.len() as u64 * cb;
if target_len <= have {
return Ok(());
}
let need_clusters = target_len.div_ceil(cb);
if need_clusters > u64::from(self.fs.boot.cluster_count) {
return Err(crate::Error::InvalidArgument(format!(
"exfat: {target_len} bytes needs {need_clusters} clusters, volume has {}",
self.fs.boot.cluster_count
)));
}
let extra = (need_clusters - self.chain.len() as u64) as u32;
self.grow_chain(extra)
}
fn zero_range(&mut self, from: u64, to: u64) -> Result<()> {
if from >= to {
return Ok(());
}
let cb = self.cluster_size();
let mut pos = from;
let zero = vec![0u8; cb.min(64 * 1024) as usize];
while pos < to {
let cluster_idx = (pos / cb) as usize;
let cluster_off = pos % cb;
if cluster_idx >= self.chain.len() {
return Err(crate::Error::InvalidImage(
"exfat: zero_range past chain".into(),
));
}
let cluster = self.chain[cluster_idx];
let in_cluster = (cb - cluster_off).min(to - pos);
let mut written: u64 = 0;
while written < in_cluster {
let n = (in_cluster - written).min(zero.len() as u64) as usize;
let disk_off = self.cluster_disk_offset(cluster) + cluster_off + written;
self.dev.write_at(disk_off, &zero[..n])?;
written += n as u64;
}
pos += in_cluster;
}
Ok(())
}
fn read_internal(&mut self, buf: &mut [u8]) -> crate::io::Result<usize> {
if self.pos >= self.len || buf.is_empty() {
return Ok(0);
}
let cb = self.cluster_size();
let avail = self.len - self.pos;
let cluster_idx = (self.pos / cb) as usize;
let cluster_off = self.pos % cb;
if cluster_idx >= self.chain.len() {
return Ok(0);
}
let in_cluster = cb - cluster_off;
let mut want = (buf.len() as u64).min(in_cluster).min(avail);
if self.pos < self.valid_len {
want = want.min(self.valid_len - self.pos);
let cluster = self.chain[cluster_idx];
let disk_off = self.cluster_disk_offset(cluster) + cluster_off;
self.dev
.read_at(disk_off, &mut buf[..want as usize])
.map_err(crate::io::Error::other)?;
} else {
buf[..want as usize].fill(0);
}
self.pos += want;
Ok(want as usize)
}
fn materialise_valid_gap(&mut self, upto: u64) -> Result<()> {
let upto = upto.min(self.len);
if self.valid_len < upto {
self.zero_range(self.valid_len, upto)?;
self.valid_len = upto;
}
Ok(())
}
fn write_internal(&mut self, buf: &[u8]) -> Result<usize> {
if buf.is_empty() {
return Ok(0);
}
self.materialise_valid_gap(self.len)?;
if self.pos > self.len {
self.ensure_capacity(self.pos)?;
let gap_lo = self.len;
let gap_hi = self.pos;
self.zero_range(gap_lo, gap_hi)?;
self.len = self.pos;
self.valid_len = self.pos;
self.refresh_entry_bytes();
}
let new_end = self.pos + buf.len() as u64;
self.ensure_capacity(new_end)?;
let cb = self.cluster_size();
let mut written: usize = 0;
while written < buf.len() {
let p = self.pos + written as u64;
let cluster_idx = (p / cb) as usize;
let cluster_off = p % cb;
let cluster = self.chain[cluster_idx];
let in_cluster = (cb - cluster_off).min((buf.len() - written) as u64) as usize;
let disk_off = self.cluster_disk_offset(cluster) + cluster_off;
self.dev
.write_at(disk_off, &buf[written..written + in_cluster])?;
written += in_cluster;
}
self.pos += buf.len() as u64;
if self.pos > self.len {
self.len = self.pos;
}
self.valid_len = self.len;
self.refresh_entry_bytes();
Ok(written)
}
}
impl<'a> Read for ExfatFileHandle<'a> {
fn read(&mut self, buf: &mut [u8]) -> crate::io::Result<usize> {
self.read_internal(buf)
}
}
impl<'a> Write for ExfatFileHandle<'a> {
fn write(&mut self, buf: &[u8]) -> crate::io::Result<usize> {
self.write_internal(buf).map_err(crate::io::Error::other)
}
fn flush(&mut self) -> crate::io::Result<()> {
Ok(())
}
}
impl<'a> Seek for ExfatFileHandle<'a> {
fn seek(&mut self, pos: SeekFrom) -> crate::io::Result<u64> {
let new = match pos {
SeekFrom::Start(n) => n as i128,
SeekFrom::Current(n) => self.pos as i128 + n as i128,
SeekFrom::End(n) => self.len as i128 + n as i128,
};
if new < 0 {
return Err(crate::io::Error::new(
crate::io::ErrorKind::InvalidInput,
"exfat: seek to negative offset",
));
}
self.pos = new as u64;
Ok(self.pos)
}
}
impl<'a> FileHandle for ExfatFileHandle<'a> {
fn len(&self) -> u64 {
self.len
}
fn set_len(&mut self, new_len: u64) -> Result<()> {
let cb = self.cluster_size();
if new_len == self.len {
return Ok(());
}
self.materialise_valid_gap(new_len)?;
if new_len < self.len {
let keep_clusters = new_len.div_ceil(cb) as usize;
self.shrink_chain(keep_clusters)?;
self.len = new_len;
} else {
self.ensure_capacity(new_len)?;
let old_len = self.len;
self.zero_range(old_len, new_len)?;
self.len = new_len;
}
self.valid_len = self.len;
self.refresh_entry_bytes();
Ok(())
}
fn sync(&mut self) -> Result<()> {
self.write_entry_set()?;
self.fs.flush(self.dev)?;
Ok(())
}
}
impl<'a> Drop for ExfatFileHandle<'a> {
fn drop(&mut self) {
let _ = self.write_entry_set();
}
}
impl Exfat {
pub(super) fn open_rw<'a>(
&'a mut self,
dev: &'a mut dyn BlockDevice,
path: &str,
flags: crate::fs::OpenFlags,
meta: Option<crate::fs::FileMeta>,
) -> Result<Box<dyn FileHandle + 'a>> {
self.require_allocation_bitmap()?;
self.flush_dir_batches(dev)?;
let parts = split_path(path);
if parts.is_empty() {
return Err(crate::Error::InvalidArgument(
"exfat: cannot open root as a file".into(),
));
}
let (leaf, prefix) = parts.split_last().unwrap();
let mut parent_cluster = self.boot.first_cluster_of_root_directory;
for part in prefix {
let bytes = self.read_dir_bytes(dev, parent_cluster)?;
let next = super::iter_file_sets(&bytes)?
.into_iter()
.find(|e| self.name_matches(&e.name_utf16, part))
.ok_or_else(|| {
crate::Error::InvalidArgument(format!(
"exfat: no such entry {part:?} under {path:?}"
))
})?;
if !next.is_directory {
return Err(crate::Error::InvalidArgument(format!(
"exfat: {part:?} is not a directory"
)));
}
self.note_parent(next.first_cluster, parent_cluster);
parent_cluster = next.first_cluster;
}
let found = self.find_entry_in_dir(dev, parent_cluster, leaf)?;
match found {
Some((pos, set, total)) => {
if set.is_directory {
return Err(crate::Error::InvalidArgument(format!(
"exfat: {path:?} is a directory"
)));
}
let chain =
self.build_data_chain(set.first_cluster, set.no_fat_chain(), set.data_length)?;
let mut handle =
self.handle_from_existing(dev, parent_cluster, pos, total, set, chain)?;
if flags.truncate {
handle.set_len(0)?;
}
if flags.append {
handle.pos = handle.len;
}
Ok(Box::new(handle))
}
None => {
if !flags.create {
return Err(crate::Error::InvalidArgument(format!(
"exfat: no such file {path:?}"
)));
}
let m = meta.ok_or_else(|| {
crate::Error::InvalidArgument(
"exfat: open_file_rw with create=true requires meta".into(),
)
})?;
let ts = super::unix_to_exfat_timestamp(m.mtime);
let _ =
self.create_file_in(dev, parent_cluster, leaf, &mut crate::io::empty(), 0, ts)?;
self.flush_dir_batches(dev)?;
let (pos, set, total) = self
.find_entry_in_dir(dev, parent_cluster, leaf)?
.ok_or_else(|| {
crate::Error::InvalidImage(
"exfat: created file vanished from parent directory".into(),
)
})?;
let chain =
self.build_data_chain(set.first_cluster, set.no_fat_chain(), set.data_length)?;
let mut handle =
self.handle_from_existing(dev, parent_cluster, pos, total, set, chain)?;
if flags.append {
handle.pos = handle.len;
}
Ok(Box::new(handle))
}
}
}
pub(super) fn open_ro<'a>(
&'a mut self,
dev: &'a mut dyn BlockDevice,
path: &str,
) -> Result<Box<dyn FileReadHandle + 'a>> {
let parts = split_path(path);
if parts.is_empty() {
return Err(crate::Error::InvalidArgument(
"exfat: cannot open root as a file".into(),
));
}
let (leaf, prefix) = parts.split_last().unwrap();
let mut parent_cluster = self.boot.first_cluster_of_root_directory;
for part in prefix {
let bytes = self.read_dir_bytes(dev, parent_cluster)?;
let next = super::iter_file_sets(&bytes)?
.into_iter()
.find(|e| self.name_matches(&e.name_utf16, part))
.ok_or_else(|| {
crate::Error::InvalidArgument(format!(
"exfat: no such entry {part:?} under {path:?}"
))
})?;
if !next.is_directory {
return Err(crate::Error::InvalidArgument(format!(
"exfat: {part:?} is not a directory"
)));
}
parent_cluster = next.first_cluster;
}
let (pos, set, total) = self
.find_entry_in_dir(dev, parent_cluster, leaf)?
.ok_or_else(|| {
crate::Error::InvalidArgument(format!("exfat: no such file {path:?}"))
})?;
if set.is_directory {
return Err(crate::Error::InvalidArgument(format!(
"exfat: {path:?} is a directory"
)));
}
let chain =
self.build_data_chain(set.first_cluster, set.no_fat_chain(), set.data_length)?;
let inner = self.handle_from_existing(dev, parent_cluster, pos, total, set, chain)?;
Ok(Box::new(ReadOnlyExfatHandle { inner }))
}
#[allow(clippy::too_many_arguments)]
fn handle_from_existing<'a>(
&'a mut self,
dev: &'a mut dyn BlockDevice,
parent_cluster: u32,
entry_pos: u64,
entry_total: usize,
set: FileEntrySet,
chain: Vec<u32>,
) -> Result<ExfatFileHandle<'a>> {
let cb = self.boot.bytes_per_cluster() as u64;
let dir_chain = self.dir_chain(parent_cluster)?;
let mut entry_bytes = vec![0u8; entry_total];
let n_slots = entry_total / ENTRY_SIZE;
for k in 0..n_slots {
let p = entry_pos + (k as u64) * ENTRY_SIZE as u64;
let cluster_idx = (p / cb) as usize;
let cluster_off = p % cb;
if cluster_idx >= dir_chain.len() {
return Err(crate::Error::InvalidImage(
"exfat: entry-set position past parent directory chain".into(),
));
}
let cluster = dir_chain[cluster_idx];
let disk_off = self.boot.cluster_byte_offset(cluster) + cluster_off;
dev.read_at(
disk_off,
&mut entry_bytes[k * ENTRY_SIZE..(k + 1) * ENTRY_SIZE],
)?;
}
let no_fat_chain = set.no_fat_chain();
let len = set.data_length;
let valid_len = set.valid_data_length.min(len);
Ok(ExfatFileHandle {
fs: self,
dev,
parent_cluster,
entry_pos,
entry_total,
entry_bytes,
chain,
no_fat_chain,
len,
valid_len,
pos: 0,
entry_dirty: false,
})
}
}
pub struct ReadOnlyExfatHandle<'a> {
inner: ExfatFileHandle<'a>,
}
impl<'a> Read for ReadOnlyExfatHandle<'a> {
fn read(&mut self, buf: &mut [u8]) -> crate::io::Result<usize> {
self.inner.read(buf)
}
}
impl<'a> Seek for ReadOnlyExfatHandle<'a> {
fn seek(&mut self, pos: SeekFrom) -> crate::io::Result<u64> {
self.inner.seek(pos)
}
}
impl<'a> FileReadHandle for ReadOnlyExfatHandle<'a> {
fn len(&self) -> u64 {
FileHandle::len(&self.inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::block::MemoryBackend;
use crate::fs::{FileMeta, Filesystem, OpenFlags};
use alloc::string::ToString;
#[test]
fn write_beyond_volume_capacity_is_rejected_without_allocating() {
let mut dev = MemoryBackend::new(4 * 1024 * 1024);
let opts = super::super::FormatOpts {
bytes_per_sector_shift: 9,
sectors_per_cluster_shift: 3,
volume_serial_number: 1,
volume_label: "CAP".to_string(),
};
let mut fs = Exfat::format(&mut dev, &opts).unwrap();
let cb = fs.cluster_size() as u64;
let used_before: u32 = fs.bitmap.iter().map(|b| b.count_ones()).sum();
{
let mut h = fs
.open_file_rw(
&mut dev,
crate::path::Path::new("/huge.bin"),
OpenFlags {
create: true,
..Default::default()
},
Some(FileMeta::default()),
)
.unwrap();
let target = (1u64 << 32) * cb;
h.seek(SeekFrom::Start(target)).unwrap();
let err = h.write(b"x").unwrap_err();
assert_eq!(err.kind(), crate::io::ErrorKind::Other, "{err}");
let err = h.set_len(target + 1).unwrap_err();
assert!(matches!(err, crate::Error::InvalidArgument(_)), "{err:?}");
assert_eq!(h.len(), 0);
}
let used_after: u32 = fs.bitmap.iter().map(|b| b.count_ones()).sum();
assert_eq!(used_before, used_after, "clusters were allocated");
}
}