// Copyright (c) 2026 Petr BalvĂ­n (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<= 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) }