feat(nfs): NFSv4.1 alongside NFSv3 - #7
Merged
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… rpc.ts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mapping Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Brings the 9P2000.L transport (mountx/9p) and the S3 gateway (mountx/s3)
onto the NFSv4.1 branch, plus main's three fixes (short-write completion
in the loopback harness, open files preserved across rename in the
unstorage driver, retained decoder bytes copied in the FUSE codec).
Conflicts and how each was resolved:
- AGENTS.md — woven, not picked: the intro names FUSE, 9P, NFS (v3 and
v4.1) and the S3 gateway; the tests section keeps both the v3/v4 nfs/
layout and main's s3/ entry; the wire-constants and FUSE_DESTROY
invariants carry both the RFC 8881/5662 and the 9p.h clauses; the
errno-discipline invariant now names all five reply shapes; the docs
paragraph lists the 9P, NFS and S3 transport pages.
- test/matrix.ts — six columns, union of both COLUMNS arrays: loopback,
FUSE, 9P, NFSv3, NFSv4.1, S3. The doc comment lists them in that order
and the THROUGH_* list names THROUGH_NFS4 as well. Git treated this
file as binary (the merge base carried a literal NUL that both sides
independently replaced with the two-character "\0"), so it was merged
by hand through git merge-file.
- .agents/conformance-matrix.md — generated, so regenerated with
`pnpm matrix` after the merge rather than resolved by hand.
- src/nfs/mount.ts — main moved run()/describe()/errorMessage() into
src/fuse/exec.ts and gave run() an options object; the v4 mount(8) call
keeps this branch's `-t nfs` comment and takes main's { stdio: CAPTURE }.
- README.md — transports line names FUSE, 9P and NFS, and keeps main's
S3 Gateway link.
- docs/.config/docs.yaml — description and the "one API, three transports"
feature name FUSE, 9P and NFS.
- docs/1.guide/0.index.md — the mermaid transport node and the comparison
table both gain the 9P column while keeping the NFS column's "NFSv3 or
NFSv4.1 client".
- docs/1.guide/3.drivers/2.custom.md — the conformance suite now runs six
ways (loopback, FUSE, 9P, NFSv3, NFSv4.1, S3).
- docs/1.guide/6.troubleshooting.md — the ESTALE answer keeps this
branch's v4.1 explanation and main's "FUSE and 9P both keep it
readable".
- docs/2.transports/0.index.md — six conflicts, all union: the mermaid
graph gains the 9P and S3 nodes and the NFS node keeps NFSv4.1; the
comparison table is the three-column one with the NFS column's v4.1
wording and a state row; the prose keeps 9P's paragraph, NFS's
two-versions paragraph and the stateless trade-off with v4.1's note.
- docs/2.transports/1.auto.md, 2.fuse.md, 3.reference/0.index.md — link
lists and the subpath table carry mountx/9p, mountx/nfs (v3 + v4.1)
and mountx/s3.
Semantic (non-textual) resolutions, found by grepping main's new files
for paths this branch moved:
- src/9p/protocol.ts, test/9p/{client,conformance,golden}.test.ts and
test/s3/client.ts referenced src/nfs/protocol.ts, test/nfs/client.ts,
test/nfs/conformance.test.ts and test/nfs/golden.test.ts, all of which
moved under v3/ in 5dabd79. Repointed. (.agents/9p-plan.md and
.agents/s3-plan.md keep the old paths deliberately — they are records
of a plan as authored.)
- Checked and confirmed unchanged: mountx/auto stays NFSv3 (it never
passes `version`, and nfsClientProbe's new `v4` field is not consulted
by probeNfs); main's two new conformance cases run in the NFSv4.1
column and skip there, since THROUGH_NFS4 declares handles: false.
pnpm lint, pnpm typecheck and pnpm build pass. pnpm matrix ran all six
columns (FUSE 130 passed under sudo, NFSv4.1 122 passed / 8 skipped).
vitest: 2808 passed, 233 skipped, 2 failed — the two failures are
main's own, reproduced verbatim at origin/main (test/s3/conformance
"keeps an open file attached across rename"), and are untouched here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
`test/s3/client.ts`'s `s3Driver` buffers an object whole while it is open, one shared `OpenFile` per open path, exactly as `src/drivers/unstorage.ts` does — and it had the same defect the unstorage driver had before #3: the map was keyed by the path the file was opened under, so a `rename` orphaned the buffer instead of moving it. The two conformance cases #3 added ("keeps an open file attached across rename" and "… across ancestor rename") therefore failed in the S3 column, which is the only column that was red. Mirrors #3's fix method for method: - `OpenFile` carries its own `path`, so a handle reads the name off the entry rather than closing over the one it was opened under. - `flush()`/`release()`/`createFileHandle()` lose their `path` parameter; a failed flush re-marks the entry dirty and rethrows, and `removeClosed()` keeps a closed-but-dirty entry rather than dropping the only copy of bytes that never landed. `truncate` clears such an entry once its bytes do land. - `movePaths(from, to)` refiles every open buffer for `from` and its subtree under `to`, scanning the map in full before rewriting any of it. `rename` calls it in both branches: the single-file one after the copy/delete pair (orphaning only the *displaced* destination, whose bytes must stay readable but must never be written back), and the directory one after `copyTree`/`removeTree`, which is what makes an ancestor rename invisible to an open handle. - `removeTree()` no longer orphans the files it deletes. Its only caller is `rename`, which has already copied them elsewhere, so orphaning there would throw away the very entries about to be moved. `handles: true` stays declared: the gateway honestly preserves the buffer, and the matrix's capability-loss row for S3 is unchanged. pnpm test: 2810 passed, 233 skipped, 0 failed (was 2808 / 233 / 2). pnpm matrix S3 column: 48 passed, 17 skipped, 0 failed (was 46 / 17 / 2). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
pi0x
pushed a commit
that referenced
this pull request
Jul 29, 2026
Adds `docs/1.guide/5.vms.md`: serve a driver on the host, mount it inside a VM with one command and nothing installed in the guest. QEMU gets 9P over `trans=tcp`. Its default user-mode networking already routes 10.0.2.2 to the host's loopback, so there is no tap device, no bridge, no host-side `ip` command and no root on the host — the server stays bound to 127.0.0.1. Verified end to end against an Alpine 3.21.3 `virt` guest (kernel 6.12.13-0-virt): mount with three options and no `modprobe`, then ls/read/write/mkdir/dd/rename/unlink/umount. Firecracker gets NFSv4.1, because it cannot do 9P at all: it emulates no `virtio-9p` device, and its published guest kernels ship `# CONFIG_NET_9P is not set`. Those kernels do carry NFSv4.1 (and not NFSv3), which is exactly the half that landed in #7. The tap wiring that Firecracker requires is not yet witnessed here, and the page says so. Also records both in `.agents/environment.md`, and amends the NFS note in AGENTS.md: NFSv4.1 was listed as having no real-client verification for want of a `mount.nfs` on this host, and a VM guest supplies one — the same server mounted `vers=4.1` from a real Linux kernel client and passed the same workload. Still not a Tier-2 column; that would mean carrying a VM in the suite. Guide pages renumbered to slot it after Mounting; URLs are unaffected, since the numeric prefix is routing-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Summary
NFSv4.1 served from
mountx/nfsbeside NFSv3 — one port, one socket, one sharedFileHandleTable; a client picks the version and nothing about a v3 mount changes.src/nfs/v3/(RFC 1813, moved intact — proven byte-identical across a 5,440-record differential against HEAD) andsrc/nfs/v4/(RFC 8881, transcribed with sections named), with a thin version router in the shared layer peeking(prog, vers)at fixed offsets.v3/andv4/never import from each other; what both need lives in the shared layer.FsDriver— open-state driver handles, ID mapping (Nfs4IdMap), GRACE/RECLAIM_COMPLETE gating per §18.51.3.mountNfs({ version: "4.1" })(Linux-only, default 3 unchanged),vers=4.1,proto=tcp,port=Nmount options (nomountport, nonolock),nfsClientProbe().v4,NfsVersion. The conformance matrix gained an NFSv4.1 column at parity with v3 (122 passed / 4 skipped, same skip reasons); Tier-1 JS 4.1 client + deterministic session/socket fuzz included.How it was built and verified
Twelve steps, each implemented by one agent and adversarially verified by an independent one before commit (mechanical RFC cross-checks, scenario suites up to 386 assertions, driver-handle leak audits, mutation testing, differential runs against HEAD). The verification rounds caught and fixed real defects pre-commit, including a wire-reachable crash on special stateids, a READDIR
dircountMUST violation, and two factual errors in the docs. 1505 tests passing.Still owed before release claims
vers=4.1mount once the host gainsmount.nfs(nfs-common); the Tier-2 v4.1 mount column is pending on that.pnpm benchhasn't run a v4 column.🤖 Generated with Claude Code