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,319 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// The pNFS layout layer: the wire codecs of LAYOUTGET, LAYOUTCOMMIT,
|
||||
// LAYOUTRETURN and GETDEVICEINFO over the flexfiles layout of RFC 8435.
|
||||
// The shapes follow the XDR of RFC 8881 sections 18.43 to 18.45 and RFC
|
||||
// 8435 section 6.
|
||||
|
||||
package nfs4
|
||||
|
||||
import (
|
||||
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||||
)
|
||||
|
||||
// Layout types of the layouttype4 enum, the registry values of RFC 7863
|
||||
// and RFC 8435: files, objects and block volume are the NFSv4.1 core,
|
||||
// flexfiles is four, SCSI five.
|
||||
const (
|
||||
LayoutTypeFiles = 1
|
||||
LayoutTypeObjects = 2
|
||||
LayoutTypeBlock = 3
|
||||
LayoutTypeFlexfiles = 4
|
||||
LayoutTypeScsi = 5
|
||||
)
|
||||
|
||||
// Layout IO modes of the layoutiomode4 enum, RFC 7863.
|
||||
const (
|
||||
IoModeRead = 1
|
||||
IoModeRW = 2
|
||||
IoModeAny = 3
|
||||
)
|
||||
|
||||
// LAYOUTCOMMIT uses the newtime4 union for the modify time a client
|
||||
// asserts.
|
||||
type NewTime struct {
|
||||
Set bool
|
||||
Seconds int64
|
||||
Nseconds uint32
|
||||
}
|
||||
|
||||
// AppendLayoutGetArgs encodes the whole LAYOUTGET argop: the signal flag,
|
||||
// the layout type, the IO mode, the range, the stateid the layout hangs
|
||||
// from and the response size budget.
|
||||
func AppendLayoutGetArgs(b []byte, signalAvail bool, layoutType, iomode uint32, offset, length, minlength uint64, st Stateid, maxcount uint32) []byte {
|
||||
b = xdr.AppendUint32(b, OpLayoutGet)
|
||||
b = xdr.AppendBool(b, signalAvail)
|
||||
b = xdr.AppendUint32(b, layoutType)
|
||||
b = xdr.AppendUint32(b, iomode)
|
||||
b = xdr.AppendUint64(b, offset)
|
||||
b = xdr.AppendUint64(b, length)
|
||||
b = xdr.AppendUint64(b, minlength)
|
||||
b = st.Append(b)
|
||||
return xdr.AppendUint32(b, maxcount)
|
||||
}
|
||||
|
||||
// A Layout4 is one segment of a granted layout: the byte range, the IO
|
||||
// mode and the type specific body.
|
||||
type Layout4 struct {
|
||||
Offset uint64
|
||||
Length uint64
|
||||
IoMode uint32
|
||||
Type uint32
|
||||
Body []byte
|
||||
}
|
||||
|
||||
// AppendLayout4 encodes one layout4.
|
||||
func AppendLayout4(b []byte, l Layout4) []byte {
|
||||
b = xdr.AppendUint64(b, l.Offset)
|
||||
b = xdr.AppendUint64(b, l.Length)
|
||||
b = xdr.AppendUint32(b, l.IoMode)
|
||||
b = xdr.AppendUint32(b, l.Type)
|
||||
return xdr.AppendVarOpaque(b, l.Body)
|
||||
}
|
||||
|
||||
// AppendLayoutGetRes encodes a successful LAYOUTGET result: the layout
|
||||
// stateid, the return-on-close flag and the layouts.
|
||||
func AppendLayoutGetRes(b []byte, layoutSt Stateid, returnOnClose bool, layouts []Layout4) []byte {
|
||||
b = layoutSt.Append(b)
|
||||
b = xdr.AppendBool(b, returnOnClose)
|
||||
b = xdr.AppendUint32(b, uint32(len(layouts)))
|
||||
for _, l := range layouts {
|
||||
b = AppendLayout4(b, l)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// AppendLayoutCommitArgs encodes the LAYOUTCOMMIT argop body: the range,
|
||||
// the reclaim flag, the layout stateid, the last write offset, the modify
|
||||
// time and the layout update record.
|
||||
func AppendLayoutCommitArgs(b []byte, offset, length uint64, reclaim bool, layoutSt Stateid, lastWriteSet bool, lastWrite uint64, timeSet bool, seconds int64, nseconds uint32, updateType uint32, updateBody []byte) []byte {
|
||||
b = xdr.AppendUint32(b, OpLayoutCommit)
|
||||
b = xdr.AppendUint64(b, offset)
|
||||
b = xdr.AppendUint64(b, length)
|
||||
b = xdr.AppendBool(b, reclaim)
|
||||
b = layoutSt.Append(b)
|
||||
if lastWriteSet {
|
||||
b = xdr.AppendBool(b, true)
|
||||
b = xdr.AppendUint64(b, lastWrite)
|
||||
} else {
|
||||
b = xdr.AppendBool(b, false)
|
||||
}
|
||||
if timeSet {
|
||||
b = xdr.AppendBool(b, true)
|
||||
b = xdr.AppendInt64(b, seconds)
|
||||
b = xdr.AppendUint32(b, nseconds)
|
||||
} else {
|
||||
b = xdr.AppendBool(b, false)
|
||||
}
|
||||
b = xdr.AppendUint32(b, updateType)
|
||||
return xdr.AppendVarOpaque(b, updateBody)
|
||||
}
|
||||
|
||||
// AppendLayoutCommitRes encodes a successful LAYOUTCOMMIT result: the
|
||||
// newsize4 union with the size the layout commit leaves the file at.
|
||||
func AppendLayoutCommitRes(b []byte, newSize uint64) []byte {
|
||||
b = xdr.AppendBool(b, true)
|
||||
return xdr.AppendUint64(b, newSize)
|
||||
}
|
||||
|
||||
// AppendLayoutReturnArgs encodes the LAYOUTRETURN argop of the whole file
|
||||
// system or the whole client: no layoutreturn_file4 body follows.
|
||||
func AppendLayoutReturnArgs(b []byte, reclaim bool, layoutType, iomode, returnKind uint32) []byte {
|
||||
b = xdr.AppendUint32(b, OpLayoutReturn)
|
||||
b = xdr.AppendBool(b, reclaim)
|
||||
b = xdr.AppendUint32(b, layoutType)
|
||||
b = xdr.AppendUint32(b, iomode)
|
||||
return xdr.AppendUint32(b, returnKind)
|
||||
}
|
||||
|
||||
// AppendLayoutReturnFileArgs encodes the LAYOUTRETURN argop of one file:
|
||||
// the offset, the length and the layout stateid ride after the header,
|
||||
// with the empty ffsid body of NFSv4.2.
|
||||
func AppendLayoutReturnFileArgs(b []byte, reclaim bool, layoutType, iomode uint32, offset, length uint64, st Stateid) []byte {
|
||||
b = AppendLayoutReturnArgs(b, reclaim, layoutType, iomode, ReturnFile)
|
||||
b = xdr.AppendUint64(b, offset)
|
||||
b = xdr.AppendUint64(b, length)
|
||||
b = st.Append(b)
|
||||
return xdr.AppendUint32(b, 0) // ffsid_info_type: the empty default
|
||||
}
|
||||
|
||||
// AppendLayoutReturnRes encodes a successful LAYOUTRETURN result: the
|
||||
// layoutreturn_stateid4 union with the returned flag set and the stateid
|
||||
// of the layout as returned.
|
||||
func AppendLayoutReturnRes(b []byte, stateid Stateid) []byte {
|
||||
b = xdr.AppendBool(b, true)
|
||||
return stateid.Append(b)
|
||||
}
|
||||
|
||||
// AppendGetDeviceInfoArgs encodes the GETDEVICEINFO argop: the device id,
|
||||
// the layout type, the response size budget and the notification types.
|
||||
func AppendGetDeviceInfoArgs(b []byte, deviceID [16]byte, layoutType, maxcount uint32, notifyTypes Bitmap) []byte {
|
||||
b = xdr.AppendUint32(b, OpGetDeviceInfo)
|
||||
b = append(b, deviceID[:]...)
|
||||
b = xdr.AppendUint32(b, layoutType)
|
||||
b = xdr.AppendUint32(b, maxcount)
|
||||
return notifyTypes.AppendTo(b)
|
||||
}
|
||||
|
||||
// AppendGetDeviceInfoRes encodes a successful GETDEVICEINFO result: the
|
||||
// device address of the flexfiles layout and the notification bitmap.
|
||||
func AppendGetDeviceInfoRes(b []byte, addrBody []byte) []byte {
|
||||
b = xdr.AppendUint32(b, LayoutTypeFlexfiles)
|
||||
b = xdr.AppendVarOpaque(b, addrBody)
|
||||
return Bitmap{}.AppendTo(b)
|
||||
}
|
||||
|
||||
// A LayoutCommitArgs is the structured form of the LAYOUTCOMMIT argop.
|
||||
type LayoutCommitArgs struct {
|
||||
Offset uint64
|
||||
Length uint64
|
||||
Reclaim bool
|
||||
LayoutSt Stateid
|
||||
LastWriteSet bool
|
||||
LastWrite uint64
|
||||
TimeSet bool
|
||||
Seconds int64
|
||||
Nseconds uint32
|
||||
UpdateType uint32
|
||||
UpdateBody []byte
|
||||
}
|
||||
|
||||
// Encode builds the whole LAYOUTCOMMIT argop.
|
||||
func (a LayoutCommitArgs) Encode() []byte {
|
||||
return AppendLayoutCommitArgs(nil, a.Offset, a.Length, a.Reclaim, a.LayoutSt,
|
||||
a.LastWriteSet, a.LastWrite, a.TimeSet, a.Seconds, a.Nseconds, a.UpdateType, a.UpdateBody)
|
||||
}
|
||||
|
||||
// A FlexDataServer is one ff_data_server4: the device the client reads
|
||||
// and writes through, in the shape of RFC 8435 section 5.1.
|
||||
type FlexDataServer struct {
|
||||
DeviceID [16]byte
|
||||
Efficiency uint32
|
||||
Stateid Stateid
|
||||
// FHs carries one file handle per file handle format the data server
|
||||
// speaks; one entry is the common case.
|
||||
FHs [][]byte
|
||||
User string
|
||||
Group string
|
||||
}
|
||||
|
||||
// A FlexMirror is one ff_mirror4: a list of equivalent data servers that
|
||||
// hold the same bytes.
|
||||
type FlexMirror struct {
|
||||
DataServers []FlexDataServer
|
||||
}
|
||||
|
||||
// AppendFlexFileLayoutBody encodes the ff_layout4: the stripe unit, the
|
||||
// mirrors with their data servers, the flags and the stats hint.
|
||||
func AppendFlexFileLayoutBody(b []byte, flags uint32, stripeUnit uint64, mirrors []FlexMirror) []byte {
|
||||
b = xdr.AppendUint64(b, stripeUnit)
|
||||
b = xdr.AppendUint32(b, uint32(len(mirrors)))
|
||||
for _, m := range mirrors {
|
||||
b = xdr.AppendUint32(b, uint32(len(m.DataServers)))
|
||||
for _, ds := range m.DataServers {
|
||||
b = append(b, ds.DeviceID[:]...)
|
||||
b = xdr.AppendUint32(b, ds.Efficiency)
|
||||
b = ds.Stateid.Append(b)
|
||||
b = xdr.AppendUint32(b, uint32(len(ds.FHs)))
|
||||
for _, fh := range ds.FHs {
|
||||
b = xdr.AppendVarOpaque(b, fh)
|
||||
}
|
||||
b = xdr.AppendString(b, ds.User)
|
||||
b = xdr.AppendString(b, ds.Group)
|
||||
}
|
||||
}
|
||||
b = xdr.AppendUint32(b, flags)
|
||||
return xdr.AppendUint32(b, 0) // stats collect hint
|
||||
}
|
||||
|
||||
// A FlexDeviceAddr is one ff_device_addr4: the net addresses of the data
|
||||
// servers and the versions they speak.
|
||||
type FlexDeviceAddr struct {
|
||||
NetAddrs []NetAddr
|
||||
Versions []FlexVersion
|
||||
}
|
||||
|
||||
// A NetAddr is one netaddr4 of RFC 5661: a netid and a universal address.
|
||||
type NetAddr struct {
|
||||
Netid string
|
||||
Uaddr string
|
||||
}
|
||||
|
||||
// A FlexVersion is one ff_device_versions4: an NFS version a data server
|
||||
// speaks and the transfer sizes it allows.
|
||||
type FlexVersion struct {
|
||||
Version uint32
|
||||
MinorVersion uint32
|
||||
RSize uint32
|
||||
WSize uint32
|
||||
TightlyBound bool
|
||||
}
|
||||
|
||||
// AppendFlexDeviceAddr encodes the ff_device_addr4: the net addresses and
|
||||
// the NFS versions the data servers speak.
|
||||
func AppendFlexDeviceAddr(b []byte, d FlexDeviceAddr) []byte {
|
||||
b = xdr.AppendUint32(b, uint32(len(d.NetAddrs)))
|
||||
for _, na := range d.NetAddrs {
|
||||
b = xdr.AppendString(b, na.Netid)
|
||||
b = xdr.AppendString(b, na.Uaddr)
|
||||
}
|
||||
b = xdr.AppendUint32(b, uint32(len(d.Versions)))
|
||||
for _, v := range d.Versions {
|
||||
b = xdr.AppendUint32(b, v.Version)
|
||||
b = xdr.AppendUint32(b, v.MinorVersion)
|
||||
b = xdr.AppendUint32(b, v.RSize)
|
||||
b = xdr.AppendUint32(b, v.WSize)
|
||||
b = xdr.AppendBool(b, v.TightlyBound)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// DecodeFlexDeviceAddr decodes an ff_device_addr4 body.
|
||||
func DecodeFlexDeviceAddr(body []byte) (FlexDeviceAddr, error) {
|
||||
d := xdr.NewDecoder(body)
|
||||
var out FlexDeviceAddr
|
||||
netCount, err := d.Uint32()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for range netCount {
|
||||
netid, err := d.String()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
uaddr, err := d.String()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.NetAddrs = append(out.NetAddrs, NetAddr{Netid: netid, Uaddr: uaddr})
|
||||
}
|
||||
verCount, err := d.Uint32()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
for range verCount {
|
||||
v, err := d.Uint32()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
minor, err := d.Uint32()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
rsize, err := d.Uint32()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
wsize, err := d.Uint32()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
tight, err := d.Bool()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.Versions = append(out.Versions, FlexVersion{Version: v, MinorVersion: minor, RSize: rsize, WSize: wsize, TightlyBound: tight})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user