Files
petrbalvin a9b8039ef7
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
feat: full NFSv4.2 server and client in pure Go
Assisted-by: GLM 5.3 Flash
2026-09-21 18:51:17 +02:00

192 lines
6.8 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: MIT
// The layout type families beyond flexfiles, each with its wire body
// and the device address this build answers for it: files (RFC 5661
// section 13.3), block/volume (RFC 5663), objects (RFC 5664) and SCSI
// (RFC 8154). The metadata server of this build emulates every one of
// them over its own storage, so all bodies name the one device the
// server itself is.
package nfs4
import (
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
)
// AppendFileLayoutBody encodes the nfsv4_1_file_layout4: the device,
// the stripe unit descriptor, the first stripe index, the pattern
// offset and the file handle list.
func AppendFileLayoutBody(b []byte, device [16]byte, util, firstStripe uint32, patternOffset uint64, fhs [][]byte) []byte {
b = append(b, device[:]...)
b = xdr.AppendUint32(b, util)
b = xdr.AppendUint32(b, firstStripe)
b = xdr.AppendUint64(b, patternOffset)
b = xdr.AppendUint32(b, uint32(len(fhs)))
for _, fh := range fhs {
b = xdr.AppendVarOpaque(b, fh)
}
return b
}
// AppendFileDeviceAddr encodes the nfsv4_1_file_layout_ds_addr4: the
// stripe indices over the multipath list of data server addresses.
func AppendFileDeviceAddr(b []byte, stripeIndices []uint32, addrs []NetAddr) []byte {
b = xdr.AppendUint32(b, uint32(len(stripeIndices)))
for _, i := range stripeIndices {
b = xdr.AppendUint32(b, i)
}
b = xdr.AppendUint32(b, uint32(len(addrs)))
for _, a := range addrs {
b = xdr.AppendString(b, a.Netid)
b = xdr.AppendString(b, a.Uaddr)
}
return b
}
// A BlockVolume is one pnfs_block_volume4 of the emulated device: a
// simple volume with the given base offset and block count.
type BlockVolume struct {
// Type is PNFS_BLOCK_VOLUME_SIMPLE (1).
BaseOffset uint64
BlockCount uint64
DeviceID [16]byte
}
// AppendBlockDeviceAddr encodes the pnfs_block_deviceaddr4 with one
// simple volume, RFC 5663 section 5.1.
func AppendBlockDeviceAddr(b []byte, v BlockVolume) []byte {
b = xdr.AppendUint32(b, 1) // one volume
b = xdr.AppendUint32(b, 1) // PNFS_BLOCK_VOLUME_SIMPLE
b = append(b, v.DeviceID[:]...)
b = xdr.AppendUint64(b, v.BaseOffset)
return xdr.AppendUint64(b, v.BlockCount)
}
// AppendScsiLayoutBody encodes the pnfs_scsi_layout4: one extent over
// the emulated volume, RFC 8154 section 5.2.
func AppendScsiLayoutBody(b []byte, device [16]byte, fileOffset, length, storageOffset uint64) []byte {
b = xdr.AppendUint32(b, 1) // one extent
b = append(b, device[:]...)
b = xdr.AppendUint64(b, fileOffset)
b = xdr.AppendUint64(b, length)
b = xdr.AppendUint64(b, storageOffset)
return xdr.AppendUint32(b, 0) // reserved
}
// An ObjectLayout is the emulated pnfs_osd_layout4: the map header and
// one component credential over the emulated OSD.
type ObjectLayout struct {
NumComponents uint32
StripeUnit uint64
GroupWidth uint32
GroupDepth uint32
RefTagSize uint32
}
// AppendObjectLayoutBody encodes a minimal pnfs_osd_layout4 for the
// emulated OSD, RFC 5664 section 3.3: the data map and one component.
func AppendObjectLayoutBody(b []byte, device [16]byte, l ObjectLayout) []byte {
// pnfs_osd_data_map4
b = xdr.AppendUint32(b, 0) // odi_odm_raid_algorithm: PNFS_OSD_RAID_0
b = xdr.AppendUint64(b, l.StripeUnit)
b = xdr.AppendUint32(b, l.NumComponents)
b = xdr.AppendUint32(b, l.GroupWidth)
b = xdr.AppendUint32(b, l.GroupDepth)
b = xdr.AppendUint32(b, l.RefTagSize)
// olo_comps_index and the single component
b = xdr.AppendUint32(b, 0)
// pnfs_osd_object_cred4: version, credential type, opaque cred
b = xdr.AppendUint32(b, 0)
b = xdr.AppendUint32(b, 0)
b = xdr.AppendVarOpaque(b, nil)
// object id: partition id + object id
b = xdr.AppendUint64(b, 0)
b = xdr.AppendUint64(b, 1)
_ = device
return b
}
// AppendGetDeviceListRes encodes the GETDEVICELIST4resok: the cookie,
// its verifier, the device ids and the end of list flag, RFC 5661
// section 18.41.
func AppendGetDeviceListRes(b []byte, cookie uint64, cookieverf [8]byte, devices [][16]byte, eof bool) []byte {
b = xdr.AppendUint64(b, cookie)
b = append(b, cookieverf[:]...)
b = xdr.AppendUint32(b, uint32(len(devices)))
for _, d := range devices {
b = append(b, d[:]...)
}
return xdr.AppendBool(b, eof)
}
// AppendGetDeviceListArgs encodes the GETDEVICELIST argop: the layout
// type, the device count budget and the resumption cookie, RFC 5661
// section 18.41.
func AppendGetDeviceListArgs(b []byte, layoutType, maxDevices uint32, cookie uint64, cookieverf [8]byte) []byte {
b = xdr.AppendUint32(b, OpGetDeviceList)
b = xdr.AppendUint32(b, layoutType)
b = xdr.AppendUint32(b, maxDevices)
b = xdr.AppendUint64(b, cookie)
return append(b, cookieverf[:]...)
}
// LayoutTypeFlexFilesV2 is the layout type of the flexible file layout
// version 2: the layout type number 0x6 of the pNFS Layout Types
// registry, defined by the expired Internet-Draft
// draft-haynes-nfsv4-flex-filesv2-00. The draft never became an RFC;
// the number is stable and Linux clients carry the same one.
const LayoutTypeFlexFilesV2 = 6
// A FlexDataServerV2 is one ffv2_data_server4 of the draft: like the
// version one entry but with an array of stateids, the file handles
// per supported version, and the RPC credential the data server needs
// for loose coupling.
type FlexDataServerV2 struct {
DeviceID [16]byte
Efficiency uint32
Stateids []Stateid
FHs [][]byte
User string
Group string
AuthFlavor uint32
AuthBody []byte
}
// A FlexMirrorV2 is one ffv2_mirror4: the data servers of one mirror.
type FlexMirrorV2 struct {
DataServers []FlexDataServerV2
}
// AppendFlexFileLayoutBodyV2 encodes the ffv2_layout4 of
// draft-haynes-nfsv4-flex-filesv2-00 section 3.1: the stripe unit, the
// mirrors with their data servers, the flags and the stats hint. The
// device address is the version one ff_device_addr4, whose version
// array must carry one entry per stateid and per file handle.
func AppendFlexFileLayoutBodyV2(b []byte, flags uint32, stripeUnit uint64, mirrors []FlexMirrorV2) []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 = xdr.AppendUint32(b, uint32(len(ds.Stateids)))
for _, st := range ds.Stateids {
b = st.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)
// opaque_auth: the flavour and the credential body.
b = xdr.AppendUint32(b, ds.AuthFlavor)
b = xdr.AppendVarOpaque(b, ds.AuthBody)
}
}
b = xdr.AppendUint32(b, flags)
return xdr.AppendUint32(b, 0) // stats collect hint
}