forked from TrueCloudLab/restic
Merge pull request #1050 from restic/extend-fuse-mount
fuse: Add more directories
This commit is contained in:
commit
bca9566849
4 changed files with 178 additions and 88 deletions
|
@ -22,12 +22,13 @@ type dir struct {
|
||||||
root *Root
|
root *Root
|
||||||
items map[string]*restic.Node
|
items map[string]*restic.Node
|
||||||
inode uint64
|
inode uint64
|
||||||
|
parentInode uint64
|
||||||
node *restic.Node
|
node *restic.Node
|
||||||
|
|
||||||
blobsize *BlobSizeCache
|
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())
|
debug.Log("new dir for %v (%v)", node.Name, node.Subtree.Str())
|
||||||
tree, err := root.repo.LoadTree(ctx, *node.Subtree)
|
tree, err := root.repo.LoadTree(ctx, *node.Subtree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -44,6 +45,7 @@ func newDir(ctx context.Context, root *Root, inode uint64, node *restic.Node) (*
|
||||||
node: node,
|
node: node,
|
||||||
items: items,
|
items: items,
|
||||||
inode: inode,
|
inode: inode,
|
||||||
|
parentInode: parentInode,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +136,19 @@ func (d *dir) calcNumberOfLinks() uint32 {
|
||||||
|
|
||||||
func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||||
debug.Log("called")
|
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 {
|
for _, node := range d.items {
|
||||||
var typ fuse.DirentType
|
var typ fuse.DirentType
|
||||||
|
@ -166,7 +180,7 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||||
}
|
}
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "dir":
|
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":
|
case "file":
|
||||||
return newFile(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
return newFile(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
||||||
case "symlink":
|
case "symlink":
|
||||||
|
|
87
src/restic/fuse/meta_dir.go
Normal file
87
src/restic/fuse/meta_dir.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -4,13 +4,11 @@
|
||||||
package fuse
|
package fuse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"restic"
|
"restic"
|
||||||
"restic/debug"
|
"restic/debug"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
"bazil.org/fuse"
|
|
||||||
"bazil.org/fuse/fs"
|
"bazil.org/fuse/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -28,14 +26,17 @@ type Root struct {
|
||||||
cfg Config
|
cfg Config
|
||||||
inode uint64
|
inode uint64
|
||||||
snapshots restic.Snapshots
|
snapshots restic.Snapshots
|
||||||
dirSnapshots *DirSnapshots
|
|
||||||
blobSizeCache *BlobSizeCache
|
blobSizeCache *BlobSizeCache
|
||||||
|
|
||||||
|
*MetaDir
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure that *Root implements these interfaces
|
// ensure that *Root implements these interfaces
|
||||||
var _ = fs.HandleReadDirAller(&Root{})
|
var _ = fs.HandleReadDirAller(&Root{})
|
||||||
var _ = fs.NodeStringLookuper(&Root{})
|
var _ = fs.NodeStringLookuper(&Root{})
|
||||||
|
|
||||||
|
const rootInode = 1
|
||||||
|
|
||||||
// NewRoot initializes a new root node from a repository.
|
// NewRoot initializes a new root node from a repository.
|
||||||
func NewRoot(ctx context.Context, repo restic.Repository, cfg Config) (*Root, error) {
|
func NewRoot(ctx context.Context, repo restic.Repository, cfg Config) (*Root, error) {
|
||||||
debug.Log("NewRoot(), config %v", cfg)
|
debug.Log("NewRoot(), config %v", cfg)
|
||||||
|
@ -45,77 +46,65 @@ func NewRoot(ctx context.Context, repo restic.Repository, cfg Config) (*Root, er
|
||||||
|
|
||||||
root := &Root{
|
root := &Root{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
|
inode: rootInode,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
inode: 1,
|
|
||||||
snapshots: snapshots,
|
snapshots: snapshots,
|
||||||
|
blobSizeCache: NewBlobSizeCache(ctx, repo.Index()),
|
||||||
}
|
}
|
||||||
|
|
||||||
root.dirSnapshots = NewDirSnapshots(root, fs.GenerateDynamicInode(root.inode, "snapshots"), snapshots)
|
entries := map[string]fs.Node{
|
||||||
root.blobSizeCache = NewBlobSizeCache(ctx, repo.Index())
|
"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
|
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.
|
// Root is just there to satisfy fs.Root, it returns itself.
|
||||||
func (r *Root) Root() (fs.Node, error) {
|
func (r *Root) Root() (fs.Node, error) {
|
||||||
debug.Log("Root()")
|
debug.Log("Root()")
|
||||||
return r, nil
|
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,8 +16,8 @@ import (
|
||||||
"bazil.org/fuse/fs"
|
"bazil.org/fuse/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DirSnapshots is a fuse directory which contains snapshots.
|
// SnapshotsDir is a fuse directory which contains snapshots.
|
||||||
type DirSnapshots struct {
|
type SnapshotsDir struct {
|
||||||
inode uint64
|
inode uint64
|
||||||
root *Root
|
root *Root
|
||||||
snapshots restic.Snapshots
|
snapshots restic.Snapshots
|
||||||
|
@ -25,13 +25,13 @@ type DirSnapshots struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure that *DirSnapshots implements these interfaces
|
// ensure that *DirSnapshots implements these interfaces
|
||||||
var _ = fs.HandleReadDirAller(&DirSnapshots{})
|
var _ = fs.HandleReadDirAller(&SnapshotsDir{})
|
||||||
var _ = fs.NodeStringLookuper(&DirSnapshots{})
|
var _ = fs.NodeStringLookuper(&SnapshotsDir{})
|
||||||
|
|
||||||
// NewDirSnapshots returns a new directory containing snapshots.
|
// NewSnapshotsDir returns a new directory containing snapshots.
|
||||||
func NewDirSnapshots(root *Root, inode uint64, snapshots restic.Snapshots) *DirSnapshots {
|
func NewSnapshotsDir(root *Root, inode uint64, snapshots restic.Snapshots) *SnapshotsDir {
|
||||||
debug.Log("create snapshots dir with %d snapshots, inode %d", len(snapshots), inode)
|
debug.Log("create snapshots dir with %d snapshots, inode %d", len(snapshots), inode)
|
||||||
d := &DirSnapshots{
|
d := &SnapshotsDir{
|
||||||
root: root,
|
root: root,
|
||||||
inode: inode,
|
inode: inode,
|
||||||
snapshots: snapshots,
|
snapshots: snapshots,
|
||||||
|
@ -56,7 +56,7 @@ func NewDirSnapshots(root *Root, inode uint64, snapshots restic.Snapshots) *DirS
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attr returns the attributes for the root node.
|
// 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.Inode = d.inode
|
||||||
attr.Mode = os.ModeDir | 0555
|
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.
|
// 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()")
|
debug.Log("ReadDirAll()")
|
||||||
items := []fuse.Dirent{
|
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.
|
// 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)
|
debug.Log("Lookup(%s)", name)
|
||||||
|
|
||||||
sn, ok := d.names[name]
|
sn, ok := d.names[name]
|
Loading…
Reference in a new issue