use std::io::{self, Read, Seek, SeekFrom, Write};
use super::{Fat32, SECTOR, dir, table};
use crate::Result;
use crate::block::BlockDevice;
use crate::fs::{FileHandle, FileReadHandle};
pub struct FatFileHandle<'a> {
fs: &'a mut Fat32,
dev: &'a mut dyn BlockDevice,
chain: Vec<u32>,
file_size: u64,
pos: u64,
dir_chain: Vec<u32>,
entry_pos: usize,
entry_attr: u8,
entry_name_83: [u8; 11],
dirty: bool,
}
impl<'a> FatFileHandle<'a> {
pub(super) fn open_existing(
fs: &'a mut Fat32,
dev: &'a mut dyn BlockDevice,
parent_chain: Vec<u32>,
entry_pos: usize,
entry: dir::DirEntry,
) -> Result<Self> {
let chain = if entry.first_cluster < 2 {
Vec::new()
} else {
fs.chain_of(entry.first_cluster)?
};
Ok(Self {
fs,
dev,
chain,
file_size: u64::from(entry.file_size),
pos: 0,
dir_chain: parent_chain,
entry_pos,
entry_attr: entry.attr,
entry_name_83: entry.name_83,
dirty: false,
})
}
fn cb(&self) -> u64 {
self.fs.boot_sector().sectors_per_cluster as u64 * SECTOR as u64
}
fn cluster_offset(&self, cluster: u32) -> u64 {
let boot = self.fs.boot_sector();
let sector = boot.data_start_sector() + (cluster - 2) * boot.sectors_per_cluster as u32;
sector as u64 * SECTOR as u64
}
fn read_inner(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.pos >= self.file_size || buf.is_empty() {
return Ok(0);
}
let cb = self.cb();
let remaining_in_file = self.file_size - self.pos;
let want = (buf.len() as u64).min(remaining_in_file);
let mut written: u64 = 0;
while written < want {
let pos = self.pos + written;
let cluster_idx = (pos / cb) as usize;
let in_cluster = pos % cb;
let chunk = (cb - in_cluster).min(want - written);
if cluster_idx >= self.chain.len() {
break;
}
let cluster = self.chain[cluster_idx];
let off = self.cluster_offset(cluster) + in_cluster;
let dst_start = written as usize;
let dst_end = dst_start + chunk as usize;
self.dev
.read_at(off, &mut buf[dst_start..dst_end])
.map_err(io::Error::other)?;
written += chunk;
}
self.pos += written;
Ok(written as usize)
}
fn ensure_chain_clusters(&mut self, needed: u32) -> Result<()> {
if self.chain.len() as u32 >= needed {
return Ok(());
}
let extra = needed - self.chain.len() as u32;
let new_clusters = self.fs.alloc_free_clusters(extra)?;
if let Some(&last) = self.chain.last() {
self.fs.fat_mut().set(last, new_clusters[0]);
}
let cb = self.cb();
let zero = vec![0u8; cb as usize];
for &c in &new_clusters {
self.dev.write_at(self.cluster_offset(c), &zero)?;
}
self.chain.extend_from_slice(&new_clusters);
Ok(())
}
fn truncate_chain(&mut self, keep: u32) -> Result<()> {
if self.chain.len() as u32 <= keep {
return Ok(());
}
let drained: Vec<u32> = self.chain.drain(keep as usize..).collect();
for c in &drained {
self.fs.fat_mut().set(*c, table::FREE);
}
if let Some(&last) = self.chain.last() {
self.fs.fat_mut().set(last, table::EOC);
}
if let Some(&first_freed) = drained.first() {
self.fs.hint_next_free(first_freed);
}
Ok(())
}
fn write_into_chain(&mut self, off: u64, data: &[u8]) -> io::Result<()> {
let cb = self.cb();
let mut written: u64 = 0;
let total = data.len() as u64;
while written < total {
let pos = off + written;
let cluster_idx = (pos / cb) as usize;
let in_cluster = pos % cb;
let chunk = (cb - in_cluster).min(total - written);
let cluster = self.chain[cluster_idx];
let dst = self.cluster_offset(cluster) + in_cluster;
let src_start = written as usize;
let src_end = src_start + chunk as usize;
self.dev
.write_at(dst, &data[src_start..src_end])
.map_err(io::Error::other)?;
written += chunk;
}
Ok(())
}
fn flush_dir_entry(&mut self) -> Result<()> {
let first_cluster = self.chain.first().copied().unwrap_or(0);
let entry = dir::DirEntry {
name_83: self.entry_name_83,
attr: self.entry_attr,
first_cluster,
file_size: self.file_size as u32,
};
let enc = entry.encode();
let cb = self.cb() as usize;
let cluster_idx = self.entry_pos / cb;
let in_cluster = self.entry_pos % cb;
let cluster = self.dir_chain[cluster_idx];
let off = self.cluster_offset(cluster) + in_cluster as u64;
self.dev.write_at(off, &enc)?;
Ok(())
}
fn zero_range(&mut self, start: u64, end: u64) -> io::Result<()> {
if end <= start {
return Ok(());
}
let cb = self.cb();
let zero = vec![0u8; cb as usize];
let mut pos = start;
while pos < end {
let cluster_idx = (pos / cb) as usize;
let in_cluster = pos % cb;
let chunk = (cb - in_cluster).min(end - pos);
let cluster = self.chain[cluster_idx];
let dst = self.cluster_offset(cluster) + in_cluster;
self.dev
.write_at(dst, &zero[..chunk as usize])
.map_err(io::Error::other)?;
pos += chunk;
}
Ok(())
}
fn write_inner(&mut self, buf: &[u8]) -> io::Result<usize> {
if buf.is_empty() {
return Ok(0);
}
let cb = self.cb();
let new_end = self.pos + buf.len() as u64;
let needed_clusters = new_end.div_ceil(cb) as u32;
let gap_start = self.file_size;
let gap_end = self.pos.min(new_end);
self.ensure_chain_clusters(needed_clusters)
.map_err(|e| io::Error::other(e.to_string()))?;
if gap_end > gap_start {
self.zero_range(gap_start, gap_end)?;
}
self.write_into_chain(self.pos, buf)?;
self.pos += buf.len() as u64;
if self.pos > self.file_size {
self.file_size = self.pos;
}
self.dirty = true;
Ok(buf.len())
}
fn set_len_inner(&mut self, new_len: u64) -> Result<()> {
let cb = self.cb();
let needed_clusters = new_len.div_ceil(cb) as u32;
if new_len > self.file_size {
self.ensure_chain_clusters(needed_clusters)?;
let old_len = self.file_size;
self.file_size = new_len;
self.zero_range(old_len, new_len)
.map_err(crate::Error::Io)?;
} else if new_len < self.file_size {
self.truncate_chain(needed_clusters)?;
self.file_size = new_len;
if self.pos > self.file_size {
self.pos = self.file_size;
}
}
self.dirty = true;
Ok(())
}
}
impl<'a> Read for FatFileHandle<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.read_inner(buf)
}
}
impl<'a> Write for FatFileHandle<'a> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.write_inner(buf)
}
fn flush(&mut self) -> io::Result<()> {
if !self.dirty {
return Ok(());
}
self.flush_dir_entry()
.map_err(|e| io::Error::other(e.to_string()))?;
self.fs
.flush(self.dev)
.map_err(|e| io::Error::other(e.to_string()))?;
self.dirty = false;
Ok(())
}
}
impl<'a> Seek for FatFileHandle<'a> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
let new_pos: i128 = match pos {
SeekFrom::Start(n) => n as i128,
SeekFrom::End(d) => self.file_size as i128 + d as i128,
SeekFrom::Current(d) => self.pos as i128 + d as i128,
};
if new_pos < 0 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"fat32: seek to negative offset",
));
}
if new_pos > u32::MAX as i128 {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"fat32: seek past 4 GiB file-size limit",
));
}
self.pos = new_pos as u64;
Ok(self.pos)
}
}
impl<'a> FileHandle for FatFileHandle<'a> {
fn len(&self) -> u64 {
self.file_size
}
fn set_len(&mut self, new_len: u64) -> Result<()> {
if new_len > u32::MAX as u64 {
return Err(crate::Error::InvalidArgument(
"fat32: files cannot exceed 4 GiB".into(),
));
}
self.set_len_inner(new_len)
}
fn sync(&mut self) -> Result<()> {
if !self.dirty {
return Ok(());
}
self.flush_dir_entry()?;
self.fs.flush(self.dev)?;
self.dirty = false;
Ok(())
}
}
impl<'a> Drop for FatFileHandle<'a> {
fn drop(&mut self) {
if self.dirty {
let _ = self.flush_dir_entry();
let _ = self.fs.flush(self.dev);
self.dirty = false;
}
}
}
pub struct ReadOnlyFatHandle<'a> {
inner: FatFileHandle<'a>,
}
impl<'a> ReadOnlyFatHandle<'a> {
pub(super) fn new(inner: FatFileHandle<'a>) -> Self {
Self { inner }
}
}
impl<'a> Read for ReadOnlyFatHandle<'a> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl<'a> Seek for ReadOnlyFatHandle<'a> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.inner.seek(pos)
}
}
impl<'a> FileReadHandle for ReadOnlyFatHandle<'a> {
fn len(&self) -> u64 {
FileHandle::len(&self.inner)
}
}