std.fs.ntfs — NTFS lesen
import std.fs.ntfs;
Liest NTFS: Bootsektor auswerten, MFT-Sätze lesen, Attribute finden, Verzeichnisse durchsuchen, Dateien lesen. Nur lesend.
Diese Unit hat den größten Abstand zu ihren Schwestern. FAT, exFAT und ext4 haben alle irgendwo eine Tabelle, die Blöcke aufzählt. NTFS hat stattdessen die Master File Table: jede Datei ist dort ein Satz aus Attributen, und die Daten sind nur eines davon ($DATA).
→ Die Dateisystem-Sammlung · std.fs.part · Guide: Fremde Dateisysteme lesen
1. Die drei Eigenheiten
Jede von ihnen ist eine eigene Fehlerquelle, und alle drei sind still:
- Fixup-Werte. Jeder MFT-Satz und jeder Indexblock trägt am Ende jedes Sektors zwei Byte, die vor dem Auswerten zurückgetauscht werden müssen. Bei kleinen Sätzen fällt das nicht auf, weil dort nur der erste Sektor benutzt wird — bei großen liest man ohne Rücktausch Müll mitten aus der Struktur.
- Datenläufe (mapping pairs). Ein nicht-residentes Attribut nennt seine Cluster als Folge von Paaren aus Länge und relativem Versatz. Der Versatz ist vorzeichenbehaftet und baut auf dem vorigen auf — ein Lauf kann rückwärts zeigen. Wer ihn ohne Vorzeichen liest, landet irgendwo auf dem Datenträger.
- Residente Attribute. Eine Datei unter etwa 700 Byte hat gar keine Cluster; ihr Inhalt liegt im MFT-Satz. Wer nur den nicht-residenten Fall kann, liest für sie Nullen — und die sehen aus wie eine leere Datei.
2. Konstanten
| Konstante | Wert | Bedeutung |
|---|---|---|
NTFS_ERR | −1 | Lesefehler oder ungültige Struktur |
NTFS_MFT_SELF | 0 | die MFT selbst |
NTFS_MFT_VOLUME | 3 | trägt $VOLUME_NAME |
NTFS_MFT_ROOT | 5 | das Wurzelverzeichnis |
NTFS_AT_STANDARD | 0x10 | $STANDARD_INFORMATION |
NTFS_AT_FILE_NAME | 0x30 | $FILE_NAME |
NTFS_AT_VOLUME_NAME | 0x60 | $VOLUME_NAME |
NTFS_AT_DATA | 0x80 | $DATA — der Inhalt |
NTFS_AT_INDEX_ROOT · NTFS_AT_INDEX_ALLOC | 0x90 · 0xA0 | Verzeichnisindex |
NTFS_AT_END | 0xFFFFFFFF | Ende der Attributliste |
NTFS_FLAG_INUSE · NTFS_FLAG_DIR | 1 · 2 | Satz in Gebrauch, Satz ist Verzeichnis |
NTFS_LCN_LOCH | −1 | Loch in einer Lauffolge (spärliche Datei) |
3. Typen
pub type NtfsVolume = struct {
fd: int64;
partOffset: int64;
bytesPerSector: int64;
sectorsPerClus: int64;
clusterBytes: int64;
totalSectors: int64;
mftLcn: int64;
recordSize: int64; // Bytes je MFT-Satz
indexSize: int64; // Bytes je Indexblock
mftRuns: int64; // Laufliste der MFT: je 24 Byte (vcn, lcn, len)
mftRunCount: int64;
gueltig: bool;
}
pub type NtfsAttr = struct {
typ: int64;
nonResident: bool;
wertPtr: int64; // resident: Zeiger auf den Wert IM Satzpuffer
wertLen: int64;
runs: int64; // nicht-resident: Laufliste (je 24 Byte)
runCount: int64;
realSize: int64;
gefunden: bool;
}
pub type NtfsEntry = struct {
mftRef: int64;
size: int64;
isDir: bool;
}
Die Trennung in wertPtr/wertLen gegen runs/runCount bildet genau den residenten und den nicht-residenten Fall ab — nonResident sagt, welche Felder gelten.
4. Volume und Sätze
| Signatur | Zweck |
|---|---|
NtfsMount(fd: int64, byteOffset: int64): NtfsVolume | Bootsektor lesen, MFT-Laufliste aufbauen |
NtfsUnmount(ref vol: NtfsVolume): void | Laufliste freigeben |
NtfsValid(vol: NtfsVolume): bool | Wurde ein gültiges NTFS gefunden? |
NtfsReadRecord(vol, nr: int64, puffer: int64): int64 | MFT-Satz nr lesen — mit Fixup-Rücktausch |
NtfsRecordInUse(satz: int64): bool | Ist der Satz belegt? |
NtfsRecordIsDir(satz: int64): bool | Ist es ein Verzeichnis? |
NtfsVolumeLabel(vol, buf: int64, max: int64): int64 | Datenträgerkennung aus Satz 3 |
5. Attribute
| Signatur | Zweck |
|---|---|
NtfsFindAttr(vol, satz: int64, typ: int64, runPuffer: int64, ref a: NtfsAttr): int64 | Attribut im Satz suchen; Lauflisten landen in runPuffer |
NtfsReadAttr(vol, a: NtfsAttr, buf: int64, max: int64): int64 | Wert des Attributs lesen — resident wie nicht-resident |
6. Dateien und Verzeichnisse
| Signatur | Zweck |
|---|---|
NtfsReadFile(vol, mftNr: int64, buf: int64, max: int64): int64 | Datei über ihre MFT-Nummer lesen |
NtfsFileSize(vol: NtfsVolume, mftNr: int64): int64 | Größe der Datei |
NtfsDirCount(vol: NtfsVolume, dirNr: int64): int64 | Zahl der Einträge eines Verzeichnisses |
NtfsFindInDir(vol, dirNr: int64, name: pchar, ref e: NtfsEntry): int64 | Eintrag im Verzeichnis suchen |
NtfsFindPath(vol: NtfsVolume, path: pchar): int64 | Pfad ab der Wurzel auflösen → MFT-Nummer |
Ein Verzeichnis ist in NTFS ein B-Baum aus $INDEX_ROOT und $INDEX_ALLOCATION; die Unit durchläuft beide.
7. Beispiel
unit main;
import std.fs.ntfs;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var fd: int64 := open("/tmp/ntfs.img"c, 0, 0);
if (fd < 0) { return 1; }
var vol: NtfsVolume := NtfsMount(fd, 0);
if (!NtfsValid(vol)) { PrintLn("kein NTFS"); close(fd); return 1; }
var nr: int64 := NtfsFindPath(vol, "/ordner/datei.txt"c);
if (nr == NTFS_ERR) { PrintLn("nicht gefunden"); }
else {
var groesse: int64 := NtfsFileSize(vol, nr);
var buf: int64 := alloc(groesse + 1);
NtfsReadFile(vol, nr, buf, groesse);
PrintLn(StrConcat("gelesen: ", IntToStr(groesse)));
free(buf, groesse + 1);
}
NtfsUnmount(vol);
close(fd);
return 0;
}
8. Grenzen
Nicht enthalten — und im Zweifel gemeldet, nicht geraten:
- komprimierte und verschlüsselte Attribute
- alternative Datenströme unter Namen
$ATTRIBUTE_LIST— ein Satz, dessen Attribute nicht mehr in einen Satz passen- Reparse Points
$LogFile-Wiedergabe
Namen werden nach ASCII gelesen; alles darüber wird zu ? — dieselbe Angabe wie in den Schwester-Units.
Nur lesen. Schreiben verlangt zusätzlich die Pflege des $LogFile (NTFS führt ein Journal), der $Bitmap und der Indexbäume.
Prüfstand: tests/ntfs_test.sh — 33 Prüfungen gegen ein erzeugtes Abbild.
Letzte Aktualisierung: 2026-09-05 — Seite neu angelegt; erhoben aus std/fs/ntfs.lyx (#1926).
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.
