Merge pull request #4020 from greatroar/fuse-inode
fuse: Better inode generation
This commit is contained in:
commit
57d8eedb88
4 changed files with 95 additions and 12 deletions
|
@ -182,7 +182,7 @@ func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = append(ret, fuse.Dirent{
|
ret = append(ret, fuse.Dirent{
|
||||||
Inode: fs.GenerateDynamicInode(d.inode, name),
|
Inode: inodeFromNode(d.inode, node),
|
||||||
Type: typ,
|
Type: typ,
|
||||||
Name: name,
|
Name: name,
|
||||||
})
|
})
|
||||||
|
@ -204,15 +204,16 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
||||||
debug.Log(" Lookup(%v) -> not found", name)
|
debug.Log(" Lookup(%v) -> not found", name)
|
||||||
return nil, fuse.ENOENT
|
return nil, fuse.ENOENT
|
||||||
}
|
}
|
||||||
|
inode := inodeFromNode(d.inode, node)
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case "dir":
|
case "dir":
|
||||||
return newDir(d.root, fs.GenerateDynamicInode(d.inode, name), d.inode, node)
|
return newDir(d.root, inode, d.inode, node)
|
||||||
case "file":
|
case "file":
|
||||||
return newFile(d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
return newFile(d.root, inode, node)
|
||||||
case "symlink":
|
case "symlink":
|
||||||
return newLink(d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
return newLink(d.root, inode, node)
|
||||||
case "dev", "chardev", "fifo", "socket":
|
case "dev", "chardev", "fifo", "socket":
|
||||||
return newOther(d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
return newOther(d.root, inode, node)
|
||||||
default:
|
default:
|
||||||
debug.Log(" node %v has unknown type %v", name, node.Type)
|
debug.Log(" node %v has unknown type %v", name, node.Type)
|
||||||
return nil, fuse.ENOENT
|
return nil, fuse.ENOENT
|
||||||
|
|
|
@ -118,7 +118,7 @@ func TestFuseFile(t *testing.T) {
|
||||||
}
|
}
|
||||||
root := &Root{repo: repo, blobCache: bloblru.New(blobCacheSize)}
|
root := &Root{repo: repo, blobCache: bloblru.New(blobCacheSize)}
|
||||||
|
|
||||||
inode := fs.GenerateDynamicInode(1, "foo")
|
inode := inodeFromNode(1, node)
|
||||||
f, err := newFile(root, inode, node)
|
f, err := newFile(root, inode, node)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
of, err := f.Open(context.TODO(), nil, nil)
|
of, err := f.Open(context.TODO(), nil, nil)
|
||||||
|
@ -161,8 +161,8 @@ func TestFuseDir(t *testing.T) {
|
||||||
ChangeTime: time.Unix(1606773732, 0),
|
ChangeTime: time.Unix(1606773732, 0),
|
||||||
ModTime: time.Unix(1606773733, 0),
|
ModTime: time.Unix(1606773733, 0),
|
||||||
}
|
}
|
||||||
parentInode := fs.GenerateDynamicInode(0, "parent")
|
parentInode := inodeFromName(0, "parent")
|
||||||
inode := fs.GenerateDynamicInode(1, "foo")
|
inode := inodeFromName(1, "foo")
|
||||||
d, err := newDir(root, inode, parentInode, node)
|
d, err := newDir(root, inode, parentInode, node)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
@ -219,3 +219,40 @@ func testTopUIDGID(t *testing.T, cfg Config, repo restic.Repository, uid, gid ui
|
||||||
rtest.Equals(t, uint32(0), attr.Uid)
|
rtest.Equals(t, uint32(0), attr.Uid)
|
||||||
rtest.Equals(t, uint32(0), attr.Gid)
|
rtest.Equals(t, uint32(0), attr.Gid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInodeFromNode(t *testing.T) {
|
||||||
|
node := &restic.Node{Name: "foo.txt", Type: "chardev", Links: 2}
|
||||||
|
ino1 := inodeFromNode(1, node)
|
||||||
|
ino2 := inodeFromNode(2, node)
|
||||||
|
rtest.Assert(t, ino1 == ino2, "inodes %d, %d of hard links differ", ino1, ino2)
|
||||||
|
|
||||||
|
node.Links = 1
|
||||||
|
ino1 = inodeFromNode(1, node)
|
||||||
|
ino2 = inodeFromNode(2, node)
|
||||||
|
rtest.Assert(t, ino1 != ino2, "same inode %d but different parent", ino1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sink uint64
|
||||||
|
|
||||||
|
func BenchmarkInode(b *testing.B) {
|
||||||
|
for _, sub := range []struct {
|
||||||
|
name string
|
||||||
|
node restic.Node
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no_hard_links",
|
||||||
|
node: restic.Node{Name: "a somewhat long-ish filename.svg.bz2", Type: "fifo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hard_link",
|
||||||
|
node: restic.Node{Name: "some other filename", Type: "file", Links: 2},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
b.Run(sub.name, func(b *testing.B) {
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
sink = inodeFromNode(1, &sub.node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
44
internal/fuse/inode.go
Normal file
44
internal/fuse/inode.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
//go:build darwin || freebsd || linux
|
||||||
|
// +build darwin freebsd linux
|
||||||
|
|
||||||
|
package fuse
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/cespare/xxhash/v2"
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// inodeFromName generates an inode number for a file in a meta dir.
|
||||||
|
func inodeFromName(parent uint64, name string) uint64 {
|
||||||
|
inode := parent ^ xxhash.Sum64String(cleanupNodeName(name))
|
||||||
|
|
||||||
|
// Inode 0 is invalid and 1 is the root. Remap those.
|
||||||
|
if inode < 2 {
|
||||||
|
inode += 2
|
||||||
|
}
|
||||||
|
return inode
|
||||||
|
}
|
||||||
|
|
||||||
|
// inodeFromNode generates an inode number for a file within a snapshot.
|
||||||
|
func inodeFromNode(parent uint64, node *restic.Node) (inode uint64) {
|
||||||
|
if node.Links > 1 && node.Type != "dir" {
|
||||||
|
// If node has hard links, give them all the same inode,
|
||||||
|
// irrespective of the parent.
|
||||||
|
var buf [16]byte
|
||||||
|
binary.LittleEndian.PutUint64(buf[:8], node.DeviceID)
|
||||||
|
binary.LittleEndian.PutUint64(buf[8:], node.Inode)
|
||||||
|
inode = xxhash.Sum64(buf[:])
|
||||||
|
} else {
|
||||||
|
// Else, use the name and the parent inode.
|
||||||
|
// node.{DeviceID,Inode} may not even be reliable.
|
||||||
|
inode = parent ^ xxhash.Sum64String(cleanupNodeName(node.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inode 0 is invalid and 1 is the root. Remap those.
|
||||||
|
if inode < 2 {
|
||||||
|
inode += 2
|
||||||
|
}
|
||||||
|
return inode
|
||||||
|
}
|
|
@ -78,7 +78,7 @@ func (d *SnapshotsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
||||||
|
|
||||||
for name, entry := range meta.names {
|
for name, entry := range meta.names {
|
||||||
d := fuse.Dirent{
|
d := fuse.Dirent{
|
||||||
Inode: fs.GenerateDynamicInode(d.inode, name),
|
Inode: inodeFromName(d.inode, name),
|
||||||
Name: name,
|
Name: name,
|
||||||
Type: fuse.DT_Dir,
|
Type: fuse.DT_Dir,
|
||||||
}
|
}
|
||||||
|
@ -104,12 +104,13 @@ func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error)
|
||||||
|
|
||||||
entry := meta.names[name]
|
entry := meta.names[name]
|
||||||
if entry != nil {
|
if entry != nil {
|
||||||
|
inode := inodeFromName(d.inode, name)
|
||||||
if entry.linkTarget != "" {
|
if entry.linkTarget != "" {
|
||||||
return newSnapshotLink(d.root, fs.GenerateDynamicInode(d.inode, name), entry.linkTarget, entry.snapshot)
|
return newSnapshotLink(d.root, inode, entry.linkTarget, entry.snapshot)
|
||||||
} else if entry.snapshot != nil {
|
} else if entry.snapshot != nil {
|
||||||
return newDirFromSnapshot(d.root, fs.GenerateDynamicInode(d.inode, name), entry.snapshot)
|
return newDirFromSnapshot(d.root, inode, entry.snapshot)
|
||||||
} else {
|
} else {
|
||||||
return NewSnapshotsDir(d.root, fs.GenerateDynamicInode(d.inode, name), d.inode, d.dirStruct, d.prefix+"/"+name), nil
|
return NewSnapshotsDir(d.root, inode, d.inode, d.dirStruct, d.prefix+"/"+name), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue