forked from TrueCloudLab/restic
0e9716a6e6
Forget fs.Node instances once the kernel frees the corresponding nodeId. This ensures that restic does not run out of memory on large snapshots.
176 lines
4.3 KiB
Go
176 lines
4.3 KiB
Go
//go:build darwin || freebsd || linux
|
|
// +build darwin freebsd linux
|
|
|
|
package fuse
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"syscall"
|
|
|
|
"github.com/restic/restic/internal/debug"
|
|
"github.com/restic/restic/internal/restic"
|
|
|
|
"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, 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, 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 *restic.Snapshot
|
|
}
|
|
|
|
var _ = fs.NodeForgetter(&snapshotLink{})
|
|
var _ = fs.NodeReadlinker(&snapshotLink{})
|
|
|
|
// newSnapshotLink
|
|
func newSnapshotLink(root *Root, forget forgetFn, inode uint64, target string, snapshot *restic.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()
|
|
}
|