320 lines
9.8 KiB
Go
320 lines
9.8 KiB
Go
// 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
|
||
|
|
}
|