From f8176a74ec9ab129a19113ce1b99d272185d27fb Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 18 Jun 2017 17:06:27 +0200 Subject: [PATCH 1/5] fuse: Rename DirSnapshots -> SnapshotsDir --- src/restic/fuse/root.go | 2 +- .../{dir_snapshots.go => snapshots_dir.go} | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) rename src/restic/fuse/{dir_snapshots.go => snapshots_dir.go} (82%) diff --git a/src/restic/fuse/root.go b/src/restic/fuse/root.go index bf3fa9068..39a148c19 100644 --- a/src/restic/fuse/root.go +++ b/src/restic/fuse/root.go @@ -28,7 +28,7 @@ type Root struct { cfg Config inode uint64 snapshots restic.Snapshots - dirSnapshots *DirSnapshots + dirSnapshots *SnapshotsDir blobSizeCache *BlobSizeCache } diff --git a/src/restic/fuse/dir_snapshots.go b/src/restic/fuse/snapshots_dir.go similarity index 82% rename from src/restic/fuse/dir_snapshots.go rename to src/restic/fuse/snapshots_dir.go index e739f9ab2..39f07f3ba 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 { +func NewDirSnapshots(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] From f4e85a53e71d2e1a362c9ee1cdc5511fe75a2131 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 18 Jun 2017 17:12:05 +0200 Subject: [PATCH 2/5] fuse: Add '.' and '..' entries to all directories --- src/restic/fuse/dir.go | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) 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": From 64f434eca4dff764f2fb310fbd0aef903ce07f43 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 18 Jun 2017 20:25:31 +0200 Subject: [PATCH 3/5] fuse: Add hosts dir --- src/restic/fuse/hosts_dir.go | 99 ++++++++++++++++++++++++++++++++ src/restic/fuse/root.go | 24 ++++---- src/restic/fuse/snapshots_dir.go | 4 +- 3 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 src/restic/fuse/hosts_dir.go diff --git a/src/restic/fuse/hosts_dir.go b/src/restic/fuse/hosts_dir.go new file mode 100644 index 000000000..0f5f4a8af --- /dev/null +++ b/src/restic/fuse/hosts_dir.go @@ -0,0 +1,99 @@ +// +build !openbsd +// +build !windows + +package fuse + +import ( + "os" + "restic" + "restic/debug" + + "golang.org/x/net/context" + + "bazil.org/fuse" + "bazil.org/fuse/fs" +) + +// HostsDir is a fuse directory which contains hostnames. +type HostsDir struct { + inode uint64 + root *Root + snapshots restic.Snapshots + hosts map[string]*SnapshotsDir +} + +// 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) *HostsDir { + 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) + + d := &HostsDir{ + root: root, + inode: inode, + snapshots: snapshots, + hosts: make(map[string]*SnapshotsDir), + } + + for hostname, snapshots := range hosts { + debug.Log(" host %v has %v snapshots", hostname, len(snapshots)) + d.hosts[hostname] = NewSnapshotsDir(root, fs.GenerateDynamicInode(inode, hostname), snapshots) + } + + return d +} + +// Attr returns the attributes for the root node. +func (d *HostsDir) 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 *HostsDir) 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.hosts { + 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 *HostsDir) Lookup(ctx context.Context, name string) (fs.Node, error) { + debug.Log("Lookup(%s)", name) + + if dir, ok := d.hosts[name]; ok { + return dir, nil + } + + return nil, fuse.ENOENT +} diff --git a/src/restic/fuse/root.go b/src/restic/fuse/root.go index 39a148c19..45d287043 100644 --- a/src/restic/fuse/root.go +++ b/src/restic/fuse/root.go @@ -28,8 +28,10 @@ type Root struct { cfg Config inode uint64 snapshots restic.Snapshots - dirSnapshots *SnapshotsDir blobSizeCache *BlobSizeCache + + dirSnapshots *SnapshotsDir + dirHosts *HostsDir } // ensure that *Root implements these interfaces @@ -50,7 +52,8 @@ func NewRoot(ctx context.Context, repo restic.Repository, cfg Config) (*Root, er snapshots: snapshots, } - root.dirSnapshots = NewDirSnapshots(root, fs.GenerateDynamicInode(root.inode, "snapshots"), snapshots) + root.dirSnapshots = NewSnapshotsDir(root, fs.GenerateDynamicInode(root.inode, "snapshots"), snapshots) + root.dirHosts = NewHostsDir(root, fs.GenerateDynamicInode(root.inode, "hosts"), snapshots) root.blobSizeCache = NewBlobSizeCache(ctx, repo.Index()) return root, nil @@ -94,16 +97,11 @@ func (r *Root) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 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, - // }, + { + Inode: fs.GenerateDynamicInode(0, "hosts"), + Name: "hosts", + Type: fuse.DT_Dir, + }, } return items, nil @@ -115,6 +113,8 @@ func (r *Root) Lookup(ctx context.Context, name string) (fs.Node, error) { switch name { case "snapshots": return r.dirSnapshots, nil + case "hosts": + return r.dirHosts, nil } return nil, fuse.ENOENT diff --git a/src/restic/fuse/snapshots_dir.go b/src/restic/fuse/snapshots_dir.go index 39f07f3ba..44afd6bdb 100644 --- a/src/restic/fuse/snapshots_dir.go +++ b/src/restic/fuse/snapshots_dir.go @@ -28,8 +28,8 @@ type SnapshotsDir struct { var _ = fs.HandleReadDirAller(&SnapshotsDir{}) var _ = fs.NodeStringLookuper(&SnapshotsDir{}) -// NewDirSnapshots returns a new directory containing snapshots. -func NewDirSnapshots(root *Root, inode uint64, snapshots restic.Snapshots) *SnapshotsDir { +// 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, From 4b4a63ed44ccf269ccfdeb5f0e078a67de83ede1 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 18 Jun 2017 20:31:17 +0200 Subject: [PATCH 4/5] fuse: Add tags dir --- src/restic/fuse/root.go | 11 +++- src/restic/fuse/tags_dir.go | 101 ++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 src/restic/fuse/tags_dir.go diff --git a/src/restic/fuse/root.go b/src/restic/fuse/root.go index 45d287043..d1f43c6c3 100644 --- a/src/restic/fuse/root.go +++ b/src/restic/fuse/root.go @@ -32,6 +32,7 @@ type Root struct { dirSnapshots *SnapshotsDir dirHosts *HostsDir + dirTags *TagsDir } // ensure that *Root implements these interfaces @@ -54,6 +55,7 @@ func NewRoot(ctx context.Context, repo restic.Repository, cfg Config) (*Root, er root.dirSnapshots = NewSnapshotsDir(root, fs.GenerateDynamicInode(root.inode, "snapshots"), snapshots) root.dirHosts = NewHostsDir(root, fs.GenerateDynamicInode(root.inode, "hosts"), snapshots) + root.dirTags = NewTagsDir(root, fs.GenerateDynamicInode(root.inode, "tags"), snapshots) root.blobSizeCache = NewBlobSizeCache(ctx, repo.Index()) return root, nil @@ -98,10 +100,15 @@ func (r *Root) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { Type: fuse.DT_Dir, }, { - Inode: fs.GenerateDynamicInode(0, "hosts"), + Inode: fs.GenerateDynamicInode(r.inode, "hosts"), Name: "hosts", Type: fuse.DT_Dir, }, + { + Inode: fs.GenerateDynamicInode(r.inode, "tags"), + Name: "tags", + Type: fuse.DT_Dir, + }, } return items, nil @@ -115,6 +122,8 @@ func (r *Root) Lookup(ctx context.Context, name string) (fs.Node, error) { return r.dirSnapshots, nil case "hosts": return r.dirHosts, nil + case "tags": + return r.dirTags, nil } return nil, fuse.ENOENT diff --git a/src/restic/fuse/tags_dir.go b/src/restic/fuse/tags_dir.go new file mode 100644 index 000000000..30edd8b0b --- /dev/null +++ b/src/restic/fuse/tags_dir.go @@ -0,0 +1,101 @@ +// +build !openbsd +// +build !windows + +package fuse + +import ( + "os" + "restic" + "restic/debug" + + "golang.org/x/net/context" + + "bazil.org/fuse" + "bazil.org/fuse/fs" +) + +// TagsDir is a fuse directory which contains hostnames. +type TagsDir struct { + inode uint64 + root *Root + snapshots restic.Snapshots + tags map[string]*SnapshotsDir +} + +// NewTagsDir returns a new directory containing tags, which in turn contains +// snapshots with this tag set. +func NewTagsDir(root *Root, inode uint64, snapshots restic.Snapshots) *TagsDir { + 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 snapshots, inode %d", len(tags), inode) + + d := &TagsDir{ + root: root, + inode: inode, + snapshots: snapshots, + tags: make(map[string]*SnapshotsDir), + } + + for name, snapshots := range tags { + debug.Log(" tag %v has %v snapshots", name, len(snapshots)) + d.tags[name] = NewSnapshotsDir(root, fs.GenerateDynamicInode(inode, name), snapshots) + } + + return d +} + +// Attr returns the attributes for the root node. +func (d *TagsDir) 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 *TagsDir) 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.tags { + 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 *TagsDir) Lookup(ctx context.Context, name string) (fs.Node, error) { + debug.Log("Lookup(%s)", name) + + if dir, ok := d.tags[name]; ok { + return dir, nil + } + + return nil, fuse.ENOENT +} From 2c02efd1fe1d35239e0bbacabebc6c2ef9140cc4 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Sun, 18 Jun 2017 20:56:01 +0200 Subject: [PATCH 5/5] fuse: Reduce code duplication, add MetaDir --- src/restic/fuse/hosts_dir.go | 99 -------------------------- src/restic/fuse/meta_dir.go | 87 +++++++++++++++++++++++ src/restic/fuse/root.go | 130 +++++++++++++++-------------------- src/restic/fuse/tags_dir.go | 101 --------------------------- 4 files changed, 142 insertions(+), 275 deletions(-) delete mode 100644 src/restic/fuse/hosts_dir.go create mode 100644 src/restic/fuse/meta_dir.go delete mode 100644 src/restic/fuse/tags_dir.go diff --git a/src/restic/fuse/hosts_dir.go b/src/restic/fuse/hosts_dir.go deleted file mode 100644 index 0f5f4a8af..000000000 --- a/src/restic/fuse/hosts_dir.go +++ /dev/null @@ -1,99 +0,0 @@ -// +build !openbsd -// +build !windows - -package fuse - -import ( - "os" - "restic" - "restic/debug" - - "golang.org/x/net/context" - - "bazil.org/fuse" - "bazil.org/fuse/fs" -) - -// HostsDir is a fuse directory which contains hostnames. -type HostsDir struct { - inode uint64 - root *Root - snapshots restic.Snapshots - hosts map[string]*SnapshotsDir -} - -// 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) *HostsDir { - 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) - - d := &HostsDir{ - root: root, - inode: inode, - snapshots: snapshots, - hosts: make(map[string]*SnapshotsDir), - } - - for hostname, snapshots := range hosts { - debug.Log(" host %v has %v snapshots", hostname, len(snapshots)) - d.hosts[hostname] = NewSnapshotsDir(root, fs.GenerateDynamicInode(inode, hostname), snapshots) - } - - return d -} - -// Attr returns the attributes for the root node. -func (d *HostsDir) 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 *HostsDir) 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.hosts { - 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 *HostsDir) Lookup(ctx context.Context, name string) (fs.Node, error) { - debug.Log("Lookup(%s)", name) - - if dir, ok := d.hosts[name]; ok { - return dir, nil - } - - return nil, fuse.ENOENT -} 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 d1f43c6c3..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" ) @@ -30,15 +28,15 @@ type Root struct { snapshots restic.Snapshots blobSizeCache *BlobSizeCache - dirSnapshots *SnapshotsDir - dirHosts *HostsDir - dirTags *TagsDir + *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) @@ -47,84 +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 = NewSnapshotsDir(root, fs.GenerateDynamicInode(root.inode, "snapshots"), snapshots) - root.dirHosts = NewHostsDir(root, fs.GenerateDynamicInode(root.inode, "hosts"), snapshots) - root.dirTags = NewTagsDir(root, fs.GenerateDynamicInode(root.inode, "tags"), 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(r.inode, "hosts"), - Name: "hosts", - Type: fuse.DT_Dir, - }, - { - Inode: fs.GenerateDynamicInode(r.inode, "tags"), - Name: "tags", - 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 - case "hosts": - return r.dirHosts, nil - case "tags": - return r.dirTags, nil - } - - return nil, fuse.ENOENT -} diff --git a/src/restic/fuse/tags_dir.go b/src/restic/fuse/tags_dir.go deleted file mode 100644 index 30edd8b0b..000000000 --- a/src/restic/fuse/tags_dir.go +++ /dev/null @@ -1,101 +0,0 @@ -// +build !openbsd -// +build !windows - -package fuse - -import ( - "os" - "restic" - "restic/debug" - - "golang.org/x/net/context" - - "bazil.org/fuse" - "bazil.org/fuse/fs" -) - -// TagsDir is a fuse directory which contains hostnames. -type TagsDir struct { - inode uint64 - root *Root - snapshots restic.Snapshots - tags map[string]*SnapshotsDir -} - -// NewTagsDir returns a new directory containing tags, which in turn contains -// snapshots with this tag set. -func NewTagsDir(root *Root, inode uint64, snapshots restic.Snapshots) *TagsDir { - 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 snapshots, inode %d", len(tags), inode) - - d := &TagsDir{ - root: root, - inode: inode, - snapshots: snapshots, - tags: make(map[string]*SnapshotsDir), - } - - for name, snapshots := range tags { - debug.Log(" tag %v has %v snapshots", name, len(snapshots)) - d.tags[name] = NewSnapshotsDir(root, fs.GenerateDynamicInode(inode, name), snapshots) - } - - return d -} - -// Attr returns the attributes for the root node. -func (d *TagsDir) 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 *TagsDir) 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.tags { - 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 *TagsDir) Lookup(ctx context.Context, name string) (fs.Node, error) { - debug.Log("Lookup(%s)", name) - - if dir, ok := d.tags[name]; ok { - return dir, nil - } - - return nil, fuse.ENOENT -}