feat: full NFSv4.2 server and client in pure Go
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user