Merge pull request #4020 from greatroar/fuse-inode

fuse: Better inode generation
This commit is contained in:
Michael Eischer 2022-12-02 22:28:15 +01:00 committed by GitHub
commit 57d8eedb88
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 95 additions and 12 deletions

View file

@ -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,
})
@ -204,15 +204,16 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
debug.Log(" Lookup(%v) -> not found", name)
return nil, fuse.ENOENT
}
inode := inodeFromNode(d.inode, node)
switch node.Type {
case "dir":
return newDir(d.root, fs.GenerateDynamicInode(d.inode, name), d.inode, node)
return newDir(d.root, inode, d.inode, node)
case "file":
return newFile(d.root, fs.GenerateDynamicInode(d.inode, name), node)
return newFile(d.root, inode, node)
case "symlink":
return newLink(d.root, fs.GenerateDynamicInode(d.inode, name), node)
return newLink(d.root, inode, node)
case "dev", "chardev", "fifo", "socket":
return newOther(d.root, fs.GenerateDynamicInode(d.inode, name), node)
return newOther(d.root, inode, node)
default:
debug.Log(" node %v has unknown type %v", name, node.Type)
return nil, fuse.ENOENT

View file

@ -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
View 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
}

View file

@ -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,
}
@ -104,12 +104,13 @@ func (d *SnapshotsDir) Lookup(ctx context.Context, name string) (fs.Node, error)
entry := meta.names[name]
if entry != nil {
inode := inodeFromName(d.inode, name)
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 {
return newDirFromSnapshot(d.root, fs.GenerateDynamicInode(d.inode, name), entry.snapshot)
return newDirFromSnapshot(d.root, inode, entry.snapshot)
} 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
}
}