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);

 
Zuerst GPT prüfen, nicht MBR. Eine GPT-Platte trägt bei Sektor 0 einen Schutz-MBR mit einem einzigen Eintrag über die ganze Platte. Wer zuerst den MBR liest, hält eine Platte mit fünf Partitionen für eine einzige riesige. 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;
}

 
Die Reihenfolge beim Freigeben ist nicht beliebig. Ein 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.