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
134 lines
3.7 KiB
Go
134 lines
3.7 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// Migration and referrals of RFC 5661 section 11: names the handler
|
|
// marks as referrals resolve to stub handles whose GETATTR answers the
|
|
// fs_locations and fs_locations_info attributes and whose other
|
|
// operations answer NFS4ERR_MOVED.
|
|
|
|
package nfs4server
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfs4"
|
|
"sourcedock.dev/petrbalvin/nfs/internal/nfsfs"
|
|
)
|
|
|
|
// A Referral names the file system the client is sent to: the replica
|
|
// server list and the path of the referred root on those servers.
|
|
type Referral struct {
|
|
Servers []string
|
|
RootPath []string
|
|
}
|
|
|
|
// referralStore keeps the configured referrals by name and the stub
|
|
// handles issued for them.
|
|
type referralStore struct {
|
|
mu sync.Mutex
|
|
byRef map[string]Referral // by component name
|
|
byKey map[string]nfsfs.Handle // by component name: the stub
|
|
names map[string]string // by stub handle: the component
|
|
next uint64
|
|
}
|
|
|
|
func newReferralStore() *referralStore {
|
|
return &referralStore{
|
|
byRef: make(map[string]Referral),
|
|
byKey: make(map[string]nfsfs.Handle),
|
|
names: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
// put registers a referral and answers its stable stub handle.
|
|
func (s *referralStore) put(name string, r Referral) nfsfs.Handle {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.byRef[name] = r
|
|
if stub, ok := s.byKey[name]; ok {
|
|
return stub
|
|
}
|
|
s.next++
|
|
stub := nfsfs.Handle(append([]byte("REF/"), []byte(name)...))
|
|
s.byKey[name] = stub
|
|
s.names[string(stub)] = name
|
|
return stub
|
|
}
|
|
|
|
// byName resolves the referral configured for a component name.
|
|
func (s *referralStore) byName(name string) (Referral, bool) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
r, ok := s.byRef[name]
|
|
return r, ok
|
|
}
|
|
|
|
// byHandle resolves the referral a stub handle names.
|
|
func (s *referralStore) byHandle(fh nfsfs.Handle) (Referral, bool) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
name, ok := s.names[string(fh)]
|
|
if !ok {
|
|
return Referral{}, false
|
|
}
|
|
r, ok := s.byRef[name]
|
|
return r, ok
|
|
}
|
|
|
|
// referrals returns the referral store, made once per handler.
|
|
func (h *Handler) referrals() *referralStore {
|
|
h.mu.Lock()
|
|
defer h.mu.Unlock()
|
|
if h.refSt == nil {
|
|
h.refSt = newReferralStore()
|
|
}
|
|
return h.refSt
|
|
}
|
|
|
|
// SetReferral registers a referral: LOOKUP of the component name lands
|
|
// on a stub whose GETATTR answers the location attributes and whose
|
|
// other operations answer NFS4ERR_MOVED.
|
|
func (h *Handler) SetReferral(name string, r Referral) {
|
|
h.referrals().put(name, r)
|
|
}
|
|
|
|
// isReferralStub reports whether the handle names a referral stub.
|
|
func (h *Handler) isReferralStub(fh nfsfs.Handle) bool {
|
|
_, ok := h.referrals().byHandle(fh)
|
|
return ok
|
|
}
|
|
|
|
// stubAttrs builds the attributes of a referral stub: an absent
|
|
// directory whose location attributes carry the referral.
|
|
func (h *Handler) stubAttrs(fh nfsfs.Handle) nfs4.Attrs {
|
|
r, _ := h.referrals().byHandle(fh)
|
|
fsRoot := []string{""}
|
|
a := nfs4.Attrs{
|
|
Type: nfs4.NF4Dir,
|
|
FHExpireType: nfs4.FH4Persistent,
|
|
NamedAttr: false,
|
|
UniqueHandles: true,
|
|
}
|
|
var servers []nfs4.FsServer
|
|
for i, s := range r.Servers {
|
|
currency := int32(0)
|
|
info := []byte{rpcNoneInfo}
|
|
if i == 0 {
|
|
info[0] |= nfs4.Fsli4gfCurReq
|
|
}
|
|
info[0] |= nfs4.Fsli4gfWritable
|
|
servers = append(servers, nfs4.FsServer{Currency: currency, Info: info, Server: s})
|
|
}
|
|
a.FsLocations = nfs4.AppendFsLocations(nil, fsRoot, []nfs4.FsLocation{{
|
|
Servers: r.Servers,
|
|
RootPath: r.RootPath,
|
|
}})
|
|
a.FsLocationsInfo = nfs4.AppendFsLocationsInfo(nil, 0, 60, fsRoot,
|
|
[]nfs4.FsItem{{Entries: servers, RootPath: r.RootPath}})
|
|
return a
|
|
}
|
|
|
|
// The value of the fls_info class byte that marks this build's single
|
|
// homogeneous replica set.
|
|
const rpcNoneInfo = 0x00
|