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:
2026-03-08 18:07:44 +00:00
parent 63b67f5878
commit fcc9809248
17 changed files with 2774 additions and 417 deletions
+575
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -138,7 +138,7 @@ begin
{ Open for writing } { Open for writing }
debug.tracer.push_trace('edit.SaveFile.openRW'); 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 if fHandle <> 0 then begin
debug.tracer.push_trace('edit.SaveFile.writeFile'); debug.tracer.push_trace('edit.SaveFile.writeFile');
driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), totalLen); driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), totalLen);
@@ -149,7 +149,7 @@ begin
end else begin end else begin
{ Try write-only for new file } { Try write-only for new file }
debug.tracer.push_trace('edit.SaveFile.openWO'); 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 if fHandle <> 0 then begin
debug.tracer.push_trace('edit.SaveFile.writeFileNew'); debug.tracer.push_trace('edit.SaveFile.writeFileNew');
driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), totalLen); driver.storage.vfs.WriteFile(fHandle, 0, puint8(buf), totalLen);
@@ -181,7 +181,7 @@ begin
if FilePath = nil then exit; if FilePath = nil then exit;
debug.tracer.push_trace('edit.LoadFile.openFile'); 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'); debug.tracer.push_trace('edit.LoadFile.openFile.done');
if (fHandle = 0) or (fError <> eNone) then begin if (fHandle = 0) or (fError <> eNone) then begin
io.syslog.writestringln('New file.'); 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 } { Flat name buffer — heap-alloc'd in do_refresh to avoid stack bloat }
TNameBuf = array[0..ENTRY_MAX - 1] of pchar; TNameBuf = array[0..ENTRY_MAX - 1] of pchar;
PNameBuf = ^TNameBuf; PNameBuf = ^TNameBuf;
TSizeBuf = array[0..ENTRY_MAX - 1] of uint32;
PSizeBuf = ^TSizeBuf;
{ Per-call context passed to the forEach collect callback } { Per-call context passed to the forEach collect callback }
PFPCollectCtx = ^TFPCollectCtx; PFPCollectCtx = ^TFPCollectCtx;
TFPCollectCtx = record TFPCollectCtx = record
dir_names : PNameBuf; dir_names : PNameBuf;
file_names : PNameBuf; file_names : PNameBuf;
file_sizes : PSizeBuf;
dir_count : uint32; dir_count : uint32;
file_count : uint32; file_count : uint32;
filter : pchar; filter : pchar;
@@ -350,6 +353,28 @@ begin
end; end;
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 fp_collect_cb
core.ds.hashmap.forEach callback: classifies each VFS entry as dir or file 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 matchesFilter(key, ctx^.filter) then
if ctx^.file_count < ENTRY_MAX then begin if ctx^.file_count < ENTRY_MAX then begin
ctx^.file_names^[ctx^.file_count] := key; ctx^.file_names^[ctx^.file_count] := key;
ctx^.file_sizes^[ctx^.file_count] := obj^.FileSize;
ctx^.file_count := ctx^.file_count + 1; ctx^.file_count := ctx^.file_count + 1;
end; end;
end; end;
@@ -390,12 +416,14 @@ var
ctx : TFPCollectCtx; ctx : TFPCollectCtx;
dir_names : PNameBuf; { heap-alloc'd to avoid blowing the core.version stack } dir_names : PNameBuf; { heap-alloc'd to avoid blowing the core.version stack }
file_names : PNameBuf; file_names : PNameBuf;
file_sizes : PSizeBuf;
dir_count : uint32; dir_count : uint32;
file_count : uint32; file_count : uint32;
i : uint32; i : uint32;
row, name_lbl, size_lbl : Plv_obj; row, name_lbl, size_lbl : Plv_obj;
sep : pchar; sep : pchar;
copy : pchar; copy : pchar;
szStr : pchar;
s1, s2, s3 : pchar; s1, s2, s3 : pchar;
begin begin
debug.tracer.push_trace('filepicker.do_refresh'); debug.tracer.push_trace('filepicker.do_refresh');
@@ -408,8 +436,10 @@ begin
io.syslog.logln('FPCIK', 'do_refresh: kalloc name bufs'); io.syslog.logln('FPCIK', 'do_refresh: kalloc name bufs');
dir_names := PNameBuf(kalloc(sizeof(TNameBuf))); dir_names := PNameBuf(kalloc(sizeof(TNameBuf)));
file_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(dir_names), 0, sizeof(TNameBuf));
memset(uint32(file_names), 0, sizeof(TNameBuf)); memset(uint32(file_names), 0, sizeof(TNameBuf));
memset(uint32(file_sizes), 0, sizeof(TSizeBuf));
{ Sync path bar } { Sync path bar }
if p^.path_bar <> nil then if p^.path_bar <> nil then
@@ -429,6 +459,7 @@ begin
lv_label_set_text(p^.status_label, 'Could not read directory'); lv_label_set_text(p^.status_label, 'Could not read directory');
kfree(void(dir_names)); kfree(void(dir_names));
kfree(void(file_names)); kfree(void(file_names));
kfree(void(file_sizes));
debug.tracer.pop_trace; debug.tracer.pop_trace;
exit; exit;
end; end;
@@ -437,6 +468,7 @@ begin
driver.storage.vfs.FreeDirectoryListing(Map); driver.storage.vfs.FreeDirectoryListing(Map);
kfree(void(dir_names)); kfree(void(dir_names));
kfree(void(file_names)); kfree(void(file_names));
kfree(void(file_sizes));
debug.tracer.pop_trace; debug.tracer.pop_trace;
exit; exit;
end; end;
@@ -444,6 +476,7 @@ begin
ctx.dir_names := dir_names; ctx.dir_names := dir_names;
ctx.file_names := file_names; ctx.file_names := file_names;
ctx.file_sizes := file_sizes;
ctx.dir_count := 0; ctx.dir_count := 0;
ctx.file_count := 0; ctx.file_count := 0;
ctx.filter := p^.filter; ctx.filter := p^.filter;
@@ -455,7 +488,7 @@ begin
{ --- Sort both collections alphabetically --- } { --- Sort both collections alphabetically --- }
if dir_count > 0 then sortNames(dir_names^, dir_count); 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 --- } { --- Build directory rows --- }
if dir_count > 0 then 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_color(name_lbl, lv_color_make(210, 215, 230), 0);
lv_obj_set_style_text_font(name_lbl, @lv_font_montserrat_14, 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 { File size column: use metadata from directory listing }
inside the LVGL timer callback context. }
size_lbl := lv_label_create(row); 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_width(size_lbl, 70);
lv_obj_set_style_text_color(size_lbl, lv_color_make(100, 110, 130), 0); 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); lv_obj_set_style_text_font(size_lbl, @lv_font_montserrat_14, 0);
@@ -543,6 +577,7 @@ begin
kfree(void(dir_names)); kfree(void(dir_names));
kfree(void(file_names)); kfree(void(file_names));
kfree(void(file_sizes));
{ Update status label: "X dirs, Y files" } { Update status label: "X dirs, Y files" }
if p^.status_label <> nil then begin if p^.status_label <> nil then begin
+3 -3
View File
@@ -65,7 +65,7 @@ begin
{ ---- READ ---- } { ---- READ ---- }
if op[0] = '<' then begin 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 if (fHandle = 0) or (fError <> eNone) then begin
io.syslog.writestringln('Error: cannot open file for reading.'); io.syslog.writestringln('Error: cannot open file for reading.');
kfree(void(absPath)); kfree(void(absPath));
@@ -112,10 +112,10 @@ begin
contentLen := stringSize(content); contentLen := stringSize(content);
{ Try read-write rewrite first (existing file) } { 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 if (fHandle = 0) or (fError <> eNone) then begin
{ Try creating new file } { Try creating new file }
fHandle := driver.storage.vfs.OpenFile(absPath, omWriteOnly, wmNew, @fError); fHandle := driver.storage.vfs.OpenFile(absPath, omCreate, @fError);
end; end;
if (fHandle = 0) or (fError <> eNone) then begin if (fHandle = 0) or (fError <> eNone) then begin
+3 -3
View File
@@ -372,9 +372,9 @@ begin
buf := pchar(kalloc(FILE_BUF_SIZE)); buf := pchar(kalloc(FILE_BUF_SIZE));
memset(uint32(buf), 0, FILE_BUF_SIZE); memset(uint32(buf), 0, FILE_BUF_SIZE);
memcpy(uint32(txt), uint32(buf), len); 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 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 if fHandle = 0 then begin
kfree(void(buf)); kfree(void(buf));
io.syslog.logln('NOTEPAD', 'saveFile: OpenFile failed'); io.syslog.logln('NOTEPAD', 'saveFile: OpenFile failed');
@@ -413,7 +413,7 @@ var
buf : pchar; buf : pchar;
begin begin
debug.tracer.push_trace('notepad.loadFile'); 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 if (fHandle = 0) or (fError <> eNone) then begin
io.syslog.logln('NOTEPAD', 'loadFile: could not open file'); io.syslog.logln('NOTEPAD', 'loadFile: could not open file');
showErrorMsgbox('Open Failed', 'Could not open file for reading.'); showErrorMsgbox('Open Failed', 'Could not open file for reading.');
+3 -3
View File
@@ -343,7 +343,7 @@ begin
end; end;
end; end;
{ Resolve relative to per-terminal cwd } { 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 case Validity of
pvDirectory: begin pvDirectory: begin
kfree(void(state^.cwd)); kfree(void(state^.cwd));
@@ -410,7 +410,7 @@ var
begin begin
if STRLL_Size(state^.dir_stack) > 0 then begin if STRLL_Size(state^.dir_stack) > 0 then begin
wd := STRLL_Get(state^.dir_stack, STRLL_Size(state^.dir_stack) - 1); 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 if Validity = pvDirectory then begin
kfree(void(state^.cwd)); kfree(void(state^.cwd));
state^.cwd := NewDir; state^.cwd := NewDir;
@@ -696,7 +696,7 @@ begin
end; end;
end else begin end else begin
{ No built-in command found — try file dispatch } { 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 absPath <> nil then begin
if is_bg then begin if is_bg then begin
{ Background file dispatch } { Background file dispatch }
+13 -14
View File
@@ -152,36 +152,35 @@ var
fh : TFileHandle; fh : TFileHandle;
err : TError; err : TError;
fsize : uint32; fsize : uint32;
errb : uint8;
bytesRead : uint32; bytesRead : uint32;
processCtx : PProcessContext; processCtx : PProcessContext;
begin begin
debug.tracer.push_trace('wasmrunner.handler'); debug.tracer.push_trace('wasmrunner.handler');
wasm_file_handler := 0; wasm_file_handler := 0;
{ 1. Get file size } { 1. Open file once — omRead pre-loads the data and gives us the size }
errb := 0; err := eNone;
fsize := driver.storage.vfs.FileSize(path, @errb); fh := driver.storage.vfs.OpenFile(path, omRead, @err);
if (fsize = 0) or (errb <> 0) then begin if (fh = 0) or (err <> eNone) then begin
io.stdio.bufWriteStrLn(stderr_buf, 'WASM: cannot determine file size'); 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; exit;
end; end;
{ 2. Allocate buffer and read file } { 2. Allocate buffer and read file }
buf := puint8(kalloc(fsize)); buf := puint8(kalloc(fsize));
if buf = nil then begin if buf = nil then begin
driver.storage.vfs.CloseFile(fh);
io.stdio.bufWriteStrLn(stderr_buf, 'WASM: out of memory'); io.stdio.bufWriteStrLn(stderr_buf, 'WASM: out of memory');
exit; exit;
end; 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); bytesRead := driver.storage.vfs.ReadFile(fh, 0, buf, fsize);
driver.storage.vfs.CloseFile(fh); driver.storage.vfs.CloseFile(fh);
@@ -148,6 +148,10 @@ begin
entry := PDirectory_Entry(LL_Add(dirList)); entry := PDirectory_Entry(LL_Add(dirList));
entry^.fileName := stringCopy(Files[i].Name); entry^.fileName := stringCopy(Files[i].Name);
entry^.entryType := fileEntry; entry^.entryType := fileEntry;
entry^.fileSize := Files[i].Size;
entry^.modifiedDate := 0;
entry^.modifiedTime := 0;
entry^.attributes := 0;
end; end;
end; end;
if status <> nil then status^ := 0; if status <> nil then status^ := 0;
@@ -41,11 +41,12 @@ type
Directory : pchar; { directory path within the volume } Directory : pchar; { directory path within the volume }
FileName : pchar; { filename within that directory } FileName : pchar; { filename within that directory }
OpenMode : uint8; { TOpenMode ordinal — avoids VFS type dependency } OpenMode : uint8; { TOpenMode ordinal — avoids VFS type dependency }
WriteMode : uint8; { TWriteMode ordinal }
DataBuffer : puint32; { pre-loaded data for small files, nil otherwise } DataBuffer : puint32; { pre-loaded data for small files, nil otherwise }
DataSize : uint32; { size of pre-loaded data in bytes } DataSize : uint32; { size of pre-loaded data in bytes }
Loaded : boolean; { true if pre-loaded into DataBuffer } Loaded : boolean; { true if pre-loaded into DataBuffer }
StreamOff : uint32; { current byte offset for streaming / on-demand reads } 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; end;
PFDTable = ^TFDTable; PFDTable = ^TFDTable;
@@ -113,6 +114,8 @@ begin
entry^.Loaded := false; entry^.Loaded := false;
entry^.DataSize := 0; entry^.DataSize := 0;
entry^.StreamOff := 0; entry^.StreamOff := 0;
entry^.DeviceOps := nil;
entry^.DeviceData := nil;
end; end;
procedure fd_table_free(table : PFDTable); procedure fd_table_free(table : PFDTable);
@@ -133,9 +133,10 @@ begin
validity := driver.storage.vfs.PathValid(absPath); validity := driver.storage.vfs.PathValid(absPath);
if validity <> pvFile then exit; 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; err := eNone;
fh := driver.storage.vfs.OpenFile(absPath, omReadOnly, wmRewrite, @err); fh := driver.storage.vfs.OpenFile(absPath, omStream, @err);
if fh = 0 then exit; if fh = 0 then exit;
memset(uint32(@headerBuf[0]), 0, MAX_MAGIC_LEN); memset(uint32(@headerBuf[0]), 0, MAX_MAGIC_LEN);
+29 -26
View File
@@ -104,68 +104,71 @@ begin
{ ---- makeAbsolutePathFrom ---- } { ---- makeAbsolutePathFrom ---- }
{ Absolute path should be returned unchanged } { Absolute path should be returned unchanged }
p := driver.storage.vfs.makeAbsolutePathFrom('/foo/bar', '/base'); p := driver.storage.vfs.MakeAbsolutePathFrom('/foo/bar', '/base');
Assert(stringEquals(p, '/foo/bar'), 'makeAbsolutePathFrom: abs passthrough'); Assert(stringEquals(p, '/foo/bar'), 'MakeAbsolutePathFrom: abs passthrough');
kfree(void(p)); kfree(void(p));
{ Relative path joined to base (base has trailing slash) } { Relative path joined to base (base has trailing slash) }
p := driver.storage.vfs.makeAbsolutePathFrom('file.txt', '/home/'); p := driver.storage.vfs.MakeAbsolutePathFrom('file.txt', '/home/');
Assert(stringEquals(p, '/home/file.txt'), 'makeAbsolutePathFrom: rel+base trailing /'); Assert(stringEquals(p, '/home/file.txt'), 'MakeAbsolutePathFrom: rel+base trailing /');
kfree(void(p)); kfree(void(p));
{ Relative path joined to base (base has no trailing slash) } { Relative path joined to base (base has no trailing slash) }
p := driver.storage.vfs.makeAbsolutePathFrom('file.txt', '/home'); p := driver.storage.vfs.MakeAbsolutePathFrom('file.txt', '/home');
Assert(stringEquals(p, '/home/file.txt'), 'makeAbsolutePathFrom: rel+base no trailing /'); Assert(stringEquals(p, '/home/file.txt'), 'MakeAbsolutePathFrom: rel+base no trailing /');
kfree(void(p)); kfree(void(p));
{ ---- resolvePathFrom ---- } { ---- resolvePathFrom ---- }
{ Known virtual dirs resolve to pvDirectory } { Known virtual dirs resolve to pvDirectory }
res := driver.storage.vfs.resolvePathFrom('disk', '/'); res := driver.storage.vfs.ResolvePathFrom('disk', '/');
Assert(res = pvDirectory, 'resolvePathFrom: /disk from /'); Assert(res = pvDirectory, 'ResolvePathFrom: /disk from /');
res := driver.storage.vfs.resolvePathFrom('dev', '/'); res := driver.storage.vfs.ResolvePathFrom('dev', '/');
Assert(res = pvDirectory, 'resolvePathFrom: /dev from /'); Assert(res = pvDirectory, 'ResolvePathFrom: /dev from /');
res := driver.storage.vfs.resolvePathFrom('mnt', '/'); res := driver.storage.vfs.ResolvePathFrom('mnt', '/');
Assert(res = pvDirectory, 'resolvePathFrom: /mnt from /'); Assert(res = pvDirectory, 'ResolvePathFrom: /mnt from /');
res := driver.storage.vfs.resolvePathFrom('cfg', '/'); res := driver.storage.vfs.ResolvePathFrom('cfg', '/');
Assert(res = pvDirectory, 'resolvePathFrom: /cfg from /'); Assert(res = pvDirectory, 'ResolvePathFrom: /cfg from /');
{ Non-existent names resolve to pvInvalid } { Non-existent names resolve to pvInvalid }
res := driver.storage.vfs.resolvePathFrom('zzznope', '/'); res := driver.storage.vfs.ResolvePathFrom('zzznope', '/');
Assert(res = pvInvalid, 'resolvePathFrom: nonexistent from /'); Assert(res = pvInvalid, 'ResolvePathFrom: nonexistent from /');
{ ---- changeDirectoryFrom ---- } { ---- changeDirectoryFrom ---- }
newDir := nil; newDir := nil;
res := driver.storage.vfs.changeDirectoryFrom('disk', '/', newDir); res := driver.storage.vfs.ChangeDirectoryFrom('disk', '/', newDir);
Assert(res = pvDirectory, 'changeDirectoryFrom /disk: returns dir'); Assert(res = pvDirectory, 'ChangeDirectoryFrom /disk: returns dir');
Assert((newDir <> nil) and stringEquals(newDir, '/disk'), Assert((newDir <> nil) and stringEquals(newDir, '/disk'),
'changeDirectoryFrom /disk: newDir = /disk'); 'ChangeDirectoryFrom /disk: newDir = /disk');
if newDir <> nil then kfree(void(newDir)); if newDir <> nil then kfree(void(newDir));
newDir := nil; newDir := nil;
res := driver.storage.vfs.changeDirectoryFrom('zzznope', '/', newDir); res := driver.storage.vfs.ChangeDirectoryFrom('zzznope', '/', newDir);
Assert(res = pvInvalid, 'changeDirectoryFrom nonexistent: returns invalid'); Assert(res = pvInvalid, 'ChangeDirectoryFrom nonexistent: returns invalid');
Assert(newDir = nil, 'changeDirectoryFrom nonexistent: newDir nil'); Assert(newDir = nil, 'ChangeDirectoryFrom nonexistent: newDir nil');
{ ---- GetDirectoryListingFrom ---- } { ---- GetDirectoryListingFrom ---- }
{ Root listing should be non-nil (in-memory VFS always populated) } { Root listing should be non-nil (in-memory VFS always populated) }
map := driver.storage.vfs.GetDirectoryListingFrom('/', '/'); map := driver.storage.vfs.GetDirectoryListingFrom('/', '/');
Assert(map <> nil, 'GetDirectoryListingFrom(/) not nil'); Assert(map <> nil, 'GetDirectoryListingFrom(/) not nil');
driver.storage.vfs.FreeDirectoryListing(map);
{ /disk, /dev, /mnt, /cfg created at init — must appear in root listing } { /disk, /dev, /mnt, /cfg created at init — must appear in root listing }
map := driver.storage.vfs.GetDirectoryListingFrom('/', '/'); map := driver.storage.vfs.GetDirectoryListingFrom('/', '/');
Assert(map <> nil, 'GetDirectoryListingFrom(/) not nil (2)'); Assert(map <> nil, 'GetDirectoryListingFrom(/) not nil (2)');
{ Note: we don't free this map — it's the live VFS in-memory reference, { All maps are now caller-owned snapshots — always safe to free }
NOT a caller-owned alloc. Freeing it would corrupt the VFS tree. } driver.storage.vfs.FreeDirectoryListing(map);
{ Listing a leaf vdir returns non-nil (even if empty) } { Listing a leaf vdir returns non-nil (even if empty) }
map := driver.storage.vfs.GetDirectoryListingFrom('/dev', '/'); map := driver.storage.vfs.GetDirectoryListingFrom('/dev', '/');
Assert(map <> nil, 'GetDirectoryListingFrom(/dev) not nil'); Assert(map <> nil, 'GetDirectoryListingFrom(/dev) not nil');
driver.storage.vfs.FreeDirectoryListing(map);
map := driver.storage.vfs.GetDirectoryListingFrom('/cfg', '/'); map := driver.storage.vfs.GetDirectoryListingFrom('/cfg', '/');
Assert(map <> nil, 'GetDirectoryListingFrom(/cfg) not nil'); Assert(map <> nil, 'GetDirectoryListingFrom(/cfg) not nil');
driver.storage.vfs.FreeDirectoryListing(map);
{ Non-existent path returns nil (no crash) } { Non-existent path returns nil (no crash) }
map := driver.storage.vfs.GetDirectoryListingFrom('/zzznope', '/'); map := driver.storage.vfs.GetDirectoryListingFrom('/zzznope', '/');
@@ -309,7 +312,7 @@ begin
for i := 0 to 511 do for i := 0 to 511 do
wbuf[i] := $A5; wbuf[i] := $A5;
err := eNone; err := eNone;
fh := driver.storage.vfs.OpenFile('/disk/dt_vol/TEST.TXT', omWriteOnly, wmNew, @err); fh := driver.storage.vfs.OpenFile('/disk/dt_vol/TEST.TXT', omCreate, @err);
Assert(err = eNone, 'OpenFile for write: err = eNone'); Assert(err = eNone, 'OpenFile for write: err = eNone');
n := driver.storage.vfs.WriteFile(fh, 0, wbuf, 512); n := driver.storage.vfs.WriteFile(fh, 0, wbuf, 512);
Assert(n = 512, 'WriteFile 512 bytes'); Assert(n = 512, 'WriteFile 512 bytes');
@@ -319,7 +322,7 @@ begin
rbuf := puint8(kalloc(512)); rbuf := puint8(kalloc(512));
memset(uint32(rbuf), 0, 512); memset(uint32(rbuf), 0, 512);
err := eNone; err := eNone;
fh := driver.storage.vfs.OpenFile('/disk/dt_vol/TEST.TXT', omReadOnly, wmRewrite, @err); fh := driver.storage.vfs.OpenFile('/disk/dt_vol/TEST.TXT', omRead, @err);
Assert(err = eNone, 'OpenFile for read: err = eNone'); Assert(err = eNone, 'OpenFile for read: err = eNone');
n := driver.storage.vfs.ReadFile(fh, 0, rbuf, 512); n := driver.storage.vfs.ReadFile(fh, 0, rbuf, 512);
Assert(n = 512, 'ReadFile 512 bytes'); Assert(n = 512, 'ReadFile 512 bytes');
+17 -2
View File
@@ -90,6 +90,8 @@ type
eTooManyOpenFiles, { per-process FD table full } eTooManyOpenFiles, { per-process FD table full }
eInvalidHandle, { TFileHandle does not refer to an open FD } eInvalidHandle, { TFileHandle does not refer to an open FD }
eNotStreamMode, { SeekFile called on a non-omStream handle } 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 } { Disk / capacity errors }
eDiskFull, { FS reports no free clusters/blocks } eDiskFull, { FS reports no free clusters/blocks }
@@ -148,11 +150,17 @@ type
PPDeleteFileHook = procedure(volume : PStorage_Volume; filePath : pchar; status : puint32); PPDeleteFileHook = procedure(volume : PStorage_Volume; filePath : pchar; status : puint32);
PPDeleteDirHook = procedure(volume : PStorage_Volume; path : pchar; status : puint32); PPDeleteDirHook = procedure(volume : PStorage_Volume; path : pchar; status : puint32);
PPIdentifyHook = function(volume : PStorage_Volume) : boolean; 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. { Offset-based read: reads byteCount bytes starting at byte offset into the file.
Returns the number of bytes actually read. Returns the number of bytes actually read.
Filesystems that do not implement this leave the field nil and VFS falls back Filesystems that do not implement this leave the field nil and VFS falls back
to the load-all readCallback behaviour. } to the load-all readCallback behaviour. }
PPReadOffsetHook = function(volume : PStorage_Volume; directory : pchar; fileName : pchar; offset : uint32; buffer : puint32; byteCount : uint32) : uint32; 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 === { === Async filesystem hook core.types ===
Each mirrors the corresponding sync hook but receives a TIOCallback + callbackData. Each mirrors the corresponding sync hook but receives a TIOCallback + callbackData.
@@ -227,6 +235,8 @@ type
identifyCallback : PPIdentifyHook; identifyCallback : PPIdentifyHook;
{ Offset-based read for streaming mode — nil if not implemented } { Offset-based read for streaming mode — nil if not implemented }
readOffsetCallback : PPReadOffsetHook; readOffsetCallback : PPReadOffsetHook;
{ Offset-based write for streaming mode — nil if not implemented }
writeOffsetCallback : PPWriteOffsetHook;
{ Async format — nil if FS only supports synchronous create } { Async format — nil if FS only supports synchronous create }
createAsyncCallback : PPCreateAsyncHook; createAsyncCallback : PPCreateAsyncHook;
{ Async variants of the main I/O hooks — nil if FS only supports sync. { Async variants of the main I/O hooks — nil if FS only supports sync.
@@ -237,6 +247,7 @@ type
readDirAsyncCallback : PPReadDirAsyncHook; readDirAsyncCallback : PPReadDirAsyncHook;
deleteFileAsyncCallback : PPDeleteFileAsyncHook; deleteFileAsyncCallback : PPDeleteFileAsyncHook;
deleteDirAsyncCallback : PPDeleteDirAsyncHook; deleteDirAsyncCallback : PPDeleteDirAsyncHook;
renameFileCallback : PPRenameFileHook;
end; end;
{ Generic storage volume } { Generic storage volume }
@@ -253,8 +264,12 @@ type
{ Generic directory entry } { Generic directory entry }
TDirectory_Entry = record TDirectory_Entry = record
fileName : pchar; fileName : pchar;
entryType : TDirectory_Entry_Type; 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; end;
TDrive_Error = record 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 dirEntry^.entryType := TDirectory_Entry_Type.directoryEntry
else else
dirEntry^.entryType := TDirectory_Entry_Type.fileEntry; dirEntry^.entryType := TDirectory_Entry_Type.fileEntry;
dirEntry^.fileSize := fileEntrys[i].size;
dirEntry^.modifiedDate := 0;
dirEntry^.modifiedTime := 0;
dirEntry^.attributes := fileEntrys[i].attribues;
end; end;
kfree(void(compString)); kfree(void(compString));
@@ -562,6 +562,19 @@ begin
else else
dirEntry^.entryType := TDirectory_Entry_Type.fileEntry; 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; offset := offset + rec^.recLen;
end; end;
@@ -722,6 +735,7 @@ begin
filesystem.createDirCallback := nil; filesystem.createDirCallback := nil;
filesystem.deleteFileCallback := nil; filesystem.deleteFileCallback := nil;
filesystem.deleteDirCallback := nil; filesystem.deleteDirCallback := nil;
filesystem.renameFileCallback := nil;
filesystem.readAsyncCallback := nil; filesystem.readAsyncCallback := nil;
filesystem.readDirAsyncCallback := nil; filesystem.readDirAsyncCallback := nil;
+2 -2
View File
@@ -498,13 +498,13 @@ end;
function getWorkingDirectory: pchar; function getWorkingDirectory: pchar;
begin begin
getWorkingDirectory := driver.storage.vfs.getWorkingDirectory; getWorkingDirectory := driver.storage.vfs.GetWorkingDirectory;
end; end;
procedure setWorkingDirectory(str: pchar); procedure setWorkingDirectory(str: pchar);
begin begin
if str <> nil then if str <> nil then
driver.storage.vfs.changeDirectory(str); driver.storage.vfs.ChangeDirectory(str);
end; end;
{ ---- Halt mechanism ---- } { ---- Halt mechanism ---- }