hadris-fat 2.0.0

Rust FAT12/FAT16/FAT32 filesystem library for disk images, embedded devices, and no-std
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
use hadris_common::types::{
    endian::LittleEndian,
    number::{U16, U32},
};

/// The RawBpb struct represents the boot sector of any FAT partition
///
/// This only contains the common fields of the boot sector, and is not meant to be used directly
/// for reading or writing to the boot sector, for that, see `RawBootSector`, which contains
/// the boot sector and the extended boot sector
///
/// @hadris-spec FAT:BPB
/// @hadris-compliance full
/// @hadris-tests comprehensive_fat::test_valid_sector_sizes
/// @hadris-fuzz fat_read
#[repr(C)]
#[derive(Clone, Copy)]
pub struct RawBpb {
    /// BS_jmpBoot
    pub jump: [u8; 3],
    /// BS_OEMName
    /// The name of the program that formatted the partition
    pub oem_name: [u8; 8],
    /// BPB_BytsPerSec
    /// The number of bytes per sector
    pub bytes_per_sector: U16<LittleEndian>,
    /// BPB_SecPerClus
    /// The number of sectors per cluster
    pub sectors_per_cluster: u8,
    /// BPB_RsvdSecCnt
    ///
    /// The number of reserved sectors, should be nonzero, ans should be a multiple of the sectors per cluster
    /// This is used to:
    /// 1. align the start of the filesystem to the sectors per cluster
    /// 2. Move the data (cluster 2) to the end of fat tables, so that the data can be read from the start of the filesystem
    pub reserved_sector_count: U16<LittleEndian>,
    /// BPB_NumFATs
    ///
    /// The number of fats, 1 is acceptable, but 2 is recommended
    pub fat_count: u8,
    /// BPB_RootEntCnt
    ///
    /// The number of root directory entries
    /// For FAT32, this should be 0
    /// For FAT12/16, this value multiplied by 32 should be a multiple of the bytes per sector
    /// For FAT16, it is recommended to set this to 512 for maximum compatibility
    pub root_entry_count: [u8; 2],
    /// BPB_TotSec16
    ///
    /// The number of sectors
    /// For FAT32, this should be 0
    /// For FAT16, if the number of sectors is greater than 0x10000, you should use total_sectors_32
    pub total_sectors_16: [u8; 2],
    /// BPB_Media
    ///
    /// See the MediaType enum for more information
    pub media_type: u8,
    /// BPB_FATSz16
    ///
    /// The number of sectors per fat
    /// For FAT32, this should be 0
    pub sectors_per_fat_16: [u8; 2],
    /// BPB_SecPerTrk
    ///
    /// The number of sectors per track
    /// This is only relevant for media with have a geometry and used by BIOS interrupt 0x13
    pub sectors_per_track: [u8; 2],
    /// BPB_NumHeads
    ///
    /// Similar situation as sectors_per_track
    pub num_heads: [u8; 2],
    /// BPB_HiddSec
    ///
    /// The number of hidden sectors predicing the partition that contains the FAT volume.
    /// This must be 0 on media that isn't partitioned
    pub hidden_sector_count: [u8; 4],
    /// BPB_TotSec32
    ///
    /// The total number of sectors for FAT32
    /// For FAT16 use, see total_sectors_16
    pub total_sectors_32: [u8; 4],
}

// Safety: RawBpb is a C-repr struct with no padding issues for its fields
unsafe impl bytemuck::NoUninit for RawBpb {}
unsafe impl bytemuck::Zeroable for RawBpb {}
unsafe impl bytemuck::AnyBitPattern for RawBpb {}

#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
/// FAT12/16 extended BIOS parameter block and boot-sector payload.
pub struct RawBpbExt16 {
    /// BS_DrvNum
    pub drive_number: u8,
    /// BS_Reserved1
    pub reserved1: u8,
    /// BS_BootSig
    ///
    /// The extended boot signature, should be 0x29
    pub ext_boot_signature: u8,
    /// BS_VolID
    ///
    /// Volumme Serial Number
    /// This ID should be unique for each volume
    pub volume_id: [u8; 4],
    /// BS_VolLab
    ///
    /// Volume label
    /// This should be "NO NAME    " if the volume is not labeled
    pub volume_label: [u8; 11],
    /// BS_FilSysType
    ///
    /// Must be set to the strings "FAT12   ","FAT16   ", or "FAT     "
    pub fs_type: [u8; 8],
    /// Zeros
    /// To make it compatible with bytemuck, instead of using [u8; 448], we use 256 + 128 + 64
    pub padding1: [u8; 448],
    /// Signature_word
    ///
    /// The signature word, should be 0xAA55
    pub signature_word: [u8; 2],
}

#[repr(C)]
#[derive(Clone, Copy)]
/// FAT32 extended BIOS parameter block and boot-sector payload.
pub struct RawBpbExt32 {
    /// BPB_FatSz32
    ///
    /// The number of sectors per fat
    /// BPB_FATSz16 must be 0
    pub sectors_per_fat_32: U32<LittleEndian>,
    /// BPB_ExtFlags
    ///
    /// See the BpbExt32Flags struct for more information
    pub ext_flags: [u8; 2],
    /// BPB_FSVer
    ///
    /// The version of the file system
    /// This must be set to 0x00
    pub version: [u8; 2],
    /// BPB_RootClus
    ///
    /// The cluster number of the root directory
    /// This should be 2, or the first usable (not bad) cluster usable
    pub root_cluster: U32<LittleEndian>,
    /// BPB_FSInfo
    ///
    /// The sector number of the FSINFO structure
    /// NOTE: There is a copy of the FSINFO structure in the
    /// sequence of backup boot sectors, but only the copy
    /// pointed to by this field is kept up to date (i.e., both the
    /// primary and backup boot record point to the same
    /// FSINFO sector)
    pub fs_info_sector: U16<LittleEndian>,
    /// BPB_BkBootSec
    ///
    /// The sector number of the backup boot sector
    /// If set to 6 (only valid non-zero value), the boot sector
    /// in the reserved area is used to store the backup boot sector
    pub boot_sector: [u8; 2],
    /// BPB_Reserved
    /// Reserved, should be zero
    pub reserved: [u8; 12],
    /// BS_DrvNum
    ///
    /// The BIOS interrupt 0x13 drive number
    /// Should be 0x80 or 0x00
    pub drive_number: u8,
    /// BS_Reserved1
    /// Reserved, should be zero
    pub reserved1: u8,
    /// BS_BootSig
    ///
    /// The extended boot signature, should be 0x29
    pub ext_boot_signature: u8,
    /// BS_VolID
    ///
    /// Volumme Serial Number
    /// This ID should be unique for each volume
    pub volume_id: [u8; 4],
    /// BS_VolLab
    ///
    /// Volume label
    /// This should be "NO NAME    " if the volume is not labeled
    pub volume_label: [u8; 11],
    /// BS_FilSysType
    ///
    /// Must be set to the string "FAT32   "
    pub fs_type: [u8; 8],
    /// Zeros
    pub padding1: [u8; 420],
    /// Signature_word
    ///
    /// The signature word, should be 0xAA55
    pub signature_word: U16<LittleEndian>,
}

// Safety: RawBpbExt32 is a C-repr struct with no padding issues
unsafe impl bytemuck::NoUninit for RawBpbExt32 {}
unsafe impl bytemuck::Zeroable for RawBpbExt32 {}
unsafe impl bytemuck::AnyBitPattern for RawBpbExt32 {}

/// BPB_ExtFlags
///
/// This is a union of the flags that are set in the BPB_ExtFlags field
/// The flags are the following:
/// bits 0-3: zero based index of the active FAT, mirroring must be disabled
/// bits 4-6: reserved
/// bit 7: FAT mirroring is enabled
/// bits 8-15: reserved
#[repr(transparent)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BpbExt32Flags(u16);

/// Raw 32-byte FAT short-name file or directory entry.
///
/// @hadris-spec FAT:DirEntry
/// @hadris-compliance partial
/// @hadris-note Name/attributes/timestamps/cluster/size and NT case flags (`DIR_NTRes`) are read and written; extended access-time granularity is not modeled.
/// @hadris-tests test_write::test_lowercase_short_name_uses_nt_case_flags
/// @hadris-fuzz fat_read
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct RawFileEntry {
    /// DIR_Name
    ///
    /// The name of the file, padded with spaces, and in the 8.3 format
    /// A value of 0xE5 indicates that the directory is free. For kanji, 0x05 is used instead of 0xE5
    /// The special value 0x00 also indicates that the directory is free, but also all the entries
    /// following it are free
    /// The name cannot start with a space
    /// Only upper case letters, digits, and the following characters are allowed:
    /// $ % ' - _ @ ~ ` ! ( ) { } ^ # &
    pub name: [u8; 11],
    /// DIR_Attr
    ///
    /// The file attributes
    pub attributes: u8,
    /// DIR_NTRes
    ///
    /// Reserved for use by Windows NT. In practice Windows stores 8.3 name
    /// case information here: bit 3 (`0x08`) marks the base name as originally
    /// lowercase and bit 4 (`0x10`) marks the extension as originally
    /// lowercase. See [`NtCaseFlags`].
    pub reserved: u8,
    /// DIR_CrtTimeTenth
    ///
    /// The creation time, in tenths of a second
    pub creation_time_tenth: u8,
    /// DIR_CrtTime
    ///
    /// The creation time, granularity is 2 seconds
    pub creation_time: [u8; 2],
    /// DIR_CrtDate
    ///
    /// The creation date
    pub creation_date: [u8; 2],
    /// DIR_LstAccDate
    ///
    /// The last access date
    pub last_access_date: [u8; 2],
    /// DIR_FstClusHI
    ///
    /// The high word of the first cluster number
    pub first_cluster_high: U16<LittleEndian>,
    /// DIR_WrtTime
    ///
    /// The last write time, granularity is 2 seconds
    pub last_write_time: [u8; 2],
    /// DIR_WrtDate
    ///
    /// The last write date
    pub last_write_date: [u8; 2],
    /// DIR_FstClusLO
    ///
    /// The low word of the first cluster number
    pub first_cluster_low: U16<LittleEndian>,
    /// DIR_FileSize
    ///
    /// The size of the file, in bytes
    pub size: U32<LittleEndian>,
}

unsafe impl bytemuck::NoUninit for RawFileEntry {}
unsafe impl bytemuck::Zeroable for RawFileEntry {}
unsafe impl bytemuck::AnyBitPattern for RawFileEntry {}

/// A long file name entry
/// The maximum length of a long file name is 255 characters, not including the null terminator
/// The characters allowed extend these characters:
///  . + , ; = [ ]
/// Embedded paces are also allowed
/// The name is stored in UTF-16 encoding (UNICODE)
/// When the unicode character cannot be translated to ANSI, an underscore is used
///
/// @hadris-spec FAT:LFN
/// @hadris-compliance partial
/// @hadris-note This raw on-disk structure is complete, while semantic validation and legacy ANSI fallback behavior are implemented by higher-level LFN readers and writers.
/// @hadris-tests comprehensive_fat::test_lfn_builder_sequence
/// @hadris-fuzz fat_read
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct RawLfnEntry {
    /// LFN_Ord
    ///
    /// The order of the LFN entry, the contents must be masked with 0x40 for the last entry
    pub sequence_number: u8,
    /// LFN_Name1
    ///
    /// The first part of the long file name
    pub name1: [u8; 10],
    /// LDIR_Attr
    ///
    /// Attributes, must be set to: ATTR_LONG_NAME, which is:
    /// ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID
    pub attributes: u8,
    /// LFN_Type
    ///
    /// The type of the LFN entry, must be set to 0
    pub ty: u8,
    /// LFN_Chksum
    ///
    /// Checksum of name in the associated short name directory entry at the end of the LFN sequence
    /// THe algorithm described in the FAT spec is:
    /// unsigned char ChkSum (unsigned char \*pFcbName)
    /// {
    ///     short FcbNameLen;
    ///     unsigned char Sum;
    ///     Sum = 0;
    ///     for (FcbNameLen=11; FcbNameLen!=0; FcbNameLen--) {
    ///         // NOTE: The operation is an unsigned char rotate right
    ///         Sum = ((Sum & 1) ? 0x80 : 0) + (Sum >> 1) + *pFcbName++;
    ///     }
    ///     return (Sum);
    /// }
    pub checksum: u8,
    /// LFN_Name2
    ///
    /// The second part of the long file name
    pub name2: [u8; 12],
    /// LDIR_FstClusLO
    ///
    /// The low word of the first cluster number
    pub first_cluster_low: [u8; 2],
    /// LFN_Name3
    ///
    /// The third part of the long file name
    pub name3: [u8; 4],
}

#[repr(C, packed)]
#[derive(Clone, Copy)]
/// Raw directory entry interpreted as a short entry, LFN entry, or bytes.
pub union RawDirectoryEntry {
    /// Short-name file or directory representation.
    pub file: RawFileEntry,
    #[cfg(feature = "lfn")]
    /// Long-file-name component representation.
    pub lfn: RawLfnEntry,
    /// Uninterpreted 32-byte representation.
    pub bytes: [u8; 32],
}

impl RawDirectoryEntry {
    /// Returns the entry's raw attribute byte.
    pub fn attributes(&self) -> u8 {
        unsafe { self.file }.attributes
    }
}

// Bytemuck implementations for RawDirectoryEntry union
// These are needed for read_struct to work
unsafe impl bytemuck::NoUninit for RawDirectoryEntry {}
unsafe impl bytemuck::Zeroable for RawDirectoryEntry {}
unsafe impl bytemuck::AnyBitPattern for RawDirectoryEntry {}

/// Raw FAT32 FSInfo sector.
///
/// @hadris-spec FAT:FSInfo
/// @hadris-compliance full
/// @hadris-tests comprehensive_fat::test_fsinfo_free_cluster_unknown
/// @hadris-fuzz fat_read
#[repr(C, packed)]
#[derive(Clone, Copy)]
pub struct RawFsInfo {
    /// FSI_LeadSig
    ///
    /// The lead signature, this have to be 0x41615252, or 'RRaA'
    pub signature: [u8; 4],
    /// FSI_Reserved1
    pub reserved1: [u8; 480],
    /// FSI_StrucSig
    ///
    /// The structure signature, this have to be 0x61417272, or 'rrAa'
    pub structure_signature: [u8; 4],
    /// FSI_Free_Count
    ///
    /// The number of free clusters, this have to be bigger than 0, and less than or equal to the
    /// total number of clusters
    /// This should remove any used clusters for headers, FAT tables, etc...
    pub free_count: U32<LittleEndian>,
    /// FSI_Nxt_Free
    ///
    /// The next free cluster number, this have to be bigger than 2, and less than or equal to the
    pub next_free: U32<LittleEndian>,
    /// FSI_Reserved2
    pub reserved2: [u8; 12],
    /// FSI_TrailSig
    ///
    /// The trail signature, this have to be 0xAA550000
    pub trail_signature: U32<LittleEndian>,
}

// Safety: RawFsInfo is a C-repr packed struct
unsafe impl bytemuck::NoUninit for RawFsInfo {}
unsafe impl bytemuck::Zeroable for RawFsInfo {}
unsafe impl bytemuck::AnyBitPattern for RawFsInfo {}

bitflags::bitflags! {
    /// Attribute bits stored in a FAT directory entry.
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct DirEntryAttrFlags: u8 {
        /// Entry is read-only.
        const READ_ONLY = 1 << 0;
        /// Entry is hidden.
        const HIDDEN = 1 << 1;
        /// Entry is a system file.
        const SYSTEM = 1 << 2;
        /// Entry contains a volume label.
        const VOLUME_ID = 1 << 3;
        /// Entry is a directory.
        const DIRECTORY = 1 << 4;
        /// Entry has the archive bit set.
        const ARCHIVE = 1 << 5;
    }
}

bitflags::bitflags! {
    /// Windows NT 8.3 name case flags stored in the `DIR_NTRes` byte.
    ///
    /// FAT stores 8.3 short names uppercase on disk. Windows records in this
    /// byte whether the base name and/or extension were originally lowercase
    /// so an all-lowercase (or lowercase-base / lowercase-ext) 8.3 name can be
    /// presented in its original case without spending a long-file-name entry.
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub struct NtCaseFlags: u8 {
        /// The base name (characters before the dot) was originally lowercase.
        const LOWER_BASE = 0x08;
        /// The extension (characters after the dot) was originally lowercase.
        const LOWER_EXT = 0x10;
    }
}

impl DirEntryAttrFlags {
    /// Attribute combination identifying a long-file-name component.
    pub const LONG_NAME: Self = Self::from_bits_truncate(
        Self::READ_ONLY.bits() | Self::HIDDEN.bits() | Self::SYSTEM.bits() | Self::VOLUME_ID.bits(),
    );

    /// True for a root-directory volume label entry (`VOLUME_ID` set, not a
    /// directory, not an LFN component).
    pub fn is_volume_label_entry(self) -> bool {
        self.contains(Self::VOLUME_ID) && !self.contains(Self::DIRECTORY) && self != Self::LONG_NAME
    }
}