ONC RPC handler for NFS v3 (program 100003, version 3) — see RFC 1813 §3.
This handler implements:
| Proc | Name |
|---|---|
| 0 | NULL |
| 1 | GETATTR |
| 2 | SETATTR |
| 3 | LOOKUP |
| 4 | ACCESS |
| 5 | READLINK |
| 6 | READ |
| 7 | WRITE |
| 8 | CREATE |
| 9 | MKDIR |
| 10 | SYMLINK |
| 11 | MKNOD |
| 12 | REMOVE |
| 13 | RMDIR |
| 14 | RENAME |
| 15 | LINK |
| 16 | READDIR |
| 17 | READDIRPLUS |
| 18 | FSSTAT |
| 19 | FSINFO |
| 20 | PATHCONF |
| 21 | COMMIT |
Locking procedures (NLM) and the remaining read/write opcodes (NFSv3 has no other procedures past 21) are out of scope here — this handler covers the full NFSv3 procedure set.
READ streaming
READ (proc 6) returns an iolist body — Backend.read/5 hands back
a lazy Enumerable.t() of binary chunks plus an EOF flag. The
handler accumulates chunks up to the kernel's count cap (typically
≤ 1 MiB, never the whole file), builds the reply body as nested
iolist (status | post_op_attr | count | eof | xdr-opaque chunks),
and lets RPC.RecordMarking.encode/1 propagate the iolist all the
way to :gen_tcp.send/2 — an unbounded file is never materialised
into a single binary.
Cookie pagination (READDIR / READDIRPLUS)
Both directory-iteration procs use the same scheme: the client
passes a cookie (initially zero) plus an opaque cookieverf3
back to the server, which returns a slice of entries plus a new
cookie for the next call. The handler treats cookies as opaque —
the backend chooses the encoding (offset, name, inode, …). The
cookieverf3 is typically the directory's mtime so a writer
invalidates outstanding paginations on the next mtime bump.
When the backend's verifier disagrees with the client's, the
handler maps that to NFS3ERR_BAD_COOKIE (10003) so the client
knows to restart pagination from cookie 0.
Filesystem decisions are delegated to a Tahr.NFSv3.Backend
module; the handler stays storage-agnostic. Bind a backend via
with_backend/1:
programs = %{100_003 => %{3 => Tahr.NFSv3.Handler.with_backend(MyBackend)}}Same shape as Tahr.Mount.Handler.with_backend/1. Tests can
alternatively pre-stamp :nfs_v3_backend onto the dispatcher's
ctx and invoke this module directly.
Procedure layout
Every procedure follows the same shape: XDR-decode the args via
helpers in Tahr.NFSv3.Types, invoke the backend callback,
XDR-encode the reply. RFC 1813 reply unions all share the
nfsstat3 discriminant on the wire — we encode the
status integer first, then the OK or FAIL arm.
Errors from the backend that don't include a post_op_attr
(e.g. {:error, :stale}) get nil in the post-op slot, which
encodes as the FALSE-flag arm of post_op_attr.
ACCESS3_* bit flags
ACCESS uses bitmasks per RFC 1813 §3.3.4. The constants are exposed as module attributes so backends and tests can reference them by name:
| Flag | Mask |
|---|---|
ACCESS3_READ | 0x0001 |
ACCESS3_LOOKUP | 0x0002 |
ACCESS3_MODIFY | 0x0004 |
ACCESS3_EXTEND | 0x0008 |
ACCESS3_DELETE | 0x0010 |
ACCESS3_EXECUTE | 0x0020 |
Summary
Functions
ACCESS3_DELETE — permission to remove an entry from a directory.
ACCESS3_EXECUTE — permission to execute a file (search a directory does not use this).
ACCESS3_EXTEND — permission to grow a file or add an entry to a directory.
ACCESS3_LOOKUP — permission to look up a name within a directory.
ACCESS3_MODIFY — permission to rewrite an existing file or directory.
ACCESS3_READ — permission to read file data or list a directory.
NFS program number (always 100003).
NFS version this handler implements (always 3).
Build a thin handler module that dispatches to backend. Same
shape as Tahr.Mount.Handler.with_backend/1.
Functions
@spec access3_delete() :: 16
ACCESS3_DELETE — permission to remove an entry from a directory.
@spec access3_execute() :: 32
ACCESS3_EXECUTE — permission to execute a file (search a directory does not use this).
@spec access3_extend() :: 8
ACCESS3_EXTEND — permission to grow a file or add an entry to a directory.
@spec access3_lookup() :: 2
ACCESS3_LOOKUP — permission to look up a name within a directory.
@spec access3_modify() :: 4
ACCESS3_MODIFY — permission to rewrite an existing file or directory.
@spec access3_read() :: 1
ACCESS3_READ — permission to read file data or list a directory.
@spec program() :: 100_003
NFS program number (always 100003).
@spec version() :: 3
NFS version this handler implements (always 3).
Build a thin handler module that dispatches to backend. Same
shape as Tahr.Mount.Handler.with_backend/1.