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
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:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user