mirror of https://github.com/restic/restic.git
184 lines
4.4 KiB
Go
184 lines
4.4 KiB
Go
//go:build darwin || freebsd || linux
|
|
// +build darwin freebsd linux
|
|
|
|
package fuse
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/restic/restic/internal/debug"
|
|
"github.com/restic/restic/internal/restic"
|
|
|
|
"bazil.org/fuse"
|
|
"bazil.org/fuse/fs"
|
|
)
|
|
|
|
// SnapshotsDir is a actual fuse directory generated from SnapshotsDirStructure
|
|
// It uses the saved prefix to filter out the relevant subtrees or entries
|
|
// from SnapshotsDirStructure.names and .latest, respectively.
|
|
type SnapshotsDir struct {
|
|
root *Root
|
|
inode uint64
|
|
dirStruct *SnapshotsDirStructure
|
|
prefix string
|
|
}
|
|
|
|
// ensure that *SnapshotsDir implements these interfaces
|
|
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
|
|
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
|
|
|
|
// NewSnapshotsDir returns a new directory structure containing snapshots and "latest" links
|
|
func NewSnapshotsDir(root *Root, inode uint64, dirStruct *SnapshotsDirStructure, prefix string) *SnapshotsDir {
|
|
debug.Log("create snapshots dir, inode %d", inode)
|
|
return &SnapshotsDir{
|
|
root: root,
|
|
inode: inode,
|
|
dirStruct: dirStruct,
|
|
prefix: prefix,
|
|
}
|
|
}
|
|
|
|
// Attr returns the attributes for any dir in the snapshots directory structure
|
|
func (d *SnapshotsDir) Attr(ctx 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
|
|
err := d.dirStruct.updateSnapshots(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := []fuse.Dirent{
|
|
{
|
|
Inode: d.inode,
|
|
Name: ".",
|
|
Type: fuse.DT_Dir,
|
|
},
|
|
{
|
|
Inode: d.root.inode,
|
|
Name: "..",
|
|
Type: fuse.DT_Dir,
|
|
},
|
|
}
|
|
|
|
// map to ensure that all names are only listed once
|
|
hasName := make(map[string]struct{})
|
|
|
|
for name := range d.dirStruct.names {
|
|
if !strings.HasPrefix(name, d.prefix) {
|
|
continue
|
|
}
|
|
shortname := strings.Split(name[len(d.prefix):], "/")[0]
|
|
if shortname == "" {
|
|
continue
|
|
}
|
|
if _, ok := hasName[shortname]; ok {
|
|
continue
|
|
}
|
|
hasName[shortname] = struct{}{}
|
|
items = append(items, fuse.Dirent{
|
|
Inode: fs.GenerateDynamicInode(d.inode, shortname),
|
|
Name: shortname,
|
|
Type: fuse.DT_Dir,
|
|
})
|
|
}
|
|
|
|
// Latest
|
|
if _, ok := d.dirStruct.latest[d.prefix]; ok {
|
|
items = append(items, fuse.Dirent{
|
|
Inode: fs.GenerateDynamicInode(d.inode, "latest"),
|
|
Name: "latest",
|
|
Type: fuse.DT_Link,
|
|
})
|
|
}
|
|
|
|
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)
|
|
|
|
err := d.dirStruct.updateSnapshots(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fullname := d.prefix + name
|
|
|
|
// check if this is already a complete snapshot path
|
|
sn := d.dirStruct.names[fullname]
|
|
if sn != nil {
|
|
return newDirFromSnapshot(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), sn)
|
|
}
|
|
|
|
// handle latest case
|
|
if name == "latest" {
|
|
link := d.dirStruct.latest[d.prefix]
|
|
sn := d.dirStruct.names[d.prefix+link]
|
|
if sn != nil {
|
|
return newSnapshotLink(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), link, sn)
|
|
}
|
|
}
|
|
|
|
// check if this is a valid subdir
|
|
fullname = fullname + "/"
|
|
for name := range d.dirStruct.names {
|
|
if strings.HasPrefix(name, fullname) {
|
|
return NewSnapshotsDir(d.root, fs.GenerateDynamicInode(d.inode, name), d.dirStruct, fullname), nil
|
|
}
|
|
}
|
|
|
|
return nil, fuse.ENOENT
|
|
}
|
|
|
|
// SnapshotLink
|
|
type snapshotLink struct {
|
|
root *Root
|
|
inode uint64
|
|
target string
|
|
snapshot *restic.Snapshot
|
|
}
|
|
|
|
var _ = fs.NodeReadlinker(&snapshotLink{})
|
|
|
|
// newSnapshotLink
|
|
func newSnapshotLink(ctx context.Context, root *Root, inode uint64, target string, snapshot *restic.Snapshot) (*snapshotLink, error) {
|
|
return &snapshotLink{root: root, inode: inode, target: target, snapshot: snapshot}, nil
|
|
}
|
|
|
|
// Readlink
|
|
func (l *snapshotLink) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
|
|
return l.target, nil
|
|
}
|
|
|
|
// Attr
|
|
func (l *snapshotLink) Attr(ctx context.Context, a *fuse.Attr) error {
|
|
a.Inode = l.inode
|
|
a.Mode = os.ModeSymlink | 0777
|
|
a.Size = uint64(len(l.target))
|
|
a.Blocks = 1 + a.Size/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
|
|
}
|