nfsv4

package module
v0.0.0-...-e2e0633 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: BSD-3-Clause Imports: 22 Imported by: 0

README

nfsv4

Go Reference

github.com/tailscale/nfsv4 is a Go library for writing read-only NFSv4.1 servers for virtual filesystems, such as gomodfs.

It's designed to give the filesystem control rather than hide the protocol:

  • Cache control. Filesystems decide per object whether clients may cache it without revalidating, using NFSv4.1 read delegations on files and directories. Immutable subtrees can be cached by clients forever; live objects can be revalidated by clients or recalled by the server when they change.
  • Filehandles your way. Implement nfsv4.FS with your own opaque filehandles (up to 128 bytes), or use nodefs and never think about them: it derives filehandles from paths so they survive server restarts with no persistent state.
  • No root, no rpcbind. NFSv4 needs only one TCP port, any port. Clients mount it directly.

Mounting

# Linux
sudo mount -t nfs4 -o vers=4.2,port=2049,ro HOST:/ /mnt

# macOS 26+ (vers=4.1 is required, as plain vers=4 means 4.0; without
# rsize, macOS reads only 32 KiB at a time; "soft" isn't allowed)
sudo mount -t nfs -o vers=4.1,port=2049,rdonly,rsize=1048576 HOST:/ /mnt

Try it with the demo server:

go run ./cmd/nfs4serve                 # in-memory demo filesystem
go run ./cmd/nfs4serve -dir ~/go/pkg/mod -immutable

Using it

With nodefs, implement a tree of nodes:

type myDir struct{ /* ... */ }

func (d *myDir) Attr(r *nfsv4.Request) (*nfsv4.Attrs, error) {
	return &nfsv4.Attrs{Type: nfsv4.TypeDir, Mode: 0o555, Change: 1}, nil
}
func (d *myDir) Lookup(r *nfsv4.Request, name string) (nodefs.Node, error) { /* ... */ }
func (d *myDir) ReadDir(r *nfsv4.Request) ([]nodefs.DirEntry, error)       { /* ... */ }

// Optional: let clients cache this directory forever.
func (d *myDir) CachePolicy(r *nfsv4.Request) nfsv4.Delegation {
	return nfsv4.Delegation{Grant: true}
}

// Files implement ReadAt(r, p, off); symlinks implement Readlink(r).

srv := &nfsv4.Server{FS: nodefs.New(root, nil)}
ln, _ := net.Listen("tcp", ":2049")
srv.Serve(ln)

To change an object that clients may hold delegations for, recall them first, then change it, then release:

release, err := fs.Recall(ctx, srv, "live/status.txt")
if err != nil {
	return err
}
updateStatus()
release()

See memfs for a complete example, and the package documentation for the details of client caching behavior.

Status

Tested against the Linux 7.0 kernel client (NFSv4.1 and 4.2) and the macOS 26 client (NFSv4.1). The tests that mount with the kernel need root or passwordless sudo and run with go test -mount (or NFSV4_MOUNT_TEST=1). Among other things, they check:

  • reads, directory listings, symlinks, and executing files
  • that with delegations, the Linux client sends no requests at all for repeated access to delegated files and directories, even with actimeo=1
  • that changes to live files and directories are seen promptly
  • that a mount survives a server restart with a fresh nodefs (no state carried over), including open files and processes whose working directory is deep inside the mount

Independent read-only checks with pynfs and Linux client workloads with NFSTest are documented in the testing report. See tests/conformance for pinned selections, runners, and results. These checks are not a full protocol compliance certification.

Read-only only: no writes, no NFSv4.0, no Kerberos, no ACLs or extended attributes, no pNFS.

Documentation

Overview

Package nfsv4 implements a read-only NFSv4.1 server for virtual filesystems.

The server speaks NFSv4 minor versions 1 and 2 (RFC 8881, RFC 7862) over TCP, which the Linux and macOS (since macOS 26) kernels can mount directly on any port, with no portmapper, mountd, or root privileges on the server side. Write operations fail with ErrROFS.

Layers

The FS interface is close to the protocol: it deals in opaque, caller-chosen filehandles (up to 128 bytes), NFSv4 attributes, READDIR cookies, and NFSv4 status codes. Callers that want full control over filehandle encoding implement it directly.

Callers that don't want to think about filehandles use package nodefs, which implements FS on top of a tree of directory, file, and symlink nodes, deriving filehandles from paths so that they survive server restarts without any persistent state. Package memfs (in-memory) and package osfs (a local directory) are built on nodefs.

Cache control

NFSv4 has no notion of "cache this for N seconds" or "valid forever" on the wire. Clients cache attributes and directory entries for a time set by their own mount options (acregmin, acregmax, acdirmin, acdirmax, actimeo) and revalidate using the change attribute (Attrs.Change).

The server-side lever is delegations: an FS implementing Delegator can grant clients read delegations on files (when they're opened) and on directories (when the client asks for a directory delegation). A delegation is a promise that the object won't change without the client being told first. While holding one, the Linux client doesn't revalidate at all: no GETATTR, LOOKUP, ACCESS, or READDIR for delegated directories and their entries, and no GETATTR or OPEN round trips for delegated files. That makes delegations the way to mark immutable subtrees as valid forever.

For objects that can change, either don't grant delegations (clients then revalidate on their own schedule), or grant them and use Server.Recall around changes. Delegation.MaxAge bounds how long a delegation lives.

Client behavior worth knowing about:

  • The Linux client (as of 7.0) asks for directory delegations when it revalidates a directory it has checked access to (the nfs4.directory_delegations module parameter, on by default), and holds them until they're recalled or the directory's inode is evicted from its cache.
  • The Linux client returns file delegations for files it hasn't used in a while (about one to two lease periods after the last close), and returns the least recently used ones when it holds more than the nfs.delegation_watermark module parameter (5000 by default). Reopening a file whose delegation was returned costs one OPEN round trip, which also revalidates it.
  • The macOS client (as of macOS 26) doesn't use directory delegations, and file delegations only extend its attribute cache lifetime to acregmax. Long-lived caching on macOS mostly comes from mount options. After a recall, it may serve one more read from its cache before noticing a change.
  • Delegations need a callback channel. NFSv4.1 clients run it over their own TCP connection to the server, so it works through NAT and userspace networking. Delegations aren't granted to clients without one.

A recall releases the delegation before a change. It does not disable ordinary client caching after the delegation is returned. On Linux 7.0, an already-open file can still return old data after a local change until its attributes are revalidated. A cached name can also remain visible for a short time after removal. The client mount options affect these delays.

Filehandles and restarts

The server advertises persistent filehandles. Clients keep using filehandles across server restarts (the server's client state is lost, and clients transparently reclaim it). Opens are always reclaimable, since read-only opens can't conflict. A filehandle the FS no longer recognizes should get ErrStale, from which the Linux and macOS clients recover by looking names up again from the parent directory, but that doesn't work for open files or processes whose working directory is in the mount, and never for the root filehandle. The Linux client recovers transparently for path-based system calls; the macOS client (as of macOS 26) returns ESTALE to the application once before looking names up again. FileIDs must be as stable as filehandles: clients consider an object whose FileID changed to be gone.

Not supported

Not supported: writes, NFSv4.0, Kerberos (only AUTH_SYS and AUTH_NONE are accepted, and credentials are passed to the FS for its own checks), ACLs, named attributes and extended attributes, pNFS, referrals, and directory change notifications (directory delegations are recalled instead). Byte-range locks are always granted, since locks on read-only files never conflict.

Index

Constants

View Source
const (
	AccessRead    uint32 = 0x01 // read data or list directory
	AccessLookup  uint32 = 0x02 // look up names in a directory
	AccessModify  uint32 = 0x04 // rewrite data or modify directory entries
	AccessExtend  uint32 = 0x08 // append data or add directory entries
	AccessDelete  uint32 = 0x10 // delete directory entries
	AccessExecute uint32 = 0x20 // execute a file
)

Access bits, as used by the ACCESS operation (ACCESS4_*).

View Source
const (
	FHPersistent       uint32 = 0x00
	FHNoExpireWithOpen uint32 = 0x01
	FHVolatileAny      uint32 = 0x02
	FHVolMigration     uint32 = 0x04
	FHVolRename        uint32 = 0x08
)

Filehandle expiration types, as reported by the fh_expire_type attribute (FH4_*).

View Source
const (
	// MaxFileHandleSize is the maximum length of a filehandle (NFS4_FHSIZE).
	MaxFileHandleSize = 128
)

Protocol limits.

Variables

View Source
var ErrServerClosed = errors.New("nfsv4: server closed")

ErrServerClosed is returned by Serve after Close is called.

Functions

This section is empty.

Types

type Accesser

type Accesser interface {
	// Access returns which of the requested access bits (AccessRead
	// etc.) are allowed for fh.
	Access(r *Request, fh FileHandle, attrs *Attrs, requested uint32) (allowed uint32, err error)
}

Accesser is an optional interface an FS can implement to decide the results of ACCESS operations, which clients use for permission checks.

Without it, the server computes access from the Mode, UID, and GID attributes and the request's credential, never granting AccessModify, AccessExtend, or AccessDelete.

type Attr

type Attr uint32

Attr is an NFSv4 file attribute number (FATTR4_*).

const (
	AttrSupportedAttrs    Attr = 0
	AttrType              Attr = 1
	AttrFHExpireType      Attr = 2
	AttrChange            Attr = 3
	AttrSize              Attr = 4
	AttrLinkSupport       Attr = 5
	AttrSymlinkSupport    Attr = 6
	AttrNamedAttr         Attr = 7
	AttrFSID              Attr = 8
	AttrUniqueHandles     Attr = 9
	AttrLeaseTime         Attr = 10
	AttrRdAttrError       Attr = 11
	AttrACL               Attr = 12
	AttrACLSupport        Attr = 13
	AttrArchive           Attr = 14
	AttrCanSetTime        Attr = 15
	AttrCaseInsensitive   Attr = 16
	AttrCasePreserving    Attr = 17
	AttrChownRestricted   Attr = 18
	AttrFileHandle        Attr = 19
	AttrFileID            Attr = 20
	AttrFilesAvail        Attr = 21
	AttrFilesFree         Attr = 22
	AttrFilesTotal        Attr = 23
	AttrFSLocations       Attr = 24
	AttrHidden            Attr = 25
	AttrHomogeneous       Attr = 26
	AttrMaxFileSize       Attr = 27
	AttrMaxLink           Attr = 28
	AttrMaxName           Attr = 29
	AttrMaxRead           Attr = 30
	AttrMaxWrite          Attr = 31
	AttrMIMEType          Attr = 32
	AttrMode              Attr = 33
	AttrNoTrunc           Attr = 34
	AttrNumLinks          Attr = 35
	AttrOwner             Attr = 36
	AttrOwnerGroup        Attr = 37
	AttrQuotaAvailHard    Attr = 38
	AttrQuotaAvailSoft    Attr = 39
	AttrQuotaUsed         Attr = 40
	AttrRawDev            Attr = 41
	AttrSpaceAvail        Attr = 42
	AttrSpaceFree         Attr = 43
	AttrSpaceTotal        Attr = 44
	AttrSpaceUsed         Attr = 45
	AttrSystem            Attr = 46
	AttrTimeAccess        Attr = 47
	AttrTimeAccessSet     Attr = 48
	AttrTimeBackup        Attr = 49
	AttrTimeCreate        Attr = 50
	AttrTimeDelta         Attr = 51
	AttrTimeMetadata      Attr = 52
	AttrTimeModify        Attr = 53
	AttrTimeModifySet     Attr = 54
	AttrMountedOnFileID   Attr = 55
	AttrDirNotifDelay     Attr = 56
	AttrDirentNotifDelay  Attr = 57
	AttrDACL              Attr = 58
	AttrSACL              Attr = 59
	AttrChangePolicy      Attr = 60
	AttrFSStatus          Attr = 61
	AttrFSLayoutTypes     Attr = 62
	AttrLayoutHint        Attr = 63
	AttrLayoutTypes       Attr = 64
	AttrLayoutBlkSize     Attr = 65
	AttrLayoutAlignment   Attr = 66
	AttrFSLocationsInfo   Attr = 67
	AttrMDSThreshold      Attr = 68
	AttrRetentionGet      Attr = 69
	AttrRetentionSet      Attr = 70
	AttrRetentEvtGet      Attr = 71
	AttrRetentEvtSet      Attr = 72
	AttrRetentionHold     Attr = 73
	AttrModeSetMasked     Attr = 74
	AttrSuppAttrExclCreat Attr = 75
	AttrFSCharsetCap      Attr = 76
	AttrCloneBlkSize      Attr = 77
	AttrSpaceFreed        Attr = 78
	AttrChangeAttrType    Attr = 79
	AttrSecLabel          Attr = 80
)

NFSv4 attribute numbers (FATTR4_*).

func (Attr) String

func (a Attr) String() string

type AttrMask

type AttrMask struct {
	// contains filtered or unexported fields
}

AttrMask is a set of attributes (an NFSv4 bitmap4).

func MakeAttrMask

func MakeAttrMask(attrs ...Attr) AttrMask

MakeAttrMask returns an AttrMask containing attrs.

func (AttrMask) All

func (m AttrMask) All() []Attr

All returns the attributes in m, in increasing order.

func (AttrMask) And

func (m AttrMask) And(o AttrMask) AttrMask

And returns the intersection of m and o.

func (AttrMask) AndNot

func (m AttrMask) AndNot(o AttrMask) AttrMask

AndNot returns m without the attributes in o.

func (*AttrMask) Clear

func (m *AttrMask) Clear(a Attr)

Clear removes a from m.

func (AttrMask) ContainsAll

func (m AttrMask) ContainsAll(o AttrMask) bool

ContainsAll reports whether every attribute in o is also in m.

func (AttrMask) Has

func (m AttrMask) Has(a Attr) bool

Has reports whether a is in m.

func (AttrMask) IsEmpty

func (m AttrMask) IsEmpty() bool

IsEmpty reports whether m is empty.

func (AttrMask) Or

func (m AttrMask) Or(o AttrMask) AttrMask

Or returns the union of m and o.

func (*AttrMask) Set

func (m *AttrMask) Set(a Attr)

Set adds a to m. Attributes beyond the highest one known are ignored.

func (AttrMask) String

func (m AttrMask) String() string

type Attrs

type Attrs struct {
	// Type is the object's type. It's required.
	Type FileType

	// Change is the change attribute: an opaque value that must change
	// whenever the object's contents or attributes change (for
	// directories: whenever entries are added, removed, or renamed).
	// Clients compare it to decide whether their cached data is still
	// valid, so an object that never changes should keep a constant
	// Change, and a live object should always report a new value after
	// changing, even if its size and modification time are unchanged.
	Change uint64

	// Size is the size in bytes. For symlinks, it should be the length of
	// the target.
	Size uint64

	// SpaceUsed is the number of bytes of storage used. If zero, the
	// server reports Size.
	SpaceUsed uint64

	// FileID is the object's unique number within its filesystem (its
	// "inode number"). It must be unique, and as stable as the object's
	// filehandle: clients that see a different FileID for a filehandle
	// they know (such as after a server restart) consider the object
	// gone and return ESTALE to applications.
	FileID uint64

	// MountedOnFileID is the mounted_on_fileid attribute. If zero, the
	// server reports FileID, which is what's wanted unless the object is
	// the root of a filesystem mounted on another object (see FSID).
	MountedOnFileID uint64

	// Mode holds the permission bits (0o7777). Type bits are ignored.
	Mode uint32

	// NumLinks is the number of hard links. If zero, the server reports
	// 1 (or 2 for directories).
	NumLinks uint32

	// UID and GID are the numeric owner and group. They're sent as
	// decimal strings, which both the Linux and macOS clients accept with
	// AUTH_SYS.
	UID, GID uint32

	// Owner and Group, if non-empty, are sent as the owner and
	// owner_group attributes verbatim instead of UID and GID (for
	// instance "alice@example.com").
	Owner, Group string

	// RawDev is the device number for block and character devices.
	RawDev Device

	// ModTime is the time of the last modification of the contents.
	ModTime time.Time

	// ChangeTime is the time of the last change of contents or
	// attributes (time_metadata, ctime). If zero, ModTime is reported.
	ChangeTime time.Time

	// AccessTime is the time of last access. If zero, ModTime is
	// reported.
	AccessTime time.Time

	// BirthTime is the creation time. If zero, it's omitted.
	BirthTime time.Time

	// FSID, if non-nil, is the identifier of the filesystem containing
	// this object. If nil, the Server's FSID is reported.
	//
	// Clients treat a directory whose FSID differs from its parent's as
	// the root of a different filesystem and make it a separate
	// (automatic) mount point. Most FS implementations should leave this
	// nil.
	FSID *FSID
}

Attrs are the per-object attributes of a file, directory, or other filesystem object.

Attributes that describe the filesystem as a whole (such as maxname or lease_time) are not here; they come from the Server configuration, and space and file counts come from the optional StatFSer interface.

type CBOp

type CBOp uint32

CBOp is an NFSv4 callback operation number (nfs_cb_opnum4).

const (
	CBOpGetAttr            CBOp = 3
	CBOpRecall             CBOp = 4
	CBOpLayoutRecall       CBOp = 5
	CBOpNotify             CBOp = 6
	CBOpPushDeleg          CBOp = 7
	CBOpRecallAny          CBOp = 8
	CBOpRecallableObjAvail CBOp = 9
	CBOpRecallSlot         CBOp = 10
	CBOpSequence           CBOp = 11
	CBOpWantsCancelled     CBOp = 12
	CBOpNotifyLock         CBOp = 13
	CBOpNotifyDeviceID     CBOp = 14
	CBOpOffload            CBOp = 15
	CBOpIllegal            CBOp = 10044
)

NFSv4 callback operation numbers (nfs_cb_opnum4).

func (CBOp) String

func (o CBOp) String() string

type ClientInfo

type ClientInfo struct {
	// ID is the server-assigned client ID.
	ID uint64

	// OwnerID is the client's self-chosen unique identifier
	// (co_ownerid). The Linux client uses a string like
	// "Linux NFSv4.1 hostname".
	OwnerID []byte

	// ImplDomain, ImplName, and ImplDate describe the client
	// implementation (such as "kernel.org" and "Linux 7.0.0 ..."), if the
	// client sent them.
	ImplDomain string
	ImplName   string
	ImplDate   time.Time
}

ClientInfo describes an NFSv4 client, as identified by the EXCHANGE_ID operation.

type Cred

type Cred struct {
	// Flavor is the RPC auth flavor: 0 (AUTH_NONE) or 1 (AUTH_SYS).
	Flavor uint32

	// UID, GID, and GIDs are the user's numeric identity for AUTH_SYS.
	// For AUTH_NONE they're 65534 ("nobody") and nil.
	UID, GID uint32
	GIDs     []uint32

	// MachineName is the AUTH_SYS machine name (usually the client's
	// hostname).
	MachineName string
}

Cred is an RPC credential.

type Delegation

type Delegation struct {
	// Grant says whether to grant a read delegation.
	Grant bool

	// MaxAge, if non-zero, limits how long a delegation may be held. The
	// server recalls it after this long. Zero means the delegation is
	// held until it's recalled with Server.Recall (for immutable
	// objects: forever) or the client returns it voluntarily.
	MaxAge time.Duration
}

Delegation is a cache-control decision made by a Delegator.

An NFSv4 read delegation is a promise from the server that an object won't change without the client being told first (with a recall). While a client holds a delegation it can cache the object without revalidating: the Linux client stops sending GETATTR, LOOKUP, and ACCESS revalidations for delegated files and directories, and for a delegated directory also trusts its cached directory listing and child names. The macOS client uses delegations to extend its attribute cache lifetime to the acregmax mount option's value and doesn't use directory delegations.

The zero value grants no delegation; the client then revalidates on its own schedule (governed by its acregmin/acregmax/acdirmin/acdirmax mount options).

type Delegator

type Delegator interface {
	// Delegate decides whether to grant a read delegation for fh, a
	// regular file being opened or a directory for which the client asked
	// for a directory delegation. attrs are the object's current
	// attributes.
	Delegate(r *Request, fh FileHandle, attrs *Attrs) Delegation
}

Delegator is an optional interface an FS can implement to control client caching by granting delegations.

Delegations require a working callback channel to the client, which NFSv4.1 clients normally establish over their own connection. The server only consults the Delegator when one exists.

An FS that grants delegations for an object that can change must change it only while its delegations are recalled; see Server.Recall. For immutable objects there's nothing to do.

type Device

type Device struct {
	Major, Minor uint32
}

Device is a block or character device number.

type DirEntry

type DirEntry struct {
	// Name is the entry's name.
	Name string

	// Cookie identifies the entry's position, such that listing again
	// with ReadDirArgs.Cookie set to this value resumes with the next
	// entry. Cookies must be at least 3; the values 0, 1, and 2 are
	// reserved by the protocol.
	Cookie uint64

	// Handle is the entry's filehandle. It's required if the client asked
	// for any attributes (ReadDirArgs.Want is non-empty).
	Handle FileHandle

	// Attrs are the entry's attributes, if already known. If nil and the
	// client wants attributes, the server calls FS.GetAttr(Handle).
	Attrs *Attrs
}

DirEntry is a directory entry returned by FS.ReadDir.

type FS

type FS interface {
	// Root returns the root filehandle. It's called for each PUTROOTFH
	// operation, so it should be cheap. It must return the same filehandle
	// for the lifetime of the filesystem, including across restarts.
	Root(r *Request) (FileHandle, error)

	// GetAttr returns the attributes of fh.
	//
	// The want mask says which attributes the client asked for. It's a
	// hint: implementations may use it to skip expensive work, but may
	// also return attributes that weren't asked for. It always includes
	// the basic attributes (type, change, size, fileid, mode, numlinks,
	// owner, owner_group, and the times), so it's mostly useful for
	// skipping work for clients that only want those. Attributes the
	// server computes itself (such as lease_time) are never in want.
	GetAttr(r *Request, fh FileHandle, want AttrMask) (*Attrs, error)

	// Lookup looks up name in the directory dir and returns its
	// filehandle. If convenient, it may also return the child's
	// attributes, saving a subsequent GetAttr call; otherwise attrs may be
	// nil.
	//
	// Lookup must return ErrNoEnt if the name doesn't exist and ErrNotDir
	// if dir isn't a directory (or ErrSymlink if it's a symlink). The
	// names "." and ".." are never passed to Lookup.
	Lookup(r *Request, dir FileHandle, name string) (fh FileHandle, attrs *Attrs, err error)

	// LookupParent returns the parent directory of the directory dir.
	// It's never called for the root. Implementations that can't
	// determine parents may return ErrNotSupp, but some client
	// operations (such as resolving ".." from an open-by-handle
	// directory, or NFS re-exports) won't work.
	LookupParent(r *Request, dir FileHandle) (FileHandle, error)

	// ReadDir lists the directory dir, calling emit for each entry in
	// order, starting after the position described by args.Cookie. It
	// stops early, returning nil, if emit returns false, which means the
	// client's reply buffer is full. Not returning "." or ".." entries is
	// the FS's responsibility.
	//
	// See ReadDirArgs and DirEntry for the cookie and verifier rules.
	ReadDir(r *Request, dir FileHandle, args ReadDirArgs, emit func(DirEntry) bool) (ReadDirResult, error)

	// Read reads up to len(p) bytes of the regular file fh starting at
	// offset off into p, returning the number of bytes read and whether
	// the read reached the end of the file. Reading at or past the end
	// of the file returns 0, true, nil. Returning fewer bytes than
	// requested without eof is allowed; the client will issue another
	// read.
	//
	// p is the server's reply buffer, so data is written directly into
	// the reply with no extra copy. Read must return ErrIsDir for
	// directories and ErrInval for other non-regular files.
	Read(r *Request, fh FileHandle, off uint64, p []byte) (n int, eof bool, err error)

	// ReadLink returns the target of the symlink fh. It must return
	// ErrInval if fh isn't a symlink (ErrIsDir for directories).
	ReadLink(r *Request, fh FileHandle) (string, error)
}

FS is a read-only filesystem served over NFSv4 by a Server.

Methods are called concurrently. Methods that operate on a filehandle should return ErrStale or ErrBadHandle for unknown filehandles. Errors may be Status values (such as ErrNoEnt) or other errors that are mapped to a Status (see Server.Logf for errors that have no natural mapping). PUTFH uses GetAttr to validate handles and returns ErrServerFault if the mapped error is not valid for that operation.

Optional behavior is provided by implementing additional interfaces: Delegator (cache control), Opener, Accesser, and StatFSer.

type FSID

type FSID struct {
	Major, Minor uint64
}

FSID is a filesystem identifier (the fsid attribute).

type FSStat

type FSStat struct {
	SpaceTotal, SpaceFree, SpaceAvail uint64 // bytes
	FilesTotal, FilesFree, FilesAvail uint64
}

FSStat holds filesystem-wide space and file counts.

type FileHandle

type FileHandle []byte

FileHandle is an NFSv4 filehandle: opaque bytes, at most MaxFileHandleSize (128) bytes long, chosen by the FS implementation.

Clients treat filehandles as opaque identifiers and hold onto them indefinitely, including across server restarts. The server advertises its filehandles as persistent (FH4_PERSISTENT, unless configured otherwise), so an FS should be able to resolve any filehandle it ever handed out for as long as the object exists, even after the process restarts. Return ErrStale for filehandles that no longer resolve; clients recover from that by looking the name up again from the parent directory. The root filehandle must never become stale: neither the Linux nor the macOS client can recover a mount whose root filehandle stops working.

FS implementations that don't want to think about any of this can use the nodefs package, which manages filehandles itself.

type FileType

type FileType uint32

FileType is an NFSv4 file type (nfs_ftype4).

const (
	TypeReg       FileType = 1 // NF4REG: regular file
	TypeDir       FileType = 2 // NF4DIR: directory
	TypeBlock     FileType = 3 // NF4BLK: block device
	TypeChar      FileType = 4 // NF4CHR: character device
	TypeSymlink   FileType = 5 // NF4LNK: symbolic link
	TypeSocket    FileType = 6 // NF4SOCK: socket
	TypeFIFO      FileType = 7 // NF4FIFO: named pipe
	TypeAttrDir   FileType = 8 // NF4ATTRDIR: named attribute directory (unused)
	TypeNamedAttr FileType = 9 // NF4NAMEDATTR: named attribute (unused)
)

File types.

func (FileType) String

func (t FileType) String() string

type Op

type Op uint32

Op is an NFSv4 COMPOUND operation number (nfs_opnum4).

const (
	OpAccess             Op = 3
	OpClose              Op = 4
	OpCommit             Op = 5
	OpCreate             Op = 6
	OpDelegPurge         Op = 7
	OpDelegReturn        Op = 8
	OpGetAttr            Op = 9
	OpGetFH              Op = 10
	OpLink               Op = 11
	OpLock               Op = 12
	OpLockT              Op = 13
	OpLockU              Op = 14
	OpLookup             Op = 15
	OpLookupp            Op = 16
	OpNVerify            Op = 17
	OpOpen               Op = 18
	OpOpenAttr           Op = 19
	OpOpenConfirm        Op = 20
	OpOpenDowngrade      Op = 21
	OpPutFH              Op = 22
	OpPutPubFH           Op = 23
	OpPutRootFH          Op = 24
	OpRead               Op = 25
	OpReadDir            Op = 26
	OpReadLink           Op = 27
	OpRemove             Op = 28
	OpRename             Op = 29
	OpRenew              Op = 30
	OpRestoreFH          Op = 31
	OpSaveFH             Op = 32
	OpSecInfo            Op = 33
	OpSetAttr            Op = 34
	OpSetClientID        Op = 35
	OpSetClientIDConfirm Op = 36
	OpVerify             Op = 37
	OpWrite              Op = 38
	OpReleaseLockOwner   Op = 39
	OpBackchannelCtl     Op = 40
	OpBindConnToSession  Op = 41
	OpExchangeID         Op = 42
	OpCreateSession      Op = 43
	OpDestroySession     Op = 44
	OpFreeStateID        Op = 45
	OpGetDirDelegation   Op = 46
	OpGetDeviceInfo      Op = 47
	OpGetDeviceList      Op = 48
	OpLayoutCommit       Op = 49
	OpLayoutGet          Op = 50
	OpLayoutReturn       Op = 51
	OpSecInfoNoName      Op = 52
	OpSequence           Op = 53
	OpSetSSV             Op = 54
	OpTestStateID        Op = 55
	OpWantDelegation     Op = 56
	OpDestroyClientID    Op = 57
	OpReclaimComplete    Op = 58
	OpAllocate           Op = 59
	OpCopy               Op = 60
	OpCopyNotify         Op = 61
	OpDeallocate         Op = 62
	OpIOAdvise           Op = 63
	OpLayoutError        Op = 64
	OpLayoutStats        Op = 65
	OpOffloadCancel      Op = 66
	OpOffloadStatus      Op = 67
	OpReadPlus           Op = 68
	OpSeek               Op = 69
	OpWriteSame          Op = 70
	OpClone              Op = 71
	OpIllegal            Op = 10044
	OpGetXattr           Op = 72
	OpSetXattr           Op = 73
	OpListXattrs         Op = 74
	OpRemoveXattr        Op = 75
)

NFSv4 COMPOUND operation numbers (nfs_opnum4). Numbers 72 through 75 are the extended attribute operations from RFC 8276.

func (Op) String

func (o Op) String() string

type Opener

type Opener interface {
	// Open is called when a client opens fh, a regular file, for
	// reading. Returning an error fails the open.
	Open(r *Request, fh FileHandle, attrs *Attrs) error

	// Close is called when the client closes the open state for fh,
	// or when the server discards it (such as when a client's lease
	// expires).
	Close(fh FileHandle)
}

Opener is an optional interface an FS can implement to observe or veto opens of regular files.

Clients open files before reading them, but may also read without an open (using special stateids), and clients holding a delegation open files locally without telling the server, so Open and Close are not a reliable way to track file usage.

type ReadDirArgs

type ReadDirArgs struct {
	// Cookie is where to resume listing. Zero means the beginning of the
	// directory. Otherwise it's a DirEntry.Cookie value the FS returned
	// earlier, and listing resumes with the entry after that one.
	Cookie uint64

	// Verifier is the ReadDirResult.Verifier value the FS returned along
	// with Cookie, or zero when Cookie is zero. An FS whose cookies can be
	// invalidated by directory changes uses the verifier to detect stale
	// cookies, returning ErrNotSame (or ErrBadCookie) for them.
	Verifier uint64

	// Want is the set of attributes the client wants for each entry, as
	// in FS.GetAttr. If it includes AttrFileHandle, the client wants
	// each entry's filehandle. If it is empty, the client wants only
	// names and cookies.
	Want AttrMask
}

ReadDirArgs are the arguments to FS.ReadDir.

type ReadDirResult

type ReadDirResult struct {
	// Verifier is the cookie verifier the client should send back with
	// future cookies from this listing. Zero is fine for FSes with
	// stable cookies.
	Verifier uint64
}

ReadDirResult is the result of FS.ReadDir.

type Request

type Request struct {

	// Cred is the RPC credential of the caller.
	Cred Cred

	// RemoteAddr and LocalAddr are the connection's addresses.
	RemoteAddr, LocalAddr net.Addr

	// Client is the client that sent the request. It's nil only for
	// requests that aren't part of a session, which never reach an FS.
	Client *ClientInfo

	// MinorVersion is the NFSv4 minor version (1 or 2) of the request.
	MinorVersion uint32
	// contains filtered or unexported fields
}

Request describes the context of an FS method call: one COMPOUND procedure from a client.

func (*Request) Context

func (r *Request) Context() context.Context

Context returns the request's context. It's canceled when the client's connection closes or the server shuts down.

func (*Request) WithContext

func (r *Request) WithContext(ctx context.Context) *Request

WithContext returns a shallow copy of r with its context set to ctx. It does not change r.

type Server

type Server struct {
	// FS is the filesystem to serve. It's required.
	FS FS

	// Logf, if non-nil, is used to log errors and unusual events. If
	// nil, log.Printf is used.
	Logf func(format string, args ...any)

	// Debugf, if non-nil, logs every operation. It's very verbose.
	Debugf func(format string, args ...any)

	// LeaseTime is the lease time clients must renew their state within.
	// Clients send a SEQUENCE heartbeat at least this often, and the
	// Linux client returns idle file delegations it isn't using after one
	// or two thirds of it. If zero, 90 seconds is used.
	LeaseTime time.Duration

	// ClientExpiry is how long after a client stops renewing its lease
	// that the server discards the client's state. Read-only state never
	// conflicts with other clients, so the server is "courteous" and
	// keeps the state of unresponsive clients (such as laptops that went
	// to sleep) much longer than the lease time, so they can resume
	// seamlessly. If zero, one hour is used.
	ClientExpiry time.Duration

	// MaxIO is the maximum READ size (the maxread attribute) and the
	// basis for the session request and response size limits. If zero,
	// 1 MiB is used.
	MaxIO int

	// FSID is the default fsid attribute, used for objects whose
	// Attrs.FSID is nil. The zero value is fine.
	FSID FSID

	// FHExpireType is the value of the fh_expire_type attribute. The
	// zero value, FHPersistent, is the right choice for filehandles that
	// survive server restarts.
	FHExpireType uint32

	// ChangeIsMonotonic promises that every object's Attrs.Change value
	// only ever increases. It's reported to NFSv4.2 clients in the
	// change_attr_type attribute, and the Linux client then uses Change
	// values to order concurrent attribute updates. Leave it false if
	// Change values are arbitrary (such as hashes).
	ChangeIsMonotonic bool

	// ServerScope identifies the server for the purposes of client
	// state (eir_server_scope). Servers sharing a scope (and a
	// ServerOwner) are considered the same server by clients. If empty,
	// "github.com/tailscale/nfsv4" is used.
	ServerScope string

	// ServerOwner is the server owner major ID (so_major_id), used by
	// clients for trunking detection. If empty, a random value is chosen
	// at startup.
	ServerOwner string
	// contains filtered or unexported fields
}

Server is an NFSv4.1 server serving an FS read-only.

It speaks NFSv4 minor versions 1 and 2 over TCP. Clients mount it directly on whatever port it listens on, with no portmapper or mountd:

Linux: mount -t nfs4 -o vers=4.2,port=N,ro HOST:/ /mnt
macOS: mount -t nfs -o vers=4.1,port=N,rdonly,rsize=1048576 HOST:/ /mnt

The exported fields must not be changed after the first call to Serve or ServeConn.

func (*Server) Close

func (s *Server) Close() error

Close stops all listeners and closes all connections. Client state is discarded. It waits for in-progress requests to finish.

func (*Server) Recall

func (s *Server) Recall(ctx context.Context, fhs ...FileHandle) (release func(), err error)

Recall recalls the delegations clients hold for the objects fhs and prevents new delegations for them from being granted until release is called. An FS that grants delegations for objects that can change must change them only between Recall and release:

release, err := srv.Recall(ctx, fileFH, dirFH)
if err != nil {
	return err
}
defer release()
// ... change the file, and the directory's entries ...

The order matters. Clients send their final GETATTR for a delegated object when returning the delegation and, still trusting their delegation at that moment, record the new change attribute without discarding their cached data. So clients must return their delegations before the object changes; after release, they notice the new change attribute (which the FS must report) and discard their stale caches.

Recall returns once all delegations for fhs have been returned by their clients, or revoked from clients that didn't return them within the lease time. If ctx is done first, Recall returns ctx's error, and delegations aren't blocked; the caller should retry before changing anything.

Objects with no delegations outstanding (including all objects of an FS that doesn't implement Delegator) are handled quickly, without contacting any clients.

func (*Server) Serve

func (s *Server) Serve(ln net.Listener) error

Serve accepts connections on ln and serves them until ln fails or Close is called, in which case it returns ErrServerClosed.

func (*Server) ServeConn

func (s *Server) ServeConn(c net.Conn) error

ServeConn serves a single client connection until it closes or the server is closed. It closes c before returning.

func (*Server) Stats

func (s *Server) Stats() Stats

Stats returns a snapshot of the server's statistics.

type StatFSer

type StatFSer interface {
	StatFS(r *Request, fh FileHandle) (*FSStat, error)
}

StatFSer is an optional interface an FS can implement to report filesystem space and file counts (the attributes behind df(1)). Without it, all values are reported as zero.

type Stats

type Stats struct {
	// Ops counts the operations processed, by operation.
	Ops map[Op]uint64

	// Compounds is the number of COMPOUND procedures processed.
	Compounds uint64

	// Clients and Sessions are the current numbers of clients and
	// sessions.
	Clients, Sessions int

	// Opens, Locks, and Delegations are the current numbers of open,
	// lock, and delegation stateids.
	Opens, Locks, Delegations int

	// Recalls is the number of delegation recalls sent. Revocations is
	// the number of delegations revoked because the client failed to
	// return them in time.
	Recalls, Revocations uint64
}

Stats are server statistics, as returned by Server.Stats.

type Status

type Status uint32

Status is an NFSv4 status code (nfsstat4).

Status implements error, so FS implementations can return Status values such as ErrNoEnt directly to control exactly what the client sees. OK should not be returned as an error.

const (
	OK                       Status = 0     // NFS4_OK
	ErrPerm                  Status = 1     // NFS4ERR_PERM
	ErrNoEnt                 Status = 2     // NFS4ERR_NOENT
	ErrIO                    Status = 5     // NFS4ERR_IO
	ErrNXIO                  Status = 6     // NFS4ERR_NXIO
	ErrAccess                Status = 13    // NFS4ERR_ACCESS
	ErrExist                 Status = 17    // NFS4ERR_EXIST
	ErrXDev                  Status = 18    // NFS4ERR_XDEV
	ErrNotDir                Status = 20    // NFS4ERR_NOTDIR
	ErrIsDir                 Status = 21    // NFS4ERR_ISDIR
	ErrInval                 Status = 22    // NFS4ERR_INVAL
	ErrFBig                  Status = 27    // NFS4ERR_FBIG
	ErrNoSpc                 Status = 28    // NFS4ERR_NOSPC
	ErrROFS                  Status = 30    // NFS4ERR_ROFS
	ErrMLink                 Status = 31    // NFS4ERR_MLINK
	ErrNameTooLong           Status = 63    // NFS4ERR_NAMETOOLONG
	ErrNotEmpty              Status = 66    // NFS4ERR_NOTEMPTY
	ErrDQuot                 Status = 69    // NFS4ERR_DQUOT
	ErrStale                 Status = 70    // NFS4ERR_STALE
	ErrBadHandle             Status = 10001 // NFS4ERR_BADHANDLE
	ErrBadCookie             Status = 10003 // NFS4ERR_BAD_COOKIE
	ErrNotSupp               Status = 10004 // NFS4ERR_NOTSUPP
	ErrTooSmall              Status = 10005 // NFS4ERR_TOOSMALL
	ErrServerFault           Status = 10006 // NFS4ERR_SERVERFAULT
	ErrBadType               Status = 10007 // NFS4ERR_BADTYPE
	ErrDelay                 Status = 10008 // NFS4ERR_DELAY
	ErrSame                  Status = 10009 // NFS4ERR_SAME
	ErrDenied                Status = 10010 // NFS4ERR_DENIED
	ErrExpired               Status = 10011 // NFS4ERR_EXPIRED
	ErrLocked                Status = 10012 // NFS4ERR_LOCKED
	ErrGrace                 Status = 10013 // NFS4ERR_GRACE
	ErrFHExpired             Status = 10014 // NFS4ERR_FHEXPIRED
	ErrShareDenied           Status = 10015 // NFS4ERR_SHARE_DENIED
	ErrWrongSec              Status = 10016 // NFS4ERR_WRONGSEC
	ErrClidInUse             Status = 10017 // NFS4ERR_CLID_INUSE
	ErrResource              Status = 10018 // NFS4ERR_RESOURCE
	ErrMoved                 Status = 10019 // NFS4ERR_MOVED
	ErrNoFileHandle          Status = 10020 // NFS4ERR_NOFILEHANDLE
	ErrMinorVersMismatch     Status = 10021 // NFS4ERR_MINOR_VERS_MISMATCH
	ErrStaleClientID         Status = 10022 // NFS4ERR_STALE_CLIENTID
	ErrStaleStateID          Status = 10023 // NFS4ERR_STALE_STATEID
	ErrOldStateID            Status = 10024 // NFS4ERR_OLD_STATEID
	ErrBadStateID            Status = 10025 // NFS4ERR_BAD_STATEID
	ErrBadSeqID              Status = 10026 // NFS4ERR_BAD_SEQID
	ErrNotSame               Status = 10027 // NFS4ERR_NOT_SAME
	ErrLockRange             Status = 10028 // NFS4ERR_LOCK_RANGE
	ErrSymlink               Status = 10029 // NFS4ERR_SYMLINK
	ErrRestoreFH             Status = 10030 // NFS4ERR_RESTOREFH
	ErrLeaseMoved            Status = 10031 // NFS4ERR_LEASE_MOVED
	ErrAttrNotSupp           Status = 10032 // NFS4ERR_ATTRNOTSUPP
	ErrNoGrace               Status = 10033 // NFS4ERR_NO_GRACE
	ErrReclaimBad            Status = 10034 // NFS4ERR_RECLAIM_BAD
	ErrReclaimConflict       Status = 10035 // NFS4ERR_RECLAIM_CONFLICT
	ErrBadXDR                Status = 10036 // NFS4ERR_BADXDR
	ErrLocksHeld             Status = 10037 // NFS4ERR_LOCKS_HELD
	ErrOpenMode              Status = 10038 // NFS4ERR_OPENMODE
	ErrBadOwner              Status = 10039 // NFS4ERR_BADOWNER
	ErrBadChar               Status = 10040 // NFS4ERR_BADCHAR
	ErrBadName               Status = 10041 // NFS4ERR_BADNAME
	ErrBadRange              Status = 10042 // NFS4ERR_BAD_RANGE
	ErrLockNotSupp           Status = 10043 // NFS4ERR_LOCK_NOTSUPP
	ErrOpIllegal             Status = 10044 // NFS4ERR_OP_ILLEGAL
	ErrDeadlock              Status = 10045 // NFS4ERR_DEADLOCK
	ErrFileOpen              Status = 10046 // NFS4ERR_FILE_OPEN
	ErrAdminRevoked          Status = 10047 // NFS4ERR_ADMIN_REVOKED
	ErrCBPathDown            Status = 10048 // NFS4ERR_CB_PATH_DOWN
	ErrBadIOMode             Status = 10049 // NFS4ERR_BADIOMODE
	ErrBadLayout             Status = 10050 // NFS4ERR_BADLAYOUT
	ErrBadSessionDigest      Status = 10051 // NFS4ERR_BAD_SESSION_DIGEST
	ErrBadSession            Status = 10052 // NFS4ERR_BADSESSION
	ErrBadSlot               Status = 10053 // NFS4ERR_BADSLOT
	ErrCompleteAlready       Status = 10054 // NFS4ERR_COMPLETE_ALREADY
	ErrConnNotBoundToSession Status = 10055 // NFS4ERR_CONN_NOT_BOUND_TO_SESSION
	ErrDelegAlreadyWanted    Status = 10056 // NFS4ERR_DELEG_ALREADY_WANTED
	ErrBackChanBusy          Status = 10057 // NFS4ERR_BACK_CHAN_BUSY
	ErrLayoutTryLater        Status = 10058 // NFS4ERR_LAYOUTTRYLATER
	ErrLayoutUnavailable     Status = 10059 // NFS4ERR_LAYOUTUNAVAILABLE
	ErrNoMatchingLayout      Status = 10060 // NFS4ERR_NOMATCHING_LAYOUT
	ErrRecallConflict        Status = 10061 // NFS4ERR_RECALLCONFLICT
	ErrUnknownLayoutType     Status = 10062 // NFS4ERR_UNKNOWN_LAYOUTTYPE
	ErrSeqMisordered         Status = 10063 // NFS4ERR_SEQ_MISORDERED
	ErrSequencePos           Status = 10064 // NFS4ERR_SEQUENCE_POS
	ErrReqTooBig             Status = 10065 // NFS4ERR_REQ_TOO_BIG
	ErrRepTooBig             Status = 10066 // NFS4ERR_REP_TOO_BIG
	ErrRepTooBigToCache      Status = 10067 // NFS4ERR_REP_TOO_BIG_TO_CACHE
	ErrRetryUncachedRep      Status = 10068 // NFS4ERR_RETRY_UNCACHED_REP
	ErrUnsafeCompound        Status = 10069 // NFS4ERR_UNSAFE_COMPOUND
	ErrTooManyOps            Status = 10070 // NFS4ERR_TOO_MANY_OPS
	ErrOpNotInSession        Status = 10071 // NFS4ERR_OP_NOT_IN_SESSION
	ErrHashAlgUnsupp         Status = 10072 // NFS4ERR_HASH_ALG_UNSUPP
	ErrClientIDBusy          Status = 10074 // NFS4ERR_CLIENTID_BUSY
	ErrPNFSIOHole            Status = 10075 // NFS4ERR_PNFS_IO_HOLE
	ErrSeqFalseRetry         Status = 10076 // NFS4ERR_SEQ_FALSE_RETRY
	ErrBadHighSlot           Status = 10077 // NFS4ERR_BAD_HIGH_SLOT
	ErrDeadSession           Status = 10078 // NFS4ERR_DEADSESSION
	ErrEncrAlgUnsupp         Status = 10079 // NFS4ERR_ENCR_ALG_UNSUPP
	ErrPNFSNoLayout          Status = 10080 // NFS4ERR_PNFS_NO_LAYOUT
	ErrNotOnlyOp             Status = 10081 // NFS4ERR_NOT_ONLY_OP
	ErrWrongCred             Status = 10082 // NFS4ERR_WRONG_CRED
	ErrWrongType             Status = 10083 // NFS4ERR_WRONG_TYPE
	ErrDirDelegUnavail       Status = 10084 // NFS4ERR_DIRDELEG_UNAVAIL
	ErrRejectDeleg           Status = 10085 // NFS4ERR_REJECT_DELEG
	ErrReturnConflict        Status = 10086 // NFS4ERR_RETURNCONFLICT
	ErrDelegRevoked          Status = 10087 // NFS4ERR_DELEG_REVOKED
	ErrPartnerNotSupp        Status = 10088 // NFS4ERR_PARTNER_NOTSUPP
	ErrPartnerNoAuth         Status = 10089 // NFS4ERR_PARTNER_NO_AUTH
	ErrUnionNotSupp          Status = 10090 // NFS4ERR_UNION_NOTSUPP
	ErrOffloadDenied         Status = 10091 // NFS4ERR_OFFLOAD_DENIED
	ErrWrongLFS              Status = 10092 // NFS4ERR_WRONG_LFS
	ErrBadLabel              Status = 10093 // NFS4ERR_BADLABEL
	ErrOffloadNoReqs         Status = 10094 // NFS4ERR_OFFLOAD_NO_REQS
)

NFSv4 status codes (nfsstat4).

func (Status) Error

func (s Status) Error() string

func (Status) String

func (s Status) String() string

Directories

Path Synopsis
cmd
nfs4serve command
The nfs4serve command serves a local directory, or a demo in-memory filesystem, read-only over NFSv4.1.
The nfs4serve command serves a local directory, or a demo in-memory filesystem, read-only over NFSv4.1.
internal
nfs4client
Package nfs4client is a minimal NFSv4.1 client for testing the server at the protocol level.
Package nfs4client is a minimal NFSv4.1 client for testing the server at the protocol level.
oncrpc
Package oncrpc implements the parts of ONC RPC version 2 (RFC 5531) needed by an NFSv4 server: TCP record marking, call and reply headers, and the AUTH_NONE and AUTH_SYS credential flavors.
Package oncrpc implements the parts of ONC RPC version 2 (RFC 5531) needed by an NFSv4 server: TCP record marking, call and reply headers, and the AUTH_NONE and AUTH_SYS credential flavors.
testfs
Package testfs is a simple in-memory nfsv4.FS using raw filehandles (the 8-byte inode number), for tests.
Package testfs is a simple in-memory nfsv4.FS using raw filehandles (the 8-byte inode number), for tests.
xdr
Package xdr implements the subset of XDR (RFC 4506) encoding and decoding needed by ONC RPC and NFSv4.
Package xdr implements the subset of XDR (RFC 4506) encoding and decoding needed by ONC RPC and NFSv4.
Package memfs is an in-memory filesystem served with nodefs.
Package memfs is an in-memory filesystem served with nodefs.
Package nodefs adapts a tree of nodes to an nfsv4.FS, managing NFSv4 filehandles so that implementations don't have to think about them.
Package nodefs adapts a tree of nodes to an nfsv4.FS, managing NFSv4 filehandles so that implementations don't have to think about them.
Package osfs serves a local directory tree read-only, using nodefs.
Package osfs serves a local directory tree read-only, using nodefs.
tests
conformance/cmd/nfstestserve command
The nfstestserve command serves test data and a local control socket.
The nfstestserve command serves test data and a local control socket.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL