feat: full NFSv4.2 server and client in pure Go
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-21 18:51:17 +02:00
commit a9b8039ef7
153 changed files with 34403 additions and 0 deletions
+205
View File
@@ -0,0 +1,205 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
// The callback channel of RFC 8881: the CB_COMPOUND procedure the server
// calls on the connection the client opened, CB_SEQUENCE driving the back
// channel slot table, and CB_RECALL recalling a delegation.
package nfs4
import (
"fmt"
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
)
// Callback operation numbers, RFC 8881 section 15.5.
const (
OpCBGetattr = 3
OpCBRecall = 4
OpCBSequence = 11
OpCBPushDeleg = 7
OpCBNotify = 6
OpCBNotifyLock = 13
)
// The callback program rides on the same minor version and the COMPOUND
// shape as the fore channel.
const (
CBDefaultProgram = 0x40000000
)
// AppendCBCompoundArgs encodes the CB_COMPOUND4args: the tag, the minor
// version, the callback identifier and the operations. The callback
// identifier is the slot the server reserved for this client when the
// back channel was negotiated; with sessions it stays zero.
func AppendCBCompoundArgs(b []byte, tag string, minorversion, callbackIdent uint32, ops [][]byte) []byte {
b = xdr.AppendString(b, tag)
b = xdr.AppendUint32(b, minorversion)
b = xdr.AppendUint32(b, callbackIdent)
b = xdr.AppendUint32(b, uint32(len(ops)))
for _, op := range ops {
b = append(b, op...)
}
return b
}
// CBCompoundHeader is the head of a decoded CB_COMPOUND4args.
type CBCompoundHeader struct {
Tag string
Minor uint32
CallbackIdent uint32
OpCount uint32
}
// DecodeCBCompoundArgs splits CB_COMPOUND4args into its header and a
// decoder positioned at the first argop.
func DecodeCBCompoundArgs(payload []byte) (CBCompoundHeader, *xdr.Decoder, error) {
d := xdr.NewDecoder(payload)
var h CBCompoundHeader
var err error
if h.Tag, err = d.String(); err != nil {
return h, nil, err
}
if h.Minor, err = d.Uint32(); err != nil {
return h, nil, err
}
if h.CallbackIdent, err = d.Uint32(); err != nil {
return h, nil, err
}
if h.OpCount, err = d.Uint32(); err != nil {
return h, nil, err
}
if h.OpCount > maxOps {
return h, nil, fmt.Errorf("nfs4: %d operations exceeds %d", h.OpCount, maxOps)
}
return h, d, nil
}
// AppendCBCompoundRes encodes a CB_COMPOUND4res: the top level status, the
// echoed tag and the result array.
func AppendCBCompoundRes(b []byte, status uint32, tag string, results [][]byte) []byte {
return AppendCompoundRes(b, status, tag, results)
}
// AppendCBSequenceArgs encodes the CB_SEQUENCE argop.
func AppendCBSequenceArgs(b []byte, id SessionID, sequence, slot, highestSlot uint32, cacheThis bool) []byte {
b = xdr.AppendUint32(b, OpCBSequence)
return AppendSequenceArgsTail(b, id, sequence, slot, highestSlot, cacheThis)
}
// AppendSequenceArgsTail appends the shared SEQUENCE argument fields: the
// session id, the sequence, the slot ids and the caching flag.
func AppendSequenceArgsTail(b []byte, id SessionID, sequence, slot, highestSlot uint32, cacheThis bool) []byte {
b = append(b, id[:]...)
b = xdr.AppendUint32(b, sequence)
b = xdr.AppendUint32(b, slot)
b = xdr.AppendUint32(b, highestSlot)
return xdr.AppendBool(b, cacheThis)
}
// A CBSequenceArgs is the decoded CB_SEQUENCE4args.
type CBSequenceArgs struct {
SessionID SessionID
Sequence uint32
Slot uint32
HighestSlot uint32
CacheThis bool
}
// DecodeCBSequenceArgs decodes the CB_SEQUENCE4args from the decoder,
// which is positioned after the operation number.
func DecodeCBSequenceArgs(d *xdr.Decoder) (CBSequenceArgs, error) {
var a CBSequenceArgs
raw, err := d.Raw(16)
if err != nil {
return a, err
}
copy(a.SessionID[:], raw)
var err2 error
if a.Sequence, err2 = d.Uint32(); err2 != nil {
return a, err2
}
if a.Slot, err2 = d.Uint32(); err2 != nil {
return a, err2
}
if a.HighestSlot, err2 = d.Uint32(); err2 != nil {
return a, err2
}
if a.CacheThis, err2 = d.Bool(); err2 != nil {
return a, err2
}
return a, nil
}
// AppendCBSequenceRes encodes the successful CB_SEQUENCE4resok of
// RFC 7863: the session id, the echoed identifiers and the target
// highest slot. No further word follows.
func AppendCBSequenceRes(b []byte, id SessionID, sequence, slot, highestSlot uint32) []byte {
b = append(b, id[:]...)
b = xdr.AppendUint32(b, sequence)
b = xdr.AppendUint32(b, slot)
b = xdr.AppendUint32(b, highestSlot)
return xdr.AppendUint32(b, highestSlot) // cbsr_target_highest_slotid
}
// DecodeCBSequenceRes decodes the successful CB_SEQUENCE4resok.
func DecodeCBSequenceRes(d *xdr.Decoder) (id SessionID, sequence, slot, highestSlot, targetHighestSlot uint32, err error) {
raw, rerr := d.Raw(16)
if rerr != nil {
return id, 0, 0, 0, 0, rerr
}
copy(id[:], raw)
if sequence, err = d.Uint32(); err != nil {
return
}
if slot, err = d.Uint32(); err != nil {
return
}
if highestSlot, err = d.Uint32(); err != nil {
return
}
targetHighestSlot, err = d.Uint32()
return
}
// AppendCBRecallArgs encodes the CB_RECALL argop: the delegation stateid,
// whether to truncate, and the file handle the delegation names.
func AppendCBRecallArgs(b []byte, stateid Stateid, truncate bool, fh []byte) []byte {
b = xdr.AppendUint32(b, OpCBRecall)
b = stateid.Append(b)
b = xdr.AppendBool(b, truncate)
return xdr.AppendVarOpaque(b, fh)
}
// A CBRecallArgs is the decoded CB_RECALL argop.
type CBRecallArgs struct {
Stateid Stateid
Truncate bool
FH []byte
}
// DecodeCBRecallArgs decodes the CB_RECALL args from the decoder, which is
// positioned after the operation number.
func DecodeCBRecallArgs(d *xdr.Decoder) (CBRecallArgs, error) {
var a CBRecallArgs
raw, err := d.Raw(16)
if err != nil {
return a, err
}
copy(a.Stateid[:], raw)
var err2 error
if a.Truncate, err2 = d.Bool(); err2 != nil {
return a, err2
}
a.FH, err2 = d.VarOpaque()
return a, err2
}
// AppendCBGetattrArgs encodes the CB_GETATTR argop: the handle and the
// requested attribute bitmap.
func AppendCBGetattrArgs(b []byte, fh []byte, request Bitmap) []byte {
b = xdr.AppendUint32(b, OpCBGetattr)
b = xdr.AppendVarOpaque(b, fh)
return request.AppendTo(b)
}