use alloc::string::ToString;
use alloc::vec::Vec;
use hadris_common::types::endian::Endian;
use spin::Mutex;
use crate::error::{FatError, Result};
#[cfg(feature = "write")]
use crate::io::Write;
use crate::io::{Read, ReadExt, SectorCursor, Seek, SeekFrom};
use super::bitmap::AllocationBitmap;
use super::boot::{ExFatBootSector, ExFatInfo};
use super::dir::ExFatDir;
#[cfg(feature = "write")]
use super::entry::FileAttributes;
use super::entry::{ExFatFileEntry, RawDirectoryEntry, entry_type};
#[cfg(feature = "write")]
use super::entry_writer::EntrySetBuilder;
use super::fat::ExFatTable;
use super::file::ExFatFileReader;
#[cfg(feature = "write")]
use super::file::ExFatFileWriter;
use super::upcase::UpcaseTable;
pub struct ExFatFs<DATA: Seek> {
data: Mutex<SectorCursor<DATA>>,
info: ExFatInfo,
bitmap: Mutex<AllocationBitmap>,
fat: ExFatTable,
upcase: UpcaseTable,
root_cluster: u32,
root_contiguous: bool,
root_size: u64,
}
impl<DATA> ExFatFs<DATA>
where
DATA: Read + Seek,
{
pub fn open(mut data: DATA) -> Result<Self> {
let boot = ExFatBootSector::read(&mut data)?;
let info = boot.info().clone();
let cursor = SectorCursor::new(data, info.bytes_per_sector, info.bytes_per_cluster);
let data = Mutex::new(cursor);
let fat = ExFatTable::new(&info);
let mut bitmap = AllocationBitmap::new(0, 0, info.cluster_count, true);
let mut upcase = UpcaseTable::new();
let root_cluster = info.root_cluster;
let root_contiguous = true; let root_size = 0u64;
{
let mut guard = data.lock();
let mut offset = info.cluster_to_offset(root_cluster);
let mut entries_read = 0;
const MAX_SYSTEM_ENTRIES: usize = 100;
while entries_read < MAX_SYSTEM_ENTRIES {
guard.seek(SeekFrom::Start(offset))?;
let entry: RawDirectoryEntry = guard.data.read_struct()?;
let entry_type_byte = unsafe { entry.entry_type };
if entry_type_byte == entry_type::END_OF_DIRECTORY {
break;
}
match entry_type_byte {
entry_type::ALLOCATION_BITMAP => {
let bitmap_entry = unsafe { &entry.bitmap };
bitmap = AllocationBitmap::new(
bitmap_entry.first_cluster.get(),
bitmap_entry.data_length.get(),
info.cluster_count,
true, );
}
entry_type::UPCASE_TABLE => {
let upcase_entry = unsafe { &entry.upcase };
let first_cluster = upcase_entry.first_cluster.get();
let size = upcase_entry.data_length.get();
drop(guard);
let mut guard2 = data.lock();
upcase.load(
&mut guard2.data,
&info,
first_cluster,
size,
true, )?;
guard = guard2;
}
_ => {}
}
offset += size_of::<RawDirectoryEntry>() as u64;
entries_read += 1;
}
if bitmap.first_cluster() != 0 {
bitmap.load(&mut guard.data, &info)?;
}
}
if !upcase.is_valid() {
upcase = UpcaseTable::create_default();
}
Ok(Self {
data,
info,
bitmap: Mutex::new(bitmap),
fat,
upcase,
root_cluster,
root_contiguous,
root_size,
})
}
pub fn info(&self) -> &ExFatInfo {
&self.info
}
pub fn root_dir(&self) -> ExFatDir<'_, DATA> {
ExFatDir {
fs: self,
first_cluster: self.root_cluster,
is_contiguous: self.root_contiguous,
size: self.root_size,
}
}
pub fn open_file(&self, path: &str) -> Result<ExFatFileReader<'_, DATA>> {
let entry = self.open_path(path)?;
ExFatFileReader::new(self, &entry)
}
pub fn open_dir(&self, path: &str) -> Result<ExFatDir<'_, DATA>> {
let entry = self.open_path(path)?;
if !entry.is_directory() {
return Err(FatError::NotADirectory);
}
Ok(ExFatDir {
fs: self,
first_cluster: entry.first_cluster,
is_contiguous: entry.no_fat_chain,
size: entry.data_length,
})
}
pub fn open_path(&self, path: &str) -> Result<ExFatFileEntry> {
let path = path.trim_start_matches('/');
if path.is_empty() {
return Err(FatError::InvalidPath);
}
let components: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect();
if components.is_empty() {
return Err(FatError::InvalidPath);
}
let mut current_dir = self.root_dir();
for (i, component) in components.iter().enumerate() {
let entry = current_dir
.find(component)?
.ok_or(FatError::EntryNotFound)?;
if i < components.len() - 1 {
if !entry.is_directory() {
return Err(FatError::NotADirectory);
}
current_dir = ExFatDir {
fs: self,
first_cluster: entry.first_cluster,
is_contiguous: entry.no_fat_chain,
size: entry.data_length,
};
} else {
return Ok(entry);
}
}
Err(FatError::EntryNotFound)
}
pub(crate) fn next_cluster(&self, cluster: u32) -> Result<Option<u32>> {
let mut guard = self.data.lock();
self.fat.next_cluster(&mut guard.data, cluster)
}
pub(crate) fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()> {
let mut guard = self.data.lock();
guard.seek(SeekFrom::Start(offset))?;
guard.read_exact(buf)?;
Ok(())
}
pub(crate) fn read_entry_at(&self, offset: u64) -> Result<RawDirectoryEntry> {
let mut guard = self.data.lock();
guard.seek(SeekFrom::Start(offset))?;
let entry: RawDirectoryEntry = guard.data.read_struct()?;
Ok(entry)
}
pub(crate) fn names_equal(&self, name1: &str, name2: &str) -> Result<bool> {
Ok(self.upcase.names_equal(name1, name2))
}
pub fn name_hash(&self, name: &str) -> u16 {
self.upcase.name_hash(name)
}
pub fn free_cluster_count(&self) -> u32 {
self.bitmap.lock().free_cluster_count()
}
pub fn is_cluster_allocated(&self, cluster: u32) -> Result<bool> {
self.bitmap.lock().is_allocated(cluster)
}
pub(crate) fn upcase(&self) -> &UpcaseTable {
&self.upcase
}
pub fn volume_serial(&self) -> u32 {
self.info.volume_serial
}
}
#[cfg(feature = "write")]
impl<DATA> ExFatFs<DATA>
where
DATA: Read + Write + Seek,
{
pub(crate) fn write_at(&self, offset: u64, buf: &[u8]) -> Result<()> {
let mut guard = self.data.lock();
guard.seek(SeekFrom::Start(offset))?;
guard.write_all(buf)?;
Ok(())
}
pub(crate) fn flush(&self) -> crate::io::IoResult<()> {
let mut guard = self.data.lock();
guard.flush()
}
pub fn allocate_cluster(&self, hint: u32) -> Result<u32> {
let mut bitmap = self.bitmap.lock();
let cluster = bitmap
.find_free_cluster(hint)?
.ok_or(FatError::NoFreeSpace)?;
bitmap.set_allocated(cluster, true)?;
let mut data = self.data.lock();
self.fat
.write_entry(&mut data.data, cluster, ExFatTable::END_OF_CHAIN)?;
Ok(cluster)
}
pub fn allocate_clusters(&self, count: u32, hint: u32) -> Result<(u32, bool)> {
if count == 0 {
return Ok((0, true));
}
let mut bitmap = self.bitmap.lock();
if let Some(first) = bitmap.find_contiguous_free(count, hint)? {
for i in 0..count {
bitmap.set_allocated(first + i, true)?;
}
return Ok((first, true));
}
drop(bitmap);
let mut data = self.data.lock();
let first = self.fat.allocate_chain(&mut data.data, count, hint)?;
drop(data);
let mut bitmap = self.bitmap.lock();
let mut current = first;
let mut data = self.data.lock();
for _ in 0..count {
bitmap.set_allocated(current, true)?;
if let Some(next) = self.fat.next_cluster(&mut data.data, current)? {
current = next;
} else {
break;
}
}
Ok((first, false))
}
pub fn free_clusters(&self, first: u32, count: u32, is_contiguous: bool) -> Result<()> {
if first < 2 {
return Ok(());
}
let mut bitmap = self.bitmap.lock();
if is_contiguous {
for i in 0..count {
bitmap.set_allocated(first + i, false)?;
}
} else {
let mut current = first;
let mut data = self.data.lock();
loop {
bitmap.set_allocated(current, false)?;
let next = self.fat.read_entry(&mut data.data, current)?;
self.fat
.write_entry(&mut data.data, current, ExFatTable::FREE_CLUSTER)?;
if next == ExFatTable::END_OF_CHAIN || next >= ExFatTable::MEDIA_DESCRIPTOR {
break;
}
current = next;
}
}
Ok(())
}
pub fn sync_bitmap(&self) -> Result<()> {
let bitmap = self.bitmap.lock();
let mut data = self.data.lock();
bitmap.flush(&mut data.data, &self.info)
}
fn find_free_entry_slots(
&self,
dir: &ExFatDir<'_, DATA>,
slots_needed: usize,
) -> Result<(u32, u64)> {
let cluster_size = self.info.bytes_per_cluster;
let mut current_cluster = dir.first_cluster;
let mut consecutive_free = 0;
let mut first_free_cluster = current_cluster;
let mut first_free_offset = 0u64;
loop {
let cluster_offset = self.info.cluster_to_offset(current_cluster);
for entry_idx in 0..(cluster_size / 32) {
let offset = cluster_offset + (entry_idx as u64 * 32);
let entry = self.read_entry_at(offset)?;
let entry_type_byte = unsafe { entry.entry_type };
if entry_type_byte == entry_type::END_OF_DIRECTORY
|| entry_type_byte == entry_type::DELETED_FILE
|| entry_type_byte == 0x00
{
if consecutive_free == 0 {
first_free_cluster = current_cluster;
first_free_offset = entry_idx as u64 * 32;
}
consecutive_free += 1;
if consecutive_free >= slots_needed {
return Ok((first_free_cluster, first_free_offset));
}
} else {
consecutive_free = 0;
}
}
if dir.is_contiguous {
current_cluster += 1;
if (current_cluster - dir.first_cluster) as u64 * cluster_size as u64 >= dir.size
&& dir.size > 0
{
break;
}
} else {
match self.next_cluster(current_cluster)? {
Some(next) => current_cluster = next,
None => break,
}
}
}
Err(FatError::DirectoryFull)
}
fn write_entry_set(
&self,
cluster: u32,
offset_in_cluster: u64,
entries: &[RawDirectoryEntry],
) -> Result<()> {
let cluster_offset = self.info.cluster_to_offset(cluster);
let base_offset = cluster_offset + offset_in_cluster;
for (i, entry) in entries.iter().enumerate() {
let entry_offset = base_offset + (i as u64 * 32);
self.write_at(entry_offset, unsafe { &entry.bytes })?;
}
Ok(())
}
pub fn create_file(&self, parent: &ExFatDir<'_, DATA>, name: &str) -> Result<ExFatFileEntry> {
if parent.find(name)?.is_some() {
return Err(FatError::AlreadyExists);
}
let builder = EntrySetBuilder::file(name)?;
let entries = builder.build(&self.upcase);
let entry_count = entries.len();
let (slot_cluster, slot_offset) = self.find_free_entry_slots(parent, entry_count)?;
self.write_entry_set(slot_cluster, slot_offset, &entries)?;
let now = super::time::ExFatTimestamp::now();
Ok(ExFatFileEntry {
name: name.to_string(),
attributes: FileAttributes::ARCHIVE,
first_cluster: 0,
data_length: 0,
valid_data_length: 0,
no_fat_chain: true,
name_hash: self.upcase.name_hash(name),
created: now.clone(),
modified: now.clone(),
accessed: now,
parent_cluster: slot_cluster,
entry_offset: self.info.cluster_to_offset(slot_cluster) + slot_offset,
})
}
pub fn create_dir(
&self,
parent: &ExFatDir<'_, DATA>,
name: &str,
) -> Result<ExFatDir<'_, DATA>> {
if parent.find(name)?.is_some() {
return Err(FatError::AlreadyExists);
}
let dir_cluster = self.allocate_cluster(2)?;
let cluster_offset = self.info.cluster_to_offset(dir_cluster);
let zeros = alloc::vec![0u8; self.info.bytes_per_cluster];
self.write_at(cluster_offset, &zeros)?;
let builder = EntrySetBuilder::directory(name)?
.with_cluster(dir_cluster)
.with_size(0, self.info.bytes_per_cluster as u64)
.with_contiguous(true);
let entries = builder.build(&self.upcase);
let entry_count = entries.len();
let (slot_cluster, slot_offset) = self.find_free_entry_slots(parent, entry_count)?;
self.write_entry_set(slot_cluster, slot_offset, &entries)?;
Ok(ExFatDir {
fs: self,
first_cluster: dir_cluster,
is_contiguous: true,
size: self.info.bytes_per_cluster as u64,
})
}
pub fn delete(&self, entry: &ExFatFileEntry) -> Result<()> {
if entry.is_directory() {
let dir = ExFatDir {
fs: self,
first_cluster: entry.first_cluster,
is_contiguous: entry.no_fat_chain,
size: entry.data_length,
};
for item in dir.entries() {
let _ = item?;
return Err(FatError::DirectoryNotEmpty);
}
}
if entry.first_cluster >= 2 {
let cluster_count = if entry.no_fat_chain {
let cluster_size = self.info.bytes_per_cluster as u64;
((entry.data_length + cluster_size - 1) / cluster_size) as u32
} else {
0 };
self.free_clusters(entry.first_cluster, cluster_count, entry.no_fat_chain)?;
}
let deleted_marker = [entry_type::DELETED_FILE];
self.write_at(entry.entry_offset, &deleted_marker)?;
Ok(())
}
pub fn write_file(&self, entry: &ExFatFileEntry) -> Result<ExFatFileWriter<'_, DATA>> {
ExFatFileWriter::new(self, entry.clone())
}
pub(crate) fn update_entry_size(
&self,
entry: &ExFatFileEntry,
new_valid_data_length: u64,
new_data_length: u64,
new_first_cluster: u32,
) -> Result<()> {
use super::entry::{RawStreamExtensionEntry, compute_entry_set_checksum};
use hadris_common::types::endian::LittleEndian;
use hadris_common::types::number::{U16, U32, U64};
let mut guard = self.data.lock();
guard.seek(SeekFrom::Start(entry.entry_offset))?;
let primary: RawDirectoryEntry = guard.data.read_struct()?;
let secondary_count = unsafe { primary.file.secondary_count } as usize;
let entry_count = 1 + secondary_count;
let mut entries = Vec::with_capacity(entry_count);
entries.push(primary);
for i in 1..entry_count {
let offset = entry.entry_offset + (i as u64 * 32);
guard.seek(SeekFrom::Start(offset))?;
let e: RawDirectoryEntry = guard.data.read_struct()?;
entries.push(e);
}
if entry_count >= 2 {
let stream = unsafe { &mut entries[1].stream };
stream.valid_data_length = U64::<LittleEndian>::new(new_valid_data_length);
stream.data_length = U64::<LittleEndian>::new(new_data_length);
stream.first_cluster = U32::<LittleEndian>::new(new_first_cluster);
}
let checksum = compute_entry_set_checksum(&entries);
unsafe {
entries[0].file.set_checksum = U16::<LittleEndian>::new(checksum);
}
for (i, e) in entries.iter().enumerate() {
let offset = entry.entry_offset + (i as u64 * 32);
guard.seek(SeekFrom::Start(offset))?;
guard.write_all(unsafe { &e.bytes })?;
}
Ok(())
}
pub fn truncate(&self, entry: &ExFatFileEntry, new_size: u64) -> Result<()> {
if entry.is_directory() {
return Err(FatError::NotAFile);
}
if new_size >= entry.valid_data_length {
return Ok(()); }
let cluster_size = self.info.bytes_per_cluster as u64;
if new_size == 0 {
if entry.first_cluster >= 2 {
let cluster_count = if entry.no_fat_chain {
((entry.data_length + cluster_size - 1) / cluster_size) as u32
} else {
0
};
self.free_clusters(entry.first_cluster, cluster_count, entry.no_fat_chain)?;
}
self.update_entry_size(entry, 0, 0, 0)?;
} else {
let clusters_to_keep = (new_size + cluster_size - 1) / cluster_size;
let new_data_length = clusters_to_keep * cluster_size;
if entry.no_fat_chain {
let total_clusters = (entry.data_length + cluster_size - 1) / cluster_size;
let clusters_to_free = total_clusters - clusters_to_keep;
if clusters_to_free > 0 {
let first_to_free = entry.first_cluster + clusters_to_keep as u32;
self.free_clusters(first_to_free, clusters_to_free as u32, true)?;
}
} else {
let mut current = entry.first_cluster;
for _ in 1..clusters_to_keep {
let mut data = self.data.lock();
if let Some(next) = self.fat.next_cluster(&mut data.data, current)? {
current = next;
} else {
break;
}
}
let mut data = self.data.lock();
self.fat.truncate_chain(&mut data.data, current)?;
}
self.update_entry_size(entry, new_size, new_data_length, entry.first_cluster)?;
}
Ok(())
}
}
impl<DATA: Seek> core::fmt::Debug for ExFatFs<DATA> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ExFatFs")
.field("info", &self.info)
.field("root_cluster", &self.root_cluster)
.finish_non_exhaustive()
}
}