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