====== Dateisystem & I/O mit Lyx ======
Lyx trennt Datei-Arbeit in vier Ebenen: **Builtins** (rohe Syscalls, ohne Import), **''std.fs''** (Komfort: Pfade, Verzeichnisse, Kopieren), **''std.fs_ext''** (Metadaten, positioniertes Lesen, Nullkopie) und die **Spezialisten** ''std.mmap_ext'', ''std.inotify'', ''std.xattr'', ''std.io_uring''.
Es gibt **keinen Datei-Handle-Typ und keine Streams**. Ein geöffnetes Objekt ist ein ''int64''-Deskriptor, ein Puffer ist eine Adresse — wem die Freigabe gehört, klärt [[lyx_-_programmiersprache:sprache:handles-und-fd|Handles & FD]], den Umgang mit rohen Puffern [[lyx_-_programmiersprache:sprache:rohspeicher|Rohspeicher]]. Wer das akzeptiert, kommt mit sehr wenig API aus.
→ [[lyx_-_programmiersprache:guides|Guides]] · [[lyx_-_programmiersprache:units|Standard Library]] · [[lyx_-_programmiersprache:guides:netzwerk|Netzwerk-Guide]]
import std.fs; // Pfade, Verzeichnisse, ReadFile/WriteFile, Glob
import std.fs_ext; // stat, PRead/PWrite, Fsync, Sendfile, GetCwd
import std.alloc; // alloc / free für Puffer
----
===== Die vier Ebenen =====
^ Ebene ^ Import nötig? ^ Wofür ^
| Builtins: ''open'' ''close'' ''read'' ''write'' ''lseek'' ''stat'' ''unlink'' ''rename'' ''mkdir'' ''rmdir'' ''pipe'' ''truncate'' ''ioctl'' ''getdents64'' ''symlink'' ''readlink'' ''mmap'' ''munmap'' | nein | Rohzugriff, wenn genau ein Syscall gebraucht wird |
| ''std.fs'' | ja | Datei in einem Aufruf lesen/schreiben, Pfade zerlegen, Verzeichnisse auflisten und rekursiv durchlaufen, Glob |
| ''std.fs_ext'' | ja | ''stat''-Auswertung, ''pread''/''pwrite'', ''readv''/''writev'', ''fsync'', ''sendfile'', Arbeitsverzeichnis |
| ''std.mmap_ext'' / ''std.inotify'' / ''std.xattr'' / ''std.io_uring'' | ja | Speicherabbildung, Dateiüberwachung, erweiterte Attribute + Dateisystem-Belegung, asynchrone I/O |
''std.fs'' und ''std.fs_ext'' exportieren beide ''FileExists'' und ''FileSize''. Wer **beide** importiert, bekommt:
sema error: mehrdeutiges Symbol 'FileExists' — exportiert von 'std.fs' und 'std.fs_ext'
Lyx hat keine qualifizierten Zugriffe (''std.fs.FileExists'' gibt es nicht), also: **eine der beiden Units wählen**. Faustregel — Pfad-orientiert arbeiten → ''std.fs''; über offene Deskriptoren arbeiten → ''std.fs_ext''.
----
===== Datei lesen und schreiben =====
Der kürzeste Weg braucht keinen Deskriptor und kein ''open'':
unit main;
import std.fs;
import std.io;
import std.string;
fn main(): int64 {
var path: pchar := "/tmp/lyx_fs_demo.txt"c;
var text: pchar := "Hallo Lyx\n"c;
if (WriteFile(path, text, StrLen(text)) < 0) {
PrintLn("Schreiben fehlgeschlagen");
return 1;
}
var buf: [256]char;
var n: int64 := ReadFile(path, buf as pchar, 255);
PrintLn(StrConcat("Bytes gelesen: ", IntToStr(n)));
PrintLn(StrConcat("Groesse: ", IntToStr(FileSize(path))));
if (FileExists(path)) { PrintLn("existiert"); }
DeleteFile(path);
return 0;
}
Bytes gelesen: 10
Groesse: 10
existiert
^ Funktion ^ Bedeutung ^
| ''WriteFile(path, buf, len)'' | Anlegen/Überschreiben, gibt geschriebene Bytes zurück (''<0'' = Fehler) |
| ''AppendFile(path, buf, len)'' | Anhängen |
| ''ReadFile(path, buf, max)'' | Liest bis ''max'' Bytes, gibt gelesene Bytes zurück |
| ''FileSize(path)'' / ''FileSizeFast(path)'' | Größe (letzteres über ''stat'', ohne Öffnen) |
| ''FileCopy'' / ''FileMove'' / ''Rename'' / ''DeleteFile'' / ''Remove'' | Datei-Operationen, ''bool'' |
| ''Chmod(path, mode)'' | Rechte setzen (''DEFAULT_MODE'' = 0644) |
**Puffer nicht vergessen zu terminieren.** ''ReadFile'' schreibt keine abschließende 0 — wer den Puffer als ''pchar'' ausgeben will, setzt sie selbst: ''poke8(buf + n, 0)''.
----
===== Verzeichnisse, rekursiv und per Muster =====
unit main;
import std.fs;
import std.io;
import std.string;
fn main(): int64 {
var dl: int64 := DirList("demo"c);
if (dl == 0) { PrintLn("DirList fehlgeschlagen"); return 1; }
var n: int64 := DirEntryCount(dl);
var i: int64 := 0;
while (i < n) {
var name: pchar := DirEntryName(dl, i);
var t: int64 := DirEntryTypeAt(dl, i);
var kind: pchar := "Datei";
if (t == DT_DIR) { kind := "Verz."; }
PrintLn(StrConcat(StrConcat(kind, " "), name));
i := i + 1;
}
DirFree(dl);
var g: int64 := FileGlob("demo/*.txt"c);
PrintLn(StrConcat("Glob-Treffer: ", IntToStr(GlobCount(g))));
GlobFree(g);
var w: int64 := DirWalk("demo"c); // rekursiv
PrintLn(StrConcat("Rekursiv gefunden: ", IntToStr(DirWalkCount(w))));
DirWalkFree(w);
return 0;
}
Datei a.txt
Verz. sub
Datei b.log
Glob-Treffer: 1
Rekursiv gefunden: 4
''.'' und ''..'' werden von ''DirList'' bereits herausgefiltert. Die Reihenfolge ist die des Dateisystems, **nicht sortiert**.
^ Gruppe ^ Funktionen ^
| Auflisten | ''DirList'' → ''DirEntryCount'' / ''DirEntryName(dl,i)'' / ''DirEntryTypeAt(dl,i)'' / ''DirEntryType(dl,name,len)'' → ''DirFree'' |
| Rekursiv | ''DirWalk'' → ''DirWalkCount'' / ''DirWalkPath(w,i,outBuf)'' / ''DirWalkType'' → ''DirWalkFree'' |
| Muster | ''FileGlob'' → ''GlobCount'' / ''GlobGet(g,i,outBuf)'' → ''GlobFree''; ''GlobMatch(pattern, name)'' für Einzelprüfung |
| Anlegen | ''Mkdir(path, mode)'', ''MkDirAll(path)'' (wie ''mkdir -p''), ''Rmdir'' |
| Temporär | ''MkTemp(pathBuf)'', ''TmpFile()'' |
**Jede der drei Listen-APIs braucht ihr eigenes Free.** ''DirFree'' / ''DirWalkFree'' / ''GlobFree'' sind nicht austauschbar.
**Eintragstypen:** ''DT_REG'' (Datei), ''DT_DIR'', ''DT_LNK'', ''DT_FIFO'', ''DT_SOCK'', ''DT_CHR'', ''DT_BLK'', ''DT_UNKNOWN''.
==== Pfade zerlegen ohne String-Gefummel ====
Alle Pfad-Funktionen schreiben in einen **vom Aufrufer gestellten Zielpuffer** und geben ihn zurück:
^ Funktion ^ Ergebnis für ''/var/log/app.tar.gz'' ^
| ''PathDir(dest, path)'' | ''/var/log'' |
| ''PathBase(dest, path)'' | ''app.tar.gz'' |
| ''PathExt(dest, path)'' | ''gz'' |
| ''PathJoin(dest, a, b)'' | fügt mit genau einem ''/'' zusammen |
| ''PathNormalize(dest, src)'' | löst ''.'' und ''..'' auf |
| ''PathResolve(dest, base, rel)'' | relativ gegen Basis auflösen |
| ''IsAbsolutePath(path)'' | ''bool'' |
| ''PathContainedIn(root, path)'' | ''bool'' — **Schutz gegen Path-Traversal**, vor jedem Zugriff auf vom Nutzer gelieferte Pfade verwenden |
----
===== Metadaten, positioniertes Lesen, Nullkopie (std.fs_ext) =====
unit main;
import std.fs_ext;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var fd: int64 := open("demo/a.txt"c, 0, 0); // O_RDONLY
if (fd < 0) { PrintLn("open fehlgeschlagen"); return 1; }
var st: int64 := alloc(STAT_SIZE);
if (Fstat(fd, st) < 0) { PrintLn("fstat fehlgeschlagen"); return 1; }
PrintLn(StrConcat("Groesse: ", IntToStr(StatFileSize(st))));
PrintLn(StrConcat("mtime: ", IntToStr(StatMtime(st))));
if (StatIsFile(st)) { PrintLn("regulaere Datei"); }
free(st, 0);
var buf: int64 := alloc(64);
var n: int64 := PRead(fd, buf, 16, 0); // ohne lseek
PrintLn(StrConcat("PRead Bytes: ", IntToStr(n)));
free(buf, 0);
close(fd);
var cwd: int64 := alloc(PATH_MAX);
GetCwd(cwd, PATH_MAX);
PrintLn(StrConcat("CWD: ", cwd as pchar));
free(cwd, 0);
return 0;
}
Groesse: 17
mtime: 1786470295
regulaere Datei
PRead Bytes: 16
CWD: /tmp/…/scratchpad
^ Aufgabe ^ Funktion ^
| Metadaten | ''Fstat(fd, statBuf)'' mit ''statBuf = alloc(STAT_SIZE)''; auswerten mit ''StatFileSize'' / ''StatMtime'' / ''StatMode'' / ''StatIsDir'' / ''StatIsFile'' |
| Positioniert lesen/schreiben | ''PRead(fd, buf, n, off)'' / ''PWrite(...)'' — thread-sicher, verändert die Dateiposition nicht |
| Streuend lesen/schreiben | ''IovecSet(iov, idx, base, len)'' + ''ReadV'' / ''WriteV'' (''IOVEC_SIZE'' = 16 Bytes pro Eintrag) |
| Auf Platte zwingen | ''Fsync(fd)'' (Daten + Metadaten), ''Fdatasync(fd)'' (nur Daten, schneller) |
| Kürzen/Verlängern | ''Truncate(path, len)'' / ''Ftruncate(fd, len)'' |
| Rechte prüfen | ''FileAccess(path, mode)'' mit ''R_OK''/''W_OK''/''X_OK''/''F_OK'', bequemer: ''FileReadable'' / ''FileWritable'' |
| Arbeitsverzeichnis | ''GetCwd(buf, size)'', ''Chdir(path)'' |
| Nullkopie | ''Sendfile(outFd, inFd, offsetPtr, count)'' — kopiert im Kernel, ohne Userspace-Puffer |
''Sendfile'' ist der schnellste Weg Datei→Datei und Datei→Socket:
var src: int64 := open("demo/a.txt"c, 0, 0);
var dst: int64 := open("demo/kopie.txt"c, 65 | 512, 420); // O_WRONLY|O_CREAT|O_TRUNC, 0644
var moved: int64 := Sendfile(dst, src, 0, 4096); // offsetPtr = 0 → ab aktueller Position
Fsync(dst);
close(src); close(dst);
----
===== Große Dateien speicherabbilden (std.mmap_ext) =====
''mmap'' und ''munmap'' sind Builtins; ''std.mmap_ext'' liefert die Feinsteuerung.
unit main;
import std.fs_ext;
import std.mmap_ext;
import std.io;
import std.string;
con PROT_READ: int64 := 1;
con MAP_PRIVATE: int64 := 2;
fn main(): int64 {
var fd: int64 := open("demo/a.txt"c, 0, 0);
if (fd < 0) { return 1; }
var size: int64 := 4096;
var addr: int64 := mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (addr <= 0) { PrintLn("mmap fehlgeschlagen"); close(fd); return 1; }
MmapAdvise(addr, size, MADV_SEQUENTIAL); // Kernel-Hinweis
PrintLn(StrConcat("erstes Byte: ", IntToStr(peek8(addr))));
if (MemIsResident(addr)) { PrintLn("Seite ist im RAM"); }
munmap(addr, size);
close(fd);
return 0;
}
erstes Byte: 105
Seite ist im RAM
^ Funktion ^ Zweck ^
| ''MmapAdvise(addr, len, advice)'' | ''MADV_SEQUENTIAL'' (Readahead hoch), ''MADV_RANDOM'', ''MADV_WILLNEED'' (vorladen), ''MADV_DONTNEED'', ''MADV_HUGEPAGE'', ''MADV_COLD'', ''MADV_PAGEOUT'' |
| ''MmapSync(addr, len, flags)'' | Änderungen zurückschreiben — ''MS_SYNC'' (blockierend) oder ''MS_ASYNC'' |
| ''MmapResize'' / ''MmapResizeFixed'' | ''mremap'' — Abbildung vergrößern, ohne neu zu kopieren |
| ''MemFdCreate(name, flags)'' | anonyme Datei im RAM (''MFD_CLOEXEC'', ''MFD_ALLOW_SEALING'') — ideal für IPC ohne Dateisystem |
| ''MemLock'' / ''MemUnlock'' | Seiten gegen Swap sperren (Schlüsselmaterial!) |
| ''MemIsResident(addr)'' / ''MemInCore(addr, len, vecOut)'' | prüfen, ob Seiten im RAM liegen |
| ''MmapFreePages(addr, len)'' | Speicher an den Kernel zurückgeben, Abbildung bleibt |
**Größe immer auf ''PAGE_SIZE'' (4096) aufrunden** — der Kernel arbeitet nur seitenweise, und ''munmap'' mit falscher Länge lässt Reste liegen.
----
===== Änderungen überwachen (std.inotify) =====
unit main;
import std.inotify;
import std.alloc;
import std.fs;
import std.io;
import std.string;
fn main(): int64 {
var ifd: int64 := InotifyCreate();
if (ifd < 0) { PrintLn("inotify nicht verfuegbar"); return 1; }
var wd: int64 := InotifyWatch(ifd, "demo"c, IN_CREATE | IN_DELETE | IN_MODIFY);
if (wd < 0) { PrintLn("Watch fehlgeschlagen"); return 1; }
WriteFile("demo/neu.txt"c, "x"c, 1); // löst Ereignisse aus
var buf: int64 := alloc(4096);
var n: int64 := InotifyRead(ifd, buf, 4096); // blockiert bis Ereignis
var p: int64 := buf;
var ende: int64 := buf + n;
while (p < ende) {
var mask: int64 := InotifyEventMask(p);
var name: int64 := InotifyEventName(p);
var was: pchar := "sonstiges";
if ((mask & IN_CREATE) != 0) { was := "erstellt"; }
if ((mask & IN_MODIFY) != 0) { was := "geaendert"; }
if ((mask & IN_DELETE) != 0) { was := "geloescht"; }
PrintLn(StrConcat(StrConcat(name as pchar, ": "), was));
p := InotifyEventNext(p); // nächster Eintrag im Puffer
}
free(buf, 0);
InotifyUnwatch(ifd, wd);
close(ifd);
return 0;
}
neu.txt: erstellt
neu.txt: geaendert
Ein ''InotifyRead'' liefert **mehrere Ereignisse variabler Länge am Stück**. Deshalb immer die Schleife mit ''InotifyEventNext'' verwenden — feste Schrittweiten sind falsch (''INOTIFY_EVENT_HDR_SIZE'' = 16 plus Namenslänge).
^ Maske ^ Bedeutung ^
| ''IN_CREATE'' / ''IN_DELETE'' / ''IN_MODIFY'' | Datei erstellt / gelöscht / geändert |
| ''IN_CLOSE_WRITE'' | Schreib-Datei geschlossen — **das richtige Signal für „Datei fertig geschrieben"**, nicht ''IN_MODIFY'' |
| ''IN_MOVED_FROM'' / ''IN_MOVED_TO'' | Verschiebung; über ''InotifyEventCookie'' paarbar |
| ''IN_DELETE_SELF'' / ''IN_MOVE_SELF'' | überwachtes Objekt selbst betroffen |
| ''IN_ISDIR'' | zusätzliches Bit: Ereignis betrifft ein Verzeichnis |
| ''IN_Q_OVERFLOW'' | Queue übergelaufen — Ereignisse **wurden verloren**, Zustand neu einlesen |
| ''IN_ALL_EVENTS'' | alles |
''InotifyCreate'' überwacht **nicht rekursiv**. Für Bäume: ''DirWalk'' laufen lassen und jedes Verzeichnis einzeln registrieren, bei ''IN_CREATE | IN_ISDIR'' nachregistrieren.
----
===== Erweiterte Attribute & Dateisystem-Belegung (std.xattr) =====
unit main;
import std.xattr;
import std.fs_ext;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var val: pchar := "andreas"c;
if (XattrSet("demo/a.txt"c, "user.autor"c, val as int64, StrLen(val), 0) < 0) {
PrintLn("xattr nicht unterstuetzt (Dateisystem?)");
} else {
var buf: int64 := alloc(256);
var n: int64 := XattrGet("demo/a.txt"c, "user.autor"c, buf, 256);
poke8(buf + n, 0);
PrintLn(StrConcat("Autor: ", buf as pchar));
free(buf, 0);
XattrRemove("demo/a.txt"c, "user.autor"c);
}
var sfb: int64 := alloc(STATFS_SIZE);
if (StatFs("/tmp"c, sfb) == 0) {
PrintLn(StrConcat("frei (MB): ", IntToStr(StatFsFreeBytes(sfb) / 1048576)));
}
free(sfb, 0);
return 0;
}
Autor: andreas
frei (MB): 39079
* **Namensraum beachten:** Nutzer-Attribute **müssen** mit ''user.'' beginnen, sonst lehnt der Kernel ab.
* ''XATTR_CREATE'' = nur anlegen, ''XATTR_REPLACE'' = nur ersetzen, ''0'' = beides.
* Über offene Deskriptoren: ''FXattrSet'' / ''FXattrGet'' / ''FXattrList'' / ''FXattrRemove''.
* Belegung: ''StatFs(path, buf)'' + ''StatFsTotalBytes'' / ''StatFsFreeBytes'' / ''StatFsBlockSize''.
* Platz vorab reservieren (keine Fragmentierung, kein ENOSPC mitten im Schreiben): ''FileAllocate(fd, offset, size)''; Löcher stanzen mit ''FileAllocateMode(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, off, size)''.
----
===== Asynchrone I/O (std.io_uring) =====
Für viele parallele Lese-/Schreibvorgänge ohne Threads. Ablauf: Ring anlegen → SQE holen → Operation eintragen → abschicken → CQE einsammeln → als gesehen markieren.
unit main;
import std.io_uring;
import std.alloc;
import std.io;
import std.string;
fn main(): int64 {
var ring: int64 := IoUringCreate(8); // Ringgröße (Zweierpotenz)
if (ring == 0) { PrintLn("io_uring nicht verfuegbar"); return 1; }
var fd: int64 := open("demo/a.txt"c, 0, 0);
var buf: int64 := alloc(256);
var sqe: int64 := IoUringGetSQE(ring);
IoUringSQESetRead(sqe, fd, buf, 64, 0, 42); // len=64, offset=0, userData=42
IoUringSubmitAndWait(ring, 1);
var cqe: int64 := IoUringWaitCQE(ring);
var res: int64 := IoUringCQEResult(cqe); // >=0 Bytes, <0 negativer errno
var ud: int64 := IoUringCQEUserData(cqe);
IoUringCQESeen(ring, cqe); // Pflicht — sonst läuft der Ring voll
PrintLn(StrConcat("gelesen: ", IntToStr(res)));
PrintLn(StrConcat("userData: ", IntToStr(ud)));
free(buf, 0);
close(fd);
IoUringFree(ring);
return 0;
}
gelesen: 17
userData: 42
* ''userData'' ist frei wählbar und der **einzige** Weg, eine Completion ihrer Anfrage zuzuordnen — typischerweise ein Index oder eine Objektadresse.
* Operationen: ''IoUringSQESetRead'' / ''IoUringSQESetWrite'' / ''IoUringSQESetNop''; die Opcodes für Netzwerk (''IORING_OP_ACCEPT'', ''IORING_OP_SEND'', ''IORING_OP_RECV'', ''IORING_OP_CONNECT'') sind als Konstanten vorhanden und können direkt ins SQE gepokt werden.
* Ohne Warten: ''IoUringSubmit'' + ''IoUringPeekCQE'' (gibt 0 zurück, wenn nichts fertig ist) — das ist die Ereignisschleifen-Variante.
* Puffer **müssen bis zur Completion gültig bleiben**. Ein Stack-Array einer bereits verlassenen Funktion ist der klassische Absturz.
----
===== Entscheidungshilfe =====
^ Aufgabe ^ Empfehlung ^
| Kleine Datei ganz lesen/schreiben | ''ReadFile'' / ''WriteFile'' (''std.fs'') |
| Konfigurationsdatei zeilenweise | ''ReadFile'' + ''std.string''-Funktionen |
| Datei > RAM durchsuchen | ''mmap'' + ''MADV_SEQUENTIAL'' |
| Datei kopieren | ''FileCopy'' (bequem) oder ''Sendfile'' (schnell, kein Userspace-Puffer) |
| Nebenläufig aus einer Datei lesen | ''PRead'' — kein gemeinsamer Positionszeiger |
| Datei→Socket ausliefern | ''Sendfile'' |
| Verzeichnis überwachen | ''std.inotify'', Signal ''IN_CLOSE_WRITE'' |
| Hunderte parallele I/Os | ''std.io_uring'' |
| Datenbank-artige Haltbarkeit | schreiben → ''Fdatasync(fd)'' → ''Rename'' (atomarer Austausch) |
| Metadaten an Dateien hängen | ''std.xattr'', Namensraum ''user.'' |
| Archive/Kompression | eigene Units: ''std.tar'', ''std.zip'', ''std.gzip'', ''std.zstd'', ''std.brotli'', ''std.zlib'', ''std.rar'' |
----
===== Fallstricke =====
* **Zwei Allokator-Familien mit unterschiedlichem ''free''.** ''std.alloc'' bietet ''alloc(n)'' / ''free(ptr, size)'' und ''malloc(n)'' / ''calloc(n,sz)'' / ''realloc_mem'' / ''free_mem(ptr)''. Beide arbeiten seit 1.0.17C ohne C-Bibliothek über eigene ''mmap''-Belegung (bis dahin gab ''malloc'' im statischen Binary **0** zurück — #1179). Nicht mischen: ein ''alloc''-Zeiger gehört an ''free'', ein ''malloc''-Zeiger an ''free_mem''. Rückgabe **immer** gegen 0 prüfen.
* **Fehlerkonvention ist uneinheitlich.** ''std.fs'' gibt oft ''bool'' zurück, ''std.fs_ext'' und die Builtins geben ''0'' bei Erfolg bzw. den **negativen errno** bei Fehler, ''ReadFile''/''WriteFile'' geben Bytezahlen. Immer gegen ''< 0'' prüfen, nie gegen ''!= 0''.
* **Deskriptoren lecken.** Es gibt kein ''defer'' und keine Destruktoren — jeder Pfad, der die Funktion verlässt, muss ''close'' selbst aufrufen. Bei Fehlerbehandlung mit frühem ''return'' ist das die häufigste Lücke.
* **''O_*''-Flags sind Zahlen, keine Namen.** ''std.fs'' exportiert ''O_RDONLY'' (0), ''O_WRONLY'' (1), ''O_RDWR'' (2), ''O_CREAT'' (64), ''O_TRUNC'' (512), ''O_APPEND'' (1024), ''O_EXCL'' (128), ''O_DIRECTORY'' (65536). Wer nur ''std.fs_ext'' importiert, definiert sie selbst — mit **genau diesen Werten**, sonst öffnet ''open'' scheinbar zufällig etwas anderes.
* **''mode'' ist dezimal, nicht oktal.** ''0644'' schreibt man in Lyx als ''420'', ''0755'' als ''493''. Dafür gibt es ''DEFAULT_MODE'' und ''DEFAULT_DIR_MODE''.
* **Pfade aus Fremdeingabe niemals direkt verwenden.** Erst ''PathNormalize'', dann ''PathContainedIn(root, path)'' prüfen.
* **''DirWalk'' lädt das Ergebnis komplett in den Speicher.** Bei sehr großen Bäumen besser ''DirList'' pro Ebene und selbst absteigen.
----
**Weiterführend:** [[lyx_-_programmiersprache:guides:netzwerk|Netzwerk-Guide]] (epoll, Sockets) · [[lyx_-_programmiersprache:guides:daten-serialisierung|Daten & Serialisierung]] · [[lyx_-_programmiersprache:guides:rtos-embedded-concurrency|RTOS & Nebenläufigkeit]] · [[lyx_-_programmiersprache:guides:audio-und-video|Audio & Video]]