fuse: Better inode generation
Hard links to the same file now get the same inode within the FUSE mount. Also, inode generation is faster and, more importantly, no longer allocates. Benchmarked on Linux/amd64. Old means the benchmark with sink = fs.GenerateDynamicInode(1, sub.node.Name) instead of calling inodeFromNode. Results: name old time/op new time/op delta Inode/no_hard_links-8 137ns ± 4% 34ns ± 1% -75.20% (p=0.000 n=10+10) Inode/hard_link-8 33.6ns ± 1% 9.5ns ± 0% -71.82% (p=0.000 n=9+8) name old alloc/op new alloc/op delta Inode/no_hard_links-8 48.0B ± 0% 0.0B -100.00% (p=0.000 n=10+10) Inode/hard_link-8 0.00B 0.00B ~ (all equal) name old allocs/op new allocs/op delta Inode/no_hard_links-8 1.00 ± 0% 0.00 -100.00% (p=0.000 n=10+10) Inode/hard_link-8 0.00 0.00 ~ (all equal)
This commit is contained in:
parent
59a90943bb
commit
189e0fe5a9
4 changed files with 93 additions and 12 deletions
|
@ -182,7 +182,7 @@ func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
|
|||
}
|
||||
|
||||
ret = append(ret, fuse.Dirent{
|
||||
Inode: fs.GenerateDynamicInode(d.inode, name),
|
||||
Inode: inodeFromNode(d.inode, node),
|
||||
Type: typ,
|
||||
Name: name,
|
||||
})
|
||||
|
@ -206,13 +206,13 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
|
|||
}
|
||||
switch node.Type {
|
||||
case "dir":
|
||||
return newDir(d.root, fs.GenerateDynamicInode(d.inode, name), d.inode, node)
|
||||
return newDir(d.root, inodeFromNode(d.inode, node), d.inode, node)
|
||||
case "file":
|
||||
return newFile(d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
||||
return newFile(d.root, inodeFromNode(d.inode, node), node)
|
||||
case "symlink":
|
||||
return newLink(d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
||||
return newLink(d.root, inodeFromNode(d.inode, node), node)
|
||||
case "dev", "chardev", "fifo", "socket":
|
||||
return newOther(d.root, fs.GenerateDynamicInode(d.inode, name), node)
|
||||
return newOther(d.root, inodeFromNode(d.inode, node), node)
|
||||
default:
|
||||
debug.Log(" node %v has unknown type %v", name, node.Type)
|
||||
return nil, fuse.ENOENT
|
||||
|
|
|
@ -118,7 +118,7 @@ func TestFuseFile(t *testing.T) {
|
|||
}
|
||||
root := &Root{repo: repo, blobCache: bloblru.New(blobCacheSize)}
|
||||
|
||||
inode := fs.GenerateDynamicInode(1, "foo")
|
||||
inode := inodeFromNode(1, node)
|
||||
f, err := newFile(root, inode, node)
|
||||
rtest.OK(t, err)
|
||||
of, err := f.Open(context.TODO(), nil, nil)
|
||||
|
@ -161,8 +161,8 @@ func TestFuseDir(t *testing.T) {
|
|||
ChangeTime: time.Unix(1606773732, 0),
|
||||
ModTime: time.Unix(1606773733, 0),
|
||||
}
|
||||
parentInode := fs.GenerateDynamicInode(0, "parent")
|
||||
inode := fs.GenerateDynamicInode(1, "foo")
|
||||
parentInode := inodeFromName(0, "parent")
|
||||
inode := inodeFromName(1, "foo")
|
||||
d, err := newDir(root, inode, parentInode, node)
|
||||
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.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 {
|
||||
d := fuse.Dirent{
|
||||
Inode: fs.GenerateDynamicInode(d.inode, name),
|
||||
Inode: inodeFromName(d.inode, name),
|
||||
Name: name,
|
||||
Type: fuse.DT_Dir,
|
||||
}
|
||||
|
@ -105,11 +105,11 @@ func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error)
|
|||
entry := meta.names[name]
|
||||
if entry != nil {
|
||||
if entry.linkTarget != "" {
|
||||
return newSnapshotLink(d.root, fs.GenerateDynamicInode(d.inode, name), entry.linkTarget, entry.snapshot)
|
||||
return newSnapshotLink(d.root, inodeFromName(d.inode, name), entry.linkTarget, entry.snapshot)
|
||||
} else if entry.snapshot != nil {
|
||||
return newDirFromSnapshot(d.root, fs.GenerateDynamicInode(d.inode, name), entry.snapshot)
|
||||
return newDirFromSnapshot(d.root, inodeFromName(d.inode, name), entry.snapshot)
|
||||
} else {
|
||||
return NewSnapshotsDir(d.root, fs.GenerateDynamicInode(d.inode, name), d.inode, d.dirStruct, d.prefix+"/"+name), nil
|
||||
return NewSnapshotsDir(d.root, inodeFromName(d.inode, name), d.inode, d.dirStruct, d.prefix+"/"+name), nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue