embedded_fat/fat/
mod.rs

1//! FAT16/FAT32 file system implementation
2//!
3//! Implements the File Allocation Table file system. Supports FAT16 and FAT32 volumes.
4
5/// Number of entries reserved at the start of a File Allocation Table
6pub const RESERVED_ENTRIES: u32 = 2;
7
8/// Indentifies the supported types of FAT format
9#[derive(Debug, Copy, Clone, PartialEq, Eq)]
10pub enum FatType {
11    /// FAT16 Format
12    Fat16,
13    /// FAT32 Format
14    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// ****************************************************************************
63//
64// Unit Tests
65//
66// ****************************************************************************
67
68#[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                // 32 bytes per line
82                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    /// This is the first block of this directory listing.
95    /// total 19880
96    /// -rw-r--r-- 1 jonathan jonathan   10841 2016-03-01 19:56:36.000000000 +0000  bcm2708-rpi-b.dtb
97    /// -rw-r--r-- 1 jonathan jonathan   11120 2016-03-01 19:56:34.000000000 +0000  bcm2708-rpi-b-plus.dtb
98    /// -rw-r--r-- 1 jonathan jonathan   10871 2016-03-01 19:56:36.000000000 +0000  bcm2708-rpi-cm.dtb
99    /// -rw-r--r-- 1 jonathan jonathan   12108 2016-03-01 19:56:36.000000000 +0000  bcm2709-rpi-2-b.dtb
100    /// -rw-r--r-- 1 jonathan jonathan   12575 2016-03-01 19:56:36.000000000 +0000  bcm2710-rpi-3-b.dtb
101    /// -rw-r--r-- 1 jonathan jonathan   17920 2016-03-01 19:56:38.000000000 +0000  bootcode.bin
102    /// -rw-r--r-- 1 jonathan jonathan     136 2015-11-21 20:28:30.000000000 +0000  cmdline.txt
103    /// -rw-r--r-- 1 jonathan jonathan    1635 2015-11-21 20:28:30.000000000 +0000  config.txt
104    /// -rw-r--r-- 1 jonathan jonathan   18693 2016-03-01 19:56:30.000000000 +0000  COPYING.linux
105    /// -rw-r--r-- 1 jonathan jonathan    2505 2016-03-01 19:56:38.000000000 +0000  fixup_cd.dat
106    /// -rw-r--r-- 1 jonathan jonathan    6481 2016-03-01 19:56:38.000000000 +0000  fixup.dat
107    /// -rw-r--r-- 1 jonathan jonathan    9722 2016-03-01 19:56:38.000000000 +0000  fixup_db.dat
108    /// -rw-r--r-- 1 jonathan jonathan    9724 2016-03-01 19:56:38.000000000 +0000  fixup_x.dat
109    /// -rw-r--r-- 1 jonathan jonathan     110 2015-11-21 21:32:06.000000000 +0000  issue.txt
110    /// -rw-r--r-- 1 jonathan jonathan 4046732 2016-03-01 19:56:40.000000000 +0000  kernel7.img
111    /// -rw-r--r-- 1 jonathan jonathan 3963140 2016-03-01 19:56:38.000000000 +0000  kernel.img
112    /// -rw-r--r-- 1 jonathan jonathan    1494 2016-03-01 19:56:34.000000000 +0000  LICENCE.broadcom
113    /// -rw-r--r-- 1 jonathan jonathan   18974 2015-11-21 21:32:06.000000000 +0000  LICENSE.oracle
114    /// drwxr-xr-x 2 jonathan jonathan    8192 2016-03-01 19:56:54.000000000 +0000  overlays
115    /// -rw-r--r-- 1 jonathan jonathan  612472 2016-03-01 19:56:40.000000000 +0000  start_cd.elf
116    /// -rw-r--r-- 1 jonathan jonathan 4888200 2016-03-01 19:56:42.000000000 +0000  start_db.elf
117    /// -rw-r--r-- 1 jonathan jonathan 2739672 2016-03-01 19:56:40.000000000 +0000  start.elf
118    /// -rw-r--r-- 1 jonathan jonathan 3840328 2016-03-01 19:56:44.000000000 +0000  start_x.elf
119    /// drwxr-xr-x 2 jonathan jonathan    8192 2015-12-05 21:55:06.000000000 +0000 'System Volume Information'
120    #[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        // Taken from a Raspberry Pi bootable SD-Card
318        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}