2
2
mirror of https://github.com/octoleo/restic.git synced 2024-06-03 09:30:50 +00:00
restic/src/restic/fuse/snapshot.go
2017-03-08 19:59:19 +01:00

163 lines
3.7 KiB
Go

// +build !openbsd
// +build !windows
package fuse
import (
"fmt"
"os"
"sync"
"time"
"bazil.org/fuse"
"bazil.org/fuse/fs"
"restic"
"restic/debug"
"golang.org/x/net/context"
)
type SnapshotWithId struct {
*restic.Snapshot
restic.ID
}
// These lines statically ensure that a *SnapshotsDir implement the given
// interfaces; a misplaced refactoring of the implementation that breaks
// the interface will be catched by the compiler
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
type SnapshotsDir struct {
repo restic.Repository
ownerIsRoot bool
paths []string
tags []string
host string
// knownSnapshots maps snapshot timestamp to the snapshot
sync.RWMutex
knownSnapshots map[string]SnapshotWithId
processed restic.IDSet
}
// NewSnapshotsDir returns a new dir object for the snapshots.
func NewSnapshotsDir(repo restic.Repository, ownerIsRoot bool, paths []string, tags []string, host string) *SnapshotsDir {
debug.Log("fuse mount initiated")
return &SnapshotsDir{
repo: repo,
ownerIsRoot: ownerIsRoot,
paths: paths,
tags: tags,
host: host,
knownSnapshots: make(map[string]SnapshotWithId),
processed: restic.NewIDSet(),
}
}
func (sn *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error {
attr.Inode = 0
attr.Mode = os.ModeDir | 0555
if !sn.ownerIsRoot {
attr.Uid = uint32(os.Getuid())
attr.Gid = uint32(os.Getgid())
}
debug.Log("attr is %v", attr)
return nil
}
func (sn *SnapshotsDir) updateCache(ctx context.Context) error {
debug.Log("called")
sn.Lock()
defer sn.Unlock()
for id := range sn.repo.List(restic.SnapshotFile, ctx.Done()) {
if sn.processed.Has(id) {
debug.Log("skipping snapshot %v, already in list", id.Str())
continue
}
debug.Log("found snapshot id %v", id.Str())
snapshot, err := restic.LoadSnapshot(sn.repo, id)
if err != nil {
return err
}
// Filter snapshots we don't care for.
if (sn.host != "" && sn.host != snapshot.Hostname) ||
!snapshot.HasTags(sn.tags) ||
!snapshot.HasPaths(sn.paths) {
continue
}
timestamp := snapshot.Time.Format(time.RFC3339)
for i := 1; ; i++ {
if _, ok := sn.knownSnapshots[timestamp]; !ok {
break
}
timestamp = fmt.Sprintf("%s-%d", snapshot.Time.Format(time.RFC3339), i)
}
debug.Log(" add %v as dir %v", id.Str(), timestamp)
sn.knownSnapshots[timestamp] = SnapshotWithId{snapshot, id}
sn.processed.Insert(id)
}
return nil
}
func (sn *SnapshotsDir) get(name string) (snapshot SnapshotWithId, ok bool) {
sn.RLock()
snapshot, ok = sn.knownSnapshots[name]
sn.RUnlock()
debug.Log("get(%s) -> %v %v", name, snapshot, ok)
return snapshot, ok
}
func (sn *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
debug.Log("called")
err := sn.updateCache(ctx)
if err != nil {
return nil, err
}
sn.RLock()
defer sn.RUnlock()
ret := make([]fuse.Dirent, 0)
for timestamp, snapshot := range sn.knownSnapshots {
ret = append(ret, fuse.Dirent{
Inode: inodeFromBackendID(snapshot.ID),
Type: fuse.DT_Dir,
Name: timestamp,
})
}
debug.Log(" -> %d entries", len(ret))
return ret, nil
}
func (sn *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
debug.Log("Lookup(%s)", name)
snapshot, ok := sn.get(name)
if !ok {
// We don't know about it, update the cache
err := sn.updateCache(ctx)
if err != nil {
debug.Log(" Lookup(%s) -> err %v", name, err)
return nil, err
}
snapshot, ok = sn.get(name)
if !ok {
// We still don't know about it, this time it really doesn't exist
debug.Log(" Lookup(%s) -> not found", name)
return nil, fuse.ENOENT
}
}
return newDirFromSnapshot(sn.repo, snapshot, sn.ownerIsRoot)
}