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

View file

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