Tahr.NFSv3.Handler (tahr v0.1.0)

Copy Markdown View Source

ONC RPC handler for NFS v3 (program 100003, version 3) — see RFC 1813 §3.

This handler implements:

ProcName
0NULL
1GETATTR
2SETATTR
3LOOKUP
4ACCESS
5READLINK
6READ
7WRITE
8CREATE
9MKDIR
10SYMLINK
11MKNOD
12REMOVE
13RMDIR
14RENAME
15LINK
16READDIR
17READDIRPLUS
18FSSTAT
19FSINFO
20PATHCONF
21COMMIT

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.

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:

FlagMask
ACCESS3_READ0x0001
ACCESS3_LOOKUP0x0002
ACCESS3_MODIFY0x0004
ACCESS3_EXTEND0x0008
ACCESS3_DELETE0x0010
ACCESS3_EXECUTE0x0020

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

access3_delete()

@spec access3_delete() :: 16

ACCESS3_DELETE — permission to remove an entry from a directory.

access3_execute()

@spec access3_execute() :: 32

ACCESS3_EXECUTE — permission to execute a file (search a directory does not use this).

access3_extend()

@spec access3_extend() :: 8

ACCESS3_EXTEND — permission to grow a file or add an entry to a directory.

access3_lookup()

@spec access3_lookup() :: 2

ACCESS3_LOOKUP — permission to look up a name within a directory.

access3_modify()

@spec access3_modify() :: 4

ACCESS3_MODIFY — permission to rewrite an existing file or directory.

access3_read()

@spec access3_read() :: 1

ACCESS3_READ — permission to read file data or list a directory.

program()

@spec program() :: 100_003

NFS program number (always 100003).

version()

@spec version() :: 3

NFS version this handler implements (always 3).

with_backend(backend)

@spec with_backend(module()) :: module()

Build a thin handler module that dispatches to backend. Same shape as Tahr.Mount.Handler.with_backend/1.