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,823 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package nfs4
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||||
)
|
||||
|
||||
// maxOps bounds the COMPOUND arrays on both encode and decode. The
|
||||
// protocol has no fixed limit; a request longer than this is not a client,
|
||||
// it is an attack.
|
||||
const maxOps = 1024
|
||||
|
||||
// maxTag bounds the COMPOUND tag.
|
||||
const maxTag = 128
|
||||
|
||||
// AppendCompoundArgs encodes the COMPOUND4args: the tag, the minor version
|
||||
// and the operation array, where each element is an encoded argop: its
|
||||
// operation number followed by its own arguments.
|
||||
func AppendCompoundArgs(b []byte, tag string, minor uint32, ops [][]byte) []byte {
|
||||
b = xdr.AppendString(b, tag)
|
||||
b = xdr.AppendUint32(b, minor)
|
||||
b = xdr.AppendUint32(b, uint32(len(ops)))
|
||||
for _, op := range ops {
|
||||
b = append(b, op...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// CompoundHeader is the head of a decoded COMPOUND4args: the tag, the minor
|
||||
// version and the operation count. The returned decoder is positioned at
|
||||
// the first argop, and the dispatcher reads each operation's own arguments
|
||||
// from it as it dispatches.
|
||||
type CompoundHeader struct {
|
||||
Tag string
|
||||
Minor uint32
|
||||
OpCount uint32
|
||||
}
|
||||
|
||||
// DecodeCompoundArgs splits COMPOUND4args into its header and a decoder
|
||||
// over the argops.
|
||||
func DecodeCompoundArgs(payload []byte) (CompoundHeader, *xdr.Decoder, error) {
|
||||
d := xdr.NewDecoder(payload)
|
||||
var h CompoundHeader
|
||||
var err error
|
||||
if h.Tag, err = d.String(); err != nil {
|
||||
return h, nil, err
|
||||
}
|
||||
if len(h.Tag) > maxTag {
|
||||
return h, nil, fmt.Errorf("nfs4: tag of %d bytes exceeds %d", len(h.Tag), maxTag)
|
||||
}
|
||||
if h.Minor, 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
|
||||
}
|
||||
|
||||
// AppendCompoundRes encodes the COMPOUND4res: the top level status, the
|
||||
// echoed tag and the result array. On success the results hold one entry
|
||||
// per executed operation; on failure they hold the results up to and
|
||||
// including the operation that failed, and the top level status carries its
|
||||
// status.
|
||||
func AppendCompoundRes(b []byte, status uint32, tag string, results [][]byte) []byte {
|
||||
b = xdr.AppendUint32(b, status)
|
||||
b = xdr.AppendString(b, tag)
|
||||
b = xdr.AppendUint32(b, uint32(len(results)))
|
||||
for _, op := range results {
|
||||
b = append(b, op...)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AppendOpHeader appends the head of one nfs_resop4: the operation number
|
||||
// and its status. A successful result appends its payload afterwards; a
|
||||
// failed one is complete as it stands.
|
||||
func AppendOpHeader(b []byte, op, status uint32) []byte {
|
||||
b = xdr.AppendUint32(b, op)
|
||||
return xdr.AppendUint32(b, status)
|
||||
}
|
||||
|
||||
// A ResOp is one decoded result operation: its number, its status, and the
|
||||
// bytes that follow the status. Body is meaningful only while the status is
|
||||
// NFS4ERR_OK, and its shape depends on the operation.
|
||||
type ResOp struct {
|
||||
Op uint32
|
||||
Status uint32
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// A CompoundRes is the decoded COMPOUND4res.
|
||||
type CompoundRes struct {
|
||||
Status uint32
|
||||
Tag string
|
||||
Ops []ResOp
|
||||
}
|
||||
|
||||
// DecodeCompoundResBodies decodes a COMPOUND4res and, for every operation,
|
||||
// the bytes between its status and the next operation number. A body is
|
||||
// empty when the operation carried no payload or when it failed. The
|
||||
// bodies are not length prefixed on the wire: the length of each follows
|
||||
// from its operation number, so the decoder measures each body as it goes.
|
||||
func DecodeCompoundResBodies(payload []byte) (CompoundRes, [][]byte, error) {
|
||||
d := xdr.NewDecoder(payload)
|
||||
var res CompoundRes
|
||||
var err error
|
||||
if res.Status, err = d.Uint32(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if res.Tag, err = d.String(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
count, err := d.Uint32()
|
||||
if err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if count > maxOps {
|
||||
return res, nil, fmt.Errorf("nfs4: %d results exceeds %d", count, maxOps)
|
||||
}
|
||||
|
||||
var ops []ResOp
|
||||
var bodies [][]byte
|
||||
for range count {
|
||||
var op ResOp
|
||||
if op.Op, err = d.Uint32(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if op.Status, err = d.Uint32(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
start := len(payload) - d.Remaining()
|
||||
n, err := resBodyLen(op.Op, op.Status, payload[start:])
|
||||
if err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if n < 0 || start+n > len(payload) {
|
||||
return res, nil, fmt.Errorf("nfs4: result body of %d bytes overruns the payload", n)
|
||||
}
|
||||
op.Body = payload[start : start+n]
|
||||
if _, err := d.Raw(n); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
ops = append(ops, op)
|
||||
bodies = append(bodies, op.Body)
|
||||
}
|
||||
if d.Remaining() > 0 {
|
||||
return res, nil, fmt.Errorf("nfs4: %d bytes trail the declared operations", d.Remaining())
|
||||
}
|
||||
res.Ops = ops
|
||||
return res, bodies, nil
|
||||
}
|
||||
|
||||
// resBodyLen measures how many bytes one result body occupies. A failed
|
||||
// operation carries no body. An operation this build cannot measure, with a
|
||||
// successful status, is a response the client has no business receiving.
|
||||
func resBodyLen(op, status uint32, body []byte) (int, error) {
|
||||
// Every failed operation carries an empty body, except LOCKT: its
|
||||
// DENIED answer names the conflicting lock.
|
||||
if status != ErrOK && op != OpLockt {
|
||||
return 0, nil
|
||||
}
|
||||
d := xdr.NewDecoder(body)
|
||||
switch op {
|
||||
case OpPutfh, OpPutRootfh, OpLookup, OpLookupp, OpSavefh, OpRestorefh:
|
||||
return 0, nil
|
||||
case OpGetfh:
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpGetattr:
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpAccess:
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpCreate:
|
||||
// changeinfo4 (atomic, before, after) then the bitmap of the
|
||||
// attributes set, RFC 8881 section 18.4.2.
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpWrite:
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Raw(8); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpReadlink:
|
||||
// The link text, length prefixed.
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(body) - d.Remaining(), nil
|
||||
case OpCommit:
|
||||
// The write verifier.
|
||||
return 8, nil
|
||||
case OpSecinfo, OpSecinfoNoName:
|
||||
// An array of secinfo4 unions: the flavour, and the
|
||||
// rpcsec_gss_info body only on the RPCSEC_GSS branch.
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n > maxOps {
|
||||
return 0, ErrUnknownOp
|
||||
}
|
||||
for range n {
|
||||
flavor, ferr := d.Uint32()
|
||||
if ferr != nil {
|
||||
return 0, ferr
|
||||
}
|
||||
if flavor == SecFlavorGSS {
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(body) - d.Remaining(), nil
|
||||
case OpRemove, OpOpenDowngrade:
|
||||
// change_info4: an atomic flag and two 64 bit counters.
|
||||
return 20, nil
|
||||
case OpDestroyClientID, OpReclaimComplete, OpPutPubfh,
|
||||
OpReleaseLockOwner, OpDelegReturn, OpFreeStateid, OpBackchannelCtl,
|
||||
OpVerify, OpNverify:
|
||||
return 0, nil
|
||||
case OpBindConnToSession:
|
||||
// sessionid and the granted direction.
|
||||
return 20, nil
|
||||
case OpTestStateid:
|
||||
// One status per stateid the arguments named.
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n > maxOps {
|
||||
return 0, ErrUnknownOp
|
||||
}
|
||||
return 4 + 4*int(n), nil
|
||||
case OpRename:
|
||||
// Two change_info4 values, one per directory.
|
||||
return 40, nil
|
||||
case OpLink:
|
||||
// change_info4 of the target directory.
|
||||
return 20, nil
|
||||
case OpSequence:
|
||||
// sessionid and the five words of SEQUENCE4resok, RFC 7863.
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range 5 {
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 36, nil
|
||||
case OpDestroySession:
|
||||
return 0, nil
|
||||
case OpExchangeID:
|
||||
// clientid, sequence, flags, state protect how, eir_server_owner
|
||||
// (uint64 minor id, opaque major id), eir_server_scope and the
|
||||
// impl_id array, RFC 8881 section 18.35.
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range 3 {
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range 2 { // so_major_id then server_scope
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range n {
|
||||
for range 2 { // domain and name
|
||||
if _, err := d.String(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if _, err := d.Raw(12); err != nil { // nfstime4
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return len(body) - d.Remaining(), nil
|
||||
case OpCreateSession:
|
||||
// sessionid, sequence, flags and two channel attrs.
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range 2 {
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range 5 {
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
count, cerr := d.Uint32()
|
||||
if cerr != nil {
|
||||
return 0, cerr
|
||||
}
|
||||
if count != 0xffffffff {
|
||||
for range count {
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return len(body) - d.Remaining(), nil
|
||||
case OpOpen:
|
||||
// stateid, change_info4, rflags, attrset and the delegation union.
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
delegType, derr := d.Uint32()
|
||||
if derr != nil {
|
||||
return 0, derr
|
||||
}
|
||||
switch delegType {
|
||||
case OpenDelegRead:
|
||||
// stateid, recall flag and the permissions ace.
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := walkACE(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpenDelegWrite:
|
||||
// stateid, recall flag, the space limit union and the ace.
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := walkSpaceLimit(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := walkACE(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return len(body) - d.Remaining(), nil
|
||||
case OpClose:
|
||||
return 16, nil
|
||||
case OpLock, OpLocku:
|
||||
// The stateid.
|
||||
return 16, nil
|
||||
case OpLockt:
|
||||
return resBodyLenLockt(status, body)
|
||||
case OpSetattr:
|
||||
// attrsset: only a bitmap4.
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpRead:
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpReaddir:
|
||||
if _, err := d.Raw(8); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for {
|
||||
more, err := d.Bool()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.String(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpLayoutGet:
|
||||
// The layout stateid, the return on close flag and the layout
|
||||
// array: offset, length, IO mode, type and body per segment.
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range n {
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
case OpLayoutCommit:
|
||||
// The newsize4 union: the changed flag and the size.
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpLayoutReturn:
|
||||
// The layoutreturn_stateid4 union: the returned flag and the
|
||||
// stateid.
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpSeek:
|
||||
// The end of file flag and the offset.
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpAllocate, OpDeallocate, OpClone, OpLayoutError, OpLayoutStats, OpOffloadCancel:
|
||||
return 0, nil
|
||||
case OpIoAdvise:
|
||||
// The accepted hints bitmap4.
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpCopy:
|
||||
// write_response4 then the two requirement flags.
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n == 1 {
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Raw(8); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpCopyNotify:
|
||||
// lease time, empty owner confirm, stateid, source list.
|
||||
if _, err := d.Int64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range n {
|
||||
kind, kerr := d.Uint32()
|
||||
if kerr != nil {
|
||||
return 0, kerr
|
||||
}
|
||||
switch kind {
|
||||
case 1, 2:
|
||||
if _, err := d.String(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
if _, err := d.String(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.String(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
}
|
||||
case OpOffloadStatus:
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpReadPlus:
|
||||
// eof flag and the content array.
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range n {
|
||||
kind, kerr := d.Uint32()
|
||||
if kerr != nil {
|
||||
return 0, kerr
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if kind == uint32(ContentData) {
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
} else if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
case OpWriteSame:
|
||||
// write_response4 without a callback arm.
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Raw(8); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpGetDirDelegation:
|
||||
// The non fatal union: the status, then the resok body or the
|
||||
// will signal flag.
|
||||
gdd, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if gdd == GddOK {
|
||||
if _, err := d.Raw(24); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range 3 {
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
} else if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpOpenattr:
|
||||
return 0, nil
|
||||
case OpGetDeviceList:
|
||||
// The cookie, its verifier, the device id array and the eof flag.
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Raw(8); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Raw(int(n) * 16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpGetxattr:
|
||||
// The single opaque value.
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpSetxattr, OpRemovexattr:
|
||||
return 0, nil
|
||||
case OpListxattr:
|
||||
// The cookie of the last name, the names and the end of list
|
||||
// flag.
|
||||
if _, err := d.Uint64(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := d.Uint32()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range n {
|
||||
if _, err := d.String(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if _, err := d.Bool(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
case OpGetDeviceInfo:
|
||||
// The address type, the address body and the notification bitmap.
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := d.VarOpaque(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, err := ReadBitmap(d); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
default:
|
||||
return 0, ErrUnknownOp
|
||||
}
|
||||
return len(body) - d.Remaining(), nil
|
||||
}
|
||||
|
||||
// walkACE walks one nfsace4 of a result body.
|
||||
func walkACE(d *xdr.Decoder) error {
|
||||
for range 3 {
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := d.VarOpaque()
|
||||
return err
|
||||
}
|
||||
|
||||
// walkSpaceLimit walks one nfs_space_limit4 union of a result body.
|
||||
func walkSpaceLimit(d *xdr.Decoder) error {
|
||||
limitBy, err := d.Uint32()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch limitBy {
|
||||
case LimitBySize:
|
||||
_, err = d.Uint64()
|
||||
return err
|
||||
case 2: // NFS_LIMIT_BLOCKS
|
||||
if _, err = d.Uint32(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = d.Uint32()
|
||||
return err
|
||||
default:
|
||||
return ErrBadDeleg
|
||||
}
|
||||
}
|
||||
|
||||
// ErrUnknownOp marks a successful result whose shape this build cannot
|
||||
// measure: the client and the server disagree on what was sent.
|
||||
var ErrUnknownOp = errors.New("nfs4: unknown operation result")
|
||||
|
||||
// resBodyLenCB measures one CB result body. CB operation numbers share
|
||||
// values with the fore channel, so the two families are never mixed in
|
||||
// one measurement.
|
||||
func resBodyLenCB(op, status uint32, body []byte) (int, error) {
|
||||
if status != ErrOK {
|
||||
return 0, nil
|
||||
}
|
||||
d := xdr.NewDecoder(body)
|
||||
switch op {
|
||||
case OpCBSequence:
|
||||
// The session id and the four words of CB_SEQUENCE4resok,
|
||||
// RFC 7863.
|
||||
if _, err := d.Raw(16); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
for range 4 {
|
||||
if _, err := d.Uint32(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return 32, nil
|
||||
case OpCBRecall, OpCBNotify, OpCBNotifyLock:
|
||||
return 0, nil
|
||||
default:
|
||||
return 0, nil
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeCompoundResBodiesCB decodes a CB_COMPOUND reply: the same walk as
|
||||
// the fore channel variant, with the callback operation shapes.
|
||||
func DecodeCompoundResBodiesCB(payload []byte) (CompoundRes, [][]byte, error) {
|
||||
d := xdr.NewDecoder(payload)
|
||||
var res CompoundRes
|
||||
var err error
|
||||
if res.Status, err = d.Uint32(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if res.Tag, err = d.String(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
count, err := d.Uint32()
|
||||
if err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if count > maxOps {
|
||||
return res, nil, fmt.Errorf("nfs4: %d results exceeds %d", count, maxOps)
|
||||
}
|
||||
var ops []ResOp
|
||||
var bodies [][]byte
|
||||
for range count {
|
||||
var op ResOp
|
||||
if op.Op, err = d.Uint32(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if op.Status, err = d.Uint32(); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
start := len(payload) - d.Remaining()
|
||||
n, err := resBodyLenCB(op.Op, op.Status, payload[start:])
|
||||
if err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
if n < 0 || start+n > len(payload) {
|
||||
return res, nil, fmt.Errorf("nfs4: result body of %d bytes overruns the payload", n)
|
||||
}
|
||||
op.Body = payload[start : start+n]
|
||||
if _, err := d.Raw(n); err != nil {
|
||||
return res, nil, err
|
||||
}
|
||||
ops = append(ops, op)
|
||||
bodies = append(bodies, op.Body)
|
||||
}
|
||||
if d.Remaining() > 0 {
|
||||
return res, nil, fmt.Errorf("nfs4: %d bytes trail the declared operations", d.Remaining())
|
||||
}
|
||||
res.Ops = ops
|
||||
return res, bodies, nil
|
||||
}
|
||||
Reference in New Issue
Block a user