fuse: cache fs.Node instances

A particular node should always be represented by a single instance.
This is necessary to allow the fuse library to assign a stable nodeId to
a node. macOS Sonoma trips over the previous, unstable behavior when
using fuse-t.
This commit is contained in:
Michael Eischer 2024-09-09 22:15:30 +02:00
parent d8e0384940
commit 75ec7d3269
3 changed files with 71 additions and 24 deletions

View file

@ -29,6 +29,7 @@ type dir struct {
parentInode uint64
node *restic.Node
m sync.Mutex
cache treeCache
}
func cleanupNodeName(name string) string {
@ -43,6 +44,7 @@ func newDir(root *Root, inode, parentInode uint64, node *restic.Node) (*dir, err
node: node,
inode: inode,
parentInode: parentInode,
cache: *newTreeCache(),
}, nil
}
@ -87,6 +89,7 @@ func newDirFromSnapshot(root *Root, inode uint64, snapshot *restic.Snapshot) (*d
Subtree: snapshot.Tree,
},
inode: inode,
cache: *newTreeCache(),
}, nil
}
@ -208,25 +211,27 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
return nil, err
}
node, ok := d.items[name]
if !ok {
debug.Log(" Lookup(%v) -> not found", name)
return nil, syscall.ENOENT
}
inode := inodeFromNode(d.inode, node)
switch node.Type {
case "dir":
return newDir(d.root, inode, d.inode, node)
case "file":
return newFile(d.root, inode, node)
case "symlink":
return newLink(d.root, inode, node)
case "dev", "chardev", "fifo", "socket":
return newOther(d.root, inode, node)
default:
debug.Log(" node %v has unknown type %v", name, node.Type)
return nil, syscall.ENOENT
}
return d.cache.lookupOrCreate(name, func() (fs.Node, error) {
node, ok := d.items[name]
if !ok {
debug.Log(" Lookup(%v) -> not found", name)
return nil, syscall.ENOENT
}
inode := inodeFromNode(d.inode, node)
switch node.Type {
case "dir":
return newDir(d.root, inode, d.inode, node)
case "file":
return newFile(d.root, inode, node)
case "symlink":
return newLink(d.root, inode, node)
case "dev", "chardev", "fifo", "socket":
return newOther(d.root, inode, node)
default:
debug.Log(" node %v has unknown type %v", name, node.Type)
return nil, syscall.ENOENT
}
})
}
func (d *dir) Listxattr(_ context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {

View file

@ -23,6 +23,7 @@ type SnapshotsDir struct {
parentInode uint64
dirStruct *SnapshotsDirStructure
prefix string
cache treeCache
}
// ensure that *SnapshotsDir implements these interfaces
@ -38,6 +39,7 @@ func NewSnapshotsDir(root *Root, inode, parentInode uint64, dirStruct *Snapshots
parentInode: parentInode,
dirStruct: dirStruct,
prefix: prefix,
cache: *newTreeCache(),
}
}
@ -107,8 +109,12 @@ func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error)
return nil, syscall.ENOENT
}
entry := meta.names[name]
if entry != nil {
return d.cache.lookupOrCreate(name, func() (fs.Node, error) {
entry := meta.names[name]
if entry == nil {
return nil, syscall.ENOENT
}
inode := inodeFromName(d.inode, name)
if entry.linkTarget != "" {
return newSnapshotLink(d.root, inode, entry.linkTarget, entry.snapshot)
@ -116,9 +122,7 @@ func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error)
return newDirFromSnapshot(d.root, inode, entry.snapshot)
}
return NewSnapshotsDir(d.root, inode, d.inode, d.dirStruct, d.prefix+"/"+name), nil
}
return nil, syscall.ENOENT
})
}
// SnapshotLink

View file

@ -0,0 +1,38 @@
//go:build darwin || freebsd || linux
// +build darwin freebsd linux
package fuse
import (
"sync"
"github.com/anacrolix/fuse/fs"
)
type treeCache struct {
nodes map[string]fs.Node
m sync.Mutex
}
func newTreeCache() *treeCache {
return &treeCache{
nodes: map[string]fs.Node{},
}
}
func (t *treeCache) lookupOrCreate(name string, create func() (fs.Node, error)) (fs.Node, error) {
t.m.Lock()
defer t.m.Unlock()
if node, ok := t.nodes[name]; ok {
return node, nil
}
node, err := create()
if err != nil {
return nil, err
}
t.nodes[name] = node
return node, nil
}