mirror of
https://github.com/restic/restic.git
synced 2026-06-26 18:44:17 +00:00
32be2e559b
The treeCache in SnapshotsDir was never cleared when snapshots were reloaded. This caused the "latest" symlink to keep pointing to the previous snapshot even after new snapshots were added. Add a generation counter to SnapshotsDirStructure that is incremented whenever the directory structure is rebuilt (in makeDirs). The treeCache checks this generation on each lookup and resets itself when the generation changes, ensuring cached nodes (including symlinks) are refreshed after a snapshot reload.
176 lines
4.2 KiB
Go
176 lines
4.2 KiB
Go
//go:build darwin || freebsd || linux
|
|
|
|
package fuse
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"syscall"
|
|
|
|
"github.com/restic/restic/internal/data"
|
|
"github.com/restic/restic/internal/debug"
|
|
|
|
"github.com/anacrolix/fuse"
|
|
"github.com/anacrolix/fuse/fs"
|
|
)
|
|
|
|
// SnapshotsDir is a actual fuse directory generated from SnapshotsDirStructure
|
|
// It uses the saved prefix to select the corresponding MetaDirData.
|
|
type SnapshotsDir struct {
|
|
root *Root
|
|
forget forgetFn
|
|
inode uint64
|
|
parentInode uint64
|
|
dirStruct *SnapshotsDirStructure
|
|
prefix string
|
|
cache treeCache
|
|
}
|
|
|
|
// ensure that *SnapshotsDir implements these interfaces
|
|
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
|
|
var _ = fs.NodeForgetter(&SnapshotsDir{})
|
|
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
|
|
|
|
// NewSnapshotsDir returns a new directory structure containing snapshots and "latest" links
|
|
func NewSnapshotsDir(root *Root, forget forgetFn, inode, parentInode uint64, dirStruct *SnapshotsDirStructure, prefix string) *SnapshotsDir {
|
|
debug.Log("create snapshots dir, inode %d", inode)
|
|
return &SnapshotsDir{
|
|
root: root,
|
|
forget: forget,
|
|
inode: inode,
|
|
parentInode: parentInode,
|
|
dirStruct: dirStruct,
|
|
prefix: prefix,
|
|
cache: *newTreeCache(),
|
|
}
|
|
}
|
|
|
|
// Attr returns the attributes for any dir in the snapshots directory structure
|
|
func (d *SnapshotsDir) Attr(_ context.Context, attr *fuse.Attr) error {
|
|
attr.Inode = d.inode
|
|
attr.Mode = os.ModeDir | 0555
|
|
attr.Uid = d.root.uid
|
|
attr.Gid = d.root.gid
|
|
|
|
debug.Log("attr: %v", attr)
|
|
return nil
|
|
}
|
|
|
|
// ReadDirAll returns all entries of the SnapshotsDir.
|
|
func (d *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|
debug.Log("ReadDirAll()")
|
|
|
|
// update snapshots
|
|
meta, _, err := d.dirStruct.UpdatePrefix(ctx, d.prefix)
|
|
if err != nil {
|
|
return nil, unwrapCtxCanceled(err)
|
|
} else if meta == nil {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
|
|
items := []fuse.Dirent{
|
|
{
|
|
Inode: d.inode,
|
|
Name: ".",
|
|
Type: fuse.DT_Dir,
|
|
},
|
|
{
|
|
Inode: d.parentInode,
|
|
Name: "..",
|
|
Type: fuse.DT_Dir,
|
|
},
|
|
}
|
|
|
|
for name, entry := range meta.names {
|
|
if ctx.Err() != nil {
|
|
return nil, ctx.Err()
|
|
}
|
|
|
|
d := fuse.Dirent{
|
|
Inode: inodeFromName(d.inode, name),
|
|
Name: name,
|
|
Type: fuse.DT_Dir,
|
|
}
|
|
if entry.linkTarget != "" {
|
|
d.Type = fuse.DT_Link
|
|
}
|
|
items = append(items, d)
|
|
}
|
|
|
|
return items, nil
|
|
}
|
|
|
|
// Lookup returns a specific entry from the SnapshotsDir.
|
|
func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|
debug.Log("Lookup(%s)", name)
|
|
|
|
meta, gen, err := d.dirStruct.UpdatePrefix(ctx, d.prefix)
|
|
if err != nil {
|
|
return nil, unwrapCtxCanceled(err)
|
|
} else if meta == nil {
|
|
return nil, syscall.ENOENT
|
|
}
|
|
|
|
return d.cache.lookupOrCreate(name, gen, func(forget forgetFn) (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, forget, inode, entry.linkTarget, entry.snapshot)
|
|
} else if entry.snapshot != nil {
|
|
return newDirFromSnapshot(d.root, forget, inode, entry.snapshot)
|
|
}
|
|
return NewSnapshotsDir(d.root, forget, inode, d.inode, d.dirStruct, d.prefix+"/"+name), nil
|
|
})
|
|
}
|
|
|
|
func (d *SnapshotsDir) Forget() {
|
|
d.forget()
|
|
}
|
|
|
|
// SnapshotLink
|
|
type snapshotLink struct {
|
|
root *Root
|
|
forget forgetFn
|
|
inode uint64
|
|
target string
|
|
snapshot *data.Snapshot
|
|
}
|
|
|
|
var _ = fs.NodeForgetter(&snapshotLink{})
|
|
var _ = fs.NodeReadlinker(&snapshotLink{})
|
|
|
|
// newSnapshotLink
|
|
func newSnapshotLink(root *Root, forget forgetFn, inode uint64, target string, snapshot *data.Snapshot) (*snapshotLink, error) {
|
|
return &snapshotLink{root: root, forget: forget, inode: inode, target: target, snapshot: snapshot}, nil
|
|
}
|
|
|
|
// Readlink
|
|
func (l *snapshotLink) Readlink(_ context.Context, _ *fuse.ReadlinkRequest) (string, error) {
|
|
return l.target, nil
|
|
}
|
|
|
|
// Attr
|
|
func (l *snapshotLink) Attr(_ context.Context, a *fuse.Attr) error {
|
|
a.Inode = l.inode
|
|
a.Mode = os.ModeSymlink | 0777
|
|
a.Size = uint64(len(l.target))
|
|
a.Blocks = (a.Size + blockSize - 1) / blockSize
|
|
a.Uid = l.root.uid
|
|
a.Gid = l.root.gid
|
|
a.Atime = l.snapshot.Time
|
|
a.Ctime = l.snapshot.Time
|
|
a.Mtime = l.snapshot.Time
|
|
|
|
a.Nlink = 1
|
|
|
|
return nil
|
|
}
|
|
|
|
func (l *snapshotLink) Forget() {
|
|
l.forget()
|
|
}
|