std.fs.fat — FAT12/16/32 lesen

import std.fs.fat;

Liest FAT-Dateisysteme aus einem Abbild oder von einem Blockgerät: Bootsektor auswerten, Clusterketten verfolgen, Verzeichnisse durchlaufen, Dateien lesen. Nur lesend — geschrieben wird nicht.

Die Unit arbeitet auf einem offenen Deskriptor und einem Byte-Versatz; ob dahinter eine Datei, eine Partition oder ein ganzes Gerät liegt, ist ihr gleich.

Die Dateisystem-Sammlung · std.fs · Units-Übersicht · Lyx OS: IOFS


1. Konstanten

Konstante Wert Bedeutung
FAT_ERR −1 Lesefehler oder ungültige Struktur
FAT_EOC −2 Kettenende (end of chain)
FAT_FREI 0 freier Cluster
FAT_TYPE_12 · FAT_TYPE_16 · FAT_TYPE_32 12 · 16 · 32 erkannter Typ in FatVolume.fatType
FAT_ATTR_READ_ONLYFAT_ATTR_ARCHIVE 1, 2, 4, 8, 16, 32 Attributbits: schreibgeschützt, versteckt, System, Datenträgerkennung, Verzeichnis, Archiv
FAT_ATTR_LONG_NAME 15 Kennung eines Namensteilstücks (VFAT)

2. Typen

pub type FatVolume = struct {
  fd:              int64;   // offener Deskriptor
  lbaOffset:       int64;   // Byte-Versatz des Volumes darin
  bytesPerSector:  int64;
  sectorsPerClus:  int64;
  reservedSectors: int64;
  numFats:         int64;
  rootEntCount:    int64;   // 0 bei FAT32
  totalSectors:    int64;
  fatSize:         int64;   // Sektoren je FAT-Kopie
  rootCluster:     int64;   // nur FAT32
  fatType:         int64;   // 12, 16, 32 — oder FAT_ERR
  firstDataSector: int64;
  rootDirSectors:  int64;
  countOfClusters: int64;
}

pub type FatEntry = struct {
  attr:      int64;
  cluster:   int64;   // erster Cluster; 0 = leere Datei
  size:      int64;   // Bytes; bei Verzeichnissen 0
  isDir:     bool;
  isVolume:  bool;
}

pub type FatDir = struct { … }   // laufender Zustand beim Durchlaufen

 
Der Name steht nicht im FatEntry. FatDirNext schreibt ihn in einen Puffer des Aufrufers. Ein Zeiger im Struct wäre eine Besitzfrage, die niemand beantworten will: wer gibt ihn frei, und was passiert beim nächsten Eintrag?

3. Volume

Funktion Rückgabe Bedeutung
FatMount(fd, lbaOffset) FatVolume Bootsektor lesen, Typ bestimmen, Geometrie füllen
FatVolumeValid(vol) bool wurde ein FAT erkannt?
FatBytesPerCluster(vol) int64 bytesPerSector · sectorsPerClus
FatClusterSector(vol, cluster) int64 erster Sektor eines Clusters

Cluster 0 und 1 gibt es nicht — die beiden ersten Plätze der FAT tragen den Medientyp.

4. Clusterketten

Funktion Rückgabe Bedeutung
FatNextCluster(vol, cluster) int64 nächster Cluster, FAT_EOC am Ende, FAT_ERR bei Lesefehler
FatChainLength(vol, start) int64 Länge der Kette in Clustern

Der Zugriff auf die FAT unterscheidet sich je Typ: FAT12 hat 12-Bit-Einträge, die sich Bytes teilen — jeder zweite Eintrag liegt über eine Bytegrenze hinweg. FAT16 und FAT32 lesen glatte 16 bzw. 28 Bit.

5. Verzeichnisse

Funktion Rückgabe Bedeutung
FatDirOpen(vol, cluster) FatDir Verzeichnis zum Durchlaufen öffnen; cluster = 0 meint die Wurzel
FatDirNext(vol, ref d, ref e, nameBuf, nameMax) int64 1 = Eintrag geliefert, 0 = Ende, FAT_ERR = Lesefehler. nameMax muss ≥ 13 sein
FatDirClose(vol, ref d) void Sektorpuffer freigeben
FatFindInDir(vol, dirCluster, name, ref e) int64 einen Namen in einem Verzeichnis suchen
FatFindPath(vol, path, ref e) int64 Pfad wie ORDNER/UNTER/DATEI.TXT auflösen; / und \\ gelten beide

Lange Namen (VFAT) werden aus den vorangehenden Teilstücken zusammengesetzt; fehlen sie, gilt der 8.3-Name. Die Datenträgerkennung wird bei der Suche übersprungen — sie trägt ihren Namen im Namensfeld und würde sonst gelegentlich treffen.

 
Zeichen außerhalb von Latin-1 werden zu ?. Ein langer Name steht als UCS-2 im Verzeichnis; die Unit setzt bei gesetztem hohem Byte ein Fragezeichen. Das ist die ehrliche Variante — das hohe Byte abzuschneiden wäre schlimmer: aus „Grüße„ würde stillschweigend etwas anderes.

6. Dateien und Zeitstempel

Funktion Rückgabe Bedeutung
FatReadFile(vol, e, buf, max) int64 Datei in den Puffer lesen, höchstens max Bytes; liefert die gelesene Zahl
FatDateYear(datum) · FatDateMonth · FatDateDay int64 FAT-Datum zerlegen (Jahr ab 1980)
FatTimeHour(zeit) · FatTimeMinute · FatTimeSecond int64 FAT-Zeit zerlegen; Sekunden in Zweierschritten

7. Beispiel

Das Programm liest ein FAT32-Abbild, listet die Wurzel und holt eine Datei aus einem Unterverzeichnis:

import std.fs.fat;
import std.fs;
import std.io;
import std.alloc;

fn main(): int64 {
    var fd: int64 := open("test.img", 0, 0);
    if (fd < 0) { PrintLn("Abbild nicht lesbar"); return 1; }

    var vol: FatVolume := FatMount(fd, 0);
    if (!FatVolumeValid(vol)) { PrintLn("kein FAT"); close(fd); return 1; }

    Print("FAT-Typ            : FAT"); PrintLn(IntToStr(vol.fatType));
    Print("Bytes je Sektor    : "); PrintLn(IntToStr(vol.bytesPerSector));
    Print("Sektoren je Cluster: "); PrintLn(IntToStr(vol.sectorsPerClus));
    Print("Bytes je Cluster   : "); PrintLn(IntToStr(FatBytesPerCluster(vol)));
    Print("Cluster gesamt     : "); PrintLn(IntToStr(vol.countOfClusters));
    Print("Wurzelcluster      : "); PrintLn(IntToStr(vol.rootCluster));

    PrintLn("");
    PrintLn("Wurzelverzeichnis:");
    var d: FatDir := FatDirOpen(vol, 0);       // 0 = Wurzel
    var e: FatEntry;
    var name: int64 := alloc(300);
    while (true) {
        var got: int64 := FatDirNext(vol, d, e, name, 300);
        if (got != 1) { break; }
        Print("  "); Print(name as pchar);
        if (e.isDir) { Print("   <DIR>"); }
        else { Print("   "); Print(IntToStr(e.size)); Print(" Byte"); }
        Print("   Cluster "); PrintLn(IntToStr(e.cluster));
    }
    FatDirClose(vol, d);

    PrintLn("");
    var f: FatEntry;
    var r: int64 := FatFindPath(vol, "ORDNER/NOTIZ.TXT", f);
    Print("Pfadsuche          : "); PrintLn(IntToStr(r));
    if (r == 1) {
        Print("  Groesse          : "); PrintLn(IntToStr(f.size));
        Print("  Kettenlaenge     : "); PrintLn(IntToStr(FatChainLength(vol, f.cluster)));
        var buf: int64 := alloc(1024);
        var n: int64 := FatReadFile(vol, f, buf, 1024);
        Print("  gelesen          : "); Print(IntToStr(n)); PrintLn(" Byte");
        Print("  Inhalt           : "); Print(buf as pchar);
        free(buf, 1024);
    }
    free(name, 300);
    close(fd);
    return 0;
}

Das Abbild dafür stammt aus fremden Werkzeugen — das ist Absicht, siehe Kasten unten:

dd if=/dev/zero of=test.img bs=1M count=40
mkfs.vfat -F 32 -n LYXTEST test.img
echo "Hallo aus Lyx!" > hallo.txt
echo "Zweite Datei"  > notiz.txt
mcopy -i test.img hallo.txt ::/
mmd   -i test.img ::/ORDNER
mcopy -i test.img notiz.txt ::/ORDNER/

FAT-Typ            : FAT32
Bytes je Sektor    : 512
Sektoren je Cluster: 1
Bytes je Cluster   : 512
Cluster gesamt     : 80628
Wurzelcluster      : 2

Wurzelverzeichnis:
  LYXTEST   0 Byte   Cluster 0
  HALLO.TXT   15 Byte   Cluster 3
  ORDNER   <DIR>   Cluster 4

Pfadsuche          : 1
  Groesse          : 13
  Kettenlaenge     : 1
  gelesen          : 13 Byte
  Inhalt           : Zweite Datei

Die Datenträgerkennung LYXTEST erscheint beim Durchlaufen als Eintrag mit isVolume — bei der Suche wird sie übersprungen.

 
Warum das Abbild von mkfs.vfat und mtools kommt und nicht aus eigener Feder: Ein selbst gebautes Abbild belegt nur, dass Leser und Schreiber dieselbe Vorstellung haben — auch eine falsche. Gegen eine fremde Umsetzung gemessen steht auf der anderen Seite jemand, der die Spezifikation unabhängig gelesen hat. Genau so arbeitet auch die Testbatterie der Unit (tests/fat_test.sh, 94 Prüfungen); fehlen die Werkzeuge, wird das gesagt und übersprungen, nicht als Fehlschlag gewertet (#1911).

Letzte Aktualisierung: 2026-09-01 — Seite neu angelegt aus std/fs/fat.lyx (Commit 3065951, #1920, 94 Prüfungen); Beispiel mit lyxc 1.1.16A gegen ein mkfs.vfat-Abbild gebaut und ausgeführt.