Skip to main content

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}