restic/internal/fuse/snapshots_dir.go
2017-09-21 16:41:20 +02:00

169 lines
3.8 KiB
Go

// +build !openbsd
// +build !windows
package fuse
import (
"fmt"
"os"
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
"golang.org/x/net/context"
"bazil.org/fuse"
"bazil.org/fuse/fs"
)
// SnapshotsDir is a fuse directory which contains snapshots.
type SnapshotsDir struct {
inode uint64
root *Root
snapshots restic.Snapshots
names map[string]*restic.Snapshot
latest string
}
// ensure that *SnapshotsDir implements these interfaces
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
var _ = fs.NodeReadlinker(&snapshotLink{})
// NewSnapshotsDir returns a new directory containing snapshots.
func NewSnapshotsDir(root *Root, inode uint64, snapshots restic.Snapshots) *SnapshotsDir {
debug.Log("create snapshots dir with %d snapshots, inode %d", len(snapshots), inode)
d := &SnapshotsDir{
root: root,
inode: inode,
snapshots: snapshots,
names: make(map[string]*restic.Snapshot, len(snapshots)),
}
// Track latest Snapshot
var latestTime time.Time
d.latest = ""
for _, sn := range snapshots {
name := sn.Time.Format(time.RFC3339)
if d.latest == "" || !sn.Time.Before(latestTime) {
latestTime = sn.Time
d.latest = name
}
for i := 1; ; i++ {
if _, ok := d.names[name]; !ok {
break
}
name = fmt.Sprintf("%s-%d", sn.Time.Format(time.RFC3339), i)
}
d.names[name] = sn
debug.Log(" add snapshot %v as dir %v", sn.ID().Str(), name)
}
return d
}
// Attr returns the attributes for the root node.
func (d *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error {
attr.Inode = d.inode
attr.Mode = os.ModeDir | 0555
if !d.root.cfg.OwnerIsRoot {
attr.Uid = uint32(os.Getuid())
attr.Gid = uint32(os.Getgid())
}
debug.Log("attr: %v", attr)
return nil
}
// ReadDirAll returns all entries of the root node.
func (d *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
debug.Log("ReadDirAll()")
items := []fuse.Dirent{
{
Inode: d.inode,
Name: ".",
Type: fuse.DT_Dir,
},
{
Inode: d.root.inode,
Name: "..",
Type: fuse.DT_Dir,
},
}
for name := range d.names {
items = append(items, fuse.Dirent{
Inode: fs.GenerateDynamicInode(d.inode, name),
Name: name,
Type: fuse.DT_Dir,
})
}
// Latest
if d.latest != "" {
items = append(items, fuse.Dirent{
Inode: fs.GenerateDynamicInode(d.inode, "latest"),
Name: "latest",
Type: fuse.DT_Link,
})
}
return items, nil
}
type snapshotLink struct {
root *Root
inode uint64
target string
snapshot *restic.Snapshot
}
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
}
func (l *snapshotLink) Readlink(ctx context.Context, req *fuse.ReadlinkRequest) (string, error) {
return l.target, nil
}
func (l *snapshotLink) Attr(ctx context.Context, a *fuse.Attr) error {
a.Inode = l.inode
a.Mode = os.ModeSymlink | 0777
if !l.root.cfg.OwnerIsRoot {
a.Uid = uint32(os.Getuid())
a.Gid = uint32(os.Getgid())
}
a.Atime = l.snapshot.Time
a.Ctime = l.snapshot.Time
a.Mtime = l.snapshot.Time
a.Nlink = 1
return nil
}
// Lookup returns a specific entry from the root node.
func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
debug.Log("Lookup(%s)", name)
sn, ok := d.names[name]
if !ok {
if name == "latest" && d.latest != "" {
sn2, ok2 := d.names[d.latest]
// internal error
if !ok2 {
return nil, fuse.ENOENT
}
return newSnapshotLink(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), d.latest, sn2)
}
return nil, fuse.ENOENT
}
return newDirFromSnapshot(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), sn)
}