====== 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.
→ [[lyx_-_programmiersprache:units:fs:dateisysteme|Die Dateisystem-Sammlung]] · [[lyx_-_programmiersprache:units:fs|std.fs]] · [[lyx_-_programmiersprache:units:units|Units-Übersicht]] · [[lyxos:iofs|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_ONLY'' … ''FAT_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(" "); }
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 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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1911|#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.