Skip to main content

hadris_fat/format/
options.rs

1//! FAT volume formatting options.
2//!
3//! This module provides configuration types for formatting FAT12/16/32 volumes.
4
5use super::super::fat_table::FatType;
6
7/// FAT volume formatting options.
8#[derive(Debug, Clone)]
9pub struct FatFormatOptions {
10    /// Total volume size in bytes
11    pub volume_size: u64,
12    /// Volume label (up to 11 characters)
13    pub volume_label: VolumeLabel,
14    /// OEM name (up to 8 characters, default "HADRISFT")
15    pub oem_name: OemName,
16    /// Sector size (512, 1024, 2048, or 4096 bytes)
17    pub sector_size: SectorSize,
18    /// Sectors per cluster (auto-calculated if None)
19    pub sectors_per_cluster: Option<u8>,
20    /// FAT type selection (auto or forced)
21    pub fat_type: FatTypeSelection,
22    /// Number of FAT copies (1 or 2, default 2)
23    pub fat_copies: u8,
24    /// Root directory entry count (FAT12/16 only, default 512)
25    pub root_entry_count: u16,
26    /// Hidden sectors (for partitioned media)
27    pub hidden_sectors: u32,
28    /// Media type descriptor
29    pub media_type: MediaType,
30    /// Volume ID (random if None)
31    pub volume_id: Option<u32>,
32}
33
34impl Default for FatFormatOptions {
35    fn default() -> Self {
36        Self {
37            volume_size: 0,
38            volume_label: VolumeLabel::default(),
39            oem_name: OemName::default(),
40            sector_size: SectorSize::default(),
41            sectors_per_cluster: None,
42            fat_type: FatTypeSelection::Auto,
43            fat_copies: 2,
44            root_entry_count: 512,
45            hidden_sectors: 0,
46            media_type: MediaType::FixedDisk,
47            volume_id: None,
48        }
49    }
50}
51
52impl FatFormatOptions {
53    /// Create new format options with the specified volume size.
54    pub fn new(volume_size: u64) -> Self {
55        Self {
56            volume_size,
57            ..Default::default()
58        }
59    }
60
61    /// Set the volume label.
62    pub fn volume_label(mut self, label: &str) -> Self {
63        self.volume_label = VolumeLabel::new(label);
64        self
65    }
66
67    /// Set the sector size.
68    pub fn sector_size(mut self, size: SectorSize) -> Self {
69        self.sector_size = size;
70        self
71    }
72
73    /// Set the FAT type selection.
74    pub fn fat_type(mut self, fat_type: FatTypeSelection) -> Self {
75        self.fat_type = fat_type;
76        self
77    }
78
79    /// Set sectors per cluster.
80    pub fn sectors_per_cluster(mut self, spc: u8) -> Self {
81        self.sectors_per_cluster = Some(spc);
82        self
83    }
84
85    /// Set the number of FAT copies.
86    pub fn fat_copies(mut self, copies: u8) -> Self {
87        self.fat_copies = copies.clamp(1, 2);
88        self
89    }
90
91    /// Set the media type.
92    pub fn media_type(mut self, media_type: MediaType) -> Self {
93        self.media_type = media_type;
94        self
95    }
96
97    /// Set hidden sectors count.
98    pub fn hidden_sectors(mut self, hidden: u32) -> Self {
99        self.hidden_sectors = hidden;
100        self
101    }
102
103    /// Set the volume ID.
104    pub fn volume_id(mut self, id: u32) -> Self {
105        self.volume_id = Some(id);
106        self
107    }
108
109}
110
111/// Volume label (11 characters max, space-padded).
112#[derive(Debug, Clone)]
113pub struct VolumeLabel([u8; 11]);
114
115impl VolumeLabel {
116    /// Create a new volume label from a string.
117    ///
118    /// The string is converted to uppercase, truncated to 11 characters,
119    /// and space-padded.
120    pub fn new(s: &str) -> Self {
121        let mut bytes = [b' '; 11];
122        for (i, c) in s.chars().take(11).enumerate() {
123            let c = c.to_ascii_uppercase();
124            if c.is_ascii() && Self::is_valid_char(c as u8) {
125                bytes[i] = c as u8;
126            } else {
127                bytes[i] = b'_';
128            }
129        }
130        Self(bytes)
131    }
132
133    /// Create a "NO NAME" volume label.
134    pub fn no_name() -> Self {
135        Self(*b"NO NAME    ")
136    }
137
138    /// Check if a character is valid for volume labels.
139    fn is_valid_char(c: u8) -> bool {
140        matches!(c, b'A'..=b'Z' | b'0'..=b'9' | b' ' | b'!' | b'#' | b'$' | b'%' | b'&' | b'\'' | b'(' | b')' | b'-' | b'@' | b'^' | b'_' | b'`' | b'{' | b'}' | b'~')
141    }
142
143    /// Get the raw bytes.
144    pub fn as_bytes(&self) -> &[u8; 11] {
145        &self.0
146    }
147}
148
149impl Default for VolumeLabel {
150    fn default() -> Self {
151        Self::no_name()
152    }
153}
154
155/// OEM name (8 characters max, space-padded).
156#[derive(Debug, Clone)]
157pub struct OemName([u8; 8]);
158
159impl OemName {
160    /// Create a new OEM name from a string.
161    pub fn new(s: &str) -> Self {
162        let mut bytes = [b' '; 8];
163        for (i, c) in s.chars().take(8).enumerate() {
164            if c.is_ascii() {
165                bytes[i] = c as u8;
166            }
167        }
168        Self(bytes)
169    }
170
171    /// Get the raw bytes.
172    pub fn as_bytes(&self) -> &[u8; 8] {
173        &self.0
174    }
175}
176
177impl Default for OemName {
178    fn default() -> Self {
179        Self(*b"HADRISFT")
180    }
181}
182
183/// Sector size options.
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
185pub enum SectorSize {
186    /// 512 bytes per sector (most common)
187    #[default]
188    S512 = 512,
189    /// 1024 bytes per sector
190    S1024 = 1024,
191    /// 2048 bytes per sector
192    S2048 = 2048,
193    /// 4096 bytes per sector
194    S4096 = 4096,
195}
196
197impl SectorSize {
198    /// Get the size in bytes.
199    pub fn bytes(self) -> usize {
200        self as usize
201    }
202}
203
204impl TryFrom<usize> for SectorSize {
205    type Error = ();
206
207    fn try_from(value: usize) -> Result<Self, Self::Error> {
208        match value {
209            512 => Ok(Self::S512),
210            1024 => Ok(Self::S1024),
211            2048 => Ok(Self::S2048),
212            4096 => Ok(Self::S4096),
213            _ => Err(()),
214        }
215    }
216}
217
218/// FAT type selection for formatting.
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
220pub enum FatTypeSelection {
221    /// Automatically select based on volume size
222    #[default]
223    Auto,
224    /// Force FAT12 (volumes < ~16 MB)
225    Fat12,
226    /// Force FAT16 (volumes < ~2 GB)
227    Fat16,
228    /// Force FAT32 (volumes >= ~32 MB)
229    Fat32,
230}
231
232impl FatTypeSelection {
233    /// Convert to FatType if not Auto.
234    pub fn as_fat_type(self) -> Option<FatType> {
235        match self {
236            Self::Auto => None,
237            Self::Fat12 => Some(FatType::Fat12),
238            Self::Fat16 => Some(FatType::Fat16),
239            Self::Fat32 => Some(FatType::Fat32),
240        }
241    }
242}
243
244impl From<FatType> for FatTypeSelection {
245    fn from(fat_type: FatType) -> Self {
246        match fat_type {
247            FatType::Fat12 => Self::Fat12,
248            FatType::Fat16 => Self::Fat16,
249            FatType::Fat32 => Self::Fat32,
250        }
251    }
252}
253
254/// Media type descriptor byte.
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
256pub enum MediaType {
257    /// Fixed disk (0xF8)
258    #[default]
259    FixedDisk,
260    /// Removable media (0xF0)
261    Removable,
262    /// Custom media type byte
263    Custom(u8),
264}
265
266impl MediaType {
267    /// Get the media type byte value.
268    pub fn value(self) -> u8 {
269        match self {
270            Self::FixedDisk => 0xF8,
271            Self::Removable => 0xF0,
272            Self::Custom(v) => v,
273        }
274    }
275}