moonfat

    FAT12/16/32 on-disk volume codec for MoonBit

    fat
    fat12
    fat16
    fat32
    filesystem
    lfn
    Download zip
    Version
    0.1.0
    License
    MIT
    Last updated
    11 hours ago
    Downloads
    2

    #MoonFat

    MoonBit 里的 FAT12/16/32 卷编解码。给它一块 Array[Byte],可以 format、mount、列目录、读写文件、建目录、再做只读校验。不接真实磁盘,也不做 FUSE,所以 wasm-gc / wasm / js / native 都能用。

    对标 rust-fatfs 的卷模型和读写接口,磁盘布局按 Microsoft FAT 规范:簇数 <4085 用 FAT12,<65525 用 FAT16,否则 FAT32。

    #能做什么

    • 写引导扇、FSInfo、双 FAT、静态根目录或 FAT32 根簇
    • 12-bit / 16-bit / 28-bit FAT 表项(FAT32 写入时保留高 4 位)
    • 8.3 短名和 LFN,~ 数字尾
    • list / stat / read_file / write_file / mkdir / remove / walk
    • 只读 verify,给出 VFY001、VFY015 这类稳定码

    #明确不做

    不管 exFAT,不挂 FUSE,不解析 GPT/MBR,也不把损坏的 FAT 写回去。默认测试不算 34MB 的最小 FAT32 镜像,只测表项打包和 compute_layout。

    #安装

    moon add chenliyi-cly/moonfat

    也可以直接 clone 本仓库当源码依赖。

    #三个可跑示例

    moon run examples/inspect moon run examples/write_payload moon run examples/verify_image

    #1. 检查 USB / EFI 目录树

    • 谁:拿着 ESP 或 U 盘镜像的固件同学
    • 输入:内存里的 FAT 字节
    • 结果:看到 /EFI/BOOT/BOOTX64.EFI 在不在、多大

    inspect 先 format 一个 80KB 的小 FAT12 卷,建 EFI/BOOT,写入假的 BOOTX64.EFI,再 list / walk。

    #2. 打包固件 payload

    • 谁:要把固件文件塞进 FAT 镜像再送给烧录器的脚本
    • 输入:payload 字节和卷参数
    • 结果:得到整卷 Bytes,重 mount 后能读回原文件

    write_payload 把 firmware-payload-v1 写到 /payload/app.bin,打印 hex 和镜像大小。

    #3. 损坏后做校验

    • 谁:镜像被改过、想先看坏在哪的人
    • 输入:一份本来干净的卷,再人工改 FAT 副本
    • 结果:verify 给出 VFY015(两份 FAT 对不上)

    verify_image 先确认干净卷 0 条 issue,再对 FAT 副本 1 做 xor。

    #最短调用

    let vol = format_volume({
    kind: Fat12,
    bytes_per_sector: 512,
    total_sectors: 160,
    volume_label: "MOONFAT",
    volume_id: 0x4D4F4F4E,
    }).unwrap()
    vol.mkdir("/EFI").unwrap()
    vol.write_file("/README.TXT", payload).unwrap()
    let names = vol.list("/").unwrap()
    let issues = vol.verify()

    1.44MB 软盘和最小 FAT16 卷也有现成入口:format_floppy、format_fat16_small。format_fat32_min 会得到约 34MB 的镜像,适合显式调用,不要放进默认 CI。

    #校验

    本机已跑过:

    moon fmt --check moon check --target wasm-gc --deny-warn moon test --target wasm-gc moon run examples/inspect moon run examples/write_payload moon run examples/verify_image

    GitHub Actions 会在 Ubuntu 上补跑 wasm / js / native。

    #许可证

    MIT。卷布局参考 Microsoft FAT 规范和 rust-fatfs(MIT)。详见 NOTICE 和 THIRD_PARTY.md。

    DirRecord

    pub(all) struct DirRecord {
    name : String
    short_name : Bytes
    attr : Int
    cluster : Int
    size : Int
    short_offset : Int
    lfn_offsets : Array[Int]
    write_time : Int
    write_date : Int
    } derive(
    Debug
    )

    DirRecord::is_dir

    fn DirRecord::is_dir(self : DirRecord) -> Bool

    DirRecord::is_dot

    fn DirRecord::is_dot(self : DirRecord) -> Bool

    DirRecord::is_volume_id

    fn DirRecord::is_volume_id(self : DirRecord) -> Bool

    DosTime

    pub(all) struct DosTime {
    year : Int
    month : Int
    day : Int
    hour : Int
    minute : Int
    second : Int
    } derive(Eq,
    Debug
    )

    DOS directory timestamp used when writing entries.

    Entry

    pub(all) struct Entry {
    name : String
    short_name : String
    attr : Int
    cluster : Int
    size : Int
    is_dir : Bool
    write : DosTime
    } derive(
    Debug
    )

    Public directory listing item.

    FatError

    pub(all) enum FatError {
    Truncated(String, Int, Int)
    InvalidBoot(String)
    Unsupported(String)
    BadCluster(Int)
    ChainLoop(Int)
    NotFound(String)
    AlreadyExists(String)
    NotDirectory(String)
    IsDirectory(String)
    DirectoryNotEmpty(String)
    NoSpace
    InvalidName(String)
    Corrupt(String)
    InvalidPath(String)
    SlotExhausted
    } derive(Eq,
    Debug
    )

    FAT12/16/32 codec errors with stable diagnostic codes.

    FatError::code

    fn FatError::code(self : FatError) -> String

    FatError::message

    fn FatError::message(self : FatError) -> String

    FatError::render

    fn FatError::render(self : FatError) -> String

    FatKind

    pub(all) enum FatKind {
    Fat12
    Fat16
    Fat32
    } derive(Eq,
    Debug
    )

    On-disk FAT kind selected from cluster count, per the Microsoft FAT spec.

    FatKind::bad_value

    fn FatKind::bad_value(self : FatKind) -> Int

    FatKind::bits

    fn FatKind::bits(self : FatKind) -> Int

    FatKind::eoc_value

    fn FatKind::eoc_value(self : FatKind) -> Int

    FatKind::is_bad

    fn FatKind::is_bad(self : FatKind, value : Int) -> Bool

    FatKind::is_eoc

    fn FatKind::is_eoc(self : FatKind, value : Int) -> Bool

    FatKind::name

    fn FatKind::name(self : FatKind) -> String

    FormatOptions

    pub(all) struct FormatOptions {
    kind : FatKind
    bytes_per_sector : Int
    total_sectors : Int
    volume_label : String
    volume_id : Int
    } derive(
    Debug
    )

    Parameters used to create a new in-memory FAT volume.

    Layout

    pub(all) struct Layout {
    kind : FatKind
    bytes_per_sector : Int
    sectors_per_cluster : Int
    reserved_sectors : Int
    fat_count : Int
    fat_sectors : Int
    root_entries : Int
    root_dir_sectors : Int
    first_fat_sector : Int
    first_root_sector : Int
    first_data_sector : Int
    cluster_count : Int
    root_cluster : Int
    total_sectors : Int
    media : Byte
    volume_id : Int
    label : String
    fsinfo_sector : Int
    backup_boot_sector : Int
    } derive(
    Debug
    )

    Cached geometry of a mounted or freshly formatted volume.

    Layout::byte_offset

    fn Layout::byte_offset(self : Layout, sector : Int) -> Int

    Layout::bytes_per_cluster

    fn Layout::bytes_per_cluster(self : Layout) -> Int

    Layout::cluster_sector

    fn Layout::cluster_sector(self : Layout, cluster : Int) -> Result[Int, FatError]

    Layout::fat_bytes

    fn Layout::fat_bytes(self : Layout) -> Int

    RawEntry

    pub(all) struct RawEntry {
    offset : Int
    name : Bytes
    attr : Int
    nt_res : Int
    create_tenth : Int
    create_time : Int
    create_date : Int
    access_date : Int
    cluster : Int
    write_time : Int
    write_date : Int
    size : Int
    } derive(
    Debug
    )

    RawEntry::is_deleted

    fn RawEntry::is_deleted(self : RawEntry) -> Bool

    RawEntry::is_dir

    fn RawEntry::is_dir(self : RawEntry) -> Bool

    RawEntry::is_dot

    fn RawEntry::is_dot(self : RawEntry) -> Bool

    RawEntry::is_end

    fn RawEntry::is_end(self : RawEntry) -> Bool

    RawEntry::is_lfn

    fn RawEntry::is_lfn(self : RawEntry) -> Bool

    RawEntry::is_volume_id

    fn RawEntry::is_volume_id(self : RawEntry) -> Bool

    VerifyIssue

    pub(all) struct VerifyIssue {
    code : String
    message : String
    cluster : Int
    } derive(Eq,
    Debug
    )

    One verifier finding. cluster is 0 when the issue is volume-wide.

    Volume

    pub(all) struct Volume {
    data : Array[Byte]
    layout : Layout
    }

    An in-memory FAT12/16/32 volume.

    Volume::boot_preview

    fn Volume::boot_preview(self : Volume) -> Result[Bytes, FatError]

    Volume::bytes_per_cluster

    fn Volume::bytes_per_cluster(self : Volume) -> Int

    Volume::cluster_count

    fn Volume::cluster_count(self : Volume) -> Int

    Volume::free_clusters

    fn Volume::free_clusters(self : Volume) -> Result[Int, FatError]

    Volume::issue_codes

    fn Volume::issue_codes(self : Volume) -> Array[String]

    Volume::kind

    fn Volume::kind(self : Volume) -> FatKind

    Volume::label

    fn Volume::label(self : Volume) -> String

    Volume::list

    fn Volume::list(self : Volume, path : String) -> Result[Array[Entry], FatError]

    Volume::mkdir

    fn Volume::mkdir(self : Volume, path : String) -> Result[Unit, FatError]

    Volume::mkdir_p

    fn Volume::mkdir_p(self : Volume, path : String) -> Result[Unit, FatError]

    Volume::read_file

    fn Volume::read_file(self : Volume, path : String) -> Result[Bytes, FatError]

    Volume::remove

    fn Volume::remove(self : Volume, path : String) -> Result[Unit, FatError]

    Volume::stat

    fn Volume::stat(self : Volume, path : String) -> Result[Entry, FatError]

    Volume::to_bytes

    fn Volume::to_bytes(self : Volume) -> Bytes

    Volume::total_bytes

    fn Volume::total_bytes(self : Volume) -> Int

    Volume::verify

    fn Volume::verify(self : Volume) -> Array[VerifyIssue]

    Volume::walk

    fn Volume::walk(self : Volume, path : String) -> Result[Array[Entry], FatError]

    Volume::write_file

    fn Volume::write_file(self : Volume, path : String, payload : Bytes) -> Result[Unit, FatError]

    ATTR_ARCHIVE

    let ATTR_ARCHIVE : Int

    ATTR_DIRECTORY

    let ATTR_DIRECTORY : Int

    ATTR_HIDDEN

    let ATTR_HIDDEN : Int

    ATTR_LFN

    let ATTR_LFN : Int

    ATTR_READ_ONLY

    let ATTR_READ_ONLY : Int

    ATTR_SYSTEM

    let ATTR_SYSTEM : Int

    ATTR_VOLUME_ID

    let ATTR_VOLUME_ID : Int

    DIR_ENTRY_SIZE

    let DIR_ENTRY_SIZE : Int

    default_dos_time

    fn default_dos_time() -> DosTime

    floppy_1440_options

    fn floppy_1440_options() -> FormatOptions

    format_fat16_small

    fn format_fat16_small() -> Result[Volume, FatError]

    format_fat32_min

    fn format_fat32_min() -> Result[Volume, FatError]

    format_floppy

    fn format_floppy() -> Result[Volume, FatError]

    format_volume

    fn format_volume(options : FormatOptions) -> Result[Volume, FatError]

    hex_encode

    fn hex_encode(data : Bytes) -> String

    min_fat32_options

    fn min_fat32_options() -> FormatOptions

    mount

    fn mount(data : Array[Byte]) -> Result[Volume, FatError]

    mount_bytes

    fn mount_bytes(bytes : Bytes) -> Result[Volume, FatError]

    mount_range

    fn mount_range(data : Array[Byte], offset : Int, length : Int) -> Result[Volume, FatError]

    parse_layout

    fn parse_layout(data : Array[Byte]) -> Result[Layout, FatError]

    small_fat16_options

    fn small_fat16_options() -> FormatOptions

    split_path

    fn split_path(path : String) -> Result[Array[String], FatError]