vfs: major overhaul — device nodes, stream I/O, mode simplification

- 5-phase VFS architecture overhaul (path resolution, dir cache, watch/notify,
  symlinks, async API)
- Add character/block device node layer with /dev/null and /dev/zero built-ins
- RegisterDevice() API for custom device nodes with read/write/size callbacks
- Merge TOpenMode + TWriteMode into single TOpenMode enum
  (omRead, omWrite, omCreate, omReadWrite, omStream)
- Add stream write support: WriteFile/WriteFileAsync now accept omStream handles
  with auto-advancing offset for both device and volume FDs
- Add PPWriteOffsetHook type and writeOffsetCallback field to TFilesystem
- Add PPReadOffsetHook-based streaming reads for FAT32 and ISO9660
- Update all callers (notepad, edit, inio, wasm runner, filepicker, filedispatch,
  vterminal, stdio) to new 2-param OpenFile API
- 77 unit tests (0 failures) covering path ops, dir cache, symlinks, watch,
  device nodes, and stream read/write
- Add doc/vfs.md — comprehensive public VFS API documentation
This commit is contained in:
Aaron committed 2026-03-08 18:07:44 +00:00
1 parent 63b67f5878
commit fcc9809248
17 files changed
+2774 -417

No files matched your search

+3 -3
View File
@@ -138,7 +138,7 @@ begin
{ Open for writing }
debug.tracer.push_trace('edit.SaveFile.openRW');
fHandle := driver.storage.vfs.OpenFile(FilePath, omReadWrite, wmRewrite, @fError);
fHandle := driver.storage.vfs.OpenFile(FilePath, omReadWrite, @fError);
if fHandle <> 0 then begin
debug.tracer.push_trace('edit.SaveFile.writeFile');
driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), totalLen);
@@ -149,7 +149,7 @@ begin
end else begin
{ Try write-only for new file }
debug.tracer.push_trace('edit.SaveFile.openWO');
fHandle := driver.storage.vfs.OpenFile(FilePath, omWriteOnly, wmNew, @fError);
fHandle := driver.storage.vfs.OpenFile(FilePath, omCreate, @fError);
if fHandle <> 0 then begin
debug.tracer.push_trace('edit.SaveFile.writeFileNew');
driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), totalLen);
@@ -181,7 +181,7 @@ begin
if FilePath = nil then exit;
debug.tracer.push_trace('edit.LoadFile.openFile');
fHandle := driver.storage.vfs.OpenFile(FilePath, omReadOnly, wmRewrite, @fError);
fHandle := driver.storage.vfs.OpenFile(FilePath, omRead, @fError);
debug.tracer.push_trace('edit.LoadFile.openFile.done');
if (fHandle = 0) or (fError <> eNone) then begin
io.syslog.writestringln('New file.');
+39 -4
View File
@@ -77,12 +77,15 @@ type
{ Flat name buffer — heap-alloc'd in do_refresh to avoid stack bloat }
TNameBuf = array[0..ENTRY_MAX - 1] of pchar;
PNameBuf = ^TNameBuf;
TSizeBuf = array[0..ENTRY_MAX - 1] of uint32;
PSizeBuf = ^TSizeBuf;
{ Per-call context passed to the forEach collect callback }
PFPCollectCtx = ^TFPCollectCtx;
TFPCollectCtx = record
dir_names : PNameBuf;
file_names : PNameBuf;
file_sizes : PSizeBuf;
dir_count : uint32;
file_count : uint32;
filter : pchar;
@@ -350,6 +353,28 @@ begin
end;
end;
{ Insertion sort on name array, swapping a parallel size array in lockstep }
procedure sortNamesWithSizes(var names: array of pchar; var sizes: array of uint32; n: uint32);
var
i, j : uint32;
tmpN : pchar;
tmpS : uint32;
begin
if n < 2 then exit;
for i := 1 to n - 1 do begin
tmpN := names[i];
tmpS := sizes[i];
j := i;
while (j > 0) and strLess(tmpN, names[j - 1]) do begin
names[j] := names[j - 1];
sizes[j] := sizes[j - 1];
j := j - 1;
end;
names[j] := tmpN;
sizes[j] := tmpS;
end;
end;
{ ============================================================
fp_collect_cb
core.ds.hashmap.forEach callback: classifies each VFS entry as dir or file
@@ -373,6 +398,7 @@ begin
if matchesFilter(key, ctx^.filter) then
if ctx^.file_count < ENTRY_MAX then begin
ctx^.file_names^[ctx^.file_count] := key;
ctx^.file_sizes^[ctx^.file_count] := obj^.FileSize;
ctx^.file_count := ctx^.file_count + 1;
end;
end;
@@ -390,12 +416,14 @@ var
ctx : TFPCollectCtx;
dir_names : PNameBuf; { heap-alloc'd to avoid blowing the core.version stack }
file_names : PNameBuf;
file_sizes : PSizeBuf;
dir_count : uint32;
file_count : uint32;
i : uint32;
row, name_lbl, size_lbl : Plv_obj;
sep : pchar;
copy : pchar;
szStr : pchar;
s1, s2, s3 : pchar;
begin
debug.tracer.push_trace('filepicker.do_refresh');
@@ -408,8 +436,10 @@ begin
io.syslog.logln('FPCIK', 'do_refresh: kalloc name bufs');
dir_names := PNameBuf(kalloc(sizeof(TNameBuf)));
file_names := PNameBuf(kalloc(sizeof(TNameBuf)));
file_sizes := PSizeBuf(kalloc(sizeof(TSizeBuf)));
memset(uint32(dir_names), 0, sizeof(TNameBuf));
memset(uint32(file_names), 0, sizeof(TNameBuf));
memset(uint32(file_sizes), 0, sizeof(TSizeBuf));
{ Sync path bar }
if p^.path_bar <> nil then
@@ -429,6 +459,7 @@ begin
lv_label_set_text(p^.status_label, 'Could not read directory');
kfree(void(dir_names));
kfree(void(file_names));
kfree(void(file_sizes));
debug.tracer.pop_trace;
exit;
end;
@@ -437,6 +468,7 @@ begin
driver.storage.vfs.FreeDirectoryListing(Map);
kfree(void(dir_names));
kfree(void(file_names));
kfree(void(file_sizes));
debug.tracer.pop_trace;
exit;
end;
@@ -444,6 +476,7 @@ begin
ctx.dir_names := dir_names;
ctx.file_names := file_names;
ctx.file_sizes := file_sizes;
ctx.dir_count := 0;
ctx.file_count := 0;
ctx.filter := p^.filter;
@@ -455,7 +488,7 @@ begin
{ --- Sort both collections alphabetically --- }
if dir_count > 0 then sortNames(dir_names^, dir_count);
if file_count > 0 then sortNames(file_names^, file_count);
if file_count > 0 then sortNamesWithSizes(file_names^, file_sizes^, file_count);
{ --- Build directory rows --- }
if dir_count > 0 then
@@ -524,10 +557,11 @@ begin
lv_obj_set_style_text_color(name_lbl, lv_color_make(210, 215, 230), 0);
lv_obj_set_style_text_font(name_lbl, @lv_font_montserrat_14, 0);
{ File size column: skipped here to avoid blocking disk I/O
inside the LVGL timer callback context. }
{ File size column: use metadata from directory listing }
size_lbl := lv_label_create(row);
lv_label_set_text(size_lbl, '--');
szStr := fmtFileSize(file_sizes^[i]);
lv_label_set_text(size_lbl, szStr);
kfree(void(szStr));
lv_obj_set_width(size_lbl, 70);
lv_obj_set_style_text_color(size_lbl, lv_color_make(100, 110, 130), 0);
lv_obj_set_style_text_font(size_lbl, @lv_font_montserrat_14, 0);
@@ -543,6 +577,7 @@ begin
kfree(void(dir_names));
kfree(void(file_names));
kfree(void(file_sizes));
{ Update status label: "X dirs, Y files" }
if p^.status_label <> nil then begin
+3 -3
View File
@@ -65,7 +65,7 @@ begin
{ ---- READ ---- }
if op[0] = '<' then begin
fHandle := driver.storage.vfs.OpenFile(absPath, omReadOnly, wmRewrite, @fError);
fHandle := driver.storage.vfs.OpenFile(absPath, omRead, @fError);
if (fHandle = 0) or (fError <> eNone) then begin
io.syslog.writestringln('Error: cannot open file for reading.');
kfree(void(absPath));
@@ -112,10 +112,10 @@ begin
contentLen := stringSize(content);
{ Try read-write rewrite first (existing file) }
fHandle := driver.storage.vfs.OpenFile(absPath, omReadWrite, wmRewrite, @fError);
fHandle := driver.storage.vfs.OpenFile(absPath, omReadWrite, @fError);
if (fHandle = 0) or (fError <> eNone) then begin
{ Try creating new file }
fHandle := driver.storage.vfs.OpenFile(absPath, omWriteOnly, wmNew, @fError);
fHandle := driver.storage.vfs.OpenFile(absPath, omCreate, @fError);
end;
if (fHandle = 0) or (fError <> eNone) then begin
+3 -3
View File
@@ -372,9 +372,9 @@ begin
buf := pchar(kalloc(FILE_BUF_SIZE));
memset(uint32(buf), 0, FILE_BUF_SIZE);
memcpy(uint32(txt), uint32(buf), len);
fHandle := driver.storage.vfs.OpenFile(state^.currentPath, omReadWrite, wmRewrite, @fError);
fHandle := driver.storage.vfs.OpenFile(state^.currentPath, omReadWrite, @fError);
if fHandle = 0 then
fHandle := driver.storage.vfs.OpenFile(state^.currentPath, omWriteOnly, wmNew, @fError);
fHandle := driver.storage.vfs.OpenFile(state^.currentPath, omCreate, @fError);
if fHandle = 0 then begin
kfree(void(buf));
io.syslog.logln('NOTEPAD', 'saveFile: OpenFile failed');
@@ -413,7 +413,7 @@ var
buf : pchar;
begin
debug.tracer.push_trace('notepad.loadFile');
fHandle := driver.storage.vfs.OpenFile(path, omReadOnly, wmRewrite, @fError);
fHandle := driver.storage.vfs.OpenFile(path, omRead, @fError);
if (fHandle = 0) or (fError <> eNone) then begin
io.syslog.logln('NOTEPAD', 'loadFile: could not open file');
showErrorMsgbox('Open Failed', 'Could not open file for reading.');
+3 -3
View File
@@ -343,7 +343,7 @@ begin
end;
end;
{ Resolve relative to per-terminal cwd }
Validity := driver.storage.vfs.changeDirectoryFrom(Path, state^.cwd, NewDir);
Validity := driver.storage.vfs.ChangeDirectoryFrom(Path, state^.cwd, NewDir);
case Validity of
pvDirectory: begin
kfree(void(state^.cwd));
@@ -410,7 +410,7 @@ var
begin
if STRLL_Size(state^.dir_stack) > 0 then begin
wd := STRLL_Get(state^.dir_stack, STRLL_Size(state^.dir_stack) - 1);
Validity := driver.storage.vfs.changeDirectoryFrom(wd, state^.cwd, NewDir);
Validity := driver.storage.vfs.ChangeDirectoryFrom(wd, state^.cwd, NewDir);
if Validity = pvDirectory then begin
kfree(void(state^.cwd));
state^.cwd := NewDir;
@@ -696,7 +696,7 @@ begin
end;
end else begin
{ No built-in command found — try file dispatch }
absPath := driver.storage.vfs.makeAbsolutePathFrom(params^.Param, state^.cwd);
absPath := driver.storage.vfs.MakeAbsolutePathFrom(params^.Param, state^.cwd);
if absPath <> nil then begin
if is_bg then begin
{ Background file dispatch }
+13 -14
View File
@@ -152,36 +152,35 @@ var
fh : TFileHandle;
err : TError;
fsize : uint32;
errb : uint8;
bytesRead : uint32;
processCtx : PProcessContext;
begin
debug.tracer.push_trace('wasmrunner.handler');
wasm_file_handler := 0;
{ 1. Get file size }
errb := 0;
fsize := driver.storage.vfs.FileSize(path, @errb);
if (fsize = 0) or (errb <> 0) then begin
io.stdio.bufWriteStrLn(stderr_buf, 'WASM: cannot determine file size');
{ 1. Open file once — omRead pre-loads the data and gives us the size }
err := eNone;
fh := driver.storage.vfs.OpenFile(path, omRead, @err);
if (fh = 0) or (err <> eNone) then begin
io.stdio.bufWriteStrLn(stderr_buf, 'WASM: cannot open file');
exit;
end;
fsize := driver.storage.vfs.FileSizeFromHandle(fh);
if fsize = 0 then begin
driver.storage.vfs.CloseFile(fh);
io.stdio.bufWriteStrLn(stderr_buf, 'WASM: file is empty or cannot determine size');
exit;
end;
{ 2. Allocate buffer and read file }
buf := puint8(kalloc(fsize));
if buf = nil then begin
driver.storage.vfs.CloseFile(fh);
io.stdio.bufWriteStrLn(stderr_buf, 'WASM: out of memory');
exit;
end;
err := eNone;
fh := driver.storage.vfs.OpenFile(path, omReadOnly, wmRewrite, @err);
if fh = 0 then begin
kfree(void(buf));
io.stdio.bufWriteStrLn(stderr_buf, 'WASM: cannot open file');
exit;
end;
bytesRead := driver.storage.vfs.ReadFile(fh, 0, buf, fsize);
driver.storage.vfs.CloseFile(fh);