sigilOS gains a POSIX-style integer file descriptor layer over the VFS. Before today, code that opened a file held a (backend_id, file_id) pair and passed both values everywhere. The new vfs_fd.sg layer wraps that into a single integer fd[0..15], the same model every UNIX program has used since 1973. 336/336 MANIFEST ALL PASS.
The fd table
A 16-slot fd table lives at 0x340000 in the OS address space, initialized by vfs_fd_init() at boot. Each slot holds:
- The VFS backend id and file id (the underlying (backend,id) pair)
- A seek position (byte offset, advanced by every
vfs_fread) - An in-use flag
vfs_open(path) finds the first free slot, asks the VFS dispatch layer to open the path (routing to whatever provider handles it — FAT, NFS, SMB, procfs, sysfs, SigDB), stores the resulting (backend,id) pair, sets seek=0, marks the slot in-use, and returns the slot index as the fd. On failure it returns -1. vfs_close(fd) marks the slot free for the next open — no deallocation, just a flag flip.
The API
| Function | Signature | What it does |
|---|---|---|
vfs_fd_init() | () → void | Zero all 16 slots at boot |
vfs_open(path) | (str) → fd | -1 | Allocate slot, open via VFS dispatch, return fd |
vfs_close(fd) | (int) → void | Mark slot free; VFS backend notified |
vfs_fread(fd,buf,len) | (int,ptr,int) → bytes | Read len bytes from current seek, advance seek |
vfs_fseek(fd,off) | (int,int) → void | Set seek position (absolute) |
vfs_ftell(fd) | (int) → int | Return current seek position |
Before and after
Before vfs_fd.sg, code that read a file from any VFS provider had to track the (backend,id) pair manually:
-- old: caller manages the backend/id pair
let bid = vfs_dispatch_open(VFS_FAT, path)
let fid = vfs_fat_open(bid, path)
let n = vfs_fat_read(bid, fid, buf, 512)
vfs_fat_close(bid, fid)
With the fd layer, the provider is an implementation detail:
-- new: caller sees an opaque integer
let fd = vfs_open(path)
let n = vfs_fread(fd, buf, 512)
vfs_close(fd)
The VFS dispatch layer still routes to the right provider — FAT, NFS, SMB, procfs, sysfs, SigDB — but the caller doesn't need to know which one. Path-based routing is the whole point of the VFS; the fd layer makes that transparent to application code.
Infrastructure fix: stub files
The MANIFEST covers all 336 tests across all providers. Some test builds only include a subset of providers — a FS-only build might not pull in the network stack, a network-only build might not pull in the disk stack. Before this change, those partial builds produced stale-cache false-passes: the linker silently omitted symbols that weren't used, so tests that depended on absent providers passed vacuously.
The fix: vfs_stubs_net.sg and vfs_stubs_disk.sg provide no-op symbol stubs for every provider function not in the current build's link set. A test that calls an absent provider function now calls the stub and gets a defined (failure) return value rather than a linker gap. This cleared 96 false-pass entries across the MANIFEST and kept all 336 tests at their correct verdicts.
Test
The OPENFDT test opens a path via vfs_open, reads 4 bytes via vfs_fread, seeks back to 0 with vfs_fseek, reads again, and verifies the two reads match. It then closes the fd and verifies the slot is free. 336/336 ALL PASS.