Skip to main content

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