====== 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.