exfat-slim 0.5.0

An exFAT file system library written in safe Rust for embedded environments
Documentation
use core::str::from_utf8;

use aligned::{Aligned, Alignment};
use bitflags::bitflags;
use thiserror::Error;

use super::utils::{read_u16_le, read_u32_le, read_u64_le};

#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[derive(Error, Debug, Clone, Copy)]
pub enum Error {
    /// if you attempt to read an exfat file system boot sector but you've actually read the
    /// master boot record (MBR) or GPT boot record of an SD card then you will typically encounter
    /// this error. The MBR typically points to the boot sector of the exFAT file system.
    /// Alternatively if you attempt to read another file system you will also encounter this
    #[error("invalid jump boot as boot_sector[..2] expected [0xeb, 0x76, 0x90]")]
    InvalidJumpBoot,

    #[error("invalid file system name at boot_sector[3..11] expected \"EXFAT   \"")]
    InvalidFileSystemName,

    #[error("invalid boot signature at boot_sector[510..512] expected [0x55, 0xaa]")]
    InvalidBootSignature,
}

/// boot sector describes the exfat volume structure
#[derive(Debug)]
#[allow(unused)]
pub(crate) struct BootSector {
    /// media-relative offset of the partition which hosts this exFAT volume
    pub partition_offset: u64,

    /// size of exFAT volume in sectors
    pub volume_length: u64,

    /// volume-relative sector offset of the first FAT
    pub fat_offset: u32,

    /// fat length in sectors
    pub fat_length: u32,

    /// volume-relative sector offset of the cluster heap
    pub cluster_heap_offset: u32,

    /// number of clusters in the cluster heap
    pub cluster_count: u32,

    /// cluster index of the first cluster of the root directory
    pub first_cluster_of_root_dir: u32,

    /// autogenerated by combining date and time when formatting the volume
    pub volume_serial_number: u32,

    /// major (high byte) and minor (low byte) revision number of the exfat structures
    pub file_system_revision: u16,

    /// status of various file system structures in the exfat volume
    pub volume_flags: VolumeFlags,

    /// num bytes per sector (512 bytes is typical for an SD card)
    pub bytes_per_sector: u16,

    /// number of sectors per cluster (64 is normal for an SD card of 8GB - that equates to 32KB per cluster)
    pub sectors_per_cluster: u8,

    /// number of fats and allocation bitmaps (normally 1 but can be two for TexFAT volumes)
    pub number_of_fats: u8,

    /// 13h drive number for bootstrapping purposes (128 is standard)
    pub drive_select: u8,

    /// percentage of clusters in the cluster heap that are allocated
    pub percent_in_use: u8,
}

impl BootSector {
    pub(crate) fn check_is_valid(value: &[u8]) -> Result<(), Error> {
        // jump boot
        // check that this is not MBR or GPT which is usually the first sector of an SD card
        if value[0] != 0xeb && value[1] != 0x76 && value[2] != 0x90 {
            return Err(Error::InvalidJumpBoot);
        }

        // file system name
        match from_utf8(&value[3..11]) {
            Ok(s) => {
                if s != "EXFAT   " {
                    return Err(Error::InvalidFileSystemName);
                }
            }
            Err(_e) => return Err(Error::InvalidFileSystemName),
        };

        // boot signature
        if value[510] != 0x55 && value[511] != 0xaa {
            return Err(Error::InvalidBootSignature);
        }

        Ok(())
    }
}

impl<A: Alignment, const SIZE: usize> TryFrom<&Aligned<A, [u8; SIZE]>> for BootSector {
    type Error = Error;

    fn try_from(value: &Aligned<A, [u8; SIZE]>) -> Result<Self, Self::Error> {
        Self::check_is_valid(value.as_slice())?;
        let partition_offset = read_u64_le::<64, _>(value);
        let volume_length = read_u64_le::<72, _>(value);
        let fat_offset = read_u32_le::<80, _>(value);
        let fat_length = read_u32_le::<84, _>(value);
        let cluster_heap_offset = read_u32_le::<88, _>(value);
        let cluster_count = read_u32_le::<92, _>(value);
        let first_cluster_of_root_dir = read_u32_le::<96, _>(value);
        let volume_serial_number = read_u32_le::<100, _>(value);
        let file_system_revision = read_u16_le::<104, _>(value);
        let volume_flags = VolumeFlags::from_bits_truncate(read_u16_le::<106, _>(value));
        let bytes_per_sector = 1u16 << value[108]; // bytes_per_sector_shift 
        let sectors_per_cluster = 1u8 << value[109]; // sectors_per_cluster_shift
        let number_of_fats = value[110];
        let drive_select = value[111];
        let percent_in_use = value[112];

        Ok(BootSector {
            partition_offset,
            volume_length,
            fat_offset,
            fat_length,
            cluster_heap_offset,
            cluster_count,
            first_cluster_of_root_dir,
            volume_serial_number,
            file_system_revision,
            volume_flags,
            bytes_per_sector,
            sectors_per_cluster,
            number_of_fats,
            drive_select,
            percent_in_use,
        })
    }
}

bitflags! {
    /// Represents a set of volume flags.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub(crate) struct VolumeFlags: u16 {
        /// The value `FirstFat`, at bit position `0`.
        /// Value 0 for ActiveFat and 1 for TexFAT
        const FirstFat = 0b0000_0001;

        /// The value `VolumeDirty`, at bit position `1`.
        /// Set on mount, restored on unmount
        const VolumeDirty = 0b0000_0010;

        /// The value `MediaFailure`, at bit position `2`.
        /// Set on unrecoverable disk error, cleared after a surface scan.
        const MediaFailure = 0b0000_0100;
    }
}