#![doc = include_str!("../README.md")]
#![cfg_attr(not(any(test, feature = "std")), no_std)]
#[cfg(all(feature = "async", feature = "std", not(any(feature = "smol", feature = "tokio"))))]
compile_error!("Either smol or tokio must be selected");
extern crate alloc;
#[macro_use]
extern crate hex_literal;
extern crate heapless;
#[cfg(feature = "logging")]
#[macro_use]
extern crate log;
#[cfg(not(feature = "logging"))]
#[macro_use]
mod logging {
#[macro_export]
macro_rules! warn {
($($arg:tt)*) => { _ = ($($arg)*) };
}
#[macro_export]
macro_rules! debug {
($($arg:tt)*) => { _ = ($($arg)*) };
}
#[macro_export]
macro_rules! trace {
($($arg:tt)*) => { _ = ($($arg)*) };
}
}
mod cluster_heap;
mod endian;
pub mod error;
mod fat;
pub mod file;
pub(crate) mod fs;
pub mod io;
mod region;
pub(crate) mod sync;
pub mod types;
mod upcase_table;
use core::fmt::Debug;
use core::mem;
use core::ops::Deref;
use memoffset::offset_of;
pub use cluster_heap::directory::{Directory, FileOrDirectory};
pub use cluster_heap::file::SeekFrom;
pub use cluster_heap::root::RootDirectory;
use cluster_heap::root::RootDirectory as Root;
use error::{DataError, Error, ImplementationError};
use io::Wrap;
pub use region::data::entryset::primary::DateTime;
use types::ClusterID;
use crate::io::Block;
use crate::sync::Shared;
use crate::types::SectorID;
pub struct ExFAT<IO> {
io: Shared<IO>,
serial_number: u32,
fat_info: fat::Info,
fs_info: fs::Info,
root: ClusterID,
}
#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
impl<B: Deref<Target = [Block]>, E: Debug, IO> ExFAT<IO>
where
IO: io::IO<Block<'static> = B, Error = E>,
{
pub async fn new(mut io: IO) -> Result<Self, Error<E>> {
let block = io.wrap().read(SectorID::BOOT).await?;
let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(&block[0]) };
if !boot_sector.is_exfat() {
return Err(DataError::NotExFAT.into());
}
if boot_sector.number_of_fats > 1 {
return Err(ImplementationError::TexFATNotSupported.into());
}
let fat_offset = boot_sector.fat_offset.to_ne();
let fat_length = boot_sector.fat_length.to_ne();
debug!("FAT offset {} length {}", fat_offset, fat_length);
io.wrap().set_sector_size_shift(boot_sector.bytes_per_sector_shift)?;
let root = ClusterID::from(boot_sector.first_cluster_of_root_directory.to_ne());
debug!("Root directory on cluster {}", root);
let sector_size_shift = boot_sector.bytes_per_sector_shift;
let fat_info = fat::Info::new(sector_size_shift, fat_offset, fat_length);
let fs_info = fs::Info {
heap_offset: boot_sector.cluster_heap_offset.to_ne(),
sectors_per_cluster_shift: boot_sector.sectors_per_cluster_shift,
sector_size_shift,
};
debug!("Filesystem info: {:?}", fs_info);
let serial_number = boot_sector.volumn_serial_number.to_ne();
Ok(Self { io: Shared::new(io), serial_number, fs_info, fat_info, root })
}
pub async fn is_dirty(&mut self) -> Result<bool, Error<E>> {
let mut io = self.io.acquire().await.wrap();
let blocks = io.read(SectorID::BOOT).await?;
let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(&blocks[0]) };
Ok(boot_sector.volume_flags().volume_dirty() > 0)
}
pub async fn percent_inuse(&mut self) -> Result<u8, Error<E>> {
let mut io = self.io.acquire().await.wrap();
let blocks = io.read(SectorID::BOOT).await?;
let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(&blocks[0]) };
Ok(boot_sector.percent_inuse)
}
pub async fn set_dirty(&mut self, dirty: bool) -> Result<(), Error<E>> {
let mut io = self.io.acquire().await.wrap();
let sector = io.read(SectorID::BOOT).await?;
let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(§or[0]) };
let mut volume_flags = boot_sector.volume_flags();
volume_flags.set_volume_dirty(dirty as u16);
let offset = offset_of!(region::boot::BootSector, volume_flags);
let bytes: [u8; 2] = unsafe { mem::transmute(volume_flags) };
io.write(SectorID::BOOT, offset, &bytes).await?;
io.flush().await
}
pub async fn validate_checksum(&mut self) -> Result<(), Error<E>> {
let mut io = self.io.acquire().await.wrap();
let mut checksum = region::boot::BootChecksum::default();
for i in 0..=10 {
let sector = io.read(i.into()).await?;
for block in sector.iter() {
checksum.write(i as usize, block);
}
}
let sector = io.read(11.into()).await?;
let array: &[u32; 128] = unsafe { core::mem::transmute(§or[0]) };
if u32::from_le(array[0]) != checksum.sum() {
return Err(DataError::BootChecksum.into());
}
Ok(())
}
pub fn serial_number(&self) -> u32 {
self.serial_number
}
pub async fn root_directory(&mut self) -> Result<Root<B, E, IO>, Error<E>> {
Root::new(self.io.clone(), self.fat_info, self.fs_info, self.root).await
}
pub async fn try_free(self) -> Result<IO, Self> {
let ExFAT { io, serial_number, fat_info, fs_info, root } = self;
io.try_unwrap().await.map_err(|io| Self { io, serial_number, fat_info, fs_info, root })
}
}
#[cfg(test)]
mod test {
#[test_log::test]
fn test_exfat() {
use std::process::Command as CMD;
use crate::io::std::FileIO;
log::set_max_level(log::LevelFilter::Trace);
let args = ["-s", "4194304", "test.img"];
let output = CMD::new("truncate").args(args).output().unwrap();
assert!(output.status.success());
let output = CMD::new("/usr/sbin/mkfs.exfat").args(["test.img"]).output().unwrap();
assert!(output.status.success());
{
let io = FileIO::open("test.img").unwrap();
let mut exfat = super::ExFAT::new(io).unwrap();
let mut root = exfat.root_directory().unwrap();
root.validate_upcase_table_checksum().unwrap();
root.update_usage().unwrap();
let mut root_dir = root.open().unwrap();
root_dir.create("ut.bin", false).unwrap();
let entry = root_dir.find("ut.bin").unwrap().unwrap();
root_dir.delete(&entry).unwrap();
}
CMD::new("rm").args(["-f", "test.img"]).output().unwrap();
}
}