exfat/
lib.rs

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: &region::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: &region::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: &region::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: &region::boot::BootSector = unsafe { mem::transmute(&sector[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(&sector[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    /// Cluster usage is calculated by default, which is inaccurate, therefore you may encounter
153    /// false allocation failure when still some clusters available.
154    /// For precise cluster usage calculation, you may call `update_usage` which will cost some time.
155    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}