embedded_sdmmc/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) 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                .map_err(Error::DeviceError)?;
42        }
43        Ok(&self.block)
44    }
45}
46
47mod bpb;
48mod info;
49mod ondiskdirentry;
50mod volume;
51
52pub use bpb::Bpb;
53pub use info::{Fat16Info, Fat32Info, FatSpecificInfo, InfoSector};
54pub use ondiskdirentry::OnDiskDirEntry;
55pub use volume::{parse_volume, FatVolume, VolumeName};
56
57use crate::{Block, BlockDevice, BlockIdx, Error};
58
59// ****************************************************************************
60//
61// Unit Tests
62//
63// ****************************************************************************
64
65#[cfg(test)]
66mod test {
67
68    use super::*;
69    use crate::{Attributes, BlockIdx, ClusterId, DirEntry, ShortFileName, Timestamp};
70
71    fn parse(input: &str) -> Vec<u8> {
72        let mut output = Vec::new();
73        for line in input.lines() {
74            let line = line.trim();
75            if !line.is_empty() {
76                // 32 bytes per line
77                for index in 0..32 {
78                    let start = index * 2;
79                    let end = start + 1;
80                    let piece = &line[start..=end];
81                    let value = u8::from_str_radix(piece, 16).unwrap();
82                    output.push(value);
83                }
84            }
85        }
86        output
87    }
88
89    /// This is the first block of this directory listing.
90    /// total 19880
91    /// -rw-r--r-- 1 jonathan jonathan   10841 2016-03-01 19:56:36.000000000 +0000  bcm2708-rpi-b.dtb
92    /// -rw-r--r-- 1 jonathan jonathan   11120 2016-03-01 19:56:34.000000000 +0000  bcm2708-rpi-b-plus.dtb
93    /// -rw-r--r-- 1 jonathan jonathan   10871 2016-03-01 19:56:36.000000000 +0000  bcm2708-rpi-cm.dtb
94    /// -rw-r--r-- 1 jonathan jonathan   12108 2016-03-01 19:56:36.000000000 +0000  bcm2709-rpi-2-b.dtb
95    /// -rw-r--r-- 1 jonathan jonathan   12575 2016-03-01 19:56:36.000000000 +0000  bcm2710-rpi-3-b.dtb
96    /// -rw-r--r-- 1 jonathan jonathan   17920 2016-03-01 19:56:38.000000000 +0000  bootcode.bin
97    /// -rw-r--r-- 1 jonathan jonathan     136 2015-11-21 20:28:30.000000000 +0000  cmdline.txt
98    /// -rw-r--r-- 1 jonathan jonathan    1635 2015-11-21 20:28:30.000000000 +0000  config.txt
99    /// -rw-r--r-- 1 jonathan jonathan   18693 2016-03-01 19:56:30.000000000 +0000  COPYING.linux
100    /// -rw-r--r-- 1 jonathan jonathan    2505 2016-03-01 19:56:38.000000000 +0000  fixup_cd.dat
101    /// -rw-r--r-- 1 jonathan jonathan    6481 2016-03-01 19:56:38.000000000 +0000  fixup.dat
102    /// -rw-r--r-- 1 jonathan jonathan    9722 2016-03-01 19:56:38.000000000 +0000  fixup_db.dat
103    /// -rw-r--r-- 1 jonathan jonathan    9724 2016-03-01 19:56:38.000000000 +0000  fixup_x.dat
104    /// -rw-r--r-- 1 jonathan jonathan     110 2015-11-21 21:32:06.000000000 +0000  issue.txt
105    /// -rw-r--r-- 1 jonathan jonathan 4046732 2016-03-01 19:56:40.000000000 +0000  kernel7.img
106    /// -rw-r--r-- 1 jonathan jonathan 3963140 2016-03-01 19:56:38.000000000 +0000  kernel.img
107    /// -rw-r--r-- 1 jonathan jonathan    1494 2016-03-01 19:56:34.000000000 +0000  LICENCE.broadcom
108    /// -rw-r--r-- 1 jonathan jonathan   18974 2015-11-21 21:32:06.000000000 +0000  LICENSE.oracle
109    /// drwxr-xr-x 2 jonathan jonathan    8192 2016-03-01 19:56:54.000000000 +0000  overlays
110    /// -rw-r--r-- 1 jonathan jonathan  612472 2016-03-01 19:56:40.000000000 +0000  start_cd.elf
111    /// -rw-r--r-- 1 jonathan jonathan 4888200 2016-03-01 19:56:42.000000000 +0000  start_db.elf
112    /// -rw-r--r-- 1 jonathan jonathan 2739672 2016-03-01 19:56:40.000000000 +0000  start.elf
113    /// -rw-r--r-- 1 jonathan jonathan 3840328 2016-03-01 19:56:44.000000000 +0000  start_x.elf
114    /// drwxr-xr-x 2 jonathan jonathan    8192 2015-12-05 21:55:06.000000000 +0000 'System Volume Information'
115    #[test]
116    fn test_dir_entries() {
117        #[derive(Debug)]
118        enum Expected {
119            Lfn(bool, u8, [char; 13]),
120            Short(DirEntry),
121        }
122        let raw_data = r#"
123        626f6f7420202020202020080000699c754775470000699c7547000000000000 boot       ...i.uGuG..i.uG......
124        416f007600650072006c000f00476100790073000000ffffffff0000ffffffff Ao.v.e.r.l...Ga.y.s.............
125        4f5645524c4159532020201000001b9f6148614800001b9f6148030000000000 OVERLAYS   .....aHaH....aH......
126        422d0070006c00750073000f00792e006400740062000000ffff0000ffffffff B-.p.l.u.s...y..d.t.b...........
127        01620063006d00320037000f0079300038002d0072007000690000002d006200 .b.c.m.2.7...y0.8.-.r.p.i...-.b.
128        42434d3237307e31445442200064119f614861480000119f61480900702b0000 BCM270~1DTB .d..aHaH....aH..p+..
129        4143004f005000590049000f00124e0047002e006c0069006e00000075007800 AC.O.P.Y.I....N.G...l.i.n...u.x.
130        434f5059494e7e314c494e2000000f9f6148614800000f9f6148050005490000 COPYIN~1LIN ....aHaH....aH...I..
131        4263006f006d000000ffff0f0067ffffffffffffffffffffffff0000ffffffff Bc.o.m.......g..................
132        014c004900430045004e000f0067430045002e00620072006f00000061006400 .L.I.C.E.N...gC.E...b.r.o...a.d.
133        4c4943454e437e3142524f200000119f614861480000119f61480800d6050000 LICENC~1BRO ....aHaH....aH......
134        422d0062002e00640074000f001962000000ffffffffffffffff0000ffffffff B-.b...d.t....b.................
135        01620063006d00320037000f0019300039002d0072007000690000002d003200 .b.c.m.2.7....0.9.-.r.p.i...-.2.
136        42434d3237307e34445442200064129f614861480000129f61480f004c2f0000 BCM270~4DTB .d..aHaH....aH..L/..
137        422e0064007400620000000f0059ffffffffffffffffffffffff0000ffffffff B..d.t.b.....Y..................
138        01620063006d00320037000f0059300038002d0072007000690000002d006200 .b.c.m.2.7...Y0.8.-.r.p.i...-.b.
139        "#;
140        let results = [
141            Expected::Short(DirEntry {
142                name: ShortFileName::create_from_str_mixed_case("boot").unwrap(),
143                mtime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
144                ctime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
145                attributes: Attributes::create_from_fat(Attributes::VOLUME),
146                cluster: ClusterId(0),
147                size: 0,
148                entry_block: BlockIdx(0),
149                entry_offset: 0,
150            }),
151            Expected::Lfn(
152                true,
153                1,
154                [
155                    'o', 'v', 'e', 'r', 'l', 'a', 'y', 's', '\u{0000}', '\u{ffff}', '\u{ffff}',
156                    '\u{ffff}', '\u{ffff}',
157                ],
158            ),
159            Expected::Short(DirEntry {
160                name: ShortFileName::create_from_str("OVERLAYS").unwrap(),
161                mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 54).unwrap(),
162                ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 54).unwrap(),
163                attributes: Attributes::create_from_fat(Attributes::DIRECTORY),
164                cluster: ClusterId(3),
165                size: 0,
166                entry_block: BlockIdx(0),
167                entry_offset: 0,
168            }),
169            Expected::Lfn(
170                true,
171                2,
172                [
173                    '-', 'p', 'l', 'u', 's', '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}',
174                    '\u{ffff}', '\u{ffff}',
175                ],
176            ),
177            Expected::Lfn(
178                false,
179                1,
180                [
181                    'b', 'c', 'm', '2', '7', '0', '8', '-', 'r', 'p', 'i', '-', 'b',
182                ],
183            ),
184            Expected::Short(DirEntry {
185                name: ShortFileName::create_from_str("BCM270~1.DTB").unwrap(),
186                mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
187                ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
188                attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
189                cluster: ClusterId(9),
190                size: 11120,
191                entry_block: BlockIdx(0),
192                entry_offset: 0,
193            }),
194            Expected::Lfn(
195                true,
196                1,
197                [
198                    'C', 'O', 'P', 'Y', 'I', 'N', 'G', '.', 'l', 'i', 'n', 'u', 'x',
199                ],
200            ),
201            Expected::Short(DirEntry {
202                name: ShortFileName::create_from_str("COPYIN~1.LIN").unwrap(),
203                mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 30).unwrap(),
204                ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 30).unwrap(),
205                attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
206                cluster: ClusterId(5),
207                size: 18693,
208                entry_block: BlockIdx(0),
209                entry_offset: 0,
210            }),
211            Expected::Lfn(
212                true,
213                2,
214                [
215                    'c', 'o', 'm', '\u{0}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
216                    '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
217                ],
218            ),
219            Expected::Lfn(
220                false,
221                1,
222                [
223                    'L', 'I', 'C', 'E', 'N', 'C', 'E', '.', 'b', 'r', 'o', 'a', 'd',
224                ],
225            ),
226            Expected::Short(DirEntry {
227                name: ShortFileName::create_from_str("LICENC~1.BRO").unwrap(),
228                mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
229                ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
230                attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
231                cluster: ClusterId(8),
232                size: 1494,
233                entry_block: BlockIdx(0),
234                entry_offset: 0,
235            }),
236            Expected::Lfn(
237                true,
238                2,
239                [
240                    '-', 'b', '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
241                    '\u{ffff}', '\u{ffff}', '\u{ffff}',
242                ],
243            ),
244            Expected::Lfn(
245                false,
246                1,
247                [
248                    'b', 'c', 'm', '2', '7', '0', '9', '-', 'r', 'p', 'i', '-', '2',
249                ],
250            ),
251            Expected::Short(DirEntry {
252                name: ShortFileName::create_from_str("BCM270~4.DTB").unwrap(),
253                mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 36).unwrap(),
254                ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 36).unwrap(),
255                attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
256                cluster: ClusterId(15),
257                size: 12108,
258                entry_block: BlockIdx(0),
259                entry_offset: 0,
260            }),
261            Expected::Lfn(
262                true,
263                2,
264                [
265                    '.', 'd', 't', 'b', '\u{0000}', '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
266                    '\u{ffff}', '\u{ffff}', '\u{ffff}', '\u{ffff}',
267                ],
268            ),
269            Expected::Lfn(
270                false,
271                1,
272                [
273                    'b', 'c', 'm', '2', '7', '0', '8', '-', 'r', 'p', 'i', '-', 'b',
274                ],
275            ),
276        ];
277
278        let data = parse(raw_data);
279        for (part, expected) in data.chunks(OnDiskDirEntry::LEN).zip(results.iter()) {
280            let on_disk_entry = OnDiskDirEntry::new(part);
281            match expected {
282                Expected::Lfn(start, index, contents) if on_disk_entry.is_lfn() => {
283                    let (calc_start, calc_index, calc_contents) =
284                        on_disk_entry.lfn_contents().unwrap();
285                    assert_eq!(*start, calc_start);
286                    assert_eq!(*index, calc_index);
287                    assert_eq!(*contents, calc_contents);
288                }
289                Expected::Short(expected_entry) if !on_disk_entry.is_lfn() => {
290                    let parsed_entry = on_disk_entry.get_entry(FatType::Fat32, BlockIdx(0), 0);
291                    assert_eq!(*expected_entry, parsed_entry);
292                }
293                _ => {
294                    panic!(
295                        "Bad dir entry, expected:\n{:#?}\nhad\n{:#?}",
296                        expected, on_disk_entry
297                    );
298                }
299            }
300        }
301    }
302
303    #[test]
304    fn test_bpb() {
305        // Taken from a Raspberry Pi bootable SD-Card
306        const BPB_EXAMPLE: [u8; 512] = hex!(
307            "EB 3C 90 6D 6B 66 73 2E 66 61 74 00 02 10 01 00
308             02 00 02 00 00 F8 20 00 3F 00 FF 00 00 00 00 00
309             00 E0 01 00 80 01 29 BB B0 71 77 62 6F 6F 74 20
310             20 20 20 20 20 20 46 41 54 31 36 20 20 20 0E 1F
311             BE 5B 7C AC 22 C0 74 0B 56 B4 0E BB 07 00 CD 10
312             5E EB F0 32 E4 CD 16 CD 19 EB FE 54 68 69 73 20
313             69 73 20 6E 6F 74 20 61 20 62 6F 6F 74 61 62 6C
314             65 20 64 69 73 6B 2E 20 20 50 6C 65 61 73 65 20
315             69 6E 73 65 72 74 20 61 20 62 6F 6F 74 61 62 6C
316             65 20 66 6C 6F 70 70 79 20 61 6E 64 0D 0A 70 72
317             65 73 73 20 61 6E 79 20 6B 65 79 20 74 6F 20 74
318             72 79 20 61 67 61 69 6E 20 2E 2E 2E 20 0D 0A 00
319             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
320             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
321             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
322             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
323             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
324             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
325             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
326             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
327             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
328             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
329             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
330             00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 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 55 AA"
339        );
340        let bpb = Bpb::create_from_bytes(&BPB_EXAMPLE).unwrap();
341        assert_eq!(bpb.footer(), Bpb::FOOTER_VALUE);
342        assert_eq!(bpb.oem_name(), b"mkfs.fat");
343        assert_eq!(bpb.bytes_per_block(), 512);
344        assert_eq!(bpb.blocks_per_cluster(), 16);
345        assert_eq!(bpb.reserved_block_count(), 1);
346        assert_eq!(bpb.num_fats(), 2);
347        assert_eq!(bpb.root_entries_count(), 512);
348        assert_eq!(bpb.total_blocks16(), 0);
349        assert_eq!(bpb.fat_size16(), 32);
350        assert_eq!(bpb.total_blocks32(), 122_880);
351        assert_eq!(bpb.footer(), 0xAA55);
352        assert_eq!(bpb.volume_label(), b"boot       ");
353        assert_eq!(bpb.fat_size(), 32);
354        assert_eq!(bpb.total_blocks(), 122_880);
355        assert_eq!(bpb.fat_type, FatType::Fat16);
356    }
357}
358
359// ****************************************************************************
360//
361// End Of File
362//
363// ****************************************************************************