Lyx kann sechs Dateisysteme lesen, ohne den Kernel zu fragen: FAT, exFAT, ext2/3/4, NTFS und SquashFS — dazu die Partitionstabelle davor. Dieser Guide zeigt, wie die Units zusammenspielen, und erklärt, warum es sechs sind und nicht eine.
→ Guides · Die Dateisystem-Sammlung · std.fs · Lyx OS: IOFS
import std.fs.part; // MBR und GPT — wo faengt was an?
import std.fs.fat; // FAT12/16/32
import std.fs.exfat; // exFAT
import std.fs.ext4; // ext2, ext3, ext4
import std.fs.ntfs; // NTFS
import std.fs.squashfs; // SquashFS 4.0
Ein Programm, das ein Dateisystem selbst liest, braucht kein Betriebssystem darunter, das es schon eingehängt hat. Das ist der Unterschied zwischen „ich lese eine Datei„ und „ich lese eine Datei aus einem Abbild“:
Alle Units arbeiten auf einem offenen Deskriptor plus Byte-Versatz. Ob dahinter eine Abbilddatei, eine Partition oder ein ganzes Gerät liegt, ist ihnen gleich.
unit main;
import std.fs.part;
import std.io;
import std.string;
fn main(): int64 {
var fd: int64 := open("/tmp/platte.img"c, 0, 0);
if (fd < 0) { PrintLn("Abbild fehlt"); return 1; }
var t: PartTable := PartScan(fd, 512);
if (!PartValid(t)) { PrintLn("keine Partitionstabelle"); close(fd); return 1; }
PrintLn(StrConcat("Schema: ", IntToStr(PartSchema(t))));
var i: int64 := 0;
while (i < PartCount(t)) {
var e: PartEntry;
PartGet(t, i, e);
PrintLn(StrConcat(StrConcat(" #", IntToStr(i)),
StrConcat(" Versatz ", IntToStr(PartByteOffset(t, i)))));
i := i + 1;
}
PartFree(t);
close(fd);
return 0;
}
Der Rückgabewert von PartByteOffset ist genau das zweite Argument von FatMount, ExfatMount, Ext4Mount, NtfsMount und SquashMount. Damit hängt die Kette:
var off: int64 := PartByteOffset(t, 0);
var vol: Ext4Volume := Ext4Mount(fd, off);
PartScan macht es richtig herum — wer selbst nachbaut, sollte es wissen.
Die Namen folgen demselben Schnitt (…Mount, …DirOpen, …DirNext, …FindPath, …ReadFile), der Aufbau dahinter ist jedes Mal ein anderer:
| Format | Wie eine Datei zu ihren Daten kommt |
|---|---|
| FAT | Eine Zuordnungstabelle, in der jeder Cluster auf den nächsten zeigt — eine verkettete Liste über die Platte |
| exFAT | Dieselbe Idee, aber zusammenhängende Dateien stehen gar nicht in der Tabelle (NoFatChain) |
| ext4 | Eine Inode-Tabelle; der Verzeichniseintrag hat nur Name und Nummer, der Rest steht im Inode — als Blockliste (ext2/3) oder Extent-Baum (ext4) |
| NTFS | Die Master File Table; jede Datei ist ein Satz aus Attributen, die Daten sind eines davon — bei kleinen Dateien im Satz selbst |
| SquashFS | Komprimierte Metadatenblöcke; eine Inode-Referenz ist ein Paar aus Blockadresse und Versatz im entpackten Block |
Eine gemeinsame Oberschicht müsste all das unter einen Hut zwängen und verlöre dabei genau die Eigenheiten, auf die es beim Lesen ankommt. Deshalb: sechs Units, ein gemeinsames Namensmuster, keine erzwungene Abstraktion.
Jede davon ist still — das Programm läuft weiter und liefert plausible, falsche Zahlen:
| Format | Falle |
|---|---|
| exFAT | Sektor- und Clustergröße stehen als Zweierpotenz-Exponent da. Wer 9 als Sektorgröße liest statt als 512, rechnet ab da alles falsch — und die Adressen bleiben plausibel |
| exFAT | Das oberste Bit der Eintragsart heißt „in Gebrauch„. Wer nur auf Gleichheit prüft, liest gelöschte Einträge mit |
| ext4 | Die Art steht im Verzeichniseintrag und im Inode. Bei alten Dateisystemen ohne filetype ist die erste 0 — dann entscheidet der Inode |
| NTFS | Fixup-Werte: am Ende jedes Sektors stehen zwei Byte, die vor dem Auswerten zurückgetauscht werden müssen. Bei kleinen Sätzen fällt das nicht auf |
| NTFS | Datenläufe tragen einen vorzeichenbehafteten relativen Versatz — ein Lauf kann rückwärts zeigen. Ohne Vorzeichen landet man irgendwo auf der Platte |
| NTFS | Dateien unter etwa 700 Byte haben keine Cluster; ihr Inhalt liegt im MFT-Satz. Wer nur den nicht-residenten Fall kann, liest Nullen — und die sehen aus wie eine leere Datei |
| SquashFS | Die Blockgrößenliste nennt die Größe auf der Platte, nicht die entpackte |
| MBR | Drei Typbytes bedeuten „erweitert“ (0x05, 0x0F, 0x85). Wer nur 0x05 kennt, übersieht fast alle modernen Platten |
Alle diese Fälle sind in den Units behandelt — die Tabelle steht hier, weil sie erklärt, warum der Code an manchen Stellen umständlicher aussieht, als er sein müsste.
Am Beispiel ext4; bei den anderen Formaten heißen nur die Bezeichner anders.
unit main;
import std.fs.ext4;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var fd: int64 := open("/tmp/ext4.img"c, 0, 0);
if (fd < 0) { return 1; }
var vol: Ext4Volume := Ext4Mount(fd, 0);
if (!Ext4Valid(vol)) { PrintLn("kein ext-Dateisystem"); close(fd); return 1; }
var wurzel: Ext4Inode;
Ext4ReadInode(vol, EXT4_ROOT_INO, wurzel);
var name: int64 := alloc(300);
var d: Ext4Dir := Ext4DirOpen(vol, wurzel);
var e: Ext4Entry;
while (Ext4DirNext(vol, d, e, name, 300) > 0) {
PrintLn(StrConcat(name as pchar, StrConcat(" Inode ", IntToStr(e.ino))));
}
Ext4DirClose(vol, d); // erst das Verzeichnis …
Ext4InodeFree(wurzel); // … dann der Inode
free(name, 300);
close(fd);
return 0;
}
Ext4Dir hält den Inode als Kopie, teilt sich mit ihm aber den iblock-Zeiger. Wer den Inode freigibt, während das Verzeichnis offen ist, zieht dem Durchlauf den Boden weg.
?.Jede Unit hat einen eigenen Testlauf gegen echte, im Test erzeugte Abbilder — nicht gegen Attrappen:
| Unit | Test | Prüfungen |
|---|---|---|
std.fs.fat | tests/fat_test.sh | 94 |
std.fs.exfat | tests/exfat_test.sh | 42 |
std.fs.ext4 | tests/ext4_test.sh | 53 |
std.fs.ntfs | tests/ntfs_test.sh | 33 |
std.fs.part | tests/part_test.sh | 47 |
std.fs.squashfs | tests/squashfs_test.sh | 37 |
Letzte Aktualisierung: 2026-09-05 — Seite neu angelegt; erhoben aus std/fs/*.lyx, Beispiele gegen lyxc 1.2.2A übersetzt.
Prüflauf 2026-09-05 mit doku-pruefer.py (Repo-Compiler lyxc 1.2.2A): 7 Vollprogramme der Sammlung, 7 übersetzen, 0 echte Fehler; 53 Aufrufe gegen die pub fn-Signaturen gehalten, 0 Abweichungen.