// Copyright (c) 2026 Petr BalvĂ­n (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