use alloc::vec::Vec;
use super::fat_table::{Fat, FatType};
use super::io::{Read, Seek, SeekFrom, Write};
use crate::error::{Error, Result};
pub const DEFAULT_CACHE_CAPACITY: usize = 16;
#[derive(Debug)]
struct CacheEntry {
sector: usize,
data: Vec<u8>,
dirty: bool,
access_count: u64,
}
#[derive(Debug)]
pub struct FatSectorCache {
entries: Vec<CacheEntry>,
capacity: usize,
sector_size: usize,
fat_start: usize,
fat_size: usize,
fat_count: usize,
access_counter: u64,
stats: CacheStats,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CacheStats {
pub hits: u64,
pub misses: u64,
pub evictions: u64,
pub dirty_writes: u64,
}
impl CacheStats {
pub fn hit_ratio(&self) -> f64 {
let total = self.hits + self.misses;
if total == 0 {
0.0
} else {
self.hits as f64 / total as f64
}
}
}
impl FatSectorCache {
pub fn new(
fat_start: usize,
fat_size: usize,
fat_count: usize,
sector_size: usize,
capacity: usize,
) -> Self {
Self {
entries: Vec::with_capacity(capacity),
capacity,
sector_size,
fat_start,
fat_size,
fat_count,
access_counter: 0,
stats: CacheStats::default(),
}
}
pub fn with_default_capacity(
fat_start: usize,
fat_size: usize,
fat_count: usize,
sector_size: usize,
) -> Self {
Self::new(
fat_start,
fat_size,
fat_count,
sector_size,
DEFAULT_CACHE_CAPACITY,
)
}
pub fn stats(&self) -> CacheStats {
self.stats
}
pub fn reset_stats(&mut self) {
self.stats = CacheStats::default();
}
pub fn clear<T: Read + Write + Seek>(&mut self, writer: Option<&mut T>) -> Result<()> {
if let Some(w) = writer {
self.flush(w)?;
}
self.entries.clear();
Ok(())
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn flush<T: Write + Seek>(&mut self, writer: &mut T) -> Result<()> {
let fat_start = self.fat_start;
let fat_size = self.fat_size;
let fat_count = self.fat_count;
let sector_size = self.sector_size;
for entry in &mut self.entries {
if entry.dirty {
for i in 0..fat_count {
let offset = fat_start + i * fat_size + entry.sector * sector_size;
writer.seek(SeekFrom::Start(offset as u64))?;
writer.write_all(&entry.data)?;
}
entry.dirty = false;
self.stats.dirty_writes += 1;
}
}
Ok(())
}
fn find_sector(&mut self, sector: usize) -> Option<usize> {
for (i, entry) in self.entries.iter().enumerate() {
if entry.sector == sector {
return Some(i);
}
}
None
}
fn get_sector<T: Read + Seek>(&mut self, reader: &mut T, sector: usize) -> Result<&[u8]> {
self.access_counter += 1;
if let Some(idx) = self.find_sector(sector) {
self.stats.hits += 1;
self.entries[idx].access_count = self.access_counter;
return Ok(&self.entries[idx].data);
}
self.stats.misses += 1;
if self.entries.len() >= self.capacity {
self.evict_lru_clean()?;
}
let mut data = alloc::vec![0u8; self.sector_size];
let offset = self.fat_start + sector * self.sector_size;
reader.seek(SeekFrom::Start(offset as u64))?;
reader.read_exact(&mut data)?;
self.entries.push(CacheEntry {
sector,
data,
dirty: false,
access_count: self.access_counter,
});
Ok(&self.entries.last().unwrap().data)
}
fn get_sector_mut<T: Read + Write + Seek>(
&mut self,
io: &mut T,
sector: usize,
) -> Result<&mut [u8]> {
self.access_counter += 1;
if let Some(idx) = self.find_sector(sector) {
self.stats.hits += 1;
self.entries[idx].access_count = self.access_counter;
self.entries[idx].dirty = true;
return Ok(&mut self.entries[idx].data);
}
self.stats.misses += 1;
if self.entries.len() >= self.capacity {
self.evict_lru_flush(io)?;
}
let mut data = alloc::vec![0u8; self.sector_size];
let offset = self.fat_start + sector * self.sector_size;
io.seek(SeekFrom::Start(offset as u64))?;
io.read_exact(&mut data)?;
self.entries.push(CacheEntry {
sector,
data,
dirty: true,
access_count: self.access_counter,
});
let idx = self.entries.len() - 1;
Ok(&mut self.entries[idx].data)
}
fn find_lru_index(&self) -> Option<usize> {
let mut lru_idx = None;
let mut lru_count = u64::MAX;
for (i, entry) in self.entries.iter().enumerate() {
if entry.access_count < lru_count {
lru_count = entry.access_count;
lru_idx = Some(i);
}
}
lru_idx
}
fn find_lru_clean_index(&self) -> Option<usize> {
let mut lru_idx = None;
let mut lru_count = u64::MAX;
for (i, entry) in self.entries.iter().enumerate() {
if !entry.dirty && entry.access_count < lru_count {
lru_count = entry.access_count;
lru_idx = Some(i);
}
}
lru_idx
}
fn evict_lru_clean(&mut self) -> Result<()> {
if self.entries.is_empty() {
return Ok(());
}
match self.find_lru_clean_index() {
Some(idx) => {
self.entries.swap_remove(idx);
self.stats.evictions += 1;
Ok(())
}
None => {
let lru = self.find_lru_index().expect("non-empty above");
Err(Error::CacheDirtyEviction {
sector: self.entries[lru].sector as u32,
})
}
}
}
fn evict_lru_flush<T: Write + Seek>(&mut self, writer: &mut T) -> Result<()> {
let Some(idx) = self.find_lru_index() else {
return Ok(());
};
if self.entries[idx].dirty {
for copy in 0..self.fat_count {
let offset = self.fat_start
+ copy * self.fat_size
+ self.entries[idx].sector * self.sector_size;
writer.seek(SeekFrom::Start(offset as u64))?;
writer.write_all(&self.entries[idx].data)?;
}
self.entries[idx].dirty = false;
self.stats.dirty_writes += 1;
}
self.entries.swap_remove(idx);
self.stats.evictions += 1;
Ok(())
}
pub fn read_fat12_entry<T: Read + Seek>(
&mut self,
reader: &mut T,
cluster: usize,
) -> Result<u16> {
let sector_size = self.sector_size;
let byte_offset = (cluster * 3) / 2;
let sector = byte_offset / sector_size;
let offset_in_sector = byte_offset % sector_size;
let bytes = if offset_in_sector + 1 < sector_size {
let data = self.get_sector(reader, sector)?;
[data[offset_in_sector], data[offset_in_sector + 1]]
} else {
let first_byte = {
let data = self.get_sector(reader, sector)?;
data[offset_in_sector]
};
let second_byte = {
let next_sector_data = self.get_sector(reader, sector + 1)?;
next_sector_data[0]
};
[first_byte, second_byte]
};
let value = if cluster.is_multiple_of(2) {
u16::from(bytes[0]) | (u16::from(bytes[1] & 0x0F) << 8)
} else {
(u16::from(bytes[0]) >> 4) | (u16::from(bytes[1]) << 4)
};
Ok(value)
}
pub fn write_fat12_entry<T: Read + Write + Seek>(
&mut self,
io: &mut T,
cluster: usize,
value: u16,
) -> Result<()> {
let sector_size = self.sector_size;
let byte_offset = (cluster * 3) / 2;
let sector = byte_offset / sector_size;
let offset_in_sector = byte_offset % sector_size;
if offset_in_sector + 1 < sector_size {
let data = self.get_sector_mut(io, sector)?;
if cluster.is_multiple_of(2) {
data[offset_in_sector] = value as u8;
data[offset_in_sector + 1] =
(data[offset_in_sector + 1] & 0xF0) | ((value >> 8) as u8 & 0x0F);
} else {
data[offset_in_sector] = (data[offset_in_sector] & 0x0F) | ((value << 4) as u8);
data[offset_in_sector + 1] = (value >> 4) as u8;
}
} else {
{
let data = self.get_sector_mut(io, sector)?;
if cluster.is_multiple_of(2) {
data[offset_in_sector] = value as u8;
} else {
data[offset_in_sector] = (data[offset_in_sector] & 0x0F) | ((value << 4) as u8);
}
}
{
let data = self.get_sector_mut(io, sector + 1)?;
if cluster.is_multiple_of(2) {
data[0] = (data[0] & 0xF0) | ((value >> 8) as u8 & 0x0F);
} else {
data[0] = (value >> 4) as u8;
}
}
}
Ok(())
}
pub fn read_fat16_entry<T: Read + Seek>(
&mut self,
reader: &mut T,
cluster: usize,
) -> Result<u16> {
let sector_size = self.sector_size;
let byte_offset = cluster * 2;
let sector = byte_offset / sector_size;
let offset_in_sector = byte_offset % sector_size;
let data = self.get_sector(reader, sector)?;
let value = u16::from_le_bytes([data[offset_in_sector], data[offset_in_sector + 1]]);
Ok(value)
}
pub fn write_fat16_entry<T: Read + Write + Seek>(
&mut self,
io: &mut T,
cluster: usize,
value: u16,
) -> Result<()> {
let sector_size = self.sector_size;
let byte_offset = cluster * 2;
let sector = byte_offset / sector_size;
let offset_in_sector = byte_offset % sector_size;
let data = self.get_sector_mut(io, sector)?;
let bytes = value.to_le_bytes();
data[offset_in_sector] = bytes[0];
data[offset_in_sector + 1] = bytes[1];
Ok(())
}
pub fn read_fat32_entry<T: Read + Seek>(
&mut self,
reader: &mut T,
cluster: usize,
) -> Result<u32> {
let sector_size = self.sector_size;
let byte_offset = cluster * 4;
let sector = byte_offset / sector_size;
let offset_in_sector = byte_offset % sector_size;
let data = self.get_sector(reader, sector)?;
let value = u32::from_le_bytes([
data[offset_in_sector],
data[offset_in_sector + 1],
data[offset_in_sector + 2],
data[offset_in_sector + 3],
]);
Ok(value)
}
pub fn write_fat32_entry<T: Read + Write + Seek>(
&mut self,
io: &mut T,
cluster: usize,
value: u32,
) -> Result<()> {
let sector_size = self.sector_size;
let byte_offset = cluster * 4;
let sector = byte_offset / sector_size;
let offset_in_sector = byte_offset % sector_size;
let data = self.get_sector_mut(io, sector)?;
let existing = u32::from_le_bytes([
data[offset_in_sector],
data[offset_in_sector + 1],
data[offset_in_sector + 2],
data[offset_in_sector + 3],
]);
let bytes = ((existing & 0xF000_0000) | (value & 0x0FFF_FFFF)).to_le_bytes();
data[offset_in_sector] = bytes[0];
data[offset_in_sector + 1] = bytes[1];
data[offset_in_sector + 2] = bytes[2];
data[offset_in_sector + 3] = bytes[3];
Ok(())
}
}
pub struct CachedFat<'a> {
cache: &'a mut FatSectorCache,
fat_type: FatType,
max_cluster: u32,
}
impl<'a> CachedFat<'a> {
pub fn new(cache: &'a mut FatSectorCache, fat: &Fat) -> Self {
let (fat_type, max_cluster) = match fat {
Fat::Fat12(f) => (FatType::Fat12, f.max_cluster() as u32),
Fat::Fat16(f) => (FatType::Fat16, f.max_cluster() as u32),
Fat::Fat32(f) => (FatType::Fat32, f.max_cluster()),
};
Self {
cache,
fat_type,
max_cluster,
}
}
pub fn next_cluster<T: Read + Seek>(
&mut self,
reader: &mut T,
cluster: usize,
) -> Result<Option<u32>> {
match self.fat_type {
FatType::Fat12 => {
let entry = self.cache.read_fat12_entry(reader, cluster)? & 0x0FFF;
if entry >= 0x0FF8 {
Ok(None) } else if entry == 0x0FF7 {
Err(Error::BadCluster {
cluster: cluster as u32,
})
} else if entry < 2 || entry as u32 > self.max_cluster {
Err(Error::ClusterOutOfBounds {
cluster: entry as u32,
max: self.max_cluster,
})
} else {
Ok(Some(entry as u32))
}
}
FatType::Fat16 => {
let entry = self.cache.read_fat16_entry(reader, cluster)?;
if entry >= 0xFFF8 {
Ok(None) } else if entry == 0xFFF7 {
Err(Error::BadCluster {
cluster: cluster as u32,
})
} else if entry < 2 || entry as u32 > self.max_cluster {
Err(Error::ClusterOutOfBounds {
cluster: entry as u32,
max: self.max_cluster,
})
} else {
Ok(Some(entry as u32))
}
}
FatType::Fat32 => {
let entry = self.cache.read_fat32_entry(reader, cluster)? & 0x0FFF_FFFF;
if entry >= 0x0FFF_FFF8 {
Ok(None) } else if entry == 0x0FFF_FFF7 {
Err(Error::BadCluster {
cluster: cluster as u32,
})
} else if entry < 2 || entry > self.max_cluster {
Err(Error::ClusterOutOfBounds {
cluster: entry,
max: self.max_cluster,
})
} else {
Ok(Some(entry))
}
}
}
}
pub fn read_chain<T: Read + Seek>(
&mut self,
reader: &mut T,
start_cluster: u32,
) -> Result<Vec<u32>> {
let mut chain = Vec::new();
let mut current = start_cluster;
let max_iterations = self.max_cluster as usize;
loop {
if current < 2 || current > self.max_cluster {
break;
}
chain.push(current);
if chain.len() > max_iterations {
return Err(Error::ClusterLoop { cluster: current });
}
match self.next_cluster(reader, current as usize)? {
Some(next) => current = next,
None => break,
}
}
Ok(chain)
}
pub fn flush<T: Write + Seek>(&mut self, writer: &mut T) -> Result<()> {
self.cache.flush(writer)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_stats() {
let stats = CacheStats {
hits: 80,
misses: 20,
evictions: 5,
dirty_writes: 3,
};
assert!((stats.hit_ratio() - 0.8).abs() < 0.001);
}
#[test]
fn test_cache_stats_empty() {
let stats = CacheStats::default();
assert_eq!(stats.hit_ratio(), 0.0);
}
}