hadris_fat/raw.rs
1use hadris_common::types::{
2 endian::LittleEndian,
3 number::{U16, U32},
4};
5
6/// The RawBpb struct represents the boot sector of any FAT partition
7///
8/// This only contains the common fields of the boot sector, and is not meant to be used directly
9/// for reading or writing to the boot sector, for that, see `RawBootSector`, which contains
10/// the boot sector and the extended boot sector
11///
12/// @hadris-spec FAT:BPB
13/// @hadris-compliance full
14/// @hadris-tests comprehensive_fat::test_valid_sector_sizes
15/// @hadris-fuzz fat_read
16#[repr(C)]
17#[derive(Clone, Copy)]
18pub struct RawBpb {
19 /// BS_jmpBoot
20 pub jump: [u8; 3],
21 /// BS_OEMName
22 /// The name of the program that formatted the partition
23 pub oem_name: [u8; 8],
24 /// BPB_BytsPerSec
25 /// The number of bytes per sector
26 pub bytes_per_sector: U16<LittleEndian>,
27 /// BPB_SecPerClus
28 /// The number of sectors per cluster
29 pub sectors_per_cluster: u8,
30 /// BPB_RsvdSecCnt
31 ///
32 /// The number of reserved sectors, should be nonzero, ans should be a multiple of the sectors per cluster
33 /// This is used to:
34 /// 1. align the start of the filesystem to the sectors per cluster
35 /// 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
36 pub reserved_sector_count: U16<LittleEndian>,
37 /// BPB_NumFATs
38 ///
39 /// The number of fats, 1 is acceptable, but 2 is recommended
40 pub fat_count: u8,
41 /// BPB_RootEntCnt
42 ///
43 /// The number of root directory entries
44 /// For FAT32, this should be 0
45 /// For FAT12/16, this value multiplied by 32 should be a multiple of the bytes per sector
46 /// For FAT16, it is recommended to set this to 512 for maximum compatibility
47 pub root_entry_count: [u8; 2],
48 /// BPB_TotSec16
49 ///
50 /// The number of sectors
51 /// For FAT32, this should be 0
52 /// For FAT16, if the number of sectors is greater than 0x10000, you should use total_sectors_32
53 pub total_sectors_16: [u8; 2],
54 /// BPB_Media
55 ///
56 /// See the MediaType enum for more information
57 pub media_type: u8,
58 /// BPB_FATSz16
59 ///
60 /// The number of sectors per fat
61 /// For FAT32, this should be 0
62 pub sectors_per_fat_16: [u8; 2],
63 /// BPB_SecPerTrk
64 ///
65 /// The number of sectors per track
66 /// This is only relevant for media with have a geometry and used by BIOS interrupt 0x13
67 pub sectors_per_track: [u8; 2],
68 /// BPB_NumHeads
69 ///
70 /// Similar situation as sectors_per_track
71 pub num_heads: [u8; 2],
72 /// BPB_HiddSec
73 ///
74 /// The number of hidden sectors predicing the partition that contains the FAT volume.
75 /// This must be 0 on media that isn't partitioned
76 pub hidden_sector_count: [u8; 4],
77 /// BPB_TotSec32
78 ///
79 /// The total number of sectors for FAT32
80 /// For FAT16 use, see total_sectors_16
81 pub total_sectors_32: [u8; 4],
82}
83
84// Safety: RawBpb is a C-repr struct with no padding issues for its fields
85unsafe impl bytemuck::NoUninit for RawBpb {}
86unsafe impl bytemuck::Zeroable for RawBpb {}
87unsafe impl bytemuck::AnyBitPattern for RawBpb {}
88
89#[repr(C)]
90#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
91/// FAT12/16 extended BIOS parameter block and boot-sector payload.
92pub struct RawBpbExt16 {
93 /// BS_DrvNum
94 pub drive_number: u8,
95 /// BS_Reserved1
96 pub reserved1: u8,
97 /// BS_BootSig
98 ///
99 /// The extended boot signature, should be 0x29
100 pub ext_boot_signature: u8,
101 /// BS_VolID
102 ///
103 /// Volumme Serial Number
104 /// This ID should be unique for each volume
105 pub volume_id: [u8; 4],
106 /// BS_VolLab
107 ///
108 /// Volume label
109 /// This should be "NO NAME " if the volume is not labeled
110 pub volume_label: [u8; 11],
111 /// BS_FilSysType
112 ///
113 /// Must be set to the strings "FAT12 ","FAT16 ", or "FAT "
114 pub fs_type: [u8; 8],
115 /// Zeros
116 /// To make it compatible with bytemuck, instead of using [u8; 448], we use 256 + 128 + 64
117 pub padding1: [u8; 448],
118 /// Signature_word
119 ///
120 /// The signature word, should be 0xAA55
121 pub signature_word: [u8; 2],
122}
123
124#[repr(C)]
125#[derive(Clone, Copy)]
126/// FAT32 extended BIOS parameter block and boot-sector payload.
127pub struct RawBpbExt32 {
128 /// BPB_FatSz32
129 ///
130 /// The number of sectors per fat
131 /// BPB_FATSz16 must be 0
132 pub sectors_per_fat_32: U32<LittleEndian>,
133 /// BPB_ExtFlags
134 ///
135 /// See the BpbExt32Flags struct for more information
136 pub ext_flags: [u8; 2],
137 /// BPB_FSVer
138 ///
139 /// The version of the file system
140 /// This must be set to 0x00
141 pub version: [u8; 2],
142 /// BPB_RootClus
143 ///
144 /// The cluster number of the root directory
145 /// This should be 2, or the first usable (not bad) cluster usable
146 pub root_cluster: U32<LittleEndian>,
147 /// BPB_FSInfo
148 ///
149 /// The sector number of the FSINFO structure
150 /// NOTE: There is a copy of the FSINFO structure in the
151 /// sequence of backup boot sectors, but only the copy
152 /// pointed to by this field is kept up to date (i.e., both the
153 /// primary and backup boot record point to the same
154 /// FSINFO sector)
155 pub fs_info_sector: U16<LittleEndian>,
156 /// BPB_BkBootSec
157 ///
158 /// The sector number of the backup boot sector
159 /// If set to 6 (only valid non-zero value), the boot sector
160 /// in the reserved area is used to store the backup boot sector
161 pub boot_sector: [u8; 2],
162 /// BPB_Reserved
163 /// Reserved, should be zero
164 pub reserved: [u8; 12],
165 /// BS_DrvNum
166 ///
167 /// The BIOS interrupt 0x13 drive number
168 /// Should be 0x80 or 0x00
169 pub drive_number: u8,
170 /// BS_Reserved1
171 /// Reserved, should be zero
172 pub reserved1: u8,
173 /// BS_BootSig
174 ///
175 /// The extended boot signature, should be 0x29
176 pub ext_boot_signature: u8,
177 /// BS_VolID
178 ///
179 /// Volumme Serial Number
180 /// This ID should be unique for each volume
181 pub volume_id: [u8; 4],
182 /// BS_VolLab
183 ///
184 /// Volume label
185 /// This should be "NO NAME " if the volume is not labeled
186 pub volume_label: [u8; 11],
187 /// BS_FilSysType
188 ///
189 /// Must be set to the string "FAT32 "
190 pub fs_type: [u8; 8],
191 /// Zeros
192 pub padding1: [u8; 420],
193 /// Signature_word
194 ///
195 /// The signature word, should be 0xAA55
196 pub signature_word: U16<LittleEndian>,
197}
198
199// Safety: RawBpbExt32 is a C-repr struct with no padding issues
200unsafe impl bytemuck::NoUninit for RawBpbExt32 {}
201unsafe impl bytemuck::Zeroable for RawBpbExt32 {}
202unsafe impl bytemuck::AnyBitPattern for RawBpbExt32 {}
203
204/// BPB_ExtFlags
205///
206/// This is a union of the flags that are set in the BPB_ExtFlags field
207/// The flags are the following:
208/// bits 0-3: zero based index of the active FAT, mirroring must be disabled
209/// bits 4-6: reserved
210/// bit 7: FAT mirroring is enabled
211/// bits 8-15: reserved
212#[repr(transparent)]
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub struct BpbExt32Flags(u16);
215
216/// Raw 32-byte FAT short-name file or directory entry.
217///
218/// @hadris-spec FAT:DirEntry
219/// @hadris-compliance partial
220/// @hadris-note Name/attributes/timestamps/cluster/size and NT case flags (`DIR_NTRes`) are read and written; extended access-time granularity is not modeled.
221/// @hadris-tests test_write::test_lowercase_short_name_uses_nt_case_flags
222/// @hadris-fuzz fat_read
223#[repr(C, packed)]
224#[derive(Clone, Copy)]
225pub struct RawFileEntry {
226 /// DIR_Name
227 ///
228 /// The name of the file, padded with spaces, and in the 8.3 format
229 /// A value of 0xE5 indicates that the directory is free. For kanji, 0x05 is used instead of 0xE5
230 /// The special value 0x00 also indicates that the directory is free, but also all the entries
231 /// following it are free
232 /// The name cannot start with a space
233 /// Only upper case letters, digits, and the following characters are allowed:
234 /// $ % ' - _ @ ~ ` ! ( ) { } ^ # &
235 pub name: [u8; 11],
236 /// DIR_Attr
237 ///
238 /// The file attributes
239 pub attributes: u8,
240 /// DIR_NTRes
241 ///
242 /// Reserved for use by Windows NT. In practice Windows stores 8.3 name
243 /// case information here: bit 3 (`0x08`) marks the base name as originally
244 /// lowercase and bit 4 (`0x10`) marks the extension as originally
245 /// lowercase. See [`NtCaseFlags`].
246 pub reserved: u8,
247 /// DIR_CrtTimeTenth
248 ///
249 /// The creation time, in tenths of a second
250 pub creation_time_tenth: u8,
251 /// DIR_CrtTime
252 ///
253 /// The creation time, granularity is 2 seconds
254 pub creation_time: [u8; 2],
255 /// DIR_CrtDate
256 ///
257 /// The creation date
258 pub creation_date: [u8; 2],
259 /// DIR_LstAccDate
260 ///
261 /// The last access date
262 pub last_access_date: [u8; 2],
263 /// DIR_FstClusHI
264 ///
265 /// The high word of the first cluster number
266 pub first_cluster_high: U16<LittleEndian>,
267 /// DIR_WrtTime
268 ///
269 /// The last write time, granularity is 2 seconds
270 pub last_write_time: [u8; 2],
271 /// DIR_WrtDate
272 ///
273 /// The last write date
274 pub last_write_date: [u8; 2],
275 /// DIR_FstClusLO
276 ///
277 /// The low word of the first cluster number
278 pub first_cluster_low: U16<LittleEndian>,
279 /// DIR_FileSize
280 ///
281 /// The size of the file, in bytes
282 pub size: U32<LittleEndian>,
283}
284
285unsafe impl bytemuck::NoUninit for RawFileEntry {}
286unsafe impl bytemuck::Zeroable for RawFileEntry {}
287unsafe impl bytemuck::AnyBitPattern for RawFileEntry {}
288
289/// A long file name entry
290/// The maximum length of a long file name is 255 characters, not including the null terminator
291/// The characters allowed extend these characters:
292/// . + , ; = [ ]
293/// Embedded paces are also allowed
294/// The name is stored in UTF-16 encoding (UNICODE)
295/// When the unicode character cannot be translated to ANSI, an underscore is used
296///
297/// @hadris-spec FAT:LFN
298/// @hadris-compliance partial
299/// @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.
300/// @hadris-tests comprehensive_fat::test_lfn_builder_sequence
301/// @hadris-fuzz fat_read
302#[repr(C, packed)]
303#[derive(Clone, Copy)]
304pub struct RawLfnEntry {
305 /// LFN_Ord
306 ///
307 /// The order of the LFN entry, the contents must be masked with 0x40 for the last entry
308 pub sequence_number: u8,
309 /// LFN_Name1
310 ///
311 /// The first part of the long file name
312 pub name1: [u8; 10],
313 /// LDIR_Attr
314 ///
315 /// Attributes, must be set to: ATTR_LONG_NAME, which is:
316 /// ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID
317 pub attributes: u8,
318 /// LFN_Type
319 ///
320 /// The type of the LFN entry, must be set to 0
321 pub ty: u8,
322 /// LFN_Chksum
323 ///
324 /// Checksum of name in the associated short name directory entry at the end of the LFN sequence
325 /// THe algorithm described in the FAT spec is:
326 /// unsigned char ChkSum (unsigned char \*pFcbName)
327 /// {
328 /// short FcbNameLen;
329 /// unsigned char Sum;
330 /// Sum = 0;
331 /// for (FcbNameLen=11; FcbNameLen!=0; FcbNameLen--) {
332 /// // NOTE: The operation is an unsigned char rotate right
333 /// Sum = ((Sum & 1) ? 0x80 : 0) + (Sum >> 1) + *pFcbName++;
334 /// }
335 /// return (Sum);
336 /// }
337 pub checksum: u8,
338 /// LFN_Name2
339 ///
340 /// The second part of the long file name
341 pub name2: [u8; 12],
342 /// LDIR_FstClusLO
343 ///
344 /// The low word of the first cluster number
345 pub first_cluster_low: [u8; 2],
346 /// LFN_Name3
347 ///
348 /// The third part of the long file name
349 pub name3: [u8; 4],
350}
351
352#[repr(C, packed)]
353#[derive(Clone, Copy)]
354/// Raw directory entry interpreted as a short entry, LFN entry, or bytes.
355pub union RawDirectoryEntry {
356 /// Short-name file or directory representation.
357 pub file: RawFileEntry,
358 #[cfg(feature = "lfn")]
359 /// Long-file-name component representation.
360 pub lfn: RawLfnEntry,
361 /// Uninterpreted 32-byte representation.
362 pub bytes: [u8; 32],
363}
364
365impl RawDirectoryEntry {
366 /// Returns the entry's raw attribute byte.
367 pub fn attributes(&self) -> u8 {
368 unsafe { self.file }.attributes
369 }
370}
371
372// Bytemuck implementations for RawDirectoryEntry union
373// These are needed for read_struct to work
374unsafe impl bytemuck::NoUninit for RawDirectoryEntry {}
375unsafe impl bytemuck::Zeroable for RawDirectoryEntry {}
376unsafe impl bytemuck::AnyBitPattern for RawDirectoryEntry {}
377
378/// Raw FAT32 FSInfo sector.
379///
380/// @hadris-spec FAT:FSInfo
381/// @hadris-compliance full
382/// @hadris-tests comprehensive_fat::test_fsinfo_free_cluster_unknown
383/// @hadris-fuzz fat_read
384#[repr(C, packed)]
385#[derive(Clone, Copy)]
386pub struct RawFsInfo {
387 /// FSI_LeadSig
388 ///
389 /// The lead signature, this have to be 0x41615252, or 'RRaA'
390 pub signature: [u8; 4],
391 /// FSI_Reserved1
392 pub reserved1: [u8; 480],
393 /// FSI_StrucSig
394 ///
395 /// The structure signature, this have to be 0x61417272, or 'rrAa'
396 pub structure_signature: [u8; 4],
397 /// FSI_Free_Count
398 ///
399 /// The number of free clusters, this have to be bigger than 0, and less than or equal to the
400 /// total number of clusters
401 /// This should remove any used clusters for headers, FAT tables, etc...
402 pub free_count: U32<LittleEndian>,
403 /// FSI_Nxt_Free
404 ///
405 /// The next free cluster number, this have to be bigger than 2, and less than or equal to the
406 pub next_free: U32<LittleEndian>,
407 /// FSI_Reserved2
408 pub reserved2: [u8; 12],
409 /// FSI_TrailSig
410 ///
411 /// The trail signature, this have to be 0xAA550000
412 pub trail_signature: U32<LittleEndian>,
413}
414
415// Safety: RawFsInfo is a C-repr packed struct
416unsafe impl bytemuck::NoUninit for RawFsInfo {}
417unsafe impl bytemuck::Zeroable for RawFsInfo {}
418unsafe impl bytemuck::AnyBitPattern for RawFsInfo {}
419
420bitflags::bitflags! {
421 /// Attribute bits stored in a FAT directory entry.
422 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
423 pub struct DirEntryAttrFlags: u8 {
424 /// Entry is read-only.
425 const READ_ONLY = 1 << 0;
426 /// Entry is hidden.
427 const HIDDEN = 1 << 1;
428 /// Entry is a system file.
429 const SYSTEM = 1 << 2;
430 /// Entry contains a volume label.
431 const VOLUME_ID = 1 << 3;
432 /// Entry is a directory.
433 const DIRECTORY = 1 << 4;
434 /// Entry has the archive bit set.
435 const ARCHIVE = 1 << 5;
436 }
437}
438
439bitflags::bitflags! {
440 /// Windows NT 8.3 name case flags stored in the `DIR_NTRes` byte.
441 ///
442 /// FAT stores 8.3 short names uppercase on disk. Windows records in this
443 /// byte whether the base name and/or extension were originally lowercase
444 /// so an all-lowercase (or lowercase-base / lowercase-ext) 8.3 name can be
445 /// presented in its original case without spending a long-file-name entry.
446 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
447 pub struct NtCaseFlags: u8 {
448 /// The base name (characters before the dot) was originally lowercase.
449 const LOWER_BASE = 0x08;
450 /// The extension (characters after the dot) was originally lowercase.
451 const LOWER_EXT = 0x10;
452 }
453}
454
455impl DirEntryAttrFlags {
456 /// Attribute combination identifying a long-file-name component.
457 pub const LONG_NAME: Self = Self::from_bits_truncate(
458 Self::READ_ONLY.bits() | Self::HIDDEN.bits() | Self::SYSTEM.bits() | Self::VOLUME_ID.bits(),
459 );
460
461 /// True for a root-directory volume label entry (`VOLUME_ID` set, not a
462 /// directory, not an LFN component).
463 pub fn is_volume_label_entry(self) -> bool {
464 self.contains(Self::VOLUME_ID) && !self.contains(Self::DIRECTORY) && self != Self::LONG_NAME
465 }
466}