1pub const RESERVED_ENTRIES: u32 = 2;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq)]
10pub enum FatType {
11 Fat16,
13 Fat32,
15}
16
17pub(crate) struct BlockCache {
18 block: Block,
19 idx: Option<BlockIdx>,
20}
21impl BlockCache {
22 pub fn empty() -> Self {
23 BlockCache {
24 block: Block::new(),
25 idx: None,
26 }
27 }
28 pub(crate) async fn read<D>(
29 &mut self,
30 block_device: &D,
31 block_idx: BlockIdx,
32 reason: &str,
33 ) -> Result<&Block, Error<D::Error>>
34 where
35 D: BlockDevice,
36 {
37 if Some(block_idx) != self.idx {
38 self.idx = Some(block_idx);
39 block_device
40 .read(core::slice::from_mut(&mut self.block), block_idx, reason)
41 .await
42 .map_err(Error::DeviceError)?;
43 }
44 Ok(&self.block)
45 }
46}
47
48mod bpb;
49mod info;
50mod lfn;
51mod ondiskdirentry;
52mod volume;
53
54pub use bpb::Bpb;
55pub use info::{Fat16Info, Fat32Info, FatSpecificInfo, InfoSector};
56pub use lfn::LfnEntry;
57pub use ondiskdirentry::OnDiskDirEntry;
58pub use volume::{parse_volume, FatVolume, VolumeName};
59
60use crate::{Block, BlockDevice, BlockIdx, Error};
61
62#[cfg(test)]
69mod test {
70
71 use super::*;
72 use crate::{
73 fat::LfnEntry, Attributes, BlockIdx, ClusterId, DirEntry, ShortFileName, Timestamp,
74 };
75
76 fn parse(input: &str) -> Vec<u8> {
77 let mut output = Vec::new();
78 for line in input.lines() {
79 let line = line.trim();
80 if !line.is_empty() {
81 for index in 0..32 {
83 let start = index * 2;
84 let end = start + 1;
85 let piece = &line[start..=end];
86 let value = u8::from_str_radix(piece, 16).unwrap();
87 output.push(value);
88 }
89 }
90 }
91 output
92 }
93
94 #[test]
121 fn test_dir_entries() {
122 #[derive(Debug)]
123 enum Expected {
124 Lfn(LfnEntry),
125 Short(DirEntry),
126 }
127 let raw_data = r#"
128 626f6f7420202020202020080000699c754775470000699c7547000000000000 boot ...i.uGuG..i.uG......
129 416f007600650072006c000f00476100790073000000ffffffff0000ffffffff Ao.v.e.r.l...Ga.y.s.............
130 4f5645524c4159532020201000001b9f6148614800001b9f6148030000000000 OVERLAYS .....aHaH....aH......
131 422d0070006c00750073000f00792e006400740062000000ffff0000ffffffff B-.p.l.u.s...y..d.t.b...........
132 01620063006d00320037000f0079300038002d0072007000690000002d006200 .b.c.m.2.7...y0.8.-.r.p.i...-.b.
133 42434d3237307e31445442200064119f614861480000119f61480900702b0000 BCM270~1DTB .d..aHaH....aH..p+..
134 4143004f005000590049000f00124e0047002e006c0069006e00000075007800 AC.O.P.Y.I....N.G...l.i.n...u.x.
135 434f5059494e7e314c494e2000000f9f6148614800000f9f6148050005490000 COPYIN~1LIN ....aHaH....aH...I..
136 4263006f006d000000ffff0f0067ffffffffffffffffffffffff0000ffffffff Bc.o.m.......g..................
137 014c004900430045004e000f0067430045002e00620072006f00000061006400 .L.I.C.E.N...gC.E...b.r.o...a.d.
138 4c4943454e437e3142524f200000119f614861480000119f61480800d6050000 LICENC~1BRO ....aHaH....aH......
139 422d0062002e00640074000f001962000000ffffffffffffffff0000ffffffff B-.b...d.t....b.................
140 01620063006d00320037000f0019300039002d0072007000690000002d003200 .b.c.m.2.7....0.9.-.r.p.i...-.2.
141 42434d3237307e34445442200064129f614861480000129f61480f004c2f0000 BCM270~4DTB .d..aHaH....aH..L/..
142 422e0064007400620000000f0059ffffffffffffffffffffffff0000ffffffff B..d.t.b.....Y..................
143 01620063006d00320037000f0059300038002d0072007000690000002d006200 .b.c.m.2.7...Y0.8.-.r.p.i...-.b.
144 "#;
145 let results = [
146 Expected::Short(DirEntry {
147 name: ShortFileName::create_from_str_mixed_case("boot").unwrap(),
148 mtime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
149 ctime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
150 attributes: Attributes::create_from_fat(Attributes::VOLUME),
151 cluster: ClusterId(0),
152 size: 0,
153 entry_block: BlockIdx(0),
154 entry_offset: 0,
155 }),
156 Expected::Lfn(LfnEntry {
157 is_start: true,
158 sequence: 1,
159 checksum: 0x47,
160 buffer: [
161 'o', 'v', 'e', 'r', 'l', 'a', 'y', 's', '\u{0000}', '\u{ffff}', '\u{ffff}',
162 '\u{ffff}', '\u{ffff}',
163 ],
164 }),
165 Expected::Short(DirEntry {
166 name: ShortFileName::create_from_str("OVERLAYS").unwrap(),
167 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 54).unwrap(),
168 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 54).unwrap(),
169 attributes: Attributes::create_from_fat(Attributes::DIRECTORY),
170 cluster: ClusterId(3),
171 size: 0,
172 entry_block: BlockIdx(0),
173 entry_offset: 0,
174 }),
175 Expected::Lfn(LfnEntry {
176 is_start: true,
177 sequence: 2,
178 checksum: 0x79,
179 buffer: [
180 '-', 'p', 'l', 'u', 's', '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}',
181 '\u{ffff}', '\u{ffff}',
182 ],
183 }),
184 Expected::Lfn(LfnEntry {
185 is_start: false,
186 sequence: 1,
187 checksum: 0x79,
188 buffer: [
189 'b', 'c', 'm', '2', '7', '0', '8', '-', 'r', 'p', 'i', '-', 'b',
190 ],
191 }),
192 Expected::Short(DirEntry {
193 name: ShortFileName::create_from_str("BCM270~1.DTB").unwrap(),
194 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
195 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
196 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
197 cluster: ClusterId(9),
198 size: 11120,
199 entry_block: BlockIdx(0),
200 entry_offset: 0,
201 }),
202 Expected::Lfn(LfnEntry {
203 is_start: true,
204 sequence: 1,
205 checksum: 0x12,
206 buffer: [
207 'C', 'O', 'P', 'Y', 'I', 'N', 'G', '.', 'l', 'i', 'n', 'u', 'x',
208 ],
209 }),
210 Expected::Short(DirEntry {
211 name: ShortFileName::create_from_str("COPYIN~1.LIN").unwrap(),
212 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 30).unwrap(),
213 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 30).unwrap(),
214 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
215 cluster: ClusterId(5),
216 size: 18693,
217 entry_block: BlockIdx(0),
218 entry_offset: 0,
219 }),
220 Expected::Lfn(LfnEntry {
221 is_start: true,
222 sequence: 2,
223 checksum: 0x67,
224 buffer: [
225 'c', 'o', 'm', '\u{0}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
226 '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
227 ],
228 }),
229 Expected::Lfn(LfnEntry {
230 is_start: false,
231 sequence: 1,
232 checksum: 0x67,
233 buffer: [
234 'L', 'I', 'C', 'E', 'N', 'C', 'E', '.', 'b', 'r', 'o', 'a', 'd',
235 ],
236 }),
237 Expected::Short(DirEntry {
238 name: ShortFileName::create_from_str("LICENC~1.BRO").unwrap(),
239 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
240 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
241 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
242 cluster: ClusterId(8),
243 size: 1494,
244 entry_block: BlockIdx(0),
245 entry_offset: 0,
246 }),
247 Expected::Lfn(LfnEntry {
248 is_start: true,
249 sequence: 2,
250 checksum: 0x19,
251 buffer: [
252 '-', 'b', '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
253 '\u{ffff}', '\u{ffff}', '\u{ffff}',
254 ],
255 }),
256 Expected::Lfn(LfnEntry {
257 is_start: false,
258 sequence: 1,
259 checksum: 0x19,
260 buffer: [
261 'b', 'c', 'm', '2', '7', '0', '9', '-', 'r', 'p', 'i', '-', '2',
262 ],
263 }),
264 Expected::Short(DirEntry {
265 name: ShortFileName::create_from_str("BCM270~4.DTB").unwrap(),
266 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 36).unwrap(),
267 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 36).unwrap(),
268 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
269 cluster: ClusterId(15),
270 size: 12108,
271 entry_block: BlockIdx(0),
272 entry_offset: 0,
273 }),
274 Expected::Lfn(LfnEntry {
275 is_start: true,
276 sequence: 2,
277 checksum: 0x59,
278 buffer: [
279 '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
280 '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
281 ],
282 }),
283 Expected::Lfn(LfnEntry {
284 is_start: false,
285 sequence: 1,
286 checksum: 0x59,
287 buffer: [
288 'b', 'c', 'm', '2', '7', '0', '8', '-', 'r', 'p', 'i', '-', 'b',
289 ],
290 }),
291 ];
292
293 let data = parse(raw_data);
294 for (part, expected) in data.chunks(OnDiskDirEntry::LEN).zip(results.iter()) {
295 let on_disk_entry = OnDiskDirEntry::new(part);
296 match expected {
297 Expected::Lfn(expected_lfn_contents) if on_disk_entry.is_lfn() => {
298 let lfn_contents = on_disk_entry.lfn_contents().unwrap();
299 assert_eq!(expected_lfn_contents, &lfn_contents);
300 }
301 Expected::Short(expected_entry) if !on_disk_entry.is_lfn() => {
302 let parsed_entry = on_disk_entry.get_entry(FatType::Fat32, BlockIdx(0), 0);
303 assert_eq!(*expected_entry, parsed_entry);
304 }
305 _ => {
306 panic!(
307 "Bad dir entry, expected:\n{:#?}\nhad\n{:#?}",
308 expected, on_disk_entry
309 );
310 }
311 }
312 }
313 }
314
315 #[test]
316 fn test_bpb() {
317 const BPB_EXAMPLE: [u8; 512] = hex!(
319 "EB 3C 90 6D 6B 66 73 2E 66 61 74 00 02 10 01 00
320 02 00 02 00 00 F8 20 00 3F 00 FF 00 00 00 00 00
321 00 E0 01 00 80 01 29 BB B0 71 77 62 6F 6F 74 20
322 20 20 20 20 20 20 46 41 54 31 36 20 20 20 0E 1F
323 BE 5B 7C AC 22 C0 74 0B 56 B4 0E BB 07 00 CD 10
324 5E EB F0 32 E4 CD 16 CD 19 EB FE 54 68 69 73 20
325 69 73 20 6E 6F 74 20 61 20 62 6F 6F 74 61 62 6C
326 65 20 64 69 73 6B 2E 20 20 50 6C 65 61 73 65 20
327 69 6E 73 65 72 74 20 61 20 62 6F 6F 74 61 62 6C
328 65 20 66 6C 6F 70 70 79 20 61 6E 64 0D 0A 70 72
329 65 73 73 20 61 6E 79 20 6B 65 79 20 74 6F 20 74
330 72 79 20 61 67 61 69 6E 20 2E 2E 2E 20 0D 0A 00
331 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
332 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
333 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
334 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
335 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
336 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
337 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
338 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
339 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
341 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
342 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
343 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
344 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
345 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
346 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
347 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
348 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
349 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
350 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA"
351 );
352 let bpb = Bpb::create_from_bytes(&BPB_EXAMPLE).unwrap();
353 assert_eq!(bpb.footer(), Bpb::FOOTER_VALUE);
354 assert_eq!(bpb.oem_name(), b"mkfs.fat");
355 assert_eq!(bpb.bytes_per_block(), 512);
356 assert_eq!(bpb.blocks_per_cluster(), 16);
357 assert_eq!(bpb.reserved_block_count(), 1);
358 assert_eq!(bpb.num_fats(), 2);
359 assert_eq!(bpb.root_entries_count(), 512);
360 assert_eq!(bpb.total_blocks16(), 0);
361 assert_eq!(bpb.fat_size16(), 32);
362 assert_eq!(bpb.total_blocks32(), 122_880);
363 assert_eq!(bpb.footer(), 0xAA55);
364 assert_eq!(bpb.volume_label(), b"boot ");
365 assert_eq!(bpb.fat_size(), 32);
366 assert_eq!(bpb.total_blocks(), 122_880);
367 assert_eq!(bpb.fat_type, FatType::Fat16);
368 }
369}