mount: record a hard link's looked-up names on its inode

Lookup mapped a second name of a hard-linked file to the existing inode
without adding it to the inode's names, so GetAllPaths missed it and the
final forget left it mapped to a released inode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chris LuandClaude Opus 5.5 committed 2026-11-13 02:48:16 -08:00
1 parent c851066f79
commit 70d6639155
2 files changed
+28 -2

No files matched your search

+6 -2
View File
@@ -201,9 +201,13 @@ func (i *InodeToPath) Lookup(path util.FullPath, unixTime int64, isDirectory boo
}
i.path2inode[path] = inode
if _, found := i.inode2path[inode]; found {
if ie, known := i.inode2path[inode]; known {
// Another name of a hard link the table already holds.
if !found {
ie.addPath(path)
}
if isLookup {
i.inode2path[inode].nlookup++
ie.nlookup++
}
} else {
nlookup := uint64(0)
+22
View File
@@ -208,3 +208,25 @@ func TestMovePathWithNoSourceLeavesTheTarget(t *testing.T) {
t.Errorf("/a/g.txt resolves to %d (found %v), want %d", got, found, inode)
}
}
// A second hard link name found by a lookup belongs to the inode, so it is
// reported with the others and released by the final forget.
func TestLookupRecordsHardLinkNames(t *testing.T) {
table := NewInodeToPath(util.FullPath("/"), 0)
inode := table.Lookup("/a", 1, false, true, 42, true)
if got := table.Lookup("/b", 1, false, true, 42, true); got != inode {
t.Fatalf("second name got inode %d, want %d", got, inode)
}
table.Lookup("/b", 1, false, true, 42, true)
if got := table.GetAllPaths(inode); len(got) != 2 || got[0] != "/a" || got[1] != "/b" {
t.Fatalf("paths %v, want [/a /b]", got)
}
table.RemovePath("/a", nil)
if got, status := table.GetPath(inode); status != fuse.OK || got != "/b" {
t.Fatalf("after removing /a the inode answers to %q (%v), want /b", got, status)
}
table.Forget(inode, 3, nil, nil)
if _, found := table.GetInode("/b"); found {
t.Fatal("the final forget left /b mapped to a released inode")
}
}