Files

244 lines
7.0 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 (
"slices"
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
)
// Attribute numbers of the fattr4 table, RFC 8881 section 5. The numbers
// were verified against the XDR description of the standard rather than
// recalled: an off by one here shows up as garbage on the client.
const (
AttrSupportedAttrs = 0 // bitmap4
AttrType = 1 // enum
AttrFHExpireType = 2 // uint32
AttrChange = 3 // uint64
AttrSize = 4 // uint64
AttrLinkSupport = 5 // bool
AttrSymlinkSupport = 6 // bool
AttrNamedAttr = 7 // bool
AttrFSID = 8 // uint64, uint64
AttrUniqueHandles = 9 // bool
AttrLeaseTime = 10 // uint32
AttrRdattrError = 11 // enum
AttrFileHandle = 19 // opaque
AttrFileID = 20 // uint64
AttrMaxName = 29 // uint32
AttrMaxRead = 30 // uint64
AttrMaxWrite = 31 // uint64
AttrMode = 33 // uint32
AttrNumlinks = 35 // uint32
AttrOwner = 36 // string
AttrOwnerGroup = 37 // string
AttrRawDev = 41 // specdata
AttrSpaceUsed = 45 // uint64
AttrTimeAccessSet = 48 // settime4
AttrTimeModifySet = 54 // settime4
AttrTimeAccess = 47 // nfstime4
AttrTimeMetadata = 52 // nfstime4
AttrTimeModify = 53 // nfstime4
AttrMountedOnFileID = 55 // uint64
AttrSuppattrExclCreat = 75 // bitmap4
)
// SupportedAttrs is the attribute set this server advertises. A client asks
// only for attributes inside the set it read from here.
var SupportedAttrs = OfBits(
AttrSupportedAttrs,
AttrType,
AttrFHExpireType,
AttrChange,
AttrSize,
AttrLinkSupport,
AttrSymlinkSupport,
AttrNamedAttr,
AttrFSID,
AttrUniqueHandles,
AttrFileHandle,
AttrFileID,
AttrMaxName,
AttrMaxRead,
AttrMaxWrite,
AttrMode,
AttrNumlinks,
AttrOwner,
AttrOwnerGroup,
AttrSpaceUsed,
AttrTimeAccess,
AttrTimeMetadata,
AttrTimeModify,
AttrMountedOnFileID,
)
// ServerLimits carries the per export constants the advertised attributes
// report back.
type ServerLimits struct {
MaxName uint32
MaxRead uint64
MaxWrite uint64
}
// DefaultLimits are the limits the server advertises.
var DefaultLimits = ServerLimits{MaxName: 255, MaxRead: 1 << 22, MaxWrite: 1 << 22}
// Attrs holds the values the fattr4 builder emits. A zero field for an
// attribute the server does not have is simply not requested by a client
// that honoured the advertised set.
type Attrs struct {
Type uint32
FHExpireType uint32
Change uint64
Size uint64
LinkSupport bool
SymlinkSupport bool
NamedAttr bool
FSID [2]uint64
UniqueHandles bool
FileHandle []byte
FileID uint64
Mode uint32
Numlinks uint32
Owner string // decimal uid, RFC 8881 section 5.8
OwnerGroup string // decimal gid
SpaceUsed uint64
TimeAccess NfsTime
TimeMetadata NfsTime
TimeModify NfsTime
MountedOnFileID uint64
Limits ServerLimits
// FsLocations and FsLocationsInfo carry the pre encoded attributes
// for referral stubs; nil omits them.
FsLocations []byte
FsLocationsInfo []byte
}
// A NfsTime is an nfstime4: seconds and nanoseconds since the epoch.
type NfsTime struct {
Seconds int64
Nseconds uint32
}
// NfsTimeOf converts a Go time.
func NfsTimeOf(sec int64, nsec uint32) NfsTime { return NfsTime{sec, nsec} }
func appendNfsTime(b []byte, t NfsTime) []byte {
b = xdr.AppendInt64(b, t.Seconds)
return xdr.AppendUint32(b, t.Nseconds)
}
// AppendFattr emits an fattr4 for the requested attributes: the request
// bitmap filtered to what this server has, then the length prefixed list of
// values in numeric order, as the encoding requires.
func AppendFattr(b []byte, request Bitmap, a Attrs) []byte {
var body []byte
response := Bitmap{}
for _, n := range sortedBits(request) {
switch n {
case AttrSupportedAttrs:
body = SupportedAttrs.AppendTo(body)
response = response.With(n)
case AttrType:
body = xdr.AppendUint32(body, a.Type)
response = response.With(n)
case AttrFHExpireType:
body = xdr.AppendUint32(body, a.FHExpireType)
response = response.With(n)
case AttrChange:
body = xdr.AppendUint64(body, a.Change)
response = response.With(n)
case AttrSize:
body = xdr.AppendUint64(body, a.Size)
response = response.With(n)
case AttrLinkSupport:
body = xdr.AppendBool(body, a.LinkSupport)
response = response.With(n)
case AttrSymlinkSupport:
body = xdr.AppendBool(body, a.SymlinkSupport)
response = response.With(n)
case AttrNamedAttr:
body = xdr.AppendBool(body, a.NamedAttr)
response = response.With(n)
case AttrFSID:
body = xdr.AppendUint64(body, a.FSID[0])
body = xdr.AppendUint64(body, a.FSID[1])
response = response.With(n)
case AttrUniqueHandles:
body = xdr.AppendBool(body, a.UniqueHandles)
response = response.With(n)
case AttrFileHandle:
body = xdr.AppendVarOpaque(body, a.FileHandle)
response = response.With(n)
case AttrFileID:
body = xdr.AppendUint64(body, a.FileID)
response = response.With(n)
case AttrMaxName:
body = xdr.AppendUint32(body, a.Limits.MaxName)
response = response.With(n)
case AttrMaxRead:
body = xdr.AppendUint64(body, a.Limits.MaxRead)
response = response.With(n)
case AttrMaxWrite:
body = xdr.AppendUint64(body, a.Limits.MaxWrite)
response = response.With(n)
case AttrMode:
body = xdr.AppendUint32(body, a.Mode)
response = response.With(n)
case AttrNumlinks:
body = xdr.AppendUint32(body, a.Numlinks)
response = response.With(n)
case AttrOwner:
body = xdr.AppendString(body, a.Owner)
response = response.With(n)
case AttrOwnerGroup:
body = xdr.AppendString(body, a.OwnerGroup)
response = response.With(n)
case AttrSpaceUsed:
body = xdr.AppendUint64(body, a.SpaceUsed)
response = response.With(n)
case AttrTimeAccess:
body = appendNfsTime(body, a.TimeAccess)
response = response.With(n)
case AttrTimeMetadata:
body = appendNfsTime(body, a.TimeMetadata)
response = response.With(n)
case AttrTimeModify:
body = appendNfsTime(body, a.TimeModify)
response = response.With(n)
case AttrMountedOnFileID:
body = xdr.AppendUint64(body, a.MountedOnFileID)
response = response.With(n)
case AttrFsLocations:
if a.FsLocations != nil {
body = append(body, a.FsLocations...)
response = response.With(n)
}
case AttrFsLocationsInfo:
if a.FsLocationsInfo != nil {
body = append(body, a.FsLocationsInfo...)
response = response.With(n)
}
}
}
b = response.AppendTo(b)
return xdr.AppendVarOpaque(b, body)
}
// sortedBits lists the attribute numbers set in the bitmap, ascending.
func sortedBits(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))
}
}
}
slices.Sort(out)
return out
}