Fremde Dateisysteme lesen
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
1. Wozu das gut ist
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“:
- Forensik und Wiederherstellung — ein Abbild untersuchen, ohne es einzuhängen und dabei zu verändern.
- Bare-Metal und Lyx OS — dort gibt es keinen Linux-Kernel, der die Arbeit abnimmt.
- Prüfwerkzeuge — ein erzeugtes Abbild gegen die Erwartung halten, ohne Root-Rechte.
- Installer und Startmedien — auf einem fremden Datenträger nachsehen, was darauf liegt.
2. Der immer gleiche Ablauf
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.
3. Warum sechs Units und nicht eine
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.
4. Die Fallen, je Format eine
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.
5. Ein Verzeichnis auflisten
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.
6. Was die Units nicht tun
- Sie schreiben nicht. Bei FAT wäre die Pflege beider FAT-Kopien nötig, bei exFAT zusätzlich Belegungsbitmap und Prüfsummen, bei ext3/ext4 und NTFS die Journalführung — und ein Schreiber, der das Journal nicht führt, macht aus einem Absturz einen Datenverlust. SquashFS ist von Haus aus nur lesbar.
- Sie hängen nichts ein. Es entsteht kein Mountpoint und kein VFS-Eintrag; die Units lesen Strukturen, mehr nicht. Unter Lyx OS macht das Einhängen der Kernel (→ ''sys_mount'', ''sys_mount_part'').
- Sie raten nicht. Was nicht umgesetzt ist — die doppelt indirekte Blockzuordnung in ext4, komprimierte Attribute in NTFS, xattr in SquashFS —, wird gemeldet. Still das Falsche zu liefern wäre schlimmer als die Meldung.
- Namen werden nach ASCII gelesen; alles darüber wird zu
?.
7. Prüfstand
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.
