use aligned::Aligned;
use alloc::{string::String, vec::Vec};
use super::{
BlockDevice,
allocation::{AllocationBitmap, Allocator, StoredChain},
bisync,
boot_sector::BootSector,
directory::{DirectoryIterator, ExactNameFilter, directory_list, get_leaf_file_entry},
directory_entry::{
AllocationBitmapDirEntry, DirectoryEntryChain, EntryType, FileAttributes, FileDirEntry,
FileNameDirEntry, GeneralSecondaryFlags, Location, RAW_ENTRY_LEN, RawDirEntry,
StreamExtensionDirEntry, UpcaseTableDirEntry, VolumeLabelDirEntry, is_end_of_directory,
update_checksum,
},
error::ExFatError,
fat::Fat,
file::{
File, FileDetails, FileDirty, NO_CLUSTER_ID, OpenOptions, Touched, TouchedKind,
TouchedSector,
},
slot_cache::SlotCache,
upcase_table::UpcaseTable,
utils::{calc_dir_entry_set_len, encode_utf16_and_hash, split_path},
};
pub type ExFatResult<T, D, const SIZE: usize> =
core::result::Result<T, ExFatError<<D as BlockDevice<SIZE>>::Error>>;
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug, Clone)]
pub(crate) struct FileSystemDetails {
pub cluster_heap_offset: u32,
pub fat_offset: u32,
pub sectors_per_cluster: u8,
pub cluster_length: u32,
pub first_cluster_of_root_dir: u32,
}
impl FileSystemDetails {
pub(crate) const fn empty() -> Self {
Self {
cluster_heap_offset: 0,
fat_offset: 0,
sectors_per_cluster: 64,
cluster_length: 32768,
first_cluster_of_root_dir: 0,
}
}
pub(crate) fn new(boot_sector: &BootSector) -> Self {
let cluster_length =
boot_sector.bytes_per_sector as u32 * boot_sector.sectors_per_cluster as u32;
Self {
cluster_heap_offset: boot_sector.cluster_heap_offset,
sectors_per_cluster: boot_sector.sectors_per_cluster,
cluster_length,
fat_offset: boot_sector.fat_offset,
first_cluster_of_root_dir: boot_sector.first_cluster_of_root_dir,
}
}
pub(crate) fn get_heap_sector_id<D, const SIZE: usize>(
&self,
cluster_id: u32,
) -> ExFatResult<u32, D, SIZE>
where
D: BlockDevice<SIZE>,
{
if cluster_id < 2 {
return Err(ExFatError::InvalidClusterId(cluster_id));
}
let sector_id =
self.cluster_heap_offset + (cluster_id - 2) * self.sectors_per_cluster as u32;
Ok(sector_id)
}
}
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Debug)]
pub struct FileSystem<D, const SIZE: usize, const N: usize>
where
D: BlockDevice<SIZE>,
{
pub(crate) dev: D,
pub(crate) is_mounted: bool,
pub(crate) fs: FileSystemDetails,
pub(crate) upcase_table: UpcaseTable,
pub(crate) allocator: Allocator<D, SIZE, N>,
pub(crate) fat: Fat<D, SIZE, N>,
pub(crate) data_blocks: SlotCache<D, SIZE, N>,
}
pub(crate) struct FileSystemMetadata<const SIZE: usize> {
details: FileSystemDetails,
upcase_table: UpcaseTable,
alloc_bitmap: AllocationBitmap<SIZE>,
}
impl<D, const SIZE: usize, const N: usize> FileSystem<D, SIZE, N>
where
D: BlockDevice<SIZE>,
{
pub fn new(io: D) -> Self {
let fs = FileSystemDetails::empty();
let upcase_table = UpcaseTable::empty();
let fat = Fat::new();
let allocator = Allocator::new();
let data_blocks: SlotCache<D, SIZE, N> = SlotCache::new();
Self {
dev: io,
fs,
upcase_table,
is_mounted: false,
fat,
allocator,
data_blocks,
}
}
}
impl<D, const SIZE: usize, const N: usize> FileSystem<D, SIZE, N>
where
D: BlockDevice<SIZE>,
{
#[bisync]
pub async fn mount(&mut self) -> ExFatResult<(), D, SIZE> {
if self.is_mounted {
return Ok(());
}
let FileSystemMetadata {
details,
upcase_table,
alloc_bitmap,
} = read_file_system_metadata(&mut self.dev).await?;
self.fat.start_of_fat_sector = Some(details.fat_offset);
self.fs = details;
self.allocator.bitmap.first_sector = self
.fs
.get_heap_sector_id::<D, SIZE>(alloc_bitmap.first_cluster)?;
self.allocator.bitmap.num_sectors = alloc_bitmap.num_sectors;
self.upcase_table = upcase_table;
self.is_mounted = true;
Ok(())
}
#[bisync]
pub async fn open(&mut self, path: &str, options: OpenOptions) -> ExFatResult<File, D, SIZE> {
self.mount().await?;
let file_details = self
.find_file_or_directory(path, Some(FileAttributes::Archive))
.await;
let file_details = match file_details {
Ok(mut file_details) => {
if options.create_new {
return Err(ExFatError::AlreadyExists);
}
if options.truncate {
self.truncate_file(&mut file_details, 0).await?;
}
file_details
}
Err(ExFatError::FileNotFound) => {
if options.create || options.create_new {
self.create_file(path).await?
} else {
return Err(ExFatError::FileNotFound);
}
}
Err(e) => return Err(e),
};
let chain = self.get_stored_chain(&file_details).await?;
let cluster_id = if options.append {
match &chain {
StoredChain::Empty => file_details.first_cluster,
StoredChain::Contiguous {
first,
cluster_count,
} => first + cluster_count - 1,
StoredChain::Fat {
first: _first,
last,
cluster_count: _cluster_count,
} => *last,
}
} else {
file_details.first_cluster
};
Ok(File::new(
&file_details,
cluster_id,
self.fs.cluster_length,
&options,
chain,
))
}
#[bisync]
pub async fn exists(&mut self, path: &str) -> ExFatResult<bool, D, SIZE> {
self.mount().await?;
match self.find_file_or_directory(path, None).await {
Ok(_file) => Ok(true),
Err(ExFatError::FileNotFound) => Ok(false),
Err(ExFatError::DirectoryNotFound) => Ok(false),
Err(e) => Err(e),
}
}
#[bisync]
pub async fn read(&mut self, path: &str) -> ExFatResult<Vec<u8>, D, SIZE> {
self.mount().await?;
let options = OpenOptions::new().read(true);
let mut file = self.open(path, options).await?;
let mut buf = Vec::new();
file.read_to_end(self, &mut buf).await?;
Ok(buf)
}
#[bisync]
pub async fn read_to_string(&mut self, path: &str) -> ExFatResult<String, D, SIZE> {
self.mount().await?;
let options = OpenOptions::new().read(true);
let mut file = self.open(path, options).await?;
file.read_to_string(self).await
}
#[bisync]
pub async fn read_dir(&mut self, path: &str) -> ExFatResult<DirectoryIterator<SIZE>, D, SIZE> {
self.mount().await?;
directory_list(self, path).await
}
#[bisync]
pub async fn remove_file(&mut self, path: &str) -> ExFatResult<(), D, SIZE> {
self.mount().await?;
let file_details = self
.find_file_inner(path, Some(FileAttributes::Archive))
.await?;
self.delete_inner(&file_details).await?;
Ok(())
}
#[bisync]
pub async fn create_directory(&mut self, path: &str) -> ExFatResult<(), D, SIZE> {
self.mount().await?;
let mut touched = FileDirty::new();
let _cluster_id = self.get_or_create_directory(&mut touched, path).await?;
touched.flush(self).await?;
Ok(())
}
#[bisync]
pub async fn copy(&mut self, from_path: &str, to_path: &str) -> ExFatResult<(), D, SIZE> {
self.mount().await?;
if from_path == to_path {
return Err(ExFatError::InvalidFileName {
reason: "cannot copy file to the same exact location",
});
}
let options = OpenOptions::new().read(true);
let mut file = self.open(from_path, options).await?;
file.copy_to(self, to_path).await?;
Ok(())
}
#[bisync]
pub async fn rename(&mut self, from_path: &str, to_path: &str) -> ExFatResult<(), D, SIZE> {
self.mount().await?;
let file_details = self.find_file_inner(from_path, None).await?;
let mut freed_dir_entries = Vec::with_capacity(1 + file_details.secondary_count as usize);
for _ in 0..freed_dir_entries.capacity() {
let mut dir_entry = [0u8; RAW_ENTRY_LEN];
Self::mark_dir_entry_free(&mut dir_entry);
freed_dir_entries.push(dir_entry);
}
let (dir_path, file_or_dir_name) = split_path(to_path);
let mut touched = FileDirty::new();
self.write_dir_entries_to_disk(file_details.location, freed_dir_entries, &mut touched)
.await?;
let directory_cluster_id = self.get_or_create_directory(&mut touched, dir_path).await?;
self.create_file_dir_entry_at(
file_or_dir_name,
directory_cluster_id,
file_details.first_cluster,
file_details.attributes,
file_details.flags,
file_details.valid_data_length,
file_details.data_length,
)
.await?;
touched.flush(self).await?;
Ok(())
}
#[bisync]
pub async fn remove_dir(&mut self, path: &str) -> ExFatResult<(), D, SIZE> {
self.mount().await?;
let file_details = self
.find_file_inner(path, Some(FileAttributes::Directory))
.await?;
self.delete_inner(&file_details).await?;
Ok(())
}
#[bisync]
pub async fn write(
&mut self,
path: &str,
contents: impl AsRef<[u8]>,
) -> ExFatResult<(), D, SIZE> {
self.mount().await?;
let options = OpenOptions::new().create(true).truncate(true).write(true);
let mut file = self.open(path, options).await?;
file.write(self, contents.as_ref()).await?;
file.close(self).await?;
Ok(())
}
#[bisync]
pub(crate) async fn get_stored_chain(
&mut self,
file_details: &FileDetails,
) -> ExFatResult<StoredChain, D, SIZE> {
let no_fat_chain = file_details
.flags
.contains(GeneralSecondaryFlags::NoFatChain);
let cluster_count = file_details
.data_length
.div_ceil(self.fs.cluster_length as u64) as u32;
let is_empty = file_details.first_cluster == NO_CLUSTER_ID;
let chain = if is_empty {
StoredChain::Empty
} else if no_fat_chain {
StoredChain::Contiguous {
first: file_details.first_cluster,
cluster_count,
}
} else {
let last = self
.get_cluster_id_at(file_details.data_length, &file_details)
.await?;
StoredChain::Fat {
first: file_details.first_cluster,
last,
cluster_count,
}
};
Ok(chain)
}
#[bisync]
pub(crate) async fn find_file_or_directory(
&mut self,
path: &str,
file_attributes: Option<FileAttributes>,
) -> ExFatResult<FileDetails, D, SIZE> {
let file_details = self.find_file_inner(path, file_attributes).await?;
Ok(file_details)
}
#[bisync]
pub(crate) async fn find_file_inner(
&mut self,
path: &str,
file_attributes: Option<FileAttributes>,
) -> ExFatResult<FileDetails, D, SIZE> {
match get_leaf_file_entry(self, path, file_attributes).await? {
Some(file_details) => Ok(file_details),
None => Err(ExFatError::FileNotFound),
}
}
#[bisync]
pub(crate) async fn truncate_file(
&mut self,
file_details: &mut FileDetails,
length: u64,
) -> ExFatResult<(), D, SIZE> {
if length > 0 {
unimplemented!("length greater than 0 not yet supported")
}
let mut chain = DirectoryEntryChain::new_from_location(&file_details.location, &self.fs);
let mut counter = 0;
let mut dir_entries = Vec::with_capacity(file_details.secondary_count as usize + 1);
while let Some((dir_entry, _location)) = chain.next(self).await? {
let mut entry = [0u8; RAW_ENTRY_LEN];
entry.copy_from_slice(dir_entry);
dir_entries.push(entry);
counter += 1;
if counter == file_details.secondary_count + 1 {
break;
}
}
file_details.data_length = length;
file_details.valid_data_length = file_details.valid_data_length.min(length);
let mut stream_ext: StreamExtensionDirEntry = (&dir_entries[1]).into();
stream_ext.data_length = file_details.data_length;
stream_ext.valid_data_length = file_details.valid_data_length;
dir_entries[1].copy_from_slice(&stream_ext.serialize());
let mut touched = FileDirty::new();
let chain = self.get_stored_chain(file_details).await?;
self.allocator
.free(&mut self.dev, &mut touched, &mut self.fat, &chain)
.await?;
update_checksum(&mut dir_entries);
self.write_dir_entries_to_disk(file_details.location, dir_entries, &mut touched)
.await?;
touched.flush(self).await?;
Ok(())
}
#[bisync]
pub(crate) async fn create_file(&mut self, path: &str) -> ExFatResult<FileDetails, D, SIZE> {
let (dir_path, file_or_dir_name) = split_path(path);
let mut touched = FileDirty::new();
let directory_cluster_id = self.get_or_create_directory(&mut touched, dir_path).await?;
let flags = GeneralSecondaryFlags::AllocationPossible | GeneralSecondaryFlags::NoFatChain;
let attributes = FileAttributes::Archive;
let first_cluster = NO_CLUSTER_ID; let file_details = self
.create_file_dir_entry_at(
file_or_dir_name,
directory_cluster_id,
first_cluster,
attributes,
flags,
0,
0,
)
.await?;
touched.flush(self).await?;
Ok(file_details)
}
#[bisync]
pub(crate) async fn get_or_create_directory(
&mut self,
touched: &mut impl Touched,
path: &str,
) -> ExFatResult<u32, D, SIZE> {
let mut names = path_to_iter(path).peekable();
let mut cluster_id = self.fs.first_cluster_of_root_dir;
while let Some(dir_name) = names.next() {
let is_last = names.peek().is_none();
let filter = ExactNameFilter::new(
dir_name,
&self.upcase_table,
Some(FileAttributes::Directory),
);
let mut entries = DirectoryEntryChain::new(cluster_id, &self.fs);
let file_details = entries.next_file_entry(self, &filter).await?;
match file_details {
Some(file_details) => {
cluster_id = file_details.first_cluster;
}
None => {
let run = self.allocator.find_free_clusters(&mut self.dev, 1).await?;
self.allocator
.mark_allocated(&mut self.dev, touched, &run, true)
.await?;
self.create_file_dir_entry_at(
dir_name,
cluster_id,
run.first_cluster,
FileAttributes::Directory,
GeneralSecondaryFlags::AllocationPossible
| GeneralSecondaryFlags::NoFatChain,
self.fs.cluster_length as u64,
self.fs.cluster_length as u64,
)
.await?;
cluster_id = run.first_cluster;
}
}
if is_last {
return Ok(cluster_id);
}
}
Ok(cluster_id)
}
#[allow(clippy::too_many_arguments)]
#[bisync]
pub(crate) async fn create_file_dir_entry_at(
&mut self,
name: &str,
directory_cluster_id: u32,
first_cluster: u32, file_attributes: FileAttributes,
stream_ext_flags: GeneralSecondaryFlags,
valid_data_length: u64,
data_length: u64,
) -> ExFatResult<FileDetails, D, SIZE> {
let (utf16_name, name_hash) = encode_utf16_and_hash(name, &self.upcase_table);
let dir_entry_set_len = calc_dir_entry_set_len(&utf16_name);
let location = self
.find_empty_dir_entry_set(directory_cluster_id, dir_entry_set_len)
.await?;
let mut dir_entries: Vec<RawDirEntry> = Vec::with_capacity(dir_entry_set_len);
let secondary_count = dir_entry_set_len as u8 - 1;
let file = FileDirEntry {
secondary_count,
set_checksum: 0,
file_attributes,
create_timestamp: 0,
last_modified_timestamp: 0,
last_accessed_timestamp: 0,
create_10ms_increment: 0,
last_modified_10ms_increment: 0,
create_utc_offset: 0,
last_modified_utc_offset: 0,
last_accessed_utc_offset: 0,
};
dir_entries.push(file.serialize());
let stream_ext = StreamExtensionDirEntry {
general_secondary_flags: stream_ext_flags,
name_length: utf16_name.len() as u8,
name_hash,
valid_data_length,
first_cluster,
data_length,
};
dir_entries.push(stream_ext.serialize());
let (chunks, remainder) = utf16_name.as_chunks::<15>();
for chunk in chunks {
let file_name = FileNameDirEntry {
general_secondary_flags: GeneralSecondaryFlags::empty(),
file_name: *chunk,
};
dir_entries.push(file_name.serialize());
}
if !remainder.is_empty() {
let mut file_name = FileNameDirEntry {
general_secondary_flags: GeneralSecondaryFlags::empty(),
file_name: [0u16; 15],
};
file_name.file_name[..remainder.len()].copy_from_slice(remainder);
dir_entries.push(file_name.serialize());
}
update_checksum(&mut dir_entries);
let mut touched = FileDirty::new();
self.write_dir_entries_to_disk(location, dir_entries, &mut touched)
.await?;
touched.flush(self).await?;
let file_details = FileDetails {
attributes: file_attributes,
data_length,
valid_data_length,
first_cluster,
flags: stream_ext_flags,
location,
name: name.into(),
secondary_count,
};
Ok(file_details)
}
pub fn unmount(self) -> D {
self.dev
}
#[inline(always)]
fn mark_dir_entry_free(dir_entry: &mut [u8; RAW_ENTRY_LEN]) {
dir_entry[0] = 0x01;
}
#[bisync]
async fn get_cluster_id_at(
&mut self,
cursor: u64,
file_details: &FileDetails,
) -> ExFatResult<u32, D, SIZE> {
if cursor == 0 {
return Ok(file_details.first_cluster);
}
let num_clusters = (cursor / self.fs.cluster_length as u64) as u32;
if file_details
.flags
.contains(GeneralSecondaryFlags::NoFatChain)
{
let cluster_id = file_details.first_cluster + num_clusters;
Ok(cluster_id)
} else {
let mut cluster_id = file_details.first_cluster;
for _i in 0..num_clusters - 1 {
if let Some(x) = self
.fat
.next_cluster_in_fat_chain(cluster_id, &mut self.dev)
.await?
{
cluster_id = x;
} else {
return Err(ExFatError::EndOfFatChain);
}
}
Ok(cluster_id)
}
}
#[bisync]
async fn confirm_has_no_children(
&mut self,
file_details: &FileDetails,
) -> ExFatResult<(), D, SIZE> {
if file_details.attributes.contains(FileAttributes::Directory) {
let mut reader = DirectoryEntryChain::new(file_details.first_cluster, &self.fs);
while let Some((dir_entry, _location)) = reader.next(self).await? {
let entry_type_val = dir_entry[0];
match EntryType::from(entry_type_val) {
EntryType::UnusedOrEndOfDirectory => {
continue;
}
_ => return Err(ExFatError::DirectoryNotEmpty),
}
}
}
Ok(())
}
#[bisync]
async fn delete_inner(&mut self, file_details: &FileDetails) -> ExFatResult<(), D, SIZE> {
self.confirm_has_no_children(file_details).await?;
let mut touched = FileDirty::new();
let chain = self.get_stored_chain(file_details).await?;
self.allocator
.free(&mut self.dev, &mut touched, &mut self.fat, &chain)
.await?;
let mut sector_id = file_details.location.sector_id;
let dir_entry_offset = file_details.location.dir_entry_offset;
let mut count = None;
let mut from = dir_entry_offset;
loop {
let slot = self.data_blocks.read_mut(sector_id, &mut self.dev).await?;
let (dir_entries, _remainder) = slot.as_mut_slice().as_chunks_mut::<RAW_ENTRY_LEN>();
let count = count.get_or_insert_with(|| {
let file_dir_entry = FileDirEntry::from(&dir_entries[dir_entry_offset]);
file_dir_entry.secondary_count as usize
});
let to = (from + *count).min(dir_entries.len());
for dir_entry in &mut dir_entries[from..to] {
Self::mark_dir_entry_free(dir_entry);
*count -= 1;
}
if *count == 0 {
break;
} else {
sector_id += 1;
from = 0;
}
}
touched.flush(self).await?;
Ok(())
}
#[bisync]
async fn find_empty_dir_entry_set(
&mut self,
cluster_id: u32,
dir_entry_set_len: usize,
) -> ExFatResult<Location, D, SIZE> {
let mut entries = DirectoryEntryChain::new(cluster_id, &self.fs);
let mut counter = 0;
let mut start = None;
while let Some((entry, location)) = entries.next(self).await? {
let entry_type_val = entry[0];
match EntryType::from(entry_type_val) {
EntryType::UnusedOrEndOfDirectory => {
if start.is_none() {
start = Some(location)
}
counter += 1;
if counter == dir_entry_set_len {
break;
}
}
_entry_type => {
counter = 0;
start = None
}
}
}
match start {
Some(location) => Ok(location),
None => unimplemented!("growing a directory not yet supported"),
}
}
#[bisync]
pub(crate) async fn write_dir_entries_to_disk(
&mut self,
location: Location,
dir_entries: Vec<RawDirEntry>,
touched: &mut impl Touched,
) -> ExFatResult<(), D, SIZE> {
let mut sector_id = location.sector_id;
touched.insert(TouchedSector::new(TouchedKind::Dir, sector_id));
let mut offset = location.dir_entry_offset * RAW_ENTRY_LEN;
for (index, dir_entry) in dir_entries.iter().enumerate() {
let slot = self.data_blocks.read_mut(sector_id, &mut self.dev).await?;
slot.as_mut_slice()[offset..offset + RAW_ENTRY_LEN].copy_from_slice(dir_entry);
offset += RAW_ENTRY_LEN;
if offset >= SIZE {
if index == dir_entries.len() - 1 {
break;
}
offset = 0;
sector_id += 1;
touched.insert(TouchedSector::new(TouchedKind::Dir, sector_id));
}
}
Ok(())
}
}
fn path_to_iter(path: &str) -> impl Iterator<Item = &str> {
path.split(['/', '\\'])
.filter(|part| !part.is_empty())
.map(|c| c.trim())
}
#[bisync]
async fn read_boot_sector<D, const SIZE: usize>(
io: &mut D,
sector_id: u32,
) -> ExFatResult<BootSector, D, SIZE>
where
D: BlockDevice<SIZE>,
{
let mut data = [Aligned([0u8; SIZE])];
io.read(sector_id, &mut data)
.await
.map_err(ExFatError::Io)?;
let boot_sector: BootSector = (&data[0]).try_into()?;
Ok(boot_sector)
}
#[bisync]
pub(crate) async fn read_file_system_metadata<D, const SIZE: usize>(
io: &mut D,
) -> ExFatResult<FileSystemMetadata<SIZE>, D, SIZE>
where
D: BlockDevice<SIZE>,
{
let boot_sector = read_boot_sector(io, 0).await?;
let details = FileSystemDetails::new(&boot_sector);
let cluster_id = details.first_cluster_of_root_dir;
let sector_id = details.get_heap_sector_id::<D, SIZE>(cluster_id)?;
let mut data = [Aligned([0u8; SIZE])];
io.read(sector_id, &mut data)
.await
.map_err(ExFatError::Io)?;
let mut allocation_bitmap_dir_entry: Option<AllocationBitmapDirEntry> = None;
let mut volume_label: Option<VolumeLabelDirEntry> = None;
let mut upcase_table_dir_entry: Option<UpcaseTableDirEntry> = None;
let (chunks, _remainder) = data[0].as_chunks::<RAW_ENTRY_LEN>();
for chunk in chunks {
let entry_type_val = chunk[0];
match EntryType::from(entry_type_val) {
EntryType::AllocationBitmap => {
let entry: AllocationBitmapDirEntry = chunk.into();
allocation_bitmap_dir_entry = Some(entry);
}
EntryType::VolumeLabel => {
let entry: VolumeLabelDirEntry = chunk.try_into()?;
volume_label = Some(entry);
}
EntryType::UpcaseTable => {
let entry: UpcaseTableDirEntry = chunk.into();
upcase_table_dir_entry = Some(entry);
}
EntryType::UnusedOrEndOfDirectory => {
if is_end_of_directory(chunk) {
break;
}
}
_entry_type => {} }
if allocation_bitmap_dir_entry.is_some()
&& upcase_table_dir_entry.is_some()
&& volume_label.is_some()
{
break;
}
}
let upcase_table_dir_entry = match upcase_table_dir_entry {
Some(entry) => entry,
None => {
return Err(ExFatError::InvalidFileSystem {
reason: "no upcase table found in root dir",
});
}
};
if volume_label.is_none() {
return Err(ExFatError::InvalidFileSystem {
reason: "no volume label found in root dir",
});
}
let allocation_bitmap_dir_entry = match allocation_bitmap_dir_entry {
Some(entry) => entry,
None => {
return Err(ExFatError::InvalidFileSystem {
reason: "no allocation bitmap found in root dir",
});
}
};
let mut upcase_table = UpcaseTable::default();
upcase_table
.load(&upcase_table_dir_entry, &details, io)
.await?;
let alloc_bitmap = AllocationBitmap::new(&allocation_bitmap_dir_entry);
Ok(FileSystemMetadata {
details,
upcase_table,
alloc_bitmap,
})
}