Files

1329 lines
37 KiB
Go
Raw Permalink Normal View History

// 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)
}