mirror of
https://github.com/octoleo/restic.git
synced 2025-01-25 16:18:34 +00:00
Protect the snapshots cache with a RWMutex
This commit is contained in:
parent
0e7c1668d5
commit
e654a9659c
@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
@ -79,7 +80,7 @@ func (cmd CmdMount) Execute(args []string) error {
|
|||||||
root := fs.Tree{}
|
root := fs.Tree{}
|
||||||
root.Add("snapshots", &snapshots{
|
root.Add("snapshots", &snapshots{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
knownSnapshots: make(map[string]*restic.Snapshot),
|
knownSnapshots: make(map[string]snapshotWithId),
|
||||||
})
|
})
|
||||||
|
|
||||||
cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint)
|
cmd.global.Printf("Now serving %s under %s\n", repo.Backend().Location(), mountpoint)
|
||||||
@ -96,6 +97,11 @@ func (cmd CmdMount) Execute(args []string) error {
|
|||||||
return c.MountError
|
return c.MountError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type snapshotWithId struct {
|
||||||
|
*restic.Snapshot
|
||||||
|
backend.ID
|
||||||
|
}
|
||||||
|
|
||||||
// These lines statically ensure that a *snapshots implement the given
|
// These lines statically ensure that a *snapshots implement the given
|
||||||
// interfaces; a misplaced refactoring of the implementation that breaks
|
// interfaces; a misplaced refactoring of the implementation that breaks
|
||||||
// the interface will be catched by the compiler
|
// the interface will be catched by the compiler
|
||||||
@ -105,8 +111,9 @@ var _ = fs.NodeStringLookuper(&snapshots{})
|
|||||||
type snapshots struct {
|
type snapshots struct {
|
||||||
repo *repository.Repository
|
repo *repository.Repository
|
||||||
|
|
||||||
// snapshot timestamp -> snapshot
|
// knownSnapshots maps snapshot timestamp to the snapshot
|
||||||
knownSnapshots map[string]*restic.Snapshot
|
sync.RWMutex
|
||||||
|
knownSnapshots map[string]snapshotWithId
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error {
|
func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error {
|
||||||
@ -115,16 +122,39 @@ func (sn *snapshots) Attr(ctx context.Context, a *fuse.Attr) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
func (sn *snapshots) updateCache(ctx context.Context) error {
|
||||||
ret := make([]fuse.Dirent, 0)
|
sn.Lock()
|
||||||
|
defer sn.Unlock()
|
||||||
|
|
||||||
for id := range sn.repo.List(backend.Snapshot, ctx.Done()) {
|
for id := range sn.repo.List(backend.Snapshot, ctx.Done()) {
|
||||||
snapshot, err := restic.LoadSnapshot(sn.repo, id)
|
snapshot, err := restic.LoadSnapshot(sn.repo, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshot
|
sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = snapshotWithId{snapshot, id}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (sn *snapshots) get(name string) (snapshot snapshotWithId, ok bool) {
|
||||||
|
sn.Lock()
|
||||||
|
snapshot, ok = sn.knownSnapshots[name]
|
||||||
|
sn.Unlock()
|
||||||
|
return snapshot, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||||
|
err := sn.updateCache(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sn.RLock()
|
||||||
|
defer sn.RUnlock()
|
||||||
|
|
||||||
|
ret := make([]fuse.Dirent, 0)
|
||||||
|
for _, snapshot := range sn.knownSnapshots {
|
||||||
ret = append(ret, fuse.Dirent{
|
ret = append(ret, fuse.Dirent{
|
||||||
Inode: binary.BigEndian.Uint64(id[:8]),
|
Inode: binary.BigEndian.Uint64(snapshot.ID[:8]),
|
||||||
Type: fuse.DT_Dir,
|
Type: fuse.DT_Dir,
|
||||||
Name: snapshot.Time.Format(time.RFC3339),
|
Name: snapshot.Time.Format(time.RFC3339),
|
||||||
})
|
})
|
||||||
@ -134,21 +164,21 @@ func (sn *snapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||||
if _, ok := sn.knownSnapshots[name]; !ok {
|
snapshot, ok := sn.get(name)
|
||||||
// At least this snapshot is not cached. We use this opportunity to
|
|
||||||
// load all missing snapshots
|
if !ok {
|
||||||
for id := range sn.repo.List(backend.Snapshot, ctx.Done()) {
|
// We don't know about it, update the cache
|
||||||
snapshot, err := restic.LoadSnapshot(sn.repo, id)
|
err := sn.updateCache(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
snapshotName := snapshot.Time.Format(time.RFC3339)
|
snapshot, ok = sn.get(name)
|
||||||
sn.knownSnapshots[snapshotName] = snapshot
|
if !ok {
|
||||||
break
|
// We still don't know about it, this time it really doesn't exist
|
||||||
|
return nil, fuse.ENOENT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshot := sn.knownSnapshots[name]
|
|
||||||
tree, err := restic.LoadTree(sn.repo, snapshot.Tree)
|
tree, err := restic.LoadTree(sn.repo, snapshot.Tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -157,7 +187,6 @@ func (sn *snapshots) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|||||||
repo: sn.repo,
|
repo: sn.repo,
|
||||||
tree: tree,
|
tree: tree,
|
||||||
}, nil
|
}, nil
|
||||||
return nil, fuse.ENOENT
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statically ensure that *dir implement those interface
|
// Statically ensure that *dir implement those interface
|
||||||
|
109
cmd/restic/fuse/snapshot.go
Normal file
109
cmd/restic/fuse/snapshot.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package fuse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"bazil.org/fuse"
|
||||||
|
"bazil.org/fuse/fs"
|
||||||
|
|
||||||
|
"github.com/restic/restic"
|
||||||
|
"github.com/restic/restic/backend"
|
||||||
|
"github.com/restic/restic/repository"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SnapshotWithId struct {
|
||||||
|
*restic.Snapshot
|
||||||
|
backend.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 *repository.Repository
|
||||||
|
|
||||||
|
// knownSnapshots maps snapshot timestamp to the snapshot
|
||||||
|
sync.RWMutex
|
||||||
|
knownSnapshots map[string]SnapshotWithId
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSnapshotsDir(repo *repository.Repository) *SnapshotsDir {
|
||||||
|
return &SnapshotsDir{
|
||||||
|
repo: repo,
|
||||||
|
knownSnapshots: make(map[string]SnapshotWithId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sn *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error {
|
||||||
|
attr.Inode = 0
|
||||||
|
attr.Mode = os.ModeDir | 0555
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sn *SnapshotsDir) updateCache(ctx context.Context) error {
|
||||||
|
sn.Lock()
|
||||||
|
defer sn.Unlock()
|
||||||
|
|
||||||
|
for id := range sn.repo.List(backend.Snapshot, ctx.Done()) {
|
||||||
|
snapshot, err := restic.LoadSnapshot(sn.repo, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sn.knownSnapshots[snapshot.Time.Format(time.RFC3339)] = SnapshotWithId{snapshot, id}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (sn *SnapshotsDir) get(name string) (snapshot SnapshotWithId, ok bool) {
|
||||||
|
sn.RLock()
|
||||||
|
snapshot, ok = sn.knownSnapshots[name]
|
||||||
|
sn.RUnlock()
|
||||||
|
return snapshot, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sn *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||||
|
err := sn.updateCache(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sn.RLock()
|
||||||
|
defer sn.RUnlock()
|
||||||
|
|
||||||
|
ret := make([]fuse.Dirent, 0)
|
||||||
|
for _, snapshot := range sn.knownSnapshots {
|
||||||
|
ret = append(ret, fuse.Dirent{
|
||||||
|
Inode: binary.BigEndian.Uint64(snapshot.ID[:8]),
|
||||||
|
Type: fuse.DT_Dir,
|
||||||
|
Name: snapshot.Time.Format(time.RFC3339),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sn *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||||
|
snapshot, ok := sn.get(name)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
// We don't know about it, update the cache
|
||||||
|
err := sn.updateCache(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
snapshot, ok = sn.get(name)
|
||||||
|
if !ok {
|
||||||
|
// We still don't know about it, this time it really doesn't exist
|
||||||
|
return nil, fuse.ENOENT
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newDirFromSnapshot(sn.repo, snapshot)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user