kernel/fs/vfs_v2/drivers/fat32/
structures.rs

1//! FAT32 data structures
2//!
3//! This module defines the on-disk data structures used by the FAT32 filesystem.
4//! All structures are packed and follow the Microsoft FAT32 specification.
5
6use alloc::{format, string::String, string::ToString, vec::Vec};
7use core::mem;
8
9/// FAT32 Boot Sector structure
10///
11/// This structure represents the boot sector (first sector) of a FAT32 filesystem.
12/// It contains essential information about the filesystem layout and parameters.
13#[derive(Debug, Clone, Copy)]
14#[repr(C, packed)]
15pub struct Fat32BootSector {
16    /// Jump instruction (3 bytes)
17    pub jump_instruction: [u8; 3],
18    /// OEM name (8 bytes)
19    pub oem_name: [u8; 8],
20    /// Bytes per sector (typically 512)
21    pub bytes_per_sector: u16,
22    /// Sectors per cluster (must be power of 2)
23    pub sectors_per_cluster: u8,
24    /// Number of reserved sectors (including boot sector)
25    pub reserved_sectors: u16,
26    /// Number of FAT copies (typically 2)
27    pub fat_count: u8,
28    /// Maximum number of root directory entries (0 for FAT32)
29    pub max_root_entries: u16,
30    /// Total sectors (16-bit, 0 for FAT32)
31    pub total_sectors_16: u16,
32    /// Media descriptor
33    pub media_descriptor: u8,
34    /// Sectors per FAT (16-bit, 0 for FAT32)
35    pub sectors_per_fat_16: u16,
36    /// Sectors per track
37    pub sectors_per_track: u16,
38    /// Number of heads
39    pub heads: u16,
40    /// Hidden sectors
41    pub hidden_sectors: u32,
42    /// Total sectors (32-bit)
43    pub total_sectors_32: u32,
44    /// Sectors per FAT (32-bit)
45    pub sectors_per_fat: u32,
46    /// Extended flags
47    pub extended_flags: u16,
48    /// Filesystem version
49    pub fs_version: u16,
50    /// Root directory cluster number
51    pub root_cluster: u32,
52    /// Filesystem info sector number
53    pub fs_info_sector: u16,
54    /// Backup boot sector location
55    pub backup_boot_sector: u16,
56    /// Reserved bytes
57    pub reserved: [u8; 12],
58    /// Drive number
59    pub drive_number: u8,
60    /// Reserved
61    pub reserved1: u8,
62    /// Boot signature
63    pub boot_signature: u8,
64    /// Volume serial number
65    pub volume_serial: u32,
66    /// Volume label
67    pub volume_label: [u8; 11],
68    /// Filesystem type string
69    pub fs_type: [u8; 8],
70    /// Boot code
71    pub boot_code: [u8; 420],
72    /// Boot sector signature (0xAA55)
73    pub signature: u16,
74}
75
76impl Fat32BootSector {
77    /// Check if this is a valid FAT32 boot sector
78    pub fn is_valid(&self) -> bool {
79        // Check signature
80        if self.signature != 0xAA55 {
81            return false;
82        }
83
84        // Check bytes per sector
85        match self.bytes_per_sector {
86            512 | 1024 | 2048 | 4096 => {}
87            _ => return false,
88        }
89
90        // Check sectors per cluster (must be power of 2)
91        if self.sectors_per_cluster == 0
92            || (self.sectors_per_cluster & (self.sectors_per_cluster - 1)) != 0
93        {
94            return false;
95        }
96
97        // For FAT32, max_root_entries should be 0
98        if self.max_root_entries != 0 {
99            return false;
100        }
101
102        // For FAT32, sectors_per_fat_16 should be 0
103        if self.sectors_per_fat_16 != 0 {
104            return false;
105        }
106
107        true
108    }
109
110    /// Get the total number of sectors
111    pub fn total_sectors(&self) -> u32 {
112        if self.total_sectors_16 != 0 {
113            self.total_sectors_16 as u32
114        } else {
115            self.total_sectors_32
116        }
117    }
118
119    /// Calculate the first data sector
120    pub fn first_data_sector(&self) -> u32 {
121        self.reserved_sectors as u32 + (self.fat_count as u32 * self.sectors_per_fat)
122    }
123
124    /// Calculate the number of data sectors
125    pub fn data_sectors(&self) -> u32 {
126        let total_sectors = self.total_sectors();
127        let first_data_sector = self.first_data_sector();
128        total_sectors - first_data_sector
129    }
130
131    /// Calculate the number of clusters
132    pub fn cluster_count(&self) -> u32 {
133        self.data_sectors() / self.sectors_per_cluster as u32
134    }
135}
136
137/// FAT32 Directory Entry structure
138///
139/// This structure represents a single directory entry in a FAT32 directory.
140/// Each entry is exactly 32 bytes.
141#[derive(Debug, Clone, Copy)]
142#[repr(C, packed)]
143pub struct Fat32DirectoryEntry {
144    /// Filename (8.3 format, padded with spaces)
145    pub name: [u8; 11],
146    /// File attributes
147    pub attributes: u8,
148    /// Reserved for Windows NT
149    pub nt_reserved: u8,
150    /// Creation time (tenths of a second)
151    pub creation_time_tenths: u8,
152    /// Creation time
153    pub creation_time: u16,
154    /// Creation date
155    pub creation_date: u16,
156    /// Last access date
157    pub last_access_date: u16,
158    /// High 16 bits of cluster number
159    pub cluster_high: u16,
160    /// Last modification time
161    pub modification_time: u16,
162    /// Last modification date
163    pub modification_date: u16,
164    /// Low 16 bits of cluster number
165    pub cluster_low: u16,
166    /// File size in bytes
167    pub file_size: u32,
168}
169
170impl Fat32DirectoryEntry {
171    /// Check if this entry is free (available for use)
172    pub fn is_free(&self) -> bool {
173        self.name[0] == 0x00 || self.name[0] == 0xE5
174    }
175
176    /// Check if this is the last entry in the directory
177    pub fn is_last(&self) -> bool {
178        self.name[0] == 0x00
179    }
180
181    /// Check if this is a long filename entry
182    pub fn is_long_filename(&self) -> bool {
183        self.attributes == 0x0F
184    }
185
186    /// Get the starting cluster number
187    pub fn cluster(&self) -> u32 {
188        (self.cluster_high as u32) << 16 | (self.cluster_low as u32)
189    }
190
191    /// Set the starting cluster number
192    pub fn set_cluster(&mut self, cluster: u32) {
193        self.cluster_high = (cluster >> 16) as u16;
194        self.cluster_low = (cluster & 0xFFFF) as u16;
195    }
196
197    /// Update only the cluster and file size fields (preserves SFN and other metadata)
198    pub fn update_cluster_and_size(&mut self, cluster: u32, size: u32) {
199        self.set_cluster(cluster);
200        self.file_size = size;
201    }
202
203    /// Check if this is a directory
204    pub fn is_directory(&self) -> bool {
205        self.attributes & 0x10 != 0
206    }
207
208    /// Check if this is a regular file
209    pub fn is_file(&self) -> bool {
210        !self.is_directory() && !self.is_volume_label() && !self.is_long_filename()
211    }
212
213    /// Check if this is a volume label
214    pub fn is_volume_label(&self) -> bool {
215        self.attributes & 0x08 != 0
216    }
217
218    /// Get the filename as a string (8.3 format)
219    pub fn filename(&self) -> String {
220        // Handle special cases
221        if self.name[0] == 0x05 {
222            // This represents a filename starting with 0xE5
223            let mut name = self.name;
224            name[0] = 0xE5;
225            return Self::parse_filename(&name);
226        }
227
228        Self::parse_filename(&self.name)
229    }
230
231    /// Parse 8.3 filename format
232    fn parse_filename(name: &[u8; 11]) -> alloc::string::String {
233        use alloc::string::String;
234
235        let mut result = String::new();
236
237        // Extract the main filename (first 8 characters)
238        let mut main_name_len = 8;
239        for i in (0..8).rev() {
240            if name[i] != b' ' {
241                main_name_len = i + 1;
242                break;
243            }
244        }
245
246        if main_name_len == 0 {
247            return result;
248        }
249
250        for i in 0..main_name_len {
251            // Convert to lowercase for case-insensitive comparison
252            result.push((name[i] as char).to_ascii_lowercase());
253        }
254
255        // Check for extension (last 3 characters)
256        let mut ext_len = 3;
257        for i in (8..11).rev() {
258            if name[i] != b' ' {
259                ext_len = i - 8 + 1;
260                break;
261            }
262        }
263
264        if ext_len > 0 && name[8] != b' ' {
265            result.push('.');
266            for i in 8..(8 + ext_len) {
267                // Convert to lowercase for case-insensitive comparison
268                result.push((name[i] as char).to_ascii_lowercase());
269            }
270        }
271
272        result
273    }
274
275    /// Create a new directory entry
276    pub fn new_file(name: &str, cluster: u32, size: u32) -> Self {
277        let mut entry = Self {
278            name: [b' '; 11],
279            attributes: 0x00, // Regular file
280            nt_reserved: 0,
281            creation_time_tenths: 0,
282            creation_time: 0,
283            creation_date: 0,
284            last_access_date: 0,
285            cluster_high: (cluster >> 16) as u16,
286            modification_time: 0,
287            modification_date: 0,
288            cluster_low: (cluster & 0xFFFF) as u16,
289            file_size: size,
290        };
291
292        let sfn = Self::generate_sfn(name, 1);
293        entry.set_name(sfn);
294        entry
295    }
296
297    /// Create a new directory entry for a directory
298    pub fn new_directory(name: &str, cluster: u32) -> Self {
299        let mut entry = Self {
300            name: [b' '; 11],
301            attributes: 0x10, // Directory
302            nt_reserved: 0,
303            creation_time_tenths: 0,
304            creation_time: 0,
305            creation_date: 0,
306            last_access_date: 0,
307            cluster_high: (cluster >> 16) as u16,
308            modification_time: 0,
309            modification_date: 0,
310            cluster_low: (cluster & 0xFFFF) as u16,
311            file_size: 0, // Directories have size 0
312        };
313
314        let sfn = Self::generate_sfn(name, 1);
315        entry.set_name(sfn);
316        entry
317    }
318
319    /// Set the filename (8.3 format) - expects properly formatted SFN bytes
320    fn set_name(&mut self, name: [u8; 11]) {
321        self.name = name;
322    }
323
324    /// Generate proper 8.3 Short File Name (SFN) from a long filename
325    pub fn generate_sfn(name: &str, numeric_tail: u32) -> [u8; 11] {
326        let mut sfn = [b' '; 11];
327
328        // Split into name and extension
329        if let Some(dot_pos) = name.rfind('.') {
330            let main_name = &name[..dot_pos];
331            let extension = &name[dot_pos + 1..];
332
333            // Process main name
334            let main_name_upper = main_name.to_ascii_uppercase();
335            let main_bytes: Vec<u8> = main_name_upper
336                .bytes()
337                .filter(|&b| Self::is_valid_sfn_char(b))
338                .collect();
339
340            // Process extension
341            let extension_upper = extension.to_ascii_uppercase();
342            let ext_bytes: Vec<u8> = extension_upper
343                .bytes()
344                .filter(|&b| Self::is_valid_sfn_char(b))
345                .collect();
346
347            // Check if we need numeric tail (long filename or case conversion)
348            let needs_numeric_tail = main_bytes.len() > 8
349                || ext_bytes.len() > 3
350                || main_name != main_name_upper
351                || extension != extension_upper;
352
353            if needs_numeric_tail {
354                // Generate numeric tail format: BASENAME~N.EXT
355                let tail_str = format!("~{}", numeric_tail);
356                let available_chars = 8 - tail_str.len();
357
358                // Copy base name (truncated to fit tail)
359                let copy_len = core::cmp::min(main_bytes.len(), available_chars);
360                for i in 0..copy_len {
361                    sfn[i] = main_bytes[i];
362                }
363
364                // Add numeric tail
365                for (i, byte) in tail_str.bytes().enumerate() {
366                    if copy_len + i < 8 {
367                        sfn[copy_len + i] = byte;
368                    }
369                }
370            } else {
371                // Copy main name as-is (fits in 8.3)
372                let copy_len = core::cmp::min(main_bytes.len(), 8);
373                for i in 0..copy_len {
374                    sfn[i] = main_bytes[i];
375                }
376            }
377
378            // Copy extension (up to 3 characters)
379            let ext_len = core::cmp::min(ext_bytes.len(), 3);
380            for i in 0..ext_len {
381                sfn[8 + i] = ext_bytes[i];
382            }
383        } else {
384            // No extension
385            let main_name_upper = name.to_ascii_uppercase();
386            let main_bytes: Vec<u8> = main_name_upper
387                .bytes()
388                .filter(|&b| Self::is_valid_sfn_char(b))
389                .collect();
390
391            // Check if we need numeric tail (long filename or case conversion)
392            let needs_numeric_tail = main_bytes.len() > 8 || name != main_name_upper;
393
394            if needs_numeric_tail {
395                // Generate numeric tail format: BASENAME~N
396                let tail_str = format!("~{}", numeric_tail);
397                let available_chars = 8 - tail_str.len();
398
399                // Copy base name (truncated to fit tail)
400                let copy_len = core::cmp::min(main_bytes.len(), available_chars);
401                for i in 0..copy_len {
402                    sfn[i] = main_bytes[i];
403                }
404
405                // Add numeric tail
406                for (i, byte) in tail_str.bytes().enumerate() {
407                    if copy_len + i < 8 {
408                        sfn[copy_len + i] = byte;
409                    }
410                }
411            } else {
412                // Copy name as-is (fits in 8 chars)
413                let copy_len = core::cmp::min(main_bytes.len(), 8);
414                for i in 0..copy_len {
415                    sfn[i] = main_bytes[i];
416                }
417            }
418        }
419
420        sfn
421    }
422
423    /// Check if a character is valid for SFN
424    fn is_valid_sfn_char(c: u8) -> bool {
425        match c {
426            // Invalid characters for SFN
427            b'"' | b'*' | b'+' | b',' | b'/' | b':' | b';' | b'<' | b'=' | b'>' | b'?' | b'['
428            | b'\\' | b']' | b'|' | b' ' => false,
429            // Control characters (0x00-0x1F)
430            0x00..=0x1F => false,
431            // Valid characters
432            _ => true,
433        }
434    }
435}
436
437/// FAT entry constants
438pub const FAT32_EOC: u32 = 0x0FFFFFF8; // End of chain marker
439pub const FAT32_BAD: u32 = 0x0FFFFFF7; // Bad cluster marker
440pub const FAT32_FREE: u32 = 0x00000000; // Free cluster marker
441
442/// Directory entry attribute constants
443pub const ATTR_READ_ONLY: u8 = 0x01;
444pub const ATTR_HIDDEN: u8 = 0x02;
445pub const ATTR_SYSTEM: u8 = 0x04;
446pub const ATTR_VOLUME_ID: u8 = 0x08;
447pub const ATTR_DIRECTORY: u8 = 0x10;
448pub const ATTR_ARCHIVE: u8 = 0x20;
449pub const ATTR_LONG_NAME: u8 = 0x0F;
450
451/// Directory entry size in bytes
452pub const DIR_ENTRY_SIZE: usize = mem::size_of::<Fat32DirectoryEntry>();
453
454// Ensure structures have correct sizes
455const _: () = assert!(mem::size_of::<Fat32BootSector>() == 512);
456const _: () = assert!(mem::size_of::<Fat32DirectoryEntry>() == 32);
457const _: () = assert!(mem::size_of::<Fat32FsInfo>() == 512);
458
459/// Internal data structures for Scarlet's FAT32 implementation
460///
461/// This module provides high-level, easy-to-use representations of FAT32 data
462/// for internal use within the filesystem implementation.
463///
464/// Internal representation of a FAT32 directory entry for Scarlet's use
465///
466/// This structure combines SFN and LFN information into a single, easy-to-use format.
467/// It abstracts away the complexities of the on-disk format while providing all
468/// necessary information for filesystem operations.
469#[derive(Debug, Clone)]
470pub struct Fat32DirectoryEntryInternal {
471    /// The primary filename (preferring LFN if available, otherwise SFN)
472    pub filename: String,
473    /// Short filename (8.3 format) for compatibility
474    pub short_filename: String,
475    /// File attributes
476    pub attributes: u8,
477    /// Starting cluster number
478    pub start_cluster: u32,
479    /// File size in bytes
480    pub file_size: u32,
481    /// Creation time and date information
482    pub creation_time: FileTime,
483    /// Last modification time and date information
484    pub modification_time: FileTime,
485    /// Last access date
486    pub last_access_date: u16,
487}
488
489/// Time and date information for files
490#[derive(Debug, Clone, Copy)]
491pub struct FileTime {
492    /// Time (packed format)
493    pub time: u16,
494    /// Date (packed format)  
495    pub date: u16,
496    /// Tenths of a second for creation time
497    pub tenths: u8,
498}
499
500impl Fat32DirectoryEntryInternal {
501    /// Create from a raw FAT32 directory entry
502    pub fn from_raw_entry(raw_entry: &Fat32DirectoryEntry) -> Self {
503        let short_filename = Self::parse_sfn(&raw_entry.name);
504
505        Self {
506            filename: short_filename.clone(), // Will be updated with LFN if available
507            short_filename,
508            attributes: raw_entry.attributes,
509            start_cluster: raw_entry.cluster(),
510            file_size: raw_entry.file_size,
511            creation_time: FileTime {
512                time: raw_entry.creation_time,
513                date: raw_entry.creation_date,
514                tenths: raw_entry.creation_time_tenths,
515            },
516            modification_time: FileTime {
517                time: raw_entry.modification_time,
518                date: raw_entry.modification_date,
519                tenths: 0,
520            },
521            last_access_date: raw_entry.last_access_date,
522        }
523    }
524
525    /// Create from a raw FAT32 directory entry (legacy compatibility)
526    pub fn from_raw(raw_entry: Fat32DirectoryEntry) -> Self {
527        Self::from_raw_entry(&raw_entry)
528    }
529
530    /// Get the primary filename (preferring LFN if different from SFN)
531    pub fn name(&self) -> String {
532        self.filename.clone()
533    }
534
535    /// Get the cluster number  
536    pub fn cluster(&self) -> u32 {
537        self.start_cluster
538    }
539
540    /// Get the file size
541    pub fn size(&self) -> u32 {
542        self.file_size
543    }
544
545    /// Set the long filename (used when LFN entries are available)
546    pub fn set_long_filename(&mut self, lfn: String) {
547        self.filename = lfn;
548    }
549
550    /// Check if this entry represents a directory
551    pub fn is_directory(&self) -> bool {
552        (self.attributes & 0x10) != 0
553    }
554
555    /// Check if this entry represents a regular file
556    pub fn is_file(&self) -> bool {
557        !self.is_directory() && (self.attributes & 0x08) == 0
558    }
559
560    /// Check if this entry is hidden
561    pub fn is_hidden(&self) -> bool {
562        (self.attributes & 0x02) != 0
563    }
564
565    /// Check if this entry is read-only
566    pub fn is_read_only(&self) -> bool {
567        (self.attributes & 0x01) != 0
568    }
569
570    /// Parse SFN (8.3 format) into a readable filename
571    fn parse_sfn(name: &[u8; 11]) -> String {
572        let mut result = String::new();
573
574        // Extract the main filename (first 8 characters)
575        let mut main_name_len = 8;
576        for i in (0..8).rev() {
577            if name[i] != b' ' {
578                main_name_len = i + 1;
579                break;
580            }
581        }
582
583        if main_name_len == 0 {
584            return result;
585        }
586
587        for i in 0..main_name_len {
588            result.push((name[i] as char).to_ascii_lowercase());
589        }
590
591        // Check for extension (last 3 characters)
592        let mut ext_len = 3;
593        for i in (8..11).rev() {
594            if name[i] != b' ' {
595                ext_len = i - 8 + 1;
596                break;
597            }
598        }
599
600        if ext_len > 0 && name[8] != b' ' {
601            result.push('.');
602            for i in 8..(8 + ext_len) {
603                result.push((name[i] as char).to_ascii_lowercase());
604            }
605        }
606
607        result
608    }
609
610    /// Convert to a raw FAT32 directory entry for writing to disk
611    pub fn to_raw_entry(&self) -> Fat32DirectoryEntry {
612        let mut raw_entry = Fat32DirectoryEntry {
613            name: [b' '; 11],
614            attributes: self.attributes,
615            nt_reserved: 0,
616            creation_time_tenths: self.creation_time.tenths,
617            creation_time: self.creation_time.time,
618            creation_date: self.creation_time.date,
619            last_access_date: self.last_access_date,
620            cluster_high: (self.start_cluster >> 16) as u16,
621            modification_time: self.modification_time.time,
622            modification_date: self.modification_time.date,
623            cluster_low: (self.start_cluster & 0xFFFF) as u16,
624            file_size: self.file_size,
625        };
626
627        // Generate and set the short filename from the original filename
628        let sfn = Fat32DirectoryEntry::generate_sfn(&self.filename, 1);
629        raw_entry.set_name(sfn);
630        raw_entry
631    }
632}
633
634/// FAT32 Filesystem Information Sector
635#[derive(Debug, Clone, Copy)]
636#[repr(C, packed)]
637pub struct Fat32FsInfo {
638    /// Lead signature (0x41615252)
639    pub lead_signature: u32,
640    /// Reserved bytes
641    pub reserved1: [u8; 480],
642    /// Structure signature (0x61417272)
643    pub structure_signature: u32,
644    /// Free cluster count (0xFFFFFFFF if unknown)
645    pub free_cluster_count: u32,
646    /// Next free cluster hint
647    pub next_free_cluster: u32,
648    /// Reserved bytes
649    pub reserved2: [u8; 12],
650    /// Trail signature (0xAA550000)
651    pub trail_signature: u32,
652}
653
654impl Fat32FsInfo {
655    /// Check if this is a valid FSInfo sector
656    pub fn is_valid(&self) -> bool {
657        self.lead_signature == 0x41615252
658            && self.structure_signature == 0x61417272
659            && self.trail_signature == 0xAA550000
660    }
661}
662
663/// Long File Name (LFN) directory entry
664#[derive(Debug, Clone, Copy)]
665#[repr(C, packed)]
666pub struct Fat32LFNEntry {
667    /// Sequence number (1-based, with 0x40 bit for last entry)
668    pub sequence: u8,
669    /// First 5 characters (10 bytes, UTF-16LE)
670    pub name1: [u16; 5],
671    /// Attributes (always 0x0F for LFN)
672    pub attributes: u8,
673    /// Type (always 0 for LFN)
674    pub entry_type: u8,
675    /// Checksum of corresponding SFN
676    pub checksum: u8,
677    /// Characters 6-11 (12 bytes, UTF-16LE)
678    pub name2: [u16; 6],
679    /// First cluster (always 0 for LFN)
680    pub cluster: u16,
681    /// Characters 12-13 (4 bytes, UTF-16LE)
682    pub name3: [u16; 2],
683}
684
685impl Fat32LFNEntry {
686    /// Check if this is an LFN entry
687    pub fn is_lfn(&self) -> bool {
688        self.attributes == 0x0F
689    }
690
691    /// Check if this is the last LFN entry in sequence
692    pub fn is_last_lfn(&self) -> bool {
693        (self.sequence & 0x40) != 0
694    }
695
696    /// Get sequence number (without the last entry flag)
697    pub fn sequence_number(&self) -> u8 {
698        self.sequence & 0x3F
699    }
700
701    /// Extract characters from this LFN entry
702    pub fn extract_chars(&self) -> Vec<u16> {
703        let mut chars = Vec::new();
704
705        // Add name1 (5 chars) - use read_unaligned for packed struct
706        let name1_copy = self.name1;
707        for ch in name1_copy {
708            if ch != 0 && ch != 0xFFFF {
709                chars.push(ch);
710            }
711        }
712
713        // Add name2 (6 chars) - use read_unaligned for packed struct
714        let name2_copy = self.name2;
715        for ch in name2_copy {
716            if ch != 0 && ch != 0xFFFF {
717                chars.push(ch);
718            }
719        }
720
721        // Add name3 (2 chars) - use read_unaligned for packed struct
722        let name3_copy = self.name3;
723        for ch in name3_copy {
724            if ch != 0 && ch != 0xFFFF {
725                chars.push(ch);
726            }
727        }
728
729        chars
730    }
731}
732
733/// Builder for constructing Fat32DirectoryEntryInternal entries
734pub struct Fat32DirectoryEntryBuilder {
735    entry: Fat32DirectoryEntryInternal,
736}
737
738impl Fat32DirectoryEntryBuilder {
739    /// Create a new builder for a file entry
740    pub fn new_file(filename: &str, cluster: u32, size: u32) -> Self {
741        let short_filename = Self::generate_short_filename(filename);
742
743        Self {
744            entry: Fat32DirectoryEntryInternal {
745                filename: filename.to_string(),
746                short_filename,
747                attributes: 0, // Regular file
748                start_cluster: cluster,
749                file_size: size,
750                creation_time: FileTime {
751                    time: 0,
752                    date: 0,
753                    tenths: 0,
754                },
755                modification_time: FileTime {
756                    time: 0,
757                    date: 0,
758                    tenths: 0,
759                },
760                last_access_date: 0,
761            },
762        }
763    }
764
765    /// Create a new builder for a directory entry
766    pub fn new_directory(dirname: &str, cluster: u32) -> Self {
767        let short_filename = Self::generate_short_filename(dirname);
768
769        Self {
770            entry: Fat32DirectoryEntryInternal {
771                filename: dirname.to_string(),
772                short_filename,
773                attributes: 0x10, // Directory
774                start_cluster: cluster,
775                file_size: 0, // Directories have size 0
776                creation_time: FileTime {
777                    time: 0,
778                    date: 0,
779                    tenths: 0,
780                },
781                modification_time: FileTime {
782                    time: 0,
783                    date: 0,
784                    tenths: 0,
785                },
786                last_access_date: 0,
787            },
788        }
789    }
790
791    /// Set attributes
792    pub fn attributes(mut self, attrs: u8) -> Self {
793        self.entry.attributes |= attrs;
794        self
795    }
796
797    /// Build the final entry
798    pub fn build(self) -> Fat32DirectoryEntryInternal {
799        self.entry
800    }
801
802    /// Generate a short filename from a long filename
803    fn generate_short_filename(filename: &str) -> String {
804        // Simple implementation - truncate to 8.3 format
805        if let Some(dot_pos) = filename.rfind('.') {
806            let name_part = &filename[..dot_pos];
807            let ext_part = &filename[dot_pos + 1..];
808
809            let short_name = if name_part.len() <= 8 {
810                name_part.to_string()
811            } else {
812                format!("{:.6}~1", name_part)
813            };
814
815            let short_ext = if ext_part.len() <= 3 {
816                ext_part.to_string()
817            } else {
818                ext_part[..3].to_string()
819            };
820
821            format!("{}.{}", short_name, short_ext).to_uppercase()
822        } else {
823            // No extension
824            if filename.len() <= 8 {
825                filename.to_uppercase()
826            } else {
827                format!("{:.6}~1", filename).to_uppercase()
828            }
829        }
830    }
831}