1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(any(test, feature = "std")), no_std)]
3
4#[cfg(all(feature = "async", feature = "std", not(any(feature = "smol", feature = "tokio"))))]
5compile_error!("Either smol or tokio must be selected");
6
7extern crate alloc;
8
9#[macro_use]
10extern crate hex_literal;
11extern crate heapless;
12#[cfg(feature = "logging")]
13#[macro_use]
14extern crate log;
15
16#[cfg(not(feature = "logging"))]
17#[macro_use]
18mod logging {
19 #[macro_export]
20 macro_rules! warn {
21 ($($arg:tt)*) => { _ = ($($arg)*) };
22 }
23 #[macro_export]
24 macro_rules! debug {
25 ($($arg:tt)*) => { _ = ($($arg)*) };
26 }
27 #[macro_export]
28 macro_rules! trace {
29 ($($arg:tt)*) => { _ = ($($arg)*) };
30 }
31}
32
33mod cluster_heap;
34mod endian;
35pub mod error;
36mod fat;
37pub mod file;
38pub(crate) mod fs;
39pub mod io;
40mod region;
41pub(crate) mod sync;
42pub mod types;
43mod upcase_table;
44
45use core::fmt::Debug;
46use core::mem;
47use core::ops::Deref;
48
49use memoffset::offset_of;
50
51pub use cluster_heap::directory::{Directory, FileOrDirectory};
52pub use cluster_heap::file::SeekFrom;
53pub use cluster_heap::root::RootDirectory;
54use cluster_heap::root::RootDirectory as Root;
55use error::{DataError, Error, ImplementationError};
56use io::Wrap;
57pub use region::data::entryset::primary::DateTime;
58use types::ClusterID;
59
60use crate::io::Block;
61use crate::sync::Shared;
62use crate::types::SectorID;
63
64pub struct ExFAT<IO> {
65 io: Shared<IO>,
66 serial_number: u32,
67 fat_info: fat::Info,
68 fs_info: fs::Info,
69 root: ClusterID,
70}
71
72#[cfg_attr(not(feature = "async"), maybe_async::must_be_sync)]
73impl<B: Deref<Target = [Block]>, E: Debug, IO> ExFAT<IO>
74where
75 IO: io::IO<Block<'static> = B, Error = E>,
76{
77 pub async fn new(mut io: IO) -> Result<Self, Error<E>> {
78 let block = io.wrap().read(SectorID::BOOT).await?;
79 let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(&block[0]) };
80 if !boot_sector.is_exfat() {
81 return Err(DataError::NotExFAT.into());
82 }
83 if boot_sector.number_of_fats > 1 {
84 return Err(ImplementationError::TexFATNotSupported.into());
85 }
86 let fat_offset = boot_sector.fat_offset.to_ne();
87 let fat_length = boot_sector.fat_length.to_ne();
88 debug!("FAT offset {} length {}", fat_offset, fat_length);
89
90 io.wrap().set_sector_size_shift(boot_sector.bytes_per_sector_shift)?;
91 let root = ClusterID::from(boot_sector.first_cluster_of_root_directory.to_ne());
92 debug!("Root directory on cluster {}", root);
93 let sector_size_shift = boot_sector.bytes_per_sector_shift;
94 let fat_info = fat::Info::new(sector_size_shift, fat_offset, fat_length);
95 let fs_info = fs::Info {
96 heap_offset: boot_sector.cluster_heap_offset.to_ne(),
97 sectors_per_cluster_shift: boot_sector.sectors_per_cluster_shift,
98 sector_size_shift,
99 };
100 debug!("Filesystem info: {:?}", fs_info);
101 let serial_number = boot_sector.volumn_serial_number.to_ne();
102 Ok(Self { io: Shared::new(io), serial_number, fs_info, fat_info, root })
103 }
104
105 pub async fn is_dirty(&mut self) -> Result<bool, Error<E>> {
106 let mut io = self.io.acquire().await.wrap();
107 let blocks = io.read(SectorID::BOOT).await?;
108 let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(&blocks[0]) };
109 Ok(boot_sector.volume_flags().volume_dirty() > 0)
110 }
111
112 pub async fn percent_inuse(&mut self) -> Result<u8, Error<E>> {
113 let mut io = self.io.acquire().await.wrap();
114 let blocks = io.read(SectorID::BOOT).await?;
115 let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(&blocks[0]) };
116 Ok(boot_sector.percent_inuse)
117 }
118
119 pub async fn set_dirty(&mut self, dirty: bool) -> Result<(), Error<E>> {
120 let mut io = self.io.acquire().await.wrap();
121 let sector = io.read(SectorID::BOOT).await?;
122 let boot_sector: ®ion::boot::BootSector = unsafe { mem::transmute(§or[0]) };
123 let mut volume_flags = boot_sector.volume_flags();
124 volume_flags.set_volume_dirty(dirty as u16);
125 let offset = offset_of!(region::boot::BootSector, volume_flags);
126 let bytes: [u8; 2] = unsafe { mem::transmute(volume_flags) };
127 io.write(SectorID::BOOT, offset, &bytes).await?;
128 io.flush().await
129 }
130
131 pub async fn validate_checksum(&mut self) -> Result<(), Error<E>> {
132 let mut io = self.io.acquire().await.wrap();
133 let mut checksum = region::boot::BootChecksum::default();
134 for i in 0..=10 {
135 let sector = io.read(i.into()).await?;
136 for block in sector.iter() {
137 checksum.write(i as usize, block);
138 }
139 }
140 let sector = io.read(11.into()).await?;
141 let array: &[u32; 128] = unsafe { core::mem::transmute(§or[0]) };
142 if u32::from_le(array[0]) != checksum.sum() {
143 return Err(DataError::BootChecksum.into());
144 }
145 Ok(())
146 }
147
148 pub fn serial_number(&self) -> u32 {
149 self.serial_number
150 }
151
152 pub async fn root_directory(&mut self) -> Result<Root<B, E, IO>, Error<E>> {
156 Root::new(self.io.clone(), self.fat_info, self.fs_info, self.root).await
157 }
158
159 pub async fn try_free(self) -> Result<IO, Self> {
160 let ExFAT { io, serial_number, fat_info, fs_info, root } = self;
161 io.try_unwrap().await.map_err(|io| Self { io, serial_number, fat_info, fs_info, root })
162 }
163}
164
165#[cfg(test)]
166mod test {
167 #[test_log::test]
168 fn test_exfat() {
169 use std::process::Command as CMD;
170
171 use crate::io::std::FileIO;
172
173 log::set_max_level(log::LevelFilter::Trace);
174
175 let args = ["-s", "4194304", "test.img"];
176 let output = CMD::new("truncate").args(args).output().unwrap();
177 assert!(output.status.success());
178 let output = CMD::new("/usr/sbin/mkfs.exfat").args(["test.img"]).output().unwrap();
179 assert!(output.status.success());
180
181 {
182 let io = FileIO::open("test.img").unwrap();
183 let mut exfat = super::ExFAT::new(io).unwrap();
184 let mut root = exfat.root_directory().unwrap();
185 root.validate_upcase_table_checksum().unwrap();
186 root.update_usage().unwrap();
187 let mut root_dir = root.open().unwrap();
188 root_dir.create("ut.bin", false).unwrap();
189 let entry = root_dir.find("ut.bin").unwrap().unwrap();
190 root_dir.delete(&entry).unwrap();
191 }
192
193 CMD::new("rm").args(["-f", "test.img"]).output().unwrap();
194 }
195}