exfat_fs/format/
mod.rs

1use core::ops::{Div, Sub};
2
3use crate::{
4    DEFAULT_BOUNDARY_ALIGNEMENT, FIRST_USABLE_CLUSTER_INDEX, GB, KB, Label, MB,
5    boot_sector::{FileSystemRevision, UnixEpochDuration, VolumeFlags, VolumeSerialNumber},
6    dir::{RawRoot, entry::DirEntry},
7    disk::{SeekFrom, WriteSeek},
8    error::ExfatError,
9    upcase_table::{DEFAULT_UPCASE_TABLE, UPCASE_TABLE_SIZE_BYTES},
10};
11use boot::{BACKUP_BOOT_OFFSET, MAIN_BOOT_OFFSET, MAX_CLUSTER_COUNT, MAX_CLUSTER_SIZE};
12use bytemuck::cast_slice;
13use checked_num::CheckedU64;
14use derive_builder::Builder;
15
16use crate::{disk, error::ExfatFormatError};
17use alloc::string::String;
18use alloc::string::ToString;
19use alloc::vec;
20/// ExFat boot sector creation.
21mod boot;
22mod fat;
23
24/// A struct of exfat formatting options. It implements the [`derive_builder::Builder`] pattern.
25#[derive(Builder, Copy, Clone, Debug)]
26#[builder(no_std, build_fn(validate = "Self::validate"))]
27pub struct FormatVolumeOptions {
28    /// Whether or not to pack the bitmap right after the FAT for better performance and space
29    /// usage. Defaults to `true`.
30    #[builder(default = true)]
31    pack_bitmap: bool,
32    /// Whether to fully format the volume, which takes longer. Defaults to `false`.
33    #[builder(default)]
34    full_format: bool,
35    /// Size of the target device (in bytes)
36    dev_size: u64,
37    /// Label of the format
38    #[builder(default)]
39    label: Label,
40    /// Optional GUID. Defaults to `None`.
41    #[builder(default)]
42    guid: Option<u128>,
43    /// Media-relative sector offset of the partition which hosts the given exFAT volume. Defaults
44    /// to `0`.
45    #[builder(default)]
46    partition_offset: u64,
47    /// Amount of bytes per sector. Must be a power of `2` and between `512` and `4096`.
48    bytes_per_sector: u16,
49    /// Byte alignment for filesystem structures like the FAT and Up-case table. Defaults to
50    /// [`DEFAULT_BOUNDARY_ALIGNEMENT`].
51    #[builder(default = DEFAULT_BOUNDARY_ALIGNEMENT)]
52    boundary_align: u32,
53}
54
55impl FormatVolumeOptionsBuilder {
56    fn validate(&self) -> Result<(), String> {
57        if let Some(ref bytes_per_sector) = self.bytes_per_sector {
58            if !bytes_per_sector.is_power_of_two() || !(512..=4096).contains(bytes_per_sector) {
59                return Err(
60                    "Bytes per sector field must be a power of two and between `512` and `4096`."
61                        .to_string(),
62                );
63            }
64        }
65
66        if let Some(ref boundary_align) = self.boundary_align {
67            if !boundary_align.is_power_of_two() {
68                return Err("Boundary alignment field must be a power of two.".to_string());
69            }
70        }
71
72        Ok(())
73    }
74}
75
76#[derive(Copy, Clone, Debug)]
77pub struct Exfat {
78    volume_length: u64,
79    fat_offset: u32,
80    fat_length: u32,
81    cluster_heap_offset: u32,
82    cluster_count: u32,
83    cluster_count_used: u32,
84    first_cluster_of_root_directory: u32,
85    file_system_revision: FileSystemRevision,
86    volume_flags: VolumeFlags,
87    bytes_per_sector_shift: u8,
88    sectors_per_cluster_shift: u8,
89    number_of_fats: u8,
90    uptable_length_bytes: u32,
91    bitmap_length_bytes: u32,
92    bitmap_offset_bytes: u32,
93    bytes_per_cluster: u32,
94    volume_serial_number: VolumeSerialNumber,
95    root_offset_bytes: u32,
96    format_options: FormatVolumeOptions,
97    root_length_bytes: u32,
98    uptable_offset_bytes: u32,
99    uptable_start_cluster: u32,
100}
101
102impl Exfat {
103    /// Attempts to initialize an exFAT formatter instance based on the [`FormatVolumeOptions`]
104    /// provided.
105    pub fn try_from<T: UnixEpochDuration>(
106        format_options: FormatVolumeOptions,
107    ) -> Result<Self, ExfatFormatError<T>> {
108        let size = format_options.dev_size;
109
110        let bytes_per_cluster = default_cluster_size(size);
111
112        // format volume with a single FAT
113        let number_of_fats = 1u8;
114        let volume_flags = VolumeFlags::empty();
115
116        // transform partition_offset to be measured by sectors
117        let partition_offset =
118            format_options.partition_offset / format_options.bytes_per_sector as u64;
119
120        if !bytes_per_cluster.is_power_of_two()
121            || !(format_options.bytes_per_sector as u32..=MAX_CLUSTER_SIZE)
122                .contains(&bytes_per_cluster)
123        {
124            return Err(ExfatFormatError::InvlaidClusterSize(bytes_per_cluster));
125        }
126        let bytes_per_sector_shift = format_options.bytes_per_sector.ilog2() as u8;
127        let sectors_per_cluster_shift =
128            (bytes_per_cluster / format_options.bytes_per_sector as u32).ilog2() as u8;
129
130        let volume_length = size / format_options.bytes_per_sector as u64;
131
132        if volume_length < (1 << (20 - bytes_per_sector_shift)) {
133            return Err(ExfatFormatError::InvalidSize(size));
134        }
135
136        let fat_offset_bytes: u32 = (CheckedU64::new(format_options.bytes_per_sector as u64) * 24
137            + partition_offset)
138            .ok_or(ExfatFormatError::InvalidPartitionOffset(partition_offset))?
139            .next_multiple_of(format_options.boundary_align as u64)
140            .sub(partition_offset)
141            .try_into()
142            .map_err(|_| {
143                ExfatFormatError::BoundaryAlignemntTooBig(format_options.boundary_align)
144            })?;
145
146        let fat_offset = fat_offset_bytes / format_options.bytes_per_sector as u32;
147
148        let max_clusters: CheckedU64 =
149            ((CheckedU64::new(size) - fat_offset_bytes as u64 - number_of_fats as u64 * 8 - 1)
150                / (bytes_per_cluster as u64 + 4 * number_of_fats as u64)
151                + 1)
152            .ok_or(ExfatFormatError::InvlaidClusterSize(bytes_per_cluster))?
153            .into();
154
155        let fat_length_bytes = ((max_clusters + 2) * 4)
156            .ok_or(ExfatFormatError::InvlaidClusterSize(bytes_per_cluster))?
157            .next_multiple_of(format_options.bytes_per_sector as u64);
158
159        let fat_length: u32 = (fat_length_bytes / format_options.bytes_per_sector as u64)
160            .try_into()
161            .map_err(|_| ExfatFormatError::InvlaidClusterSize(bytes_per_cluster))?;
162
163        let mut cluster_heap_offset_bytes = ((partition_offset
164            + fat_offset_bytes as u64
165            + fat_length_bytes * number_of_fats as u64)
166            .next_multiple_of(format_options.boundary_align as u64)
167            - partition_offset) as u32;
168
169        let mut cluster_heap_offset =
170            cluster_heap_offset_bytes / format_options.bytes_per_sector as u32;
171
172        if cluster_heap_offset_bytes as u64 >= size {
173            return Err(ExfatFormatError::BoundaryAlignemntTooBig(
174                format_options.boundary_align,
175            ));
176        }
177
178        let mut cluster_count: u32 = ((size - cluster_heap_offset_bytes as u64)
179            / bytes_per_cluster as u64)
180            .try_into()
181            .map_err(|_| ExfatFormatError::InvlaidClusterSize(bytes_per_cluster))?;
182
183        if cluster_count
184            > MAX_CLUSTER_COUNT.min(
185                ((volume_length - cluster_heap_offset as u64)
186                    / 2u64.pow(sectors_per_cluster_shift as u32)) as u32,
187            )
188        {
189            return Err(ExfatFormatError::InvlaidClusterSize(bytes_per_cluster));
190        }
191
192        // bitmap is first cluster of cluster heap
193        let mut bitmap_offset_bytes = cluster_heap_offset_bytes;
194        let mut bitmap_length_bytes = cluster_count.next_multiple_of(8) / 8;
195
196        if format_options.pack_bitmap {
197            let fat_end_bytes = fat_offset_bytes as u64 + fat_length_bytes;
198            let mut bitmap_length_bytes_packed;
199            let mut bitmap_length_clusters_packed =
200                bitmap_length_bytes.next_multiple_of(bytes_per_cluster);
201
202            loop {
203                let bitmap_cluster_count_packed = bitmap_length_clusters_packed / bytes_per_cluster;
204                // check if there is enough space to put bitmap before alignment boundary
205                if ((cluster_heap_offset_bytes - bitmap_length_clusters_packed) as u64)
206                    < fat_end_bytes
207                    || cluster_count > MAX_CLUSTER_COUNT - bitmap_cluster_count_packed
208                {
209                    return Err(ExfatFormatError::CannotPackBitmap);
210                }
211
212                let total_cluster_count = cluster_count + bitmap_cluster_count_packed;
213                bitmap_length_bytes_packed = total_cluster_count.next_multiple_of(8).div(8);
214                let new_bitmap_length_clusters =
215                    bitmap_length_bytes_packed.next_multiple_of(bytes_per_cluster);
216
217                if new_bitmap_length_clusters == bitmap_length_clusters_packed {
218                    cluster_heap_offset_bytes -= bitmap_length_clusters_packed;
219                    cluster_count = total_cluster_count;
220                    bitmap_offset_bytes -= bitmap_length_clusters_packed;
221                    bitmap_length_bytes = bitmap_length_bytes_packed;
222                    break;
223                }
224                bitmap_length_clusters_packed = new_bitmap_length_clusters;
225            }
226
227            // reassing changed variable
228            cluster_heap_offset =
229                cluster_heap_offset_bytes / format_options.bytes_per_sector as u32;
230        }
231        let cluster_length = bitmap_length_bytes.next_multiple_of(bytes_per_cluster);
232
233        let uptable_offset_bytes = bitmap_offset_bytes + cluster_length;
234        let uptable_start_cluster = FIRST_USABLE_CLUSTER_INDEX + cluster_length / bytes_per_cluster;
235        let uptable_length_bytes = UPCASE_TABLE_SIZE_BYTES;
236
237        let cluster_length = uptable_length_bytes.next_multiple_of(bytes_per_cluster);
238
239        let root_offset_bytes = uptable_offset_bytes + cluster_length;
240        let first_cluster_of_root_directory =
241            uptable_start_cluster + cluster_length / bytes_per_cluster;
242
243        let file_system_revision = FileSystemRevision::default();
244        let volume_serial_number =
245            VolumeSerialNumber::try_new::<T>().map_err(|err| ExfatFormatError::NoSerial(err))?;
246
247        let root_length_bytes = size_of::<DirEntry>() as u32 * 3;
248        let cluster_count_used = 0; // in the beginning no cluster is used
249
250        Ok(Self {
251            volume_length,
252            bytes_per_sector_shift,
253            fat_offset,
254            number_of_fats,
255            fat_length,
256            cluster_heap_offset,
257            cluster_count,
258            sectors_per_cluster_shift,
259            first_cluster_of_root_directory,
260            volume_flags,
261            volume_serial_number,
262            file_system_revision,
263            bytes_per_cluster,
264            root_offset_bytes,
265            format_options,
266            bitmap_length_bytes,
267            uptable_length_bytes,
268            root_length_bytes,
269            cluster_count_used,
270            bitmap_offset_bytes,
271            uptable_offset_bytes,
272            uptable_start_cluster,
273        })
274    }
275}
276
277impl Exfat {
278    /// Attempts to write the boot region & FAT onto the device. The file length must be the same as the
279    /// provided `dev_size` in the [`Exfat`].
280    pub fn write<T: UnixEpochDuration, O: WriteSeek>(
281        &mut self,
282        f: &mut O,
283    ) -> Result<(), ExfatError<T, O>>
284    where
285        T::Err: core::fmt::Debug,
286    {
287        let old_pos = f.stream_position().map_err(|err| ExfatError::Io(err))?;
288        let len = f
289            .seek(SeekFrom::End(0))
290            .map_err(|err| ExfatError::Io(err))?;
291
292        if old_pos != len {
293            f.seek(SeekFrom::Start(old_pos))
294                .map_err(|err| ExfatError::Io(err))?;
295        }
296
297        assert_eq!(len, self.format_options.dev_size);
298
299        if len != self.format_options.dev_size {
300            return Err(ExfatError::Format(ExfatFormatError::InvalidFileSize));
301        }
302
303        let size = if self.format_options.full_format {
304            self.format_options.dev_size
305        } else {
306            self.root_offset_bytes as u64 + self.bytes_per_cluster as u64
307        };
308
309        // clear disk size as needed
310        disk::write_zeroes(f, size, 0).map_err(|err| ExfatError::Io(err))?;
311
312        // write main boot region
313        self.write_boot_region(f, MAIN_BOOT_OFFSET)
314            .map_err(|err| ExfatError::Io(err))?;
315
316        // write backup boot region
317        self.write_boot_region(f, BACKUP_BOOT_OFFSET)
318            .map_err(|err| ExfatError::Io(err))?;
319
320        // write fat
321        self.write_fat(f).map_err(|err| ExfatError::Io(err))?;
322
323        // write bitmap
324        self.write_bitmap(f).map_err(|err| ExfatError::Io(err))?;
325
326        // write uptable
327        self.write_upcase_table(f)
328            .map_err(|err| ExfatError::Io(err))?;
329
330        // write root directory
331        self.write_root_dir(f).map_err(|err| ExfatError::Io(err))?;
332        Ok(())
333    }
334}
335
336/// default cluster size based on sector size
337fn default_cluster_size(size: u64) -> u32 {
338    const FIRST_BOUND: u64 = 256 * MB as u64;
339    const FROM_FIRST_BOUND: u64 = FIRST_BOUND + 1;
340
341    const SECOND_BOUND: u64 = 32 * GB as u64;
342    const FROM_SECOND_BOUND: u64 = SECOND_BOUND + 1;
343
344    match size {
345        ..=FIRST_BOUND => 4 * KB as u32,
346        FROM_FIRST_BOUND..=SECOND_BOUND => 32 * KB as u32,
347        FROM_SECOND_BOUND.. => 128 * KB as u32,
348    }
349}
350
351impl Exfat {
352    fn write_upcase_table<T: WriteSeek>(&self, device: &mut T) -> Result<(), T::Err> {
353        device.seek(SeekFrom::Start(self.uptable_offset_bytes as u64))?;
354        device.write_all(&DEFAULT_UPCASE_TABLE)
355    }
356
357    fn write_bitmap<T: WriteSeek>(&self, device: &mut T) -> Result<(), T::Err> {
358        let mut bitmap = vec![0u8; self.bitmap_length_bytes as usize];
359
360        // number of currently completely used bytes (set to 0xff)
361        let full_bytes = self.cluster_count_used / 8;
362        // remaining clusters that don't fully complete a byte
363        let remaining_bits = self.cluster_count_used % 8;
364
365        // offset to the first byte that can be fully used (set to 0x00)
366        let mut zero_offset = full_bytes;
367
368        bitmap[..full_bytes as usize].fill(0xff);
369
370        // set the remaining bits
371        if remaining_bits != 0 {
372            bitmap[full_bytes as usize] = (1 << remaining_bits) - 1;
373            zero_offset += 1;
374        }
375
376        if zero_offset < self.bitmap_length_bytes {
377            bitmap[(zero_offset as usize)..].fill(0);
378        }
379
380        device.seek(SeekFrom::Start(self.bitmap_offset_bytes as u64))?;
381        device.write_all(cast_slice(&bitmap))
382    }
383
384    fn write_root_dir<T: WriteSeek>(&self, device: &mut T) -> Result<(), T::Err> {
385        let root = RawRoot::new(
386            self.format_options.label,
387            self.format_options.guid,
388            self.bitmap_length_bytes as u64,
389            self.uptable_start_cluster,
390        );
391
392        device.seek(SeekFrom::Start(self.root_offset_bytes as u64))?;
393        device.write_all(&root.bytes())?;
394        Ok(())
395    }
396}
397
398#[cfg(test)]
399#[test]
400fn small_format() {
401    use crate::Label;
402    use crate::format::FormatVolumeOptionsBuilder;
403    use std::io::Read;
404    use std::vec::Vec;
405
406    let size: u64 = 32 * crate::MB as u64;
407    let mut f = std::io::Cursor::new(vec![0u8; size as usize]);
408
409    let label = Label::new("Hello".to_string()).expect("label creation failed");
410
411    let format_options = FormatVolumeOptionsBuilder::default()
412        .label(label)
413        .pack_bitmap(false)
414        .full_format(false)
415        .dev_size(size)
416        .bytes_per_sector(512)
417        .boundary_align(crate::DEFAULT_BOUNDARY_ALIGNEMENT)
418        .build()
419        .expect("building format volume option failed");
420
421    let mut formatter =
422        Exfat::try_from::<std::time::SystemTime>(format_options).expect("formatting failed");
423    formatter
424        .write::<std::time::SystemTime, std::io::Cursor<Vec<u8>>>(&mut f)
425        .expect("writing failed");
426
427    let offset_volume_label_entry_bytes = 0x203000;
428    let mut read_buffer = vec![0u8; 32];
429    f.seek(crate::disk::SeekFrom::Start(
430        offset_volume_label_entry_bytes,
431    ))
432    .unwrap();
433    f.read_exact(&mut read_buffer).unwrap();
434
435    // assert volume label root directory entry is at the expected offset
436    let vol_label_entry_type = read_buffer[0];
437    assert_eq!(
438        vol_label_entry_type, 0x83,
439        "Volume Label Root Directory Entry has invalid type"
440    );
441
442    // assert volume label length is correct
443    let vol_label_length = read_buffer[1];
444    assert_eq!(
445        vol_label_length, 5,
446        "Volume Label Root Directory Entry has invalid label length"
447    );
448
449    // assert volume label data is correct
450    assert_eq!(
451        &read_buffer[2..2 + vol_label_length as usize],
452        &label.0[..vol_label_length as usize],
453        "Volume Label Root Directory Entry has invalid data"
454    );
455    let offset_upcase_table_entry_bytes = 0x203060;
456
457    f.seek(crate::disk::SeekFrom::Start(
458        offset_upcase_table_entry_bytes,
459    ))
460    .unwrap();
461    f.read_exact(&mut read_buffer).unwrap();
462
463    // assert upcase table root directory entry is at the expected offset
464    assert_eq!(
465        read_buffer[0], 0x82,
466        "Upcase Table Root Directory Entry has invalid type"
467    );
468
469    // assert upcase table root directory entry checksum is correct
470    assert_eq!(
471        u32::from_le_bytes(read_buffer[4..8].try_into().unwrap()),
472        0xe619d30d,
473        "Upcase Table Root Directory Entry has invalid checksum"
474    );
475
476    let offset_bitmap_entry_bytes = 0x203040;
477
478    f.seek(crate::disk::SeekFrom::Start(offset_bitmap_entry_bytes))
479        .unwrap();
480    f.read_exact(&mut read_buffer).unwrap();
481
482    // assert bitmap root directory entry is at the expected offset
483    assert_eq!(
484        read_buffer[0], 0x81,
485        "Allocation Bitmap Root Directory Entry has invalid type"
486    );
487
488    // assert bitmap root directory entry is of the expected size
489    assert_eq!(
490        &read_buffer[24..],
491        960u64.to_le_bytes(),
492        "Allocation Bitmap Root Directory Entry has invalid size"
493    );
494}