forked from TrueCloudLab/restic
Merge pull request #4255 from greatroar/fuse-hash
fuse: Mix inode hashes in a non-symmetric way
This commit is contained in:
commit
26a3c47c5c
3 changed files with 33 additions and 2 deletions
18
changelog/unreleased/issue-4253
Normal file
18
changelog/unreleased/issue-4253
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
Bugfix: Mount command should no longer create spurious filesystem loops
|
||||||
|
|
||||||
|
When a backup contains a directory that has the same name as its parent,
|
||||||
|
say, a/b/b, and the GNU find command were run on this backup in a restic
|
||||||
|
mount, find command would refuse to traverse the lowest "b" directory,
|
||||||
|
instead printing "File system loop detected". This is due to the way the
|
||||||
|
restic mount command generates inode numbers for directories in the mount
|
||||||
|
point.
|
||||||
|
|
||||||
|
The rule for generating these inode numbers was changed in 0.15.0. It has
|
||||||
|
now been changed again to avoid this issue. A perfect rule does not exist,
|
||||||
|
but the probability of this behavior occurring is now extremely small.
|
||||||
|
When it does occur, the mount point is not broken, and scripts that traverse
|
||||||
|
the mount point should work as long as they don't rely on inode numbers for
|
||||||
|
detecting filesystem loops.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/issues/4253
|
||||||
|
https://github.com/restic/restic/pull/4255
|
|
@ -258,6 +258,17 @@ func TestInodeFromNode(t *testing.T) {
|
||||||
ino1 = inodeFromNode(1, node)
|
ino1 = inodeFromNode(1, node)
|
||||||
ino2 = inodeFromNode(2, node)
|
ino2 = inodeFromNode(2, node)
|
||||||
rtest.Assert(t, ino1 != ino2, "same inode %d but different parent", ino1)
|
rtest.Assert(t, ino1 != ino2, "same inode %d but different parent", ino1)
|
||||||
|
|
||||||
|
// Regression test: in a path a/b/b, the grandchild should not get the
|
||||||
|
// same inode as the grandparent.
|
||||||
|
a := &restic.Node{Name: "a", Type: "dir", Links: 2}
|
||||||
|
ab := &restic.Node{Name: "b", Type: "dir", Links: 2}
|
||||||
|
abb := &restic.Node{Name: "b", Type: "dir", Links: 2}
|
||||||
|
inoA := inodeFromNode(1, a)
|
||||||
|
inoAb := inodeFromNode(inoA, ab)
|
||||||
|
inoAbb := inodeFromNode(inoAb, abb)
|
||||||
|
rtest.Assert(t, inoA != inoAb, "inode(a/b) = inode(a)")
|
||||||
|
rtest.Assert(t, inoA != inoAbb, "inode(a/b/b) = inode(a)")
|
||||||
}
|
}
|
||||||
|
|
||||||
var sink uint64
|
var sink uint64
|
||||||
|
|
|
@ -10,9 +10,11 @@ import (
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const prime = 11400714785074694791 // prime1 from xxhash.
|
||||||
|
|
||||||
// inodeFromName generates an inode number for a file in a meta dir.
|
// inodeFromName generates an inode number for a file in a meta dir.
|
||||||
func inodeFromName(parent uint64, name string) uint64 {
|
func inodeFromName(parent uint64, name string) uint64 {
|
||||||
inode := parent ^ xxhash.Sum64String(cleanupNodeName(name))
|
inode := prime*parent ^ xxhash.Sum64String(cleanupNodeName(name))
|
||||||
|
|
||||||
// Inode 0 is invalid and 1 is the root. Remap those.
|
// Inode 0 is invalid and 1 is the root. Remap those.
|
||||||
if inode < 2 {
|
if inode < 2 {
|
||||||
|
@ -33,7 +35,7 @@ func inodeFromNode(parent uint64, node *restic.Node) (inode uint64) {
|
||||||
} else {
|
} else {
|
||||||
// Else, use the name and the parent inode.
|
// Else, use the name and the parent inode.
|
||||||
// node.{DeviceID,Inode} may not even be reliable.
|
// node.{DeviceID,Inode} may not even be reliable.
|
||||||
inode = parent ^ xxhash.Sum64String(cleanupNodeName(node.Name))
|
inode = prime*parent ^ xxhash.Sum64String(cleanupNodeName(node.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inode 0 is invalid and 1 is the root. Remap those.
|
// Inode 0 is invalid and 1 is the root. Remap those.
|
||||||
|
|
Loading…
Reference in a new issue