// +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) }