Files

134 lines
3.7 KiB
Go
Raw Permalink Normal View History

// 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