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
1329 lines
37 KiB
Go
1329 lines
37 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package nfs4
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
|
)
|
|
|
|
// ErrUnknownAttr marks an attribute number in a response that this build
|
|
// cannot decode, which means the two sides do not agree on the attribute
|
|
// table: a bug, not a protocol condition.
|
|
var ErrUnknownAttr = errors.New("nfs4: unknown attribute in response")
|
|
|
|
// Argument builders. Each appends one complete argop: the operation number
|
|
// followed by its arguments. The client joins them into a COMPOUND.
|
|
|
|
// AppendPutRootfh appends the PUTROOTFH argop.
|
|
func AppendPutRootfh(b []byte) []byte {
|
|
return xdr.AppendUint32(b, OpPutRootfh)
|
|
}
|
|
|
|
// AppendSavefh appends the SAVEFH argop.
|
|
func AppendSavefh(b []byte) []byte {
|
|
return xdr.AppendUint32(b, OpSavefh)
|
|
}
|
|
|
|
// AppendRestorefh appends the RESTOREFH argop.
|
|
func AppendRestorefh(b []byte) []byte {
|
|
return xdr.AppendUint32(b, OpRestorefh)
|
|
}
|
|
|
|
// AppendPutfh appends the PUTFH argop with the handle.
|
|
func AppendPutfh(b []byte, fh []byte) []byte {
|
|
b = xdr.AppendUint32(b, OpPutfh)
|
|
return xdr.AppendVarOpaque(b, fh)
|
|
}
|
|
|
|
// AppendGetfh appends the GETFH argop.
|
|
func AppendGetfh(b []byte) []byte {
|
|
return xdr.AppendUint32(b, OpGetfh)
|
|
}
|
|
|
|
// AppendLookup appends the LOOKUP argop with the component name.
|
|
func AppendLookup(b []byte, name string) []byte {
|
|
b = xdr.AppendUint32(b, OpLookup)
|
|
return xdr.AppendString(b, name)
|
|
}
|
|
|
|
// AppendGetattr appends the GETATTR argop asking for the attributes.
|
|
func AppendGetattr(b []byte, request Bitmap) []byte {
|
|
b = xdr.AppendUint32(b, OpGetattr)
|
|
return request.AppendTo(b)
|
|
}
|
|
|
|
// AppendAccess appends the ACCESS argop asking for the mask bits.
|
|
func AppendAccess(b []byte, mask uint32) []byte {
|
|
b = xdr.AppendUint32(b, OpAccess)
|
|
return xdr.AppendUint32(b, mask)
|
|
}
|
|
|
|
// AppendRead appends the READ argop: the anonymous stateid, the offset and
|
|
// the count.
|
|
func AppendRead(b []byte, stateid Stateid, off uint64, count uint32) []byte {
|
|
b = xdr.AppendUint32(b, OpRead)
|
|
b = stateid.Append(b)
|
|
b = xdr.AppendUint64(b, off)
|
|
return xdr.AppendUint32(b, count)
|
|
}
|
|
|
|
// AppendReaddir appends the READDIR argop: the cookie, the verifier of the
|
|
// listing the cookie belongs to, the response size budgets and the
|
|
// attributes wanted per entry.
|
|
func AppendReaddir(b []byte, cookie uint64, verifer [8]byte, dircount, maxcount uint32, request Bitmap) []byte {
|
|
b = xdr.AppendUint32(b, OpReaddir)
|
|
b = xdr.AppendUint64(b, cookie)
|
|
b = append(b, verifer[:]...)
|
|
b = xdr.AppendUint32(b, dircount)
|
|
b = xdr.AppendUint32(b, maxcount)
|
|
return request.AppendTo(b)
|
|
}
|
|
|
|
// AppendCreateArgs appends the CREATE argop. Regular files are created by
|
|
// OPEN and never by CREATE: kind is one of NF4Dir, NF4Lnk, NF4Fifo,
|
|
// NF4Sock, NF4Blk or NF4Chr, and linkdata and the device numbers apply
|
|
// only to the kinds that carry them. The arguments end in the object
|
|
// attributes, a full fattr4 of bitmap and list, RFC 8881 section 18.4;
|
|
// a zero perm encodes an empty attribute set.
|
|
func AppendCreateArgs(b []byte, kind uint32, name, linkdata string, major, minor uint32, perm uint32) []byte {
|
|
b = xdr.AppendUint32(b, OpCreate)
|
|
b = xdr.AppendUint32(b, kind)
|
|
switch kind {
|
|
case NF4Lnk:
|
|
b = xdr.AppendString(b, linkdata)
|
|
case NF4Blk, NF4Chr:
|
|
b = xdr.AppendUint32(b, major)
|
|
b = xdr.AppendUint32(b, minor)
|
|
}
|
|
b = xdr.AppendString(b, name)
|
|
if perm != 0 {
|
|
b = AppendFattr(b, OfBits(AttrMode), Attrs{Mode: perm})
|
|
} else {
|
|
b = AppendFattr(b, Bitmap{}, Attrs{})
|
|
}
|
|
return b
|
|
}
|
|
|
|
// DecodeCreateArgs decodes a CREATE argop body: everything after the
|
|
// operation number. The arguments end in the object attributes, a full
|
|
// fattr4 of bitmap and list, RFC 8881 section 18.4.
|
|
func DecodeCreateArgs(body []byte) (kind uint32, name, linkdata string, major, minor uint32, perm uint32, err error) {
|
|
d := xdr.NewDecoder(body)
|
|
if kind, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
switch kind {
|
|
case NF4Lnk:
|
|
if linkdata, err = d.String(); err != nil {
|
|
return
|
|
}
|
|
case NF4Blk, NF4Chr:
|
|
if major, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
if minor, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
}
|
|
if name, err = d.String(); err != nil {
|
|
return
|
|
}
|
|
request, err := ReadBitmap(d)
|
|
if err != nil {
|
|
return
|
|
}
|
|
blob, err := d.VarOpaque()
|
|
if err != nil {
|
|
return
|
|
}
|
|
attrs, err := DecodeFattrAttrs(blob, request)
|
|
if err != nil {
|
|
return
|
|
}
|
|
perm = attrs.Mode
|
|
return kind, name, linkdata, major, minor, perm, nil
|
|
}
|
|
|
|
// DecodeFattrAttrs decodes an fattr4 blob against its bitmap into the
|
|
// attribute values it carries. It is the dispatcher's view of what a
|
|
// client sent.
|
|
func DecodeFattrAttrs(blob []byte, response Bitmap) (Attrs, error) {
|
|
return decodeFattrBlob(blob, response)
|
|
}
|
|
|
|
// AppendWriteArgs appends the WRITE argop: the stateid, the offset, the
|
|
// stability the client asks for and the data.
|
|
func AppendWriteArgs(b []byte, stateid Stateid, off uint64, stable uint32, data []byte) []byte {
|
|
b = xdr.AppendUint32(b, OpWrite)
|
|
b = stateid.Append(b)
|
|
b = xdr.AppendUint64(b, off)
|
|
b = xdr.AppendUint32(b, stable)
|
|
return xdr.AppendVarOpaque(b, data)
|
|
}
|
|
|
|
// DecodeWriteArgs decodes a WRITE argop body.
|
|
func DecodeWriteArgs(body []byte) (stateid Stateid, off uint64, stable uint32, data []byte, err error) {
|
|
d := xdr.NewDecoder(body)
|
|
raw, err := d.Raw(16)
|
|
if err != nil {
|
|
return
|
|
}
|
|
copy(stateid[:], raw)
|
|
if off, err = d.Uint64(); err != nil {
|
|
return
|
|
}
|
|
if stable, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
data, err = d.VarOpaque()
|
|
return
|
|
}
|
|
|
|
// AppendWriteRes appends the result of a successful WRITE: how many bytes
|
|
// landed, at what stability, and the server verifier that pairs with it.
|
|
func AppendWriteRes(b []byte, count, committed uint32, verf [8]byte) []byte {
|
|
b = xdr.AppendUint32(b, count)
|
|
b = xdr.AppendUint32(b, committed)
|
|
return append(b, verf[:]...)
|
|
}
|
|
|
|
// DecodeWriteRes decodes a successful WRITE result.
|
|
func DecodeWriteRes(body []byte) (count, committed uint32, verf [8]byte, err error) {
|
|
d := xdr.NewDecoder(body)
|
|
if count, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
if committed, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
raw, err := d.Raw(8)
|
|
if err != nil {
|
|
return
|
|
}
|
|
copy(verf[:], raw)
|
|
return
|
|
}
|
|
|
|
// AppendCreateRes appends the result of a successful CREATE: the change
|
|
// information and the bitmap of the attributes set. This server reports
|
|
// the change information as not atomic and names no attributes, both of
|
|
// which tell the client to re-read them, which it prefers over pretending.
|
|
func AppendCreateRes(b []byte) []byte {
|
|
b = xdr.AppendBool(b, false) // atomic
|
|
b = xdr.AppendUint64(b, 0) // before
|
|
b = xdr.AppendUint64(b, 0) // after
|
|
return Bitmap{}.AppendTo(b)
|
|
}
|
|
|
|
// AppendRemoveArgs appends the REMOVE argop: the name relative to the
|
|
// current file handle.
|
|
func AppendRemoveArgs(b []byte, name string) []byte {
|
|
b = xdr.AppendUint32(b, OpRemove)
|
|
return xdr.AppendString(b, name)
|
|
}
|
|
|
|
// AppendChangeInfo appends a change_info4 that reports the change as not
|
|
// atomic, telling the client to re-read rather than cache. It is the whole
|
|
// REMOVE result and the first half of the RENAME result.
|
|
func AppendChangeInfo(b []byte) []byte {
|
|
b = xdr.AppendBool(b, false) // atomic
|
|
b = xdr.AppendUint64(b, 0) // before
|
|
return xdr.AppendUint64(b, 0) // after
|
|
}
|
|
|
|
// AppendRemoveRes appends the result of a successful REMOVE.
|
|
func AppendRemoveRes(b []byte) []byte {
|
|
return AppendChangeInfo(b)
|
|
}
|
|
|
|
// AppendRenameArgs appends the RENAME argop. The old name is resolved
|
|
// against the saved file handle and the new one against the current file
|
|
// handle, which is what SAVEFH sets up.
|
|
func AppendRenameArgs(b []byte, oldName, newName string) []byte {
|
|
b = xdr.AppendUint32(b, OpRename)
|
|
b = xdr.AppendString(b, oldName)
|
|
return xdr.AppendString(b, newName)
|
|
}
|
|
|
|
// AppendRenameRes appends the result of a successful RENAME: the change
|
|
// information of the source directory and of the target directory.
|
|
func AppendRenameRes(b []byte) []byte {
|
|
b = AppendChangeInfo(b)
|
|
return AppendChangeInfo(b)
|
|
}
|
|
|
|
// Result payload builders. Each appends the bytes that follow the OK
|
|
// status of one nfs_resop4.
|
|
|
|
// AppendGetfhRes appends the handle a successful GETFH returns.
|
|
func AppendGetfhRes(b []byte, fh []byte) []byte {
|
|
return xdr.AppendVarOpaque(b, fh)
|
|
}
|
|
|
|
// AppendGetattrRes appends the fattr4 a successful GETATTR returns.
|
|
func AppendGetattrRes(b []byte, request Bitmap, a Attrs) []byte {
|
|
return AppendFattr(b, request, a)
|
|
}
|
|
|
|
// AppendAccessRes appends the two masks a successful ACCESS returns: what
|
|
// the server supports and what the caller was granted.
|
|
func AppendAccessRes(b []byte, supported, access uint32) []byte {
|
|
b = xdr.AppendUint32(b, supported)
|
|
return xdr.AppendUint32(b, access)
|
|
}
|
|
|
|
// AppendReadRes appends the result of a successful READ: whether the file
|
|
// ended and the bytes read.
|
|
func AppendReadRes(b []byte, eof bool, data []byte) []byte {
|
|
b = xdr.AppendBool(b, eof)
|
|
return xdr.AppendVarOpaque(b, data)
|
|
}
|
|
|
|
// A DirEntryRes is one entry of a READDIR result.
|
|
type DirEntryRes struct {
|
|
Cookie uint64
|
|
Name string
|
|
Attrs Attrs
|
|
}
|
|
|
|
// AppendReadDirRes appends the result of a successful READDIR: the
|
|
// verifier, the entries and the end of listing flag.
|
|
func AppendReadDirRes(b []byte, verifer [8]byte, entries []DirEntryRes, request Bitmap, eof bool) []byte {
|
|
b = append(b, verifer[:]...)
|
|
for _, e := range entries {
|
|
b = xdr.AppendBool(b, true) // value_follows
|
|
b = xdr.AppendUint64(b, e.Cookie)
|
|
b = xdr.AppendString(b, e.Name)
|
|
b = AppendFattr(b, request, e.Attrs)
|
|
}
|
|
b = xdr.AppendBool(b, false) // value_follows: end of entries
|
|
return xdr.AppendBool(b, eof)
|
|
}
|
|
|
|
// Result decoders for the client.
|
|
|
|
// DecodeGetattrBody decodes an fattr4 into the attribute values it
|
|
// carries.
|
|
func DecodeGetattrBody(body []byte) (Bitmap, Attrs, error) {
|
|
d := xdr.NewDecoder(body)
|
|
response, err := ReadBitmap(d)
|
|
if err != nil {
|
|
return nil, Attrs{}, err
|
|
}
|
|
blob, err := d.VarOpaque()
|
|
if err != nil {
|
|
return nil, Attrs{}, err
|
|
}
|
|
attrs, err := decodeFattrBlob(blob, response)
|
|
return response, attrs, err
|
|
}
|
|
|
|
func decodeFattrBlob(blob []byte, response Bitmap) (Attrs, error) {
|
|
d := xdr.NewDecoder(blob)
|
|
var a Attrs
|
|
for _, n := range bitsOf(response) {
|
|
var err error
|
|
switch n {
|
|
case AttrSupportedAttrs:
|
|
if _, err = ReadBitmap(d); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrType:
|
|
if a.Type, err = d.Uint32(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrFHExpireType:
|
|
if a.FHExpireType, err = d.Uint32(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrChange:
|
|
if a.Change, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrSize:
|
|
if a.Size, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrLinkSupport:
|
|
if a.LinkSupport, err = d.Bool(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrSymlinkSupport:
|
|
if a.SymlinkSupport, err = d.Bool(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrNamedAttr:
|
|
if a.NamedAttr, err = d.Bool(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrFSID:
|
|
if a.FSID[0], err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
if a.FSID[1], err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrUniqueHandles:
|
|
if a.UniqueHandles, err = d.Bool(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrFileHandle:
|
|
if a.FileHandle, err = d.VarOpaque(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrFileID:
|
|
if a.FileID, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrMaxName:
|
|
if a.Limits.MaxName, err = d.Uint32(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrMaxRead:
|
|
if a.Limits.MaxRead, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrMaxWrite:
|
|
if a.Limits.MaxWrite, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrMode:
|
|
if a.Mode, err = d.Uint32(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrNumlinks:
|
|
if a.Numlinks, err = d.Uint32(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrOwner:
|
|
if a.Owner, err = d.String(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrOwnerGroup:
|
|
if a.OwnerGroup, err = d.String(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrSpaceUsed:
|
|
if a.SpaceUsed, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrTimeAccess:
|
|
if a.TimeAccess, err = readNfsTime(d); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrTimeMetadata:
|
|
if a.TimeMetadata, err = readNfsTime(d); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrTimeModify:
|
|
if a.TimeModify, err = readNfsTime(d); err != nil {
|
|
return a, err
|
|
}
|
|
case AttrMountedOnFileID:
|
|
if a.MountedOnFileID, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
default:
|
|
return a, ErrUnknownAttr
|
|
}
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func readNfsTime(d *xdr.Decoder) (NfsTime, error) {
|
|
sec, err := d.Int64()
|
|
if err != nil {
|
|
return NfsTime{}, err
|
|
}
|
|
nsec, err := d.Uint32()
|
|
if err != nil {
|
|
return NfsTime{}, err
|
|
}
|
|
return NfsTime{sec, nsec}, nil
|
|
}
|
|
|
|
// ErrNotSuppName is kept out of the API; unknown attributes carry
|
|
// ErrUnknownAttr.
|
|
|
|
func bitsOf(m Bitmap) []uint32 {
|
|
var out []uint32
|
|
for w, word := range m.Words() {
|
|
for bit := range 32 {
|
|
if word&(1<<bit) != 0 {
|
|
out = append(out, uint32(w*32+bit))
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// DecodeAccessBody decodes a successful ACCESS body: the supported and the
|
|
// granted masks.
|
|
func DecodeAccessBody(body []byte) (supported, access uint32, err error) {
|
|
d := xdr.NewDecoder(body)
|
|
if supported, err = d.Uint32(); err != nil {
|
|
return 0, 0, err
|
|
}
|
|
access, err = d.Uint32()
|
|
return supported, access, err
|
|
}
|
|
|
|
// DecodeReadBody decodes a successful READ body: the end of file flag and
|
|
// the data.
|
|
func DecodeReadBody(body []byte) (eof bool, data []byte, err error) {
|
|
d := xdr.NewDecoder(body)
|
|
if eof, err = d.Bool(); err != nil {
|
|
return false, nil, err
|
|
}
|
|
data, err = d.VarOpaque()
|
|
return eof, data, err
|
|
}
|
|
|
|
// A ReadDirEntry is one entry of a decoded READDIR body.
|
|
type ReadDirEntry struct {
|
|
Cookie uint64
|
|
Name string
|
|
Attrs Attrs
|
|
}
|
|
|
|
// DecodeReadDirBody decodes a successful READDIR body: the entries in
|
|
// order, then the end of listing flag. The verifier precedes the entries
|
|
// and is returned with them.
|
|
func DecodeReadDirBody(body []byte) (verifier [8]byte, entries []ReadDirEntry, eof bool, err error) {
|
|
d := xdr.NewDecoder(body)
|
|
raw, err := d.Raw(8)
|
|
if err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
copy(verifier[:], raw)
|
|
for {
|
|
more, err := d.Bool()
|
|
if err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
if !more {
|
|
break
|
|
}
|
|
var e ReadDirEntry
|
|
if e.Cookie, err = d.Uint64(); err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
if e.Name, err = d.String(); err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
resp, err := ReadBitmap(d)
|
|
if err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
blob, err := d.VarOpaque()
|
|
if err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
if e.Attrs, err = decodeFattrBlob(blob, resp); err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
entries = append(entries, e)
|
|
}
|
|
if eof, err = d.Bool(); err != nil {
|
|
return verifier, nil, false, err
|
|
}
|
|
return verifier, entries, eof, nil
|
|
}
|
|
|
|
// A NfsTimeSet is the settime4 of a SETATTR: either the server's current
|
|
// time or the time the client names.
|
|
type NfsTimeSet struct {
|
|
Server bool
|
|
Time NfsTime
|
|
}
|
|
|
|
// SetAttrUpdates carries the attribute changes a SETATTR names, decoded
|
|
// from the fattr4 against its bitmap.
|
|
type SetAttrUpdates struct {
|
|
HasMode bool
|
|
Mode uint32
|
|
HasSize bool
|
|
Size uint64
|
|
UID *uint32
|
|
GID *uint32
|
|
Atime *NfsTimeSet
|
|
Mtime *NfsTimeSet
|
|
}
|
|
|
|
// AppendSetattrArgs appends the SETATTR argop: the stateid and the fattr4
|
|
// of the changes. A caller that sets no state carries the anonymous stateid.
|
|
func AppendSetattrArgs(b []byte, stateid Stateid, request Bitmap, a Attrs) []byte {
|
|
b = xdr.AppendUint32(b, OpSetattr)
|
|
b = stateid.Append(b)
|
|
return AppendFattr(b, request, a)
|
|
}
|
|
|
|
// AppendSetattrRes appends the SETATTR result: the bitmap of the
|
|
// attributes that were set. A failed operation reports what is known to
|
|
// have been applied; this server applies atomically per call or reports
|
|
// nothing.
|
|
func AppendSetattrRes(b []byte, attrsset Bitmap) []byte {
|
|
return attrsset.AppendTo(b)
|
|
}
|
|
|
|
// AppendLinkArgs appends the LINK argop: the name for the new link, which
|
|
// lands in the current directory and points at the saved file handle.
|
|
func AppendLinkArgs(b []byte, name string) []byte {
|
|
b = xdr.AppendUint32(b, OpLink)
|
|
return xdr.AppendString(b, name)
|
|
}
|
|
|
|
// AppendLinkRes appends the result of a successful LINK.
|
|
func AppendLinkRes(b []byte) []byte {
|
|
return AppendChangeInfo(b)
|
|
}
|
|
|
|
// DecodeSetattrBlob decodes the fattr4 of a SETATTR into the changes it
|
|
// names. Time attributes carry the settime4 shape, and the owner
|
|
// attributes are parsed from their numeric form, uid@domain. An attribute
|
|
// this build does not set is refused rather than silently skipped, because
|
|
// the encoding walks the blob in order and skipping would desynchronise
|
|
// the decode.
|
|
func DecodeSetattrBlob(blob []byte, request Bitmap) (SetAttrUpdates, error) {
|
|
d := xdr.NewDecoder(blob)
|
|
var u SetAttrUpdates
|
|
for _, n := range bitsOf(request) {
|
|
var err error
|
|
switch n {
|
|
case AttrMode:
|
|
u.HasMode = true
|
|
if u.Mode, err = d.Uint32(); err != nil {
|
|
return u, err
|
|
}
|
|
case AttrSize:
|
|
u.HasSize = true
|
|
if u.Size, err = d.Uint64(); err != nil {
|
|
return u, err
|
|
}
|
|
case AttrOwner:
|
|
s, err := d.String()
|
|
if err != nil {
|
|
return u, err
|
|
}
|
|
id, err := parseOwner(s)
|
|
if err != nil {
|
|
return u, err
|
|
}
|
|
u.UID = &id
|
|
case AttrOwnerGroup:
|
|
s, err := d.String()
|
|
if err != nil {
|
|
return u, err
|
|
}
|
|
id, err := parseOwner(s)
|
|
if err != nil {
|
|
return u, err
|
|
}
|
|
u.GID = &id
|
|
case AttrTimeAccessSet:
|
|
u.Atime, err = decodeNfsTimeSet(d)
|
|
if err != nil {
|
|
return u, err
|
|
}
|
|
case AttrTimeModifySet:
|
|
u.Mtime, err = decodeNfsTimeSet(d)
|
|
if err != nil {
|
|
return u, err
|
|
}
|
|
case AttrTimeAccess:
|
|
sec, serr := d.Int64()
|
|
if serr != nil {
|
|
return u, serr
|
|
}
|
|
nsec, nerr := d.Uint32()
|
|
if nerr != nil {
|
|
return u, nerr
|
|
}
|
|
u.Atime = &NfsTimeSet{Time: NfsTime{sec, nsec}}
|
|
case AttrTimeModify:
|
|
sec, serr := d.Int64()
|
|
if serr != nil {
|
|
return u, serr
|
|
}
|
|
nsec, nerr := d.Uint32()
|
|
if nerr != nil {
|
|
return u, nerr
|
|
}
|
|
u.Mtime = &NfsTimeSet{Time: NfsTime{sec, nsec}}
|
|
default:
|
|
return u, ErrAttrNotSettable
|
|
}
|
|
}
|
|
return u, nil
|
|
}
|
|
|
|
// decodeNfsTimeSet reads a settime4: a flag for server time, then, when the
|
|
// flag is false, the client's time.
|
|
func decodeNfsTimeSet(d *xdr.Decoder) (*NfsTimeSet, error) {
|
|
server, err := d.Bool()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if server {
|
|
return &NfsTimeSet{Server: true}, nil
|
|
}
|
|
sec, err := d.Int64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
nsec, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &NfsTimeSet{Time: NfsTime{sec, nsec}}, nil
|
|
}
|
|
|
|
// parseOwner reads the numeric identifier of an owner string, which the
|
|
// protocol writes as uid@domain, with the domain optional.
|
|
func parseOwner(s string) (uint32, error) {
|
|
name := s
|
|
if i := indexByte(s, '@'); i >= 0 {
|
|
name = s[:i]
|
|
}
|
|
v, err := strconv.ParseUint(name, 10, 32)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("%w: owner %q is not numeric", ErrAttrNotSettable, s)
|
|
}
|
|
return uint32(v), nil
|
|
}
|
|
|
|
func indexByte(s string, b byte) int {
|
|
for i := range len(s) {
|
|
if s[i] == b {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// ErrAttrNotSettable marks an attribute a SETATTR names that this build
|
|
// neither sets nor skips: the caller has to answer ATTRNOTSUPP.
|
|
var ErrAttrNotSettable = errors.New("nfs4: attribute not settable")
|
|
|
|
// AppendSetattrBlob appends the attrlist4 of a SETATTR: the values of the
|
|
// named changes in ascending attribute order, which is the order the
|
|
// decoder walks.
|
|
func AppendSetattrBlob(b []byte, u SetAttrUpdates) []byte {
|
|
if u.HasSize {
|
|
b = xdr.AppendUint64(b, u.Size)
|
|
}
|
|
if u.HasMode {
|
|
b = xdr.AppendUint32(b, u.Mode)
|
|
}
|
|
if u.UID != nil {
|
|
b = xdr.AppendString(b, strconv.FormatUint(uint64(*u.UID), 10))
|
|
}
|
|
if u.GID != nil {
|
|
b = xdr.AppendString(b, strconv.FormatUint(uint64(*u.GID), 10))
|
|
}
|
|
if u.Atime != nil {
|
|
b = appendSettime(b, *u.Atime)
|
|
}
|
|
if u.Mtime != nil {
|
|
b = appendSettime(b, *u.Mtime)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// SetattrBitmap returns the attribute bitmap that names the changes.
|
|
func (u SetAttrUpdates) SetattrBitmap() Bitmap {
|
|
set := Bitmap{}
|
|
if u.HasSize {
|
|
set = set.With(AttrSize)
|
|
}
|
|
if u.HasMode {
|
|
set = set.With(AttrMode)
|
|
}
|
|
if u.UID != nil {
|
|
set = set.With(AttrOwner)
|
|
}
|
|
if u.GID != nil {
|
|
set = set.With(AttrOwnerGroup)
|
|
}
|
|
if u.Atime != nil {
|
|
set = set.With(AttrTimeAccessSet)
|
|
}
|
|
if u.Mtime != nil {
|
|
set = set.With(AttrTimeModifySet)
|
|
}
|
|
return set
|
|
}
|
|
|
|
func appendSettime(b []byte, t NfsTimeSet) []byte {
|
|
b = xdr.AppendBool(b, t.Server)
|
|
if t.Server {
|
|
return b
|
|
}
|
|
b = xdr.AppendInt64(b, t.Time.Seconds)
|
|
return xdr.AppendUint32(b, t.Time.Nseconds)
|
|
}
|
|
|
|
// AppendSetattrArgsUpdates appends the whole SETATTR argop from the
|
|
// structured updates: the stateid, the derived bitmap and the values.
|
|
func AppendSetattrArgsUpdates(b []byte, stateid Stateid, u SetAttrUpdates) []byte {
|
|
b = xdr.AppendUint32(b, OpSetattr)
|
|
b = stateid.Append(b)
|
|
return xdr.AppendVarOpaque(u.SetattrBitmap().AppendTo(b), AppendSetattrBlob(nil, u))
|
|
}
|
|
|
|
// AppendReadlinkArgs appends the READLINK argop, which carries no
|
|
// arguments: the current file handle names the symlink.
|
|
func AppendReadlinkArgs(b []byte) []byte {
|
|
return xdr.AppendUint32(b, OpReadlink)
|
|
}
|
|
|
|
// AppendReadlinkRes appends the result of a successful READLINK: the
|
|
// target the symlink points at.
|
|
func AppendReadlinkRes(b []byte, linkdata string) []byte {
|
|
return xdr.AppendString(b, linkdata)
|
|
}
|
|
|
|
// AppendCommitArgs appends the COMMIT argop: the range the client asks to
|
|
// flush and the verifier of the writes it made.
|
|
func AppendCommitArgs(b []byte, off uint64, count uint32, verf [8]byte) []byte {
|
|
b = xdr.AppendUint32(b, OpCommit)
|
|
b = xdr.AppendUint64(b, off)
|
|
b = xdr.AppendUint32(b, count)
|
|
return append(b, verf[:]...)
|
|
}
|
|
|
|
// AppendCommitRes appends the result of a successful COMMIT: the server
|
|
// verifier the flushed data is now stable under.
|
|
func AppendCommitRes(b []byte, verf [8]byte) []byte {
|
|
return append(b, verf[:]...)
|
|
}
|
|
|
|
// A GSSInfo is the rpcsec_gss_info of the secinfo4 union's RPCSEC_GSS
|
|
// branch, RFC 7863: the mechanism object identifier, the quality of
|
|
// protection and the service.
|
|
type GSSInfo struct {
|
|
OID []byte
|
|
QOP uint32
|
|
Service uint32
|
|
}
|
|
|
|
// A SecinfoEntry is one answer row of a SECINFO: a flavour the server
|
|
// accepts. Only the RPCSEC_GSS branch carries the rpcsec_gss_info;
|
|
// every other flavour, and the default branch, carry nothing.
|
|
type SecinfoEntry struct {
|
|
Flavor uint32
|
|
GSS *GSSInfo
|
|
}
|
|
|
|
// AppendSecinfoArgs appends the SECINFO argop: the name the client asks
|
|
// the security for.
|
|
func AppendSecinfoArgs(b []byte, name string) []byte {
|
|
b = xdr.AppendUint32(b, OpSecinfo)
|
|
return xdr.AppendString(b, name)
|
|
}
|
|
|
|
// AppendSecinfoNoNameArgs appends the SECINFO_NO_NAME argop: the
|
|
// secinfo_style4 enum alone, the whole argument RFC 7863 defines.
|
|
func AppendSecinfoNoNameArgs(b []byte, style uint32) []byte {
|
|
b = xdr.AppendUint32(b, OpSecinfoNoName)
|
|
return xdr.AppendUint32(b, style)
|
|
}
|
|
|
|
// DecodeSecinfoNoNameArgs decodes the secinfo_style4 enum from the
|
|
// decoder, which is positioned after the operation number.
|
|
func DecodeSecinfoNoNameArgs(d *xdr.Decoder) (uint32, error) {
|
|
style, err := d.Uint32()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
switch style {
|
|
case StyleCurrentFH, StyleParent:
|
|
return style, nil
|
|
default:
|
|
return 0, ErrBadStyle
|
|
}
|
|
}
|
|
|
|
// ErrBadStyle marks a secinfo_style4 value outside the enumeration.
|
|
var ErrBadStyle = fmt.Errorf("nfs4: unknown secinfo style")
|
|
|
|
// AppendSecinfoRes appends a successful SECINFO or SECINFO_NO_NAME
|
|
// result: one entry per accepted flavour, the RPCSEC_GSS branch with
|
|
// its rpcsec_gss_info and every other branch empty.
|
|
func AppendSecinfoRes(b []byte, entries []SecinfoEntry) []byte {
|
|
b = xdr.AppendUint32(b, uint32(len(entries)))
|
|
for _, e := range entries {
|
|
b = xdr.AppendUint32(b, e.Flavor)
|
|
if e.Flavor == SecFlavorGSS {
|
|
b = xdr.AppendVarOpaque(b, e.GSS.OID)
|
|
b = xdr.AppendUint32(b, e.GSS.QOP)
|
|
b = xdr.AppendUint32(b, e.GSS.Service)
|
|
}
|
|
}
|
|
return b
|
|
}
|
|
|
|
// DecodeSecinfoRes decodes a successful SECINFO result.
|
|
func DecodeSecinfoRes(body []byte) ([]SecinfoEntry, error) {
|
|
d := xdr.NewDecoder(body)
|
|
n, err := d.Uint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if n > maxOps {
|
|
return nil, fmt.Errorf("nfs4: %d secinfo entries exceeds %d", n, maxOps)
|
|
}
|
|
entries := make([]SecinfoEntry, 0, n)
|
|
for range n {
|
|
var e SecinfoEntry
|
|
if e.Flavor, err = d.Uint32(); err != nil {
|
|
return nil, err
|
|
}
|
|
if e.Flavor == SecFlavorGSS {
|
|
g := &GSSInfo{}
|
|
if g.OID, err = d.VarOpaque(); err != nil {
|
|
return nil, err
|
|
}
|
|
if g.QOP, err = d.Uint32(); err != nil {
|
|
return nil, err
|
|
}
|
|
if g.Service, err = d.Uint32(); err != nil {
|
|
return nil, err
|
|
}
|
|
e.GSS = g
|
|
}
|
|
entries = append(entries, e)
|
|
}
|
|
return entries, nil
|
|
}
|
|
|
|
// Share access and deny bits of the OPEN operation, RFC 8881 section
|
|
// 16.16. The deny bits sit four positions above their access twins.
|
|
const (
|
|
ShareAccessRead = 0x1
|
|
ShareAccessWrite = 0x2
|
|
ShareAccessBoth = 0x3
|
|
ShareDenyRead = 0x10
|
|
ShareDenyWrite = 0x20
|
|
ShareDenyBoth = 0x30
|
|
)
|
|
|
|
// Open claim types of the open_claim4 union.
|
|
const (
|
|
ClaimNull = 0
|
|
ClaimPrevious = 1
|
|
ClaimDelegateC = 2
|
|
ClaimDelegatePrev = 3
|
|
ClaimDelegateCFh = 4
|
|
ClaimDelegatePrevFh = 5
|
|
ClaimFH = 6
|
|
)
|
|
|
|
// OPEN result flags.
|
|
const (
|
|
OpenResultConfirm = 0x1
|
|
OpenResultLockTypePosix = 0x2
|
|
)
|
|
|
|
// AppendOpenArgs encodes the OPEN4args for a CLAIM_NULL open of name,
|
|
// optionally creating the file. The legacy seqid field is sent as zero,
|
|
// as the standard requires from a 4.1 client.
|
|
func AppendOpenArgs(b []byte, ownerClientid uint64, owner []byte, access, deny uint32, create bool, perm uint32, name string) []byte {
|
|
b = xdr.AppendUint32(b, OpOpen)
|
|
b = xdr.AppendUint32(b, 0) // seqid, deprecated
|
|
b = xdr.AppendUint32(b, access)
|
|
b = xdr.AppendUint32(b, deny)
|
|
b = xdr.AppendUint64(b, ownerClientid)
|
|
b = xdr.AppendVarOpaque(b, owner)
|
|
if create {
|
|
b = xdr.AppendUint32(b, 1) // OPEN4_CREATE
|
|
b = xdr.AppendUint32(b, CreateUnchecked)
|
|
b = AppendFattr(b, OfBits(AttrMode), Attrs{Mode: perm})
|
|
} else {
|
|
b = xdr.AppendUint32(b, 0) // OPEN4_NOCREATE
|
|
}
|
|
b = xdr.AppendUint32(b, ClaimNull)
|
|
return xdr.AppendString(b, name)
|
|
}
|
|
|
|
// AppendOpenArgsPrevious encodes the OPEN4args for a CLAIM_PREVIOUS open:
|
|
// the reclaim of a delegation-backed open after the client restarted. The
|
|
// openflag carries NOCREATE and the claim carries no extra fields.
|
|
func AppendOpenArgsPrevious(b []byte, ownerClientid uint64, owner []byte, access, deny uint32) []byte {
|
|
b = xdr.AppendUint32(b, OpOpen)
|
|
b = xdr.AppendUint32(b, 0) // seqid, deprecated
|
|
b = xdr.AppendUint32(b, access)
|
|
b = xdr.AppendUint32(b, deny)
|
|
b = xdr.AppendUint64(b, ownerClientid)
|
|
b = xdr.AppendVarOpaque(b, owner)
|
|
b = xdr.AppendUint32(b, 0) // OPEN4_NOCREATE
|
|
return xdr.AppendUint32(b, ClaimPrevious)
|
|
}
|
|
|
|
// AppendOpenArgsExclusive41 encodes the OPEN4args of an EXCLUSIVE4_1
|
|
// create, RFC 8881 section 18.16: the verifier followed by the create
|
|
// attribute set.
|
|
func AppendOpenArgsExclusive41(b []byte, ownerClientid uint64, owner []byte, verifier [8]byte, perm uint32, name string) []byte {
|
|
b = xdr.AppendUint32(b, OpOpen)
|
|
b = xdr.AppendUint32(b, 0) // seqid, deprecated
|
|
b = xdr.AppendUint32(b, ShareAccessBoth)
|
|
b = xdr.AppendUint32(b, 0)
|
|
b = xdr.AppendUint64(b, ownerClientid)
|
|
b = xdr.AppendVarOpaque(b, owner)
|
|
b = xdr.AppendUint32(b, 1) // OPEN4_CREATE
|
|
b = xdr.AppendUint32(b, CreateExclusive1)
|
|
b = append(b, verifier[:]...)
|
|
b = AppendFattr(b, OfBits(AttrMode), Attrs{Mode: perm})
|
|
b = xdr.AppendUint32(b, ClaimNull)
|
|
return xdr.AppendString(b, name)
|
|
}
|
|
|
|
// A OpenArgs is the decoded OPEN4args.
|
|
type OpenArgs struct {
|
|
Access uint32
|
|
Deny uint32
|
|
ClientID uint64
|
|
Owner []byte
|
|
Create bool
|
|
Guarded bool // the create mode refuses an existing name
|
|
Exclusive bool // the create mode carries a create verifier
|
|
ExclusiveVerf [8]byte // the verifier of an exclusive create
|
|
Truncate bool // the create attributes named size 0, the shell redirect
|
|
Perm uint32
|
|
Name string
|
|
Previous bool // CLAIM_PREVIOUS: the reclaim of a pre restart open
|
|
Claim uint32 // the open_claim4 selector as it arrived
|
|
DelegStateid Stateid // the delegation stateid of the DELEGATE_CUR claims
|
|
}
|
|
|
|
// DecodeOpenArgs decodes the OPEN4args from the decoder, which is
|
|
// positioned after the operation number.
|
|
func DecodeOpenArgs(d *xdr.Decoder) (OpenArgs, error) {
|
|
var a OpenArgs
|
|
if _, err := d.Uint32(); err != nil { // legacy seqid
|
|
return a, err
|
|
}
|
|
var err error
|
|
if a.Access, err = d.Uint32(); err != nil {
|
|
return a, err
|
|
}
|
|
if a.Deny, err = d.Uint32(); err != nil {
|
|
return a, err
|
|
}
|
|
if a.ClientID, err = d.Uint64(); err != nil {
|
|
return a, err
|
|
}
|
|
if a.Owner, err = d.VarOpaque(); err != nil {
|
|
return a, err
|
|
}
|
|
opentype, err := d.Uint32()
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
if opentype == 1 {
|
|
a.Create = true
|
|
how, err := d.Uint32()
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
switch how {
|
|
case CreateGuarded:
|
|
a.Guarded = true
|
|
fallthrough
|
|
case CreateUnchecked:
|
|
request, err := ReadBitmap(d)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
attrblob, err := d.VarOpaque()
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
attrs, err := DecodeFattrAttrs(attrblob, request)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
a.Perm = attrs.Mode
|
|
// A create that names size 0 truncates an existing file:
|
|
// the shape of a shell redirect over the file.
|
|
if request.Has(AttrSize) && attrs.Size == 0 {
|
|
a.Truncate = true
|
|
}
|
|
case CreateExclusive, CreateExclusive1:
|
|
// The exclusive forms carry an eight byte create verifier
|
|
// and, for EXCLUSIVE4_1, an attribute set, RFC 8881 section
|
|
// 18.16. The server answers both as guarded creates keyed
|
|
// by the verifier: a retry after a lost reply replays the
|
|
// same verifier and succeeds, a create over an existing
|
|
// name with a different verifier answers EXIST.
|
|
a.Guarded = true
|
|
a.Exclusive = true
|
|
verf, verr := d.Raw(8)
|
|
if verr != nil {
|
|
return a, verr
|
|
}
|
|
copy(a.ExclusiveVerf[:], verf)
|
|
if how == CreateExclusive1 {
|
|
request, err := ReadBitmap(d)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
attrblob, err := d.VarOpaque()
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
attrs, err := DecodeFattrAttrs(attrblob, request)
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
a.Perm = attrs.Mode
|
|
}
|
|
}
|
|
}
|
|
claim, err := d.Uint32()
|
|
if err != nil {
|
|
return a, err
|
|
}
|
|
a.Claim = claim
|
|
switch claim {
|
|
case ClaimNull, ClaimDelegateC, ClaimDelegatePrev:
|
|
if a.Name, err = d.String(); err != nil {
|
|
return a, err
|
|
}
|
|
if claim == ClaimDelegateC {
|
|
raw, derr := d.Raw(16)
|
|
if derr != nil {
|
|
return a, derr
|
|
}
|
|
copy(a.DelegStateid[:], raw)
|
|
}
|
|
case ClaimPrevious:
|
|
a.Previous = true
|
|
case ClaimDelegateCFh, ClaimDelegatePrevFh, ClaimFH:
|
|
// The file handle claims name no component and carry no
|
|
// stateid: the current file handle is the reference, and a
|
|
// delegation claim of that form is identified by the client and
|
|
// the handle alone, RFC 8881 section 18.16.1.
|
|
default:
|
|
return a, ErrNotSuppName
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
// AppendOpenRes encodes a successful OPEN4resok without a delegation: the
|
|
// stateid of the open, the change information of the directory, no result
|
|
// flags, an empty attribute set and the NONE delegation.
|
|
func AppendOpenRes(b []byte, stateid Stateid) []byte {
|
|
return AppendOpenResDeleg(b, stateid, OpenDelegNone, Stateid{})
|
|
}
|
|
|
|
// AppendCloseArgs encodes the CLOSE4args: the deprecated v4.0 seqid rides
|
|
// ahead of the stateid and goes out as zero, RFC 8881 section 18.2.3.
|
|
func AppendCloseArgs(b []byte, stateid Stateid) []byte {
|
|
b = xdr.AppendUint32(b, OpClose)
|
|
b = xdr.AppendUint32(b, 0) // seqid, deprecated
|
|
return stateid.Append(b)
|
|
}
|
|
|
|
// AppendCloseRes encodes a successful CLOSE4res: the stateid, now with the
|
|
// sequence bumped one past its last use.
|
|
func AppendCloseRes(b []byte, stateid Stateid) []byte {
|
|
return stateid.Append(b)
|
|
}
|
|
|
|
// ErrNotSuppName marks an OPEN the server answers NOT_SUPP to: the
|
|
// exclusive creation mode and the delegation claims.
|
|
var ErrNotSuppName = errors.New("nfs4: open form not supported")
|
|
|
|
// AppendOpenDowngradeArgs encodes the OPEN_DOWNGRADE argop: the deprecated
|
|
// seqid, the stateid of the open and the reduced share access and deny
|
|
// bits.
|
|
func AppendOpenDowngradeArgs(b []byte, stateid Stateid, access, deny uint32) []byte {
|
|
b = xdr.AppendUint32(b, OpOpenDowngrade)
|
|
b = xdr.AppendUint32(b, 0) // seqid, deprecated
|
|
b = stateid.Append(b)
|
|
b = xdr.AppendUint32(b, access)
|
|
return xdr.AppendUint32(b, deny)
|
|
}
|
|
|
|
// AppendOpenDowngradeRes encodes a successful OPEN_DOWNGRADE result: the
|
|
// change information of the file.
|
|
func AppendOpenDowngradeRes(b []byte) []byte {
|
|
return AppendChangeInfo(b)
|
|
}
|
|
|
|
// AppendDestroyClientIDArgs encodes the DESTROY_CLIENTID argop.
|
|
func AppendDestroyClientIDArgs(b []byte, clientid uint64) []byte {
|
|
b = xdr.AppendUint32(b, OpDestroyClientID)
|
|
return xdr.AppendUint64(b, clientid)
|
|
}
|
|
|
|
// AppendReclaimCompleteArgs encodes the RECLAIM_COMPLETE argop: the
|
|
// rca_one_client flag alone, the whole argument RFC 7863 defines. A
|
|
// client sends it false after it has finished reclaiming state.
|
|
func AppendReclaimCompleteArgs(b []byte, oneClient bool) []byte {
|
|
b = xdr.AppendUint32(b, OpReclaimComplete)
|
|
return xdr.AppendBool(b, oneClient)
|
|
}
|
|
|
|
// Open delegation types of the delegation4 union.
|
|
const (
|
|
OpenDelegNone = 0
|
|
OpenDelegRead = 1
|
|
OpenDelegWrite = 2
|
|
)
|
|
|
|
// The nfs_space_limit4 limit_by discriminant, RFC 7863: this build
|
|
// grants a plain file size limit.
|
|
const LimitBySize = 1
|
|
|
|
// appendACE encodes the empty nfsace4 this build grants with every
|
|
// delegation: no access bits for anyone.
|
|
func appendACE(b []byte) []byte {
|
|
b = xdr.AppendUint32(b, 0) // type
|
|
b = xdr.AppendUint32(b, 0) // flag
|
|
b = xdr.AppendUint32(b, 0) // access mask
|
|
return xdr.AppendVarOpaque(b, nil)
|
|
}
|
|
|
|
// AppendDelegation encodes one delegation4 value, RFC 7863: the type
|
|
// followed by the type specific fields. The read form carries the
|
|
// delegation stateid, the recall flag and the permissions ace; the
|
|
// write form adds the space limit ahead of the ace.
|
|
func AppendDelegation(b []byte, delegType uint32, stateid Stateid, recall bool, maxSize uint64) []byte {
|
|
b = xdr.AppendUint32(b, delegType)
|
|
switch delegType {
|
|
case OpenDelegRead:
|
|
b = stateid.Append(b)
|
|
b = xdr.AppendBool(b, recall)
|
|
b = appendACE(b)
|
|
case OpenDelegWrite:
|
|
b = stateid.Append(b)
|
|
b = xdr.AppendBool(b, recall)
|
|
// nfs_space_limit4: the file size branch with the limit.
|
|
b = xdr.AppendUint32(b, LimitBySize)
|
|
b = xdr.AppendUint64(b, maxSize)
|
|
b = appendACE(b)
|
|
}
|
|
return b
|
|
}
|
|
|
|
// readACE walks one nfsace4.
|
|
func readACE(d *xdr.Decoder) error {
|
|
for range 3 {
|
|
if _, err := d.Uint32(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err := d.VarOpaque()
|
|
return err
|
|
}
|
|
|
|
// DecodeOpenResDeleg decodes the delegation of a successful OPEN result:
|
|
// the stateid of the open, the delegation type and the delegation's own
|
|
// stateid.
|
|
func DecodeOpenResDeleg(body []byte) (openStateid Stateid, delegType uint32, delegStateid Stateid, err error) {
|
|
d := xdr.NewDecoder(body)
|
|
raw, rerr := d.Raw(16)
|
|
if rerr != nil {
|
|
err = rerr
|
|
return
|
|
}
|
|
copy(openStateid[:], raw)
|
|
// change_info4: atomic, before, after.
|
|
if _, err = d.Bool(); err != nil {
|
|
return
|
|
}
|
|
if _, err = d.Uint64(); err != nil {
|
|
return
|
|
}
|
|
if _, err = d.Uint64(); err != nil {
|
|
return
|
|
}
|
|
if _, err = d.Uint32(); err != nil { // rflags
|
|
return
|
|
}
|
|
if _, err = ReadBitmap(d); err != nil { // attrset
|
|
return
|
|
}
|
|
if delegType, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
switch delegType {
|
|
case OpenDelegRead:
|
|
if delegStateid, err = readStateid(d); err != nil {
|
|
return
|
|
}
|
|
if _, err = d.Bool(); err != nil { // recall
|
|
return
|
|
}
|
|
if err = readACE(d); err != nil {
|
|
return
|
|
}
|
|
case OpenDelegWrite:
|
|
if delegStateid, err = readStateid(d); err != nil {
|
|
return
|
|
}
|
|
if _, err = d.Bool(); err != nil { // recall
|
|
return
|
|
}
|
|
// nfs_space_limit4: the limit_by union and its payload.
|
|
limitBy, lerr := d.Uint32()
|
|
if lerr != nil {
|
|
err = lerr
|
|
return
|
|
}
|
|
switch limitBy {
|
|
case LimitBySize:
|
|
if _, err = d.Uint64(); err != nil {
|
|
return
|
|
}
|
|
case 2: // NFS_LIMIT_BLOCKS: num_blocks and bytes_per_block.
|
|
if _, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
if _, err = d.Uint32(); err != nil {
|
|
return
|
|
}
|
|
default:
|
|
err = ErrBadDeleg
|
|
return
|
|
}
|
|
if err = readACE(d); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// ErrBadDeleg marks a delegation4 body this decoder cannot walk.
|
|
var ErrBadDeleg = errors.New("nfs4: malformed delegation")
|
|
|
|
func readStateid(d *xdr.Decoder) (Stateid, error) {
|
|
var s Stateid
|
|
raw, err := d.Raw(16)
|
|
if err != nil {
|
|
return s, err
|
|
}
|
|
copy(s[:], raw)
|
|
return s, nil
|
|
}
|
|
|
|
// AppendOpenResDeleg encodes a successful OPEN4res with a granted
|
|
// delegation: the stateid of the open, the change information, no result
|
|
// flags, an empty attribute set and the delegation union.
|
|
func AppendOpenResDeleg(b []byte, stateid Stateid, delegType uint32, delegSt Stateid) []byte {
|
|
b = stateid.Append(b)
|
|
b = xdr.AppendBool(b, false) // atomic
|
|
b = xdr.AppendUint64(b, 0) // before
|
|
b = xdr.AppendUint64(b, 0) // after
|
|
b = xdr.AppendUint32(b, 0) // rflags
|
|
b = Bitmap{}.AppendTo(b) // attrset
|
|
return AppendDelegation(b, delegType, delegSt, false, 0)
|
|
}
|