Files
rclone/vfs/aux.go
T
Nick Craig-Wood e006d7c13f vfs: fix crash when multiple mounts or servers share the same VFS
The VFS is shared between users with the same remote and options, for
example two mounts created over the rc, or a mount and an NFS server.

Each node has a single Sys() slot which mount, mount2 and serve nfs
all used to attach their per-node data. With a shared VFS the users
overwrote each other's data: at best churning the cached FUSE nodes,
and since the slot was an atomic.Value, panicking with "store of
inconsistently typed value" as soon as two users stored different
types on the same node.

This change gives each node auxiliary values keyed by owner, set with
SetAux and read with Aux, so each user of the VFS has an independent
slot. The mounts now cache their FUSE nodes under their own key,
leaving Sys - which is read through the os.FileInfo interface -
reserved for users like serve nfs which need to control what that
returns.

Nodes with nothing attached use less memory than before (one pointer
instead of an atomic.Value) and reads remain lock free.

Bug discovered while thinking about #9617
2026-07-27 14:55:20 +01:00

86 lines
2.3 KiB
Go

package vfs
import "sync/atomic"
// auxEntry associates an owner with a value attached to a node.
type auxEntry struct {
owner, value any
}
// aux holds auxiliary values attached to a node, keyed by owner.
//
// It is embedded in Dir and File to provide the Aux, SetAux, Sys and
// SetSys methods of the Node interface.
//
// Reads are lock free. Writes copy the entry list, which is assumed
// to be very short.
type aux struct {
entries atomic.Pointer[[]auxEntry]
}
// sysOwner is the owner Sys and SetSys attach their value under.
type sysOwner struct{}
// Aux returns the value attached to the node for owner, or nil if
// none is attached.
func (a *aux) Aux(owner any) any {
if entries := a.entries.Load(); entries != nil {
for _, entry := range *entries {
if entry.owner == owner {
return entry.value
}
}
}
return nil
}
// SetAux attaches value to the node for owner, replacing any value
// owner attached before. Attaching nil removes owner's value.
//
// Values attached by different owners are independent, so multiple
// users of a shared VFS do not conflict. Owner must be comparable and
// unique to the user - a pointer to the user's filesystem struct is a
// good choice.
func (a *aux) SetAux(owner, value any) {
for {
old := a.entries.Load()
var entries []auxEntry
if old != nil {
entries = make([]auxEntry, 0, len(*old)+1)
for _, entry := range *old {
if entry.owner != owner {
entries = append(entries, entry)
}
}
}
if value != nil {
entries = append(entries, auxEntry{owner: owner, value: value})
}
var next *[]auxEntry
if len(entries) != 0 {
next = &entries
}
if a.entries.CompareAndSwap(old, next) {
return
}
}
}
// Sys returns the value set with SetSys (can be nil) - satisfies
// os.FileInfo.
//
// This is what callers reading the node through os.FileInfo (e.g. the
// NFS server library) will see. It is reserved for users which need
// to control that; users caching their own data on the node should
// use Aux and SetAux instead. As the VFS may be shared, the value
// stored here should be derived from the node alone so that all users
// store the same value.
func (a *aux) Sys() any {
return a.Aux(sysOwner{})
}
// SetSys sets the value returned by Sys - see Sys for the contract.
func (a *aux) SetSys(x any) {
a.SetAux(sysOwner{}, x)
}