fix: patch the fd family (open/read/close/fstat) in installFsPatches - #70
Merged
mcollina merged 1 commit intoSep 23, 2026
Merged
Conversation
installFsPatches never patched the descriptor family, so fs.openSync on a
path inside a mounted VFS threw ENOENT even though fs.readFileSync on the
same path worked:
vfs.writeFileSync('/a.txt', 'hello');
vfs.mount('/mnt');
fs.readFileSync('/mnt/a.txt', 'utf8'); // 'hello'
fs.openSync('/mnt/a.txt'); // ENOENT
VirtualFileSystem already implemented the whole family, and fd.js already
allocated virtual fds from 10000+ so they cannot shadow real ones. Only the
patch layer was missing.
- fd.js gains the fd-keyed operations. A handle carries its own content and
position, so which VFS opened it is irrelevant; VirtualFileSystem and the
node:fs patches now both delegate here instead of duplicating.
- fd.js collapses fs.read/readSync's options-object and short-form overloads,
which VirtualFileSystem.readSync did not accept either.
- findVFSForWatch becomes findVFSForExistingPath: the open patches need the
same "which VFS owns this path, honouring overlay" lookup.
Write flags on an overlay mount keep falling through to the real fs, matching
how readFileSync and createReadStream already treat overlays.
fs.promises.open stays unpatched: it must return a real FileHandle, and
VirtualFileHandle has no .fd, createReadStream, readLines or chmod. Falling
through preserves today's behaviour rather than handing back a half-shaped
object.
Reported downstream as yao-pkg/pkg#302.
Signed-off-by: robertsLando <daniel.sorridi@gmail.com>
This was referenced Sep 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
installFsPatches()never patched the descriptor family, sofs.openSyncon a path inside a mounted VFS throwsENOENTeven thoughfs.readFileSyncon the same path works:fs.openSyncisn't exotic — plenty of libraries reach for it, and none of them know to call the VFS instance directly. Reported downstream as yao-pkg/pkg#302, hit while porting the Grain compiler to a bundled-binary setup built on this VFS.What was already there
This is a gap in the patch layer only:
lib/fd.jsalready allocates virtual fds from 10000+ specifically so they cannot collide with real ones, and exposesopenVirtualFd/getVirtualFd/closeVirtualFd. That is the routing mechanism.lib/file_system.jsalready implementsopenSync,closeSync,readSync,fstatSyncand asyncopen,close,read,fstat.lib/streams.jsalready drivesvfs.openSyncforcreateReadStream, so the handle path is exercised today.Changes
lib/fd.js— now owns the fd-keyed operations (closeFdSync,readFdSync,fstatFdSync,closeFd,readFd,fstatFd). A handle carries its own content and position, and the existingVirtualFileSystemmethods never touchedthis, so which VFS opened an fd is irrelevant. BothVirtualFileSystemand thenode:fspatches delegate here rather than duplicating the logic.It also collapses
fs.read/readSync's overloads —(fd, buffer, options),(fd, options, cb),(fd, cb)— whichVirtualFileSystem.readSyncdid not accept either.lib/file_system.js— the six fd methods become thin delegations. Net −52 lines.lib/module_hooks.js— patchesopenSync/open(resolved by path) andreadSync/read,closeSync/close,fstatSync/fstat(routed viagetVirtualFd(fd)), each falling through to the original when the path or fd isn't ours.findVFSForWatchis renamedfindVFSForExistingPath, since the open patches need exactly the same "which VFS owns this path, honouring overlay" lookup; it was module-private, so nothing external moves.Deliberately out of scope
fs.promises.open. It has to return a realFileHandle, andVirtualFileHandlehas no.fd,createReadStream,readLinesorchmod. Leaving it unpatched keeps today's behaviour (falls through, throwsENOENT) instead of handing callers a half-shaped object. Happy to open a separate issue if you'd like it tracked.readFileSyncandcreateReadStreamalready treat overlays.Verification
11 new tests in
test/fs-hooks.test.js: the sync path, the callback path, the options-object overload, sequential reads advancing position,EBADFon a stale fd, real-fs descriptors still working while a VFS is mounted, and overlay fall-through.npm test→ 244/244 passing.npm run lintclean.Commit is DCO signed off. Happy to adjust naming or split the
fd.jsextraction out if you'd rather review it separately.