Merge pull request 'Major rework of the Virtual File System layer covering path resolution, I/O modes, device abstractions, symlinks, and boot mount infrastructure.' (#58) from feature/vfs_improvements into develop
continuous-integration/drone/push Build is passing

Reviewed-on: #58
Reviewed-by: admin <[email protected]>
This commit was merged in pull request #58.
This commit is contained in:
2026-03-08 20:16:43 +00:00
18 changed files with 3242 additions and 590 deletions
+587
View File
File diff suppressed because it is too large Load Diff
Generated
+1
View File
@@ -0,0 +1 @@
mnt {device}/sys /sys
+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.');
+40 -5
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
@@ -364,7 +389,7 @@ begin
obj := PVFSObject(data);
if (ctx = nil) or (obj = nil) then exit;
case obj^.ObjectType of
otVDIRECTORY, otDRIVE, otDIRECTORY, otMOUNT:
otVDIRECTORY, otDRIVE, otDIRECTORY, otMOUNT, otSYMLINK:
if ctx^.dir_count < ENTRY_MAX then begin
ctx^.dir_names^[ctx^.dir_count] := key;
ctx^.dir_count := ctx^.dir_count + 1;
@@ -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);
@@ -148,6 +148,10 @@ begin
entry := PDirectory_Entry(LL_Add(dirList));
entry^.fileName := stringCopy(Files[i].Name);
entry^.entryType := fileEntry;
entry^.fileSize := Files[i].Size;
entry^.modifiedDate := 0;
entry^.modifiedTime := 0;
entry^.attributes := 0;
end;
end;
if status <> nil then status^ := 0;
@@ -41,11 +41,12 @@ type
Directory : pchar; { directory path within the volume }
FileName : pchar; { filename within that directory }
OpenMode : uint8; { TOpenMode ordinal — avoids VFS type dependency }
WriteMode : uint8; { TWriteMode ordinal }
DataBuffer : puint32; { pre-loaded data for small files, nil otherwise }
DataSize : uint32; { size of pre-loaded data in bytes }
Loaded : boolean; { true if pre-loaded into DataBuffer }
StreamOff : uint32; { current byte offset for streaming / on-demand reads }
DeviceOps : pointer; { PVFSDeviceOps — non-nil for device FDs }
DeviceData : pointer; { opaque data passed to device callbacks }
end;
PFDTable = ^TFDTable;
@@ -113,6 +114,8 @@ begin
entry^.Loaded := false;
entry^.DataSize := 0;
entry^.StreamOff := 0;
entry^.DeviceOps := nil;
entry^.DeviceData := nil;
end;
procedure fd_table_free(table : PFDTable);
@@ -133,9 +133,10 @@ begin
validity := driver.storage.vfs.PathValid(absPath);
if validity <> pvFile then exit;
{ 2. Open the file and read the header bytes }
{ 2. Open the file in stream mode — only reads the bytes we ask for,
avoids pre-loading the entire file just to check a few magic bytes. }
err := eNone;
fh := driver.storage.vfs.OpenFile(absPath, omReadOnly, wmRewrite, @err);
fh := driver.storage.vfs.OpenFile(absPath, omStream, @err);
if fh = 0 then exit;
memset(uint32(@headerBuf[0]), 0, MAX_MAGIC_LEN);
File diff suppressed because it is too large Load Diff
+17 -2
View File
@@ -90,6 +90,8 @@ type
eTooManyOpenFiles, { per-process FD table full }
eInvalidHandle, { TFileHandle does not refer to an open FD }
eNotStreamMode, { SeekFile called on a non-omStream handle }
eFileNotLoaded, { ReadFile on a pre-load handle whose data is not yet loaded }
eAlreadyExists, { generic: target name already exists (symlink, file, etc.) }
{ Disk / capacity errors }
eDiskFull, { FS reports no free clusters/blocks }
@@ -148,11 +150,17 @@ type
PPDeleteFileHook = procedure(volume : PStorage_Volume; filePath : pchar; status : puint32);
PPDeleteDirHook = procedure(volume : PStorage_Volume; path : pchar; status : puint32);
PPIdentifyHook = function(volume : PStorage_Volume) : boolean;
PPRenameFileHook = procedure(volume : PStorage_Volume; filePath : pchar; newName : pchar; status : puint32);
{ Offset-based read: reads byteCount bytes starting at byte offset into the file.
Returns the number of bytes actually read.
Filesystems that do not implement this leave the field nil and VFS falls back
to the load-all readCallback behaviour. }
PPReadOffsetHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; offset : uint32; buffer : puint32; byteCount : uint32) : uint32;
{ Offset-based write: writes byteCount bytes starting at byte offset into the file.
Returns the number of bytes actually written.
Filesystems that do not implement this leave the field nil; VFS returns 0
for stream-mode writes on volume FDs. }
PPWriteOffsetHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; offset : uint32; buffer : puint32; byteCount : uint32) : uint32;
{ === Async filesystem hook core.types ===
Each mirrors the corresponding sync hook but receives a TIOCallback + callbackData.
@@ -227,6 +235,8 @@ type
identifyCallback : PPIdentifyHook;
{ Offset-based read for streaming mode — nil if not implemented }
readOffsetCallback : PPReadOffsetHook;
{ Offset-based write for streaming mode — nil if not implemented }
writeOffsetCallback : PPWriteOffsetHook;
{ Async format — nil if FS only supports synchronous create }
createAsyncCallback : PPCreateAsyncHook;
{ Async variants of the main I/O hooks nil if FS only supports sync.
@@ -237,6 +247,7 @@ type
readDirAsyncCallback : PPReadDirAsyncHook;
deleteFileAsyncCallback : PPDeleteFileAsyncHook;
deleteDirAsyncCallback : PPDeleteDirAsyncHook;
renameFileCallback : PPRenameFileHook;
end;
{ Generic storage volume }
@@ -253,8 +264,12 @@ type
{ Generic directory entry }
TDirectory_Entry = record
fileName : pchar;
entryType : TDirectory_Entry_Type;
fileName : pchar;
entryType : TDirectory_Entry_Type;
fileSize : uint32;
modifiedDate : uint16; { FAT-style: bits 15-9=year-1980, 8-5=month, 4-0=day }
modifiedTime : uint16; { FAT-style: bits 15-11=hour, 10-5=min, 4-0=sec/2 }
attributes : uint8; { FS-specific attribute bits }
end;
TDrive_Error = record
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -827,6 +827,10 @@ begin
dirEntry^.entryType := TDirectory_Entry_Type.directoryEntry
else
dirEntry^.entryType := TDirectory_Entry_Type.fileEntry;
dirEntry^.fileSize := fileEntrys[i].size;
dirEntry^.modifiedDate := 0;
dirEntry^.modifiedTime := 0;
dirEntry^.attributes := fileEntrys[i].attribues;
end;
kfree(void(compString));
@@ -562,6 +562,19 @@ begin
else
dirEntry^.entryType := TDirectory_Entry_Type.fileEntry;
dirEntry^.fileSize := rec^.dataLen_LSB;
dirEntry^.attributes := rec^.fileFlags;
{ Convert ISO 9660 date (year-1900,month,day,hour,min,sec) to FAT-style }
if rec^.recDate[0] >= 80 then
dirEntry^.modifiedDate := (uint16(rec^.recDate[0] - 80) shl 9)
or (uint16(rec^.recDate[1]) shl 5)
or uint16(rec^.recDate[2])
else
dirEntry^.modifiedDate := 0;
dirEntry^.modifiedTime := (uint16(rec^.recDate[3]) shl 11)
or (uint16(rec^.recDate[4]) shl 5)
or (uint16(rec^.recDate[5]) shr 1);
offset := offset + rec^.recLen;
end;
@@ -722,6 +735,7 @@ begin
filesystem.createDirCallback := nil;
filesystem.deleteFileCallback := nil;
filesystem.deleteDirCallback := nil;
filesystem.renameFileCallback := nil;
filesystem.readAsyncCallback := nil;
filesystem.readDirAsyncCallback := nil;
+2 -2
View File
@@ -498,13 +498,13 @@ end;
function getWorkingDirectory: pchar;
begin
getWorkingDirectory := driver.storage.vfs.getWorkingDirectory;
getWorkingDirectory := driver.storage.vfs.GetWorkingDirectory;
end;
procedure setWorkingDirectory(str: pchar);
begin
if str <> nil then
driver.storage.vfs.changeDirectory(str);
driver.storage.vfs.ChangeDirectory(str);
end;
{ ---- Halt mechanism ---- }