diff --git a/src/restic/fuse/dir.go b/src/restic/fuse/dir.go index 9546d81f6..97effbf42 100644 --- a/src/restic/fuse/dir.go +++ b/src/restic/fuse/dir.go @@ -19,15 +19,16 @@ var _ = fs.HandleReadDirAller(&dir{}) var _ = fs.NodeStringLookuper(&dir{}) type dir struct { - root *Root - items map[string]*restic.Node - inode uint64 - node *restic.Node + root *Root + items map[string]*restic.Node + inode uint64 + parentInode uint64 + node *restic.Node blobsize *BlobSizeCache } -func newDir(ctx context.Context, root *Root, inode uint64, node *restic.Node) (*dir, error) { +func newDir(ctx context.Context, root *Root, inode, parentInode uint64, node *restic.Node) (*dir, error) { debug.Log("new dir for %v (%v)", node.Name, node.Subtree.Str()) tree, err := root.repo.LoadTree(ctx, *node.Subtree) if err != nil { @@ -40,10 +41,11 @@ func newDir(ctx context.Context, root *Root, inode uint64, node *restic.Node) (* } return &dir{ - root: root, - node: node, - items: items, - inode: inode, + root: root, + node: node, + items: items, + inode: inode, + parentInode: parentInode, }, nil } @@ -134,7 +136,19 @@ func (d *dir) calcNumberOfLinks() uint32 { func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { debug.Log("called") - ret := make([]fuse.Dirent, 0, len(d.items)) + ret := make([]fuse.Dirent, 0, len(d.items)+2) + + ret = append(ret, fuse.Dirent{ + Inode: d.inode, + Name: ".", + Type: fuse.DT_Dir, + }) + + ret = append(ret, fuse.Dirent{ + Inode: d.parentInode, + Name: "..", + Type: fuse.DT_Dir, + }) for _, node := range d.items { var typ fuse.DirentType @@ -166,7 +180,7 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) { } switch node.Type { case "dir": - return newDir(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), node) + return newDir(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), d.inode, node) case "file": return newFile(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), node) case "symlink": diff --git a/src/restic/fuse/meta_dir.go b/src/restic/fuse/meta_dir.go new file mode 100644 index 000000000..8542b14c8 --- /dev/null +++ b/src/restic/fuse/meta_dir.go @@ -0,0 +1,87 @@ +// +build !openbsd +// +build !windows + +package fuse + +import ( + "os" + "restic/debug" + + "golang.org/x/net/context" + + "bazil.org/fuse" + "bazil.org/fuse/fs" +) + +// ensure that *DirSnapshots implements these interfaces +var _ = fs.HandleReadDirAller(&MetaDir{}) +var _ = fs.NodeStringLookuper(&MetaDir{}) + +// MetaDir is a fuse directory which contains other directories. +type MetaDir struct { + inode uint64 + root *Root + entries map[string]fs.Node +} + +// NewMetaDir returns a new meta dir. +func NewMetaDir(root *Root, inode uint64, entries map[string]fs.Node) *MetaDir { + debug.Log("new meta dir with %d entries, inode %d", len(entries), inode) + + return &MetaDir{ + root: root, + inode: inode, + entries: entries, + } +} + +// Attr returns the attributes for the root node. +func (d *MetaDir) 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 *MetaDir) 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.entries { + items = append(items, fuse.Dirent{ + Inode: fs.GenerateDynamicInode(d.inode, name), + Name: name, + Type: fuse.DT_Dir, + }) + } + + return items, nil +} + +// Lookup returns a specific entry from the root node. +func (d *MetaDir) Lookup(ctx context.Context, name string) (fs.Node, error) { + debug.Log("Lookup(%s)", name) + + if dir, ok := d.entries[name]; ok { + return dir, nil + } + + return nil, fuse.ENOENT +} diff --git a/src/restic/fuse/root.go b/src/restic/fuse/root.go index bf3fa9068..b82502c7a 100644 --- a/src/restic/fuse/root.go +++ b/src/restic/fuse/root.go @@ -4,13 +4,11 @@ package fuse import ( - "os" "restic" "restic/debug" "golang.org/x/net/context" - "bazil.org/fuse" "bazil.org/fuse/fs" ) @@ -28,14 +26,17 @@ type Root struct { cfg Config inode uint64 snapshots restic.Snapshots - dirSnapshots *DirSnapshots blobSizeCache *BlobSizeCache + + *MetaDir } // ensure that *Root implements these interfaces var _ = fs.HandleReadDirAller(&Root{}) var _ = fs.NodeStringLookuper(&Root{}) +const rootInode = 1 + // NewRoot initializes a new root node from a repository. func NewRoot(ctx context.Context, repo restic.Repository, cfg Config) (*Root, error) { debug.Log("NewRoot(), config %v", cfg) @@ -44,78 +45,66 @@ func NewRoot(ctx context.Context, repo restic.Repository, cfg Config) (*Root, er debug.Log("found %d matching snapshots", len(snapshots)) root := &Root{ - repo: repo, - cfg: cfg, - inode: 1, - snapshots: snapshots, + repo: repo, + inode: rootInode, + cfg: cfg, + snapshots: snapshots, + blobSizeCache: NewBlobSizeCache(ctx, repo.Index()), } - root.dirSnapshots = NewDirSnapshots(root, fs.GenerateDynamicInode(root.inode, "snapshots"), snapshots) - root.blobSizeCache = NewBlobSizeCache(ctx, repo.Index()) + entries := map[string]fs.Node{ + "snapshots": NewSnapshotsDir(root, fs.GenerateDynamicInode(root.inode, "snapshots"), snapshots), + "tags": NewTagsDir(root, fs.GenerateDynamicInode(root.inode, "tags"), snapshots), + "hosts": NewHostsDir(root, fs.GenerateDynamicInode(root.inode, "hosts"), snapshots), + } + + root.MetaDir = NewMetaDir(root, rootInode, entries) return root, nil } +// NewTagsDir returns a new directory containing entries, which in turn contains +// snapshots with this tag set. +func NewTagsDir(root *Root, inode uint64, snapshots restic.Snapshots) fs.Node { + tags := make(map[string]restic.Snapshots) + for _, sn := range snapshots { + for _, tag := range sn.Tags { + tags[tag] = append(tags[tag], sn) + } + } + + debug.Log("create tags dir with %d tags, inode %d", len(tags), inode) + + entries := make(map[string]fs.Node) + for name, snapshots := range tags { + debug.Log(" tag %v has %v snapshots", name, len(snapshots)) + entries[name] = NewSnapshotsDir(root, fs.GenerateDynamicInode(inode, name), snapshots) + } + + return NewMetaDir(root, inode, entries) +} + +// NewHostsDir returns a new directory containing hostnames, which in +// turn contains snapshots of a single host each. +func NewHostsDir(root *Root, inode uint64, snapshots restic.Snapshots) fs.Node { + hosts := make(map[string]restic.Snapshots) + for _, sn := range snapshots { + hosts[sn.Hostname] = append(hosts[sn.Hostname], sn) + } + + debug.Log("create hosts dir with %d snapshots, inode %d", len(hosts), inode) + + entries := make(map[string]fs.Node) + for name, snapshots := range hosts { + debug.Log(" host %v has %v snapshots", name, len(snapshots)) + entries[name] = NewSnapshotsDir(root, fs.GenerateDynamicInode(inode, name), snapshots) + } + + return NewMetaDir(root, inode, entries) +} + // Root is just there to satisfy fs.Root, it returns itself. func (r *Root) Root() (fs.Node, error) { debug.Log("Root()") return r, nil } - -// Attr returns the attributes for the root node. -func (r *Root) Attr(ctx context.Context, attr *fuse.Attr) error { - attr.Inode = r.inode - attr.Mode = os.ModeDir | 0555 - - if !r.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 (r *Root) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - debug.Log("ReadDirAll()") - items := []fuse.Dirent{ - { - Inode: r.inode, - Name: ".", - Type: fuse.DT_Dir, - }, - { - Inode: r.inode, - Name: "..", - Type: fuse.DT_Dir, - }, - { - Inode: fs.GenerateDynamicInode(r.inode, "snapshots"), - Name: "snapshots", - Type: fuse.DT_Dir, - }, - // { - // Inode: fs.GenerateDynamicInode(0, "tags"), - // Name: "tags", - // Type: fuse.DT_Dir, - // }, - // { - // Inode: fs.GenerateDynamicInode(0, "hosts"), - // Name: "hosts", - // Type: fuse.DT_Dir, - // }, - } - - return items, nil -} - -// Lookup returns a specific entry from the root node. -func (r *Root) Lookup(ctx context.Context, name string) (fs.Node, error) { - debug.Log("Lookup(%s)", name) - switch name { - case "snapshots": - return r.dirSnapshots, nil - } - - return nil, fuse.ENOENT -} diff --git a/src/restic/fuse/dir_snapshots.go b/src/restic/fuse/snapshots_dir.go similarity index 76% rename from src/restic/fuse/dir_snapshots.go rename to src/restic/fuse/snapshots_dir.go index e739f9ab2..44afd6bdb 100644 --- a/src/restic/fuse/dir_snapshots.go +++ b/src/restic/fuse/snapshots_dir.go @@ -16,8 +16,8 @@ import ( "bazil.org/fuse/fs" ) -// DirSnapshots is a fuse directory which contains snapshots. -type DirSnapshots struct { +// SnapshotsDir is a fuse directory which contains snapshots. +type SnapshotsDir struct { inode uint64 root *Root snapshots restic.Snapshots @@ -25,13 +25,13 @@ type DirSnapshots struct { } // ensure that *DirSnapshots implements these interfaces -var _ = fs.HandleReadDirAller(&DirSnapshots{}) -var _ = fs.NodeStringLookuper(&DirSnapshots{}) +var _ = fs.HandleReadDirAller(&SnapshotsDir{}) +var _ = fs.NodeStringLookuper(&SnapshotsDir{}) -// NewDirSnapshots returns a new directory containing snapshots. -func NewDirSnapshots(root *Root, inode uint64, snapshots restic.Snapshots) *DirSnapshots { +// 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 := &DirSnapshots{ + d := &SnapshotsDir{ root: root, inode: inode, snapshots: snapshots, @@ -56,7 +56,7 @@ func NewDirSnapshots(root *Root, inode uint64, snapshots restic.Snapshots) *DirS } // Attr returns the attributes for the root node. -func (d *DirSnapshots) Attr(ctx context.Context, attr *fuse.Attr) error { +func (d *SnapshotsDir) Attr(ctx context.Context, attr *fuse.Attr) error { attr.Inode = d.inode attr.Mode = os.ModeDir | 0555 @@ -69,7 +69,7 @@ func (d *DirSnapshots) Attr(ctx context.Context, attr *fuse.Attr) error { } // ReadDirAll returns all entries of the root node. -func (d *DirSnapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { +func (d *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { debug.Log("ReadDirAll()") items := []fuse.Dirent{ { @@ -96,7 +96,7 @@ func (d *DirSnapshots) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { } // Lookup returns a specific entry from the root node. -func (d *DirSnapshots) Lookup(ctx context.Context, name string) (fs.Node, error) { +func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error) { debug.Log("Lookup(%s)", name) sn, ok := d.names[name]