VFS Public Interface¶
The Virtual File System (driver.storage.vfs) provides a unified API for file I/O, directory management, path resolution, symlinks, device nodes, and directory-change notifications. All paths are UNIX-style (/-separated). The VFS namespace is a tree of virtual directories, mount points, symlinks, and device nodes.
Types¶
Enumerations¶
| Type | Values | Description |
|---|---|---|
TOpenMode |
omRead, omWrite, omCreate, omReadWrite, omStream |
How a file handle is opened. See OpenFile for details. |
TIsPathValid |
pvInvalid, pvFile, pvDirectory |
Result of path validation. |
TObjectType |
otVDIRECTORY, otDRIVE, otDEVICE, otVFILE, otMOUNT, otDIRECTORY, otFILE, otSYMLINK |
Node types in the VFS tree. |
TVFSWatchEvent |
weCreated, weDeleted, weRenamed, weModified |
Events that fire on watched directories. |
Handle Type¶
Returned by OpenFile / OpenFileAsync. Value 0 means invalid/not open. Handles are 1-based indices into the per-process file descriptor table (max 64 open files per process).
Records¶
TVFSObject¶
TVFSObject = record
Parent : PVFSObject;
ObjectName : pchar;
ObjectType : TObjectType;
Reference : void;
FileSize : uint32;
end;
Internal VFS tree node. Exposed in directory listing snapshots returned by GetDirectoryListingFrom.
TVFSDeviceOps¶
TVFSDeviceOps = record
Read : TVFSDevReadFunc;
Write : TVFSDevWriteFunc;
Size : TVFSDevSizeFunc;
end;
Function-pointer table for character/block device I/O. Any field may be nil if the device does not support that operation. See Device Nodes for callback signatures.
Initialisation¶
procedure init()¶
Initialises the VFS subsystem. Creates the root node and default virtual directories (/dev, /disk, /mnt, /cfg, /boot), registers built-in device nodes (/dev/null, /dev/zero), initialises the watch table and directory cache, and registers shell commands (LS, CD, PUSHD, POPD, MKDIR, RM, RMDIR, MOUNT, UMOUNT).
Called once during kernel boot. Must not be called again.
File I/O — Synchronous¶
OpenFile¶
Opens a file or device node. Returns a non-zero handle on success, 0 on failure. Error^ is set to the specific TError code.
- Device nodes (e.g.
/dev/null) are detected before volume resolution. The FD is set up with device callbacks; no filesystem driver is involved. omRead: Open existing file for reading. The entire file is pre-loaded into the FD's data buffer. SubsequentReadFilecalls read from this buffer by position.omWrite: Open existing file for writing (overwrite).omCreate: Create a new file for writing. Fails witheFileDoesNotExistif the file already exists.omReadWrite: Open existing file for both reading and writing. Pre-loads data likeomRead, but also allowsWriteFile.omStream: Streaming sequential I/O. No pre-load. Reads and writes use the filesystem's offset-based callbacks on demand, with an internal stream offset that auto-advances. UseSeekFileto reposition.
ReadFile¶
function ReadFile(
FileHandle : TFileHandle;
Position : uint32;
Buffer : puint8;
Length : uint32
) : uint32;
Reads up to Length bytes into Buffer. Returns the number of bytes actually read.
- Device FDs: Dispatches to
DeviceOps^.Read. In stream mode, the internal stream offset is used and auto-advanced. - Stream mode (
omStream):Positionis ignored; reads from the current stream offset. - Buffered mode (
omRead/omReadWrite): Reads from the pre-loaded buffer atPosition.
WriteFile¶
function WriteFile(
FileHandle : TFileHandle;
Position : uint32;
Buffer : puint8;
Length : uint32
) : uint32;
Writes Length bytes from Buffer. Returns bytes written (Length on success, 0 on failure).
- Handle must have been opened with
omWrite,omCreate,omReadWrite, oromStream. - Device FDs: Dispatches to
DeviceOps^.Write. In stream mode, the internal stream offset is used and auto-advanced. - Stream mode (
omStream):Positionis ignored; writes at the current stream offset via the filesystem'swriteOffsetCallback. Returns0if the FS does not implementwriteOffsetCallback. - Volume FDs (non-stream): Delegates to the filesystem's write callback. The buffer is padded to 4096 bytes internally (drivers may read full sectors).
CloseFile¶
Closes the file handle. Frees all associated buffers and strings. Returns eNone on success, eInvalidHandle if the handle is not open.
FileSize¶
Opens the file read-only, reads its size, closes it, and returns the size in bytes. Convenience wrapper — for already-open files, prefer FileSizeFromHandle.
FileSizeFromHandle¶
Returns the data size of an open file handle. For device FDs, calls DeviceOps^.Size if available. Returns 0 for invalid handles.
SeekFile¶
Sets the stream offset for a handle opened with omStream. Returns eNotStreamMode if the handle was not opened in stream mode.
DeleteFile¶
Deletes a file at the given path. Returns eNone on success.
CreateDirectory¶
Creates a directory on the underlying filesystem.
DeleteDirectory¶
Deletes an empty directory. Returns eDirectoryNotEmpty if the directory has entries.
RenameFile¶
Renames a file. NewName is just the new filename, not a full path.
File I/O — Asynchronous¶
All async variants return immediately. The callback fires when the operation completes. Callers must keep all buffers alive until the callback fires.
OpenFileAsync¶
procedure OpenFileAsync(
Filename : pchar;
OpenMode : TOpenMode;
var OutHandle : TFileHandle;
Error : PError;
Callback : TIOCallback;
CallbackData : pointer
);
Async version of OpenFile. OutHandle and Error^ are set before the callback fires.
ReadFileAsync¶
procedure ReadFileAsync(
FileHandle : TFileHandle;
Position : uint32;
Buffer : puint8;
Length : uint32;
BytesRead : puint32;
Callback : TIOCallback;
CallbackData : pointer
);
Async read. BytesRead^ is set before the callback fires. Device FDs are handled synchronously with an immediate callback.
WriteFileAsync¶
procedure WriteFileAsync(
FileHandle : TFileHandle;
Position : uint32;
Buffer : puint8;
Length : uint32;
Callback : TIOCallback;
CallbackData : pointer
);
Async write. Prefers the filesystem's async write hook; falls back to sync + immediate callback.
- Handle must have been opened with
omWrite,omCreate,omReadWrite, oromStream. - Device FDs: Dispatches to
DeviceOps^.Writesynchronously, then fires callback. In stream mode, stream offset is auto-advanced. - Stream mode (
omStream) for volume FDs: UseswriteOffsetCallback; fireseNotSupportedif the FS does not implement it. - Non-stream volume FDs: Standard async/sync write dispatch with 4096-byte padding.
DeleteFileAsync¶
procedure DeleteFileAsync(
Path : pchar;
Error : PError;
Callback : TIOCallback;
CallbackData : pointer
);
DeleteDirectoryAsync¶
procedure DeleteDirectoryAsync(
Path : pchar;
Error : PError;
Callback : TIOCallback;
CallbackData : pointer
);
GetDirectoryListingAsync¶
procedure GetDirectoryListingAsync(
Path : pchar;
ResultMap : PPHashMap;
Callback : TIOCallback;
CallbackData : pointer
);
Async directory listing. ResultMap^ is set to a caller-owned PHashMap snapshot before the callback fires. Caller must FreeDirectoryListing() the result.
Path Resolution¶
PathValid¶
Tests whether a path resolves to a file, directory, or is invalid. Follows symlinks (up to 8 hops). Device nodes return pvFile.
MakeAbsolutePath¶
Converts a path to absolute form using the current working directory. Returns a heap-allocated string — caller must kfree().
MakeAbsolutePathFrom¶
Same as MakeAbsolutePath but uses BaseDir instead of the process CWD. Returns a heap-allocated string — caller must kfree().
ResolvePathFrom¶
Resolves a relative path against BaseDir and returns its validity.
ChangeDirectory¶
Sets the per-process current working directory. Returns the validity of the new path.
ChangeDirectoryFrom¶
Changes directory relative to BaseDir. NewDir receives the new absolute path (heap-allocated, caller must kfree()).
GetWorkingDirectory¶
Returns a heap-allocated copy of the per-process CWD. Caller must kfree().
Directory Listings¶
GetDirectoryListingFrom¶
Returns a deep-copied snapshot PHashMap of the directory contents. Keys are entry names (pchar), values are PVFSObject. Returns nil if the path is invalid.
Multiple calls return independent snapshots — modifying or freeing one does not affect others.
Important: Caller must call FreeDirectoryListing() when done.
FreeDirectoryListing¶
Frees a directory listing snapshot and all its deep-copied entries. Safe to pass nil.
Virtual Directories¶
newVirtualDirectory¶
Creates a virtual directory in the VFS namespace tree. Parent directories must already exist. Returns eAlreadyExists if the name is taken.
Virtual directories exist only in the VFS tree — they are not backed by any filesystem and are lost on reboot.
Mount Points¶
mountVolume¶
Mounts a storage volume at the given VFS path. The path must be an existing virtual directory. Returns pvRegistered on success.
auto_mount_volumes¶
Automatically mounts all detected volumes under /disk/ with auto-generated names (/disk/vol0, /disk/vol1, ...).
After mounting all volumes, reads MOUNT.ASR from the root of the boot volume (ISO). Each line in the file is a command:
mnt <source> <target>— creates a symlink from<target>to<source>.{device}is replaced with the boot volume's mount path (e.g./disk/vol2).
This replaces the old asr.mnt persistent-mount mechanism.
Symlinks¶
CreateSymlink¶
Creates a symbolic link at LinkPath pointing to TargetPath. The parent directory of LinkPath must exist and be a virtual directory. The target path is stored as-is and resolved at access time. Symlink resolution is capped at 8 hops.
Returns:
- eNone — success
- eInvalidArgument — nil argument
- eInvalidPath — parent directory does not exist
- eAlreadyExists — name already taken
- eNotADirectory — parent is not a directory
Device Nodes¶
Device nodes allow non-storage I/O to be accessed through the standard VFS file API. A device is registered at a VFS path with a table of read/write/size callbacks.
Callback Signatures¶
TVFSDevReadFunc = function(devData : pointer; offset : uint32; buffer : puint8; length : uint32) : uint32;
TVFSDevWriteFunc = function(devData : pointer; offset : uint32; buffer : puint8; length : uint32) : uint32;
TVFSDevSizeFunc = function(devData : pointer) : uint32;
devData— opaque pointer passed at registration timeoffset— byte offset (for positional reads) or current stream offsetbuffer— caller's data bufferlength— number of bytes requested- Return value — number of bytes actually read/written
Any callback may be nil if the device does not support that operation.
RegisterDevice¶
Registers a device node at Path. The TVFSDeviceOps record is deep-copied so the caller's record can be stack-allocated. DevData is stored as-is (not copied).
The parent directory must exist and be a virtual directory.
Returns:
- eNone — success
- eInvalidArgument — nil path or ops
- eDirectoryDoesNotExist — parent not found
- eNotADirectory — parent is not a virtual directory
- eAlreadyExists — name already taken
Built-in Devices¶
| Path | Read | Write | Size |
|---|---|---|---|
/dev/null |
Returns 0 bytes (EOF) | Accepts and discards all data | 0 |
/dev/zero |
Fills buffer with zero bytes | Accepts and discards all data | 0 |
Example: Registering a Custom Device¶
function MyDev_Read(devData : pointer; offset : uint32; buffer : puint8; length : uint32) : uint32;
begin
{ Fill buffer with 'A' characters }
memset(uint32(buffer), ord('A'), length);
MyDev_Read := length;
end;
var
ops : TVFSDeviceOps;
begin
ops.Read := @MyDev_Read;
ops.Write := nil;
ops.Size := nil;
RegisterDevice('/dev/mydevice', @ops, nil);
end;
The device can then be used with the standard file API:
var
h : TFileHandle;
err : TError;
buf : array[0..15] of uint8;
begin
h := OpenFile('/dev/mydevice', omRead, @err);
ReadFile(h, 0, @buf[0], 16); { buf now contains 16 'A' bytes }
CloseFile(h);
end;
Watch / Notify¶
Directory watches fire a callback whenever the watched directory is mutated (child created, deleted, renamed, or modified).
WatchDirectory¶
Registers a watch on a directory path. Returns a non-zero watch ID on success, 0 on failure (nil args, path doesn't exist, or table full). Maximum 32 concurrent watches.
UnwatchDirectory¶
Removes a watch by ID. Safe to call with 0 or invalid IDs.
Callback Signature¶
event— what kind of mutation occurredpath— the absolute path of the affected entryuserdata— the opaque pointer passed at registration time
Error Codes¶
All functions that return TError use the shared error codes defined in driver.storage.types. The most commonly returned codes from VFS operations are:
| Code | Meaning |
|---|---|
eNone |
Success |
eInvalidArgument |
Nil pointer or invalid parameter |
eInvalidPath |
Malformed or unresolvable path |
eInvalidHandle |
Handle is not open |
eInvalidFileName |
Resolved filename is empty or invalid |
eFileDoesNotExist |
Path resolves but no file found |
eDirectoryDoesNotExist |
Parent directory not found |
eNotADirectory |
Path component is not a directory |
eAlreadyExists |
Target name already exists |
eTooManyOpenFiles |
Per-process FD table full (max 64) |
eNotStreamMode |
SeekFile on non-stream handle |
eFileNotLoaded |
Read on handle whose data hasn't loaded |
eReadOnly |
Write on read-only handle |
See driver.storage.types.pas for the full TError enumeration.
Ownership & Lifetime Rules¶
- File handles — Must be closed with
CloseFilewhen no longer needed. All handles are per-process and automatically closed on process exit. - Directory listings —
GetDirectoryListingFrom/GetDirectoryListingAsyncreturn caller-owned snapshots. Always callFreeDirectoryListing(). - Heap strings —
MakeAbsolutePath,MakeAbsolutePathFrom,GetWorkingDirectory, andChangeDirectoryFrom(viaNewDir) return heap-allocated strings. Caller mustkfree(). - Async buffers — All buffers passed to async functions must remain valid until the completion callback fires.
- Device ops — The
TVFSDeviceOpsrecord is deep-copied byRegisterDevice. The caller's record can be stack-local. However,DevDatais stored by pointer — its lifetime must exceed the device registration.