2017-06-18 15:02:07 +00:00
|
|
|
// +build !openbsd
|
|
|
|
// +build !windows
|
|
|
|
|
2017-06-18 12:59:44 +00:00
|
|
|
package fuse
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"time"
|
|
|
|
|
2017-07-23 12:21:03 +00:00
|
|
|
"github.com/restic/restic/internal/debug"
|
2017-07-24 15:42:25 +00:00
|
|
|
"github.com/restic/restic/internal/restic"
|
2017-07-23 12:21:03 +00:00
|
|
|
|
2017-06-18 12:59:44 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
|
|
|
|
"bazil.org/fuse"
|
|
|
|
"bazil.org/fuse/fs"
|
|
|
|
)
|
|
|
|
|
2017-06-18 15:06:27 +00:00
|
|
|
// SnapshotsDir is a fuse directory which contains snapshots.
|
|
|
|
type SnapshotsDir struct {
|
2017-06-18 12:59:44 +00:00
|
|
|
inode uint64
|
|
|
|
root *Root
|
|
|
|
snapshots restic.Snapshots
|
|
|
|
names map[string]*restic.Snapshot
|
2017-09-14 17:44:03 +00:00
|
|
|
latest string
|
2017-06-18 12:59:44 +00:00
|
|
|
}
|
|
|
|
|
2017-09-17 15:34:19 +00:00
|
|
|
// ensure that *SnapshotsDir implements these interfaces
|
2017-06-18 15:06:27 +00:00
|
|
|
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
|
|
|
|
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
|
2017-09-14 17:44:03 +00:00
|
|
|
var _ = fs.NodeReadlinker(&snapshotLink{})
|
2017-06-18 12:59:44 +00:00
|
|
|
|
2017-06-18 18:25:31 +00:00
|
|
|
// NewSnapshotsDir returns a new directory containing snapshots.
|
|
|
|
func NewSnapshotsDir(root *Root, inode uint64, snapshots restic.Snapshots) *SnapshotsDir {
|
2017-06-18 12:59:44 +00:00
|
|
|
debug.Log("create snapshots dir with %d snapshots, inode %d", len(snapshots), inode)
|
2017-06-18 15:06:27 +00:00
|
|
|
d := &SnapshotsDir{
|
2017-06-18 12:59:44 +00:00
|
|
|
root: root,
|
|
|
|
inode: inode,
|
|
|
|
snapshots: snapshots,
|
|
|
|
names: make(map[string]*restic.Snapshot, len(snapshots)),
|
|
|
|
}
|
|
|
|
|
2017-09-14 17:44:03 +00:00
|
|
|
// Track latest Snapshot
|
|
|
|
var latestTime time.Time
|
|
|
|
d.latest = ""
|
|
|
|
|
2017-06-18 12:59:44 +00:00
|
|
|
for _, sn := range snapshots {
|
|
|
|
name := sn.Time.Format(time.RFC3339)
|
2017-09-14 17:44:03 +00:00
|
|
|
if d.latest == "" || !sn.Time.Before(latestTime) {
|
|
|
|
latestTime = sn.Time
|
|
|
|
d.latest = name
|
|
|
|
}
|
2017-06-18 12:59:44 +00:00
|
|
|
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.
|
2017-06-18 15:06:27 +00:00
|
|
|
func (d *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error {
|
2017-06-18 12:59:44 +00:00
|
|
|
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.
|
2017-06-18 15:06:27 +00:00
|
|
|
func (d *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
2017-06-18 12:59:44 +00:00
|
|
|
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,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-09-14 17:44:03 +00:00
|
|
|
// Latest
|
|
|
|
if d.latest != "" {
|
|
|
|
items = append(items, fuse.Dirent{
|
|
|
|
Inode: fs.GenerateDynamicInode(d.inode, "latest"),
|
|
|
|
Name: "latest",
|
|
|
|
Type: fuse.DT_Link,
|
|
|
|
})
|
|
|
|
}
|
2017-06-18 12:59:44 +00:00
|
|
|
return items, nil
|
|
|
|
}
|
|
|
|
|
2017-09-14 17:44:03 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2017-06-18 12:59:44 +00:00
|
|
|
// Lookup returns a specific entry from the root node.
|
2017-06-18 15:06:27 +00:00
|
|
|
func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
2017-06-18 12:59:44 +00:00
|
|
|
debug.Log("Lookup(%s)", name)
|
|
|
|
|
|
|
|
sn, ok := d.names[name]
|
|
|
|
if !ok {
|
2017-09-14 17:44:03 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-06-18 12:59:44 +00:00
|
|
|
return nil, fuse.ENOENT
|
|
|
|
}
|
|
|
|
|
2017-06-18 13:11:32 +00:00
|
|
|
return newDirFromSnapshot(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), sn)
|
2017-06-18 12:59:44 +00:00
|
|
|
}
|