../src/driver/storage/driver.storage.types.pas -> ../src/driver/storage/driver.storage.types.md
18 KiB
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
TFileHandle = uint32;
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
function OpenFile(
Filename : pchar;
OpenMode : TOpenMode;
Error : PError
) : TFileHandle;
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
function CloseFile(FileHandle : TFileHandle) : TError;
Closes the file handle. Frees all associated buffers and strings. Returns eNone on success, eInvalidHandle if the handle is not open.
FileSize
function FileSize(Filename : pchar; error : PError) : uint32;
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
function FileSizeFromHandle(Handle : TFileHandle) : uint32;
Returns the data size of an open file handle. For device FDs, calls DeviceOps^.Size if available. Returns 0 for invalid handles.
SeekFile
function SeekFile(Handle : TFileHandle; Offset : uint32) : TError;
Sets the stream offset for a handle opened with omStream. Returns eNotStreamMode if the handle was not opened in stream mode.
DeleteFile
function DeleteFile(Path : pchar; Error : PError) : TError;
Deletes a file at the given path. Returns eNone on success.
CreateDirectory
function CreateDirectory(Path : pchar; Error : PError) : TError;
Creates a directory on the underlying filesystem.
DeleteDirectory
function DeleteDirectory(Path : pchar; Error : PError) : TError;
Deletes an empty directory. Returns eDirectoryNotEmpty if the directory has entries.
RenameFile
function RenameFile(OldPath : pchar; NewName : pchar; Error : PError) : TError;
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
function PathValid(Path : pchar) : TIsPathValid;
Tests whether a path resolves to a file, directory, or is invalid. Follows symlinks (up to 8 hops). Device nodes return pvFile.
MakeAbsolutePath
function MakeAbsolutePath(Path : pchar) : pchar;
Converts a path to absolute form using the current working directory. Returns a heap-allocated string — caller must kfree().
MakeAbsolutePathFrom
function MakeAbsolutePathFrom(Path : pchar; BaseDir : pchar) : pchar;
Same as MakeAbsolutePath but uses BaseDir instead of the process CWD. Returns a heap-allocated string — caller must kfree().
ResolvePathFrom
function ResolvePathFrom(Path : pchar; BaseDir : pchar) : TIsPathValid;
Resolves a relative path against BaseDir and returns its validity.
ChangeDirectory
function ChangeDirectory(Path : pchar) : TIsPathValid;
Sets the per-process current working directory. Returns the validity of the new path.
ChangeDirectoryFrom
function ChangeDirectoryFrom(
Path : pchar;
BaseDir : pchar;
var NewDir : pchar
) : TIsPathValid;
Changes directory relative to BaseDir. NewDir receives the new absolute path (heap-allocated, caller must kfree()).
GetWorkingDirectory
function GetWorkingDirectory : pchar;
Returns a heap-allocated copy of the per-process CWD. Caller must kfree().
Directory Listings
GetDirectoryListingFrom
function GetDirectoryListingFrom(Path : pchar; BaseDir : pchar) : PHashMap;
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
procedure FreeDirectoryListing(map : PHashMap);
Frees a directory listing snapshot and all its deep-copied entries. Safe to pass nil.
Virtual Directories
newVirtualDirectory
function newVirtualDirectory(Path : pchar) : TError;
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
function mountVolume(mountPath : pchar; volume : PStorage_Volume) : TRegError;
Mounts a storage volume at the given VFS path. The path must be an existing virtual directory. Returns pvRegistered on success.
auto_mount_volumes
procedure 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 {device}/boot /boot
mnt {device}/sys /sys
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
function CreateSymlink(LinkPath : pchar; TargetPath : pchar) : TError;
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— successeInvalidArgument— nil argumenteInvalidPath— parent directory does not existeAlreadyExists— name already takeneNotADirectory— 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
function RegisterDevice(
Path : pchar;
Ops : PVFSDeviceOps;
DevData : pointer
) : TError;
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— successeInvalidArgument— nil path or opseDirectoryDoesNotExist— parent not foundeNotADirectory— parent is not a virtual directoryeAlreadyExists— 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
function WatchDirectory(
Path : pchar;
Callback : TVFSWatchCallback;
UserData : pointer
) : uint32;
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
procedure UnwatchDirectory(WatchID : uint32);
Removes a watch by ID. Safe to call with 0 or invalid IDs.
Callback Signature
TVFSWatchCallback = procedure(
event : TVFSWatchEvent;
path : pchar;
userdata : pointer
);
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.