← Blog

VFS File Descriptors: Integer fd API in sigilOS

June 22, 2026 · sigil-fs · Sigil-Docs
fs vfs 0.6.0

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:

fd 0in use
fd 1free
fd 2free
fd 3free
fd 4free
fd 5free
fd 6free
fd 7free
fd 8free
fd 9free
fd 10free
fd 11free
fd 12free
fd 13free
fd 14free
fd 15free

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

FunctionSignatureWhat it does
vfs_fd_init()() → voidZero all 16 slots at boot
vfs_open(path)(str) → fd | -1Allocate slot, open via VFS dispatch, return fd
vfs_close(fd)(int) → voidMark slot free; VFS backend notified
vfs_fread(fd,buf,len)(int,ptr,int) → bytesRead len bytes from current seek, advance seek
vfs_fseek(fd,off)(int,int) → voidSet seek position (absolute)
vfs_ftell(fd)(int) → intReturn 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.