← Blog

NFS3 Complete: Full Directory Mutation Set in sigilOS

June 22, 2026 · sigil-fs · Sigil-Docs
fs nfs3 networking 0.6.0

The NFS v3 client in sigilOS now has a complete directory mutation set. nfs3_rename_call/parse (proc 14) and nfs3_rmdir_call/parse (proc 12) land today alongside the removal of a stale placeholder. A sigilOS process with Cap<Network> can now create, remove, rename, and manage directories on any NFS v3 share — all five mutation operations, all through the VFS dispatch layer. 337/337 ALL PASS.


The complete mutation set

ProcOperationStatus
8nfs3_create — create a file✓ PASS
11nfs3_remove — remove a file✓ PASS
9nfs3_mkdir — create a directory✓ PASS
12nfs3_rmdir — remove an empty directory✓ PASS (new)
14nfs3_rename — rename/move across directories✓ PASS (new)

nfs3_rename: atomic move

nfs3_rename_call(buf, mid, from_dir_fh, from_name, to_dir_fh, to_name) builds an NFS3 RENAME request (proc 14). The wire layout encodes two directory file handles and two names — the source and the destination — in a single atomic operation:

RENAME request wire layout:
  [from_dir_fh: fh3]   -- directory containing the source entry
  [from_name:   str]   -- source name
  [to_dir_fh:   fh3]   -- directory to move into (may == from_dir_fh)
  [to_name:     str]   -- destination name

RENAME response:
  [status:      u32]   -- 0 = NFS3_OK
  [from_wcc:    wcc]   -- weak cache consistency for from_dir (null)
  [to_wcc:      wcc]   -- weak cache consistency for to_dir (null)

The two-fh wire format is what makes NFS3 RENAME atomic across directories: both the source and destination directory handles are in the same RPC call, so the server can move the entry in a single operation without a visible intermediate state. A rename within the same directory passes the same fh for both from_dir_fh and to_dir_fh.

nfs3_rename_parse checks the status word and returns 0 on success, -1 on any NFS error. The wcc fields are present in the response but null — sigilOS doesn't cache directory attributes, so there is nothing to invalidate.


nfs3_rmdir: remove an empty directory

nfs3_rmdir_call(buf, mid, dir_fh, name) is structurally identical to nfs3_remove (proc 11) — both take a directory file handle and a name and return a status and a wcc record. The only difference is the proc number (12 vs 11) and the server-side semantics: REMOVE works on files, RMDIR works on directories and fails if the directory is not empty.

RMDIR request wire layout:
  [dir_fh: fh3]   -- parent directory
  [name:   str]   -- directory name to remove

RMDIR response:
  [status: u32]   -- 0 = NFS3_OK; NFS3ERR_NOTEMPTY if not empty
  [wcc:    wcc]   -- weak cache consistency for dir_fh (null)

Cleanup: stale placeholder removed

nfs3_lookup.sg was a placeholder file from an earlier scaffolding pass. The actual LOOKUP implementation (proc 3, used for path resolution) lives in nfs3.sg and has been there since the initial NFS3 codec landed. The placeholder was never wired to anything and produced a duplicate-symbol warning when both files were in scope. It is now removed.


What the NFS3 client can do now

With all five mutation operations in place, the NFS3 client covers the full lifecycle of files and directories on a remote share:

The remaining gap is TCP transport binding — the same as SMB2. Once tcp_connect/tcp_send/tcp_recv are wired, both NFS3 and SMB2 can talk to real servers. The codec layer for both is complete.