From b9fd40d18225366e6879f5b3982c703fd5c0dfbe Mon Sep 17 00:00:00 2001 From: Sandy Luppino Date: Fri, 26 Jun 2026 12:04:12 -0400 Subject: [PATCH] cmd/mount2: fix ESTALE over NFS by reporting stable inode numbers setAttr left attr.Ino unset (0) and the NewInode sites left StableAttr.Ino unset, so the kernel saw inode 0 while the node identity differed, which breaks NFS file-handle validation. Set both to the stable VFS inode. The bazil cmd/mount backend does not hit this because its framework assigns stable inodes automatically; go-fuse needs them set explicitly. Before: chmod/chown/truncate on a just-written file through an NFS-exported mount2 mount failed with ESTALE. After: they succeed. Exercising the NFS handle-validation path needs a kernel NFS server, so it isn't covered by the local vfstest harness; validated against a real Linux nfs-kernel-server export over NFSv3, NFSv4.0 and NFSv4.2. #9547 (cherry picked from commit 92b78aefa45d99c0e2c160f9a8b99c6d49073378) --- cmd/mount2/fs.go | 1 + cmd/mount2/node.go | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/mount2/fs.go b/cmd/mount2/fs.go index cbd72aad1..8e1697297 100644 --- a/cmd/mount2/fs.go +++ b/cmd/mount2/fs.go @@ -75,6 +75,7 @@ func setAttr(node vfs.Node, attr *fuse.Attr) { vfs := node.VFS() attr.Owner.Gid = vfs.Opt.GID attr.Owner.Uid = vfs.Opt.UID + attr.Ino = node.Inode() attr.Mode = getMode(node) attr.Size = Size attr.Nlink = 1 diff --git a/cmd/mount2/node.go b/cmd/mount2/node.go index 6022a315e..464ed8aaa 100644 --- a/cmd/mount2/node.go +++ b/cmd/mount2/node.go @@ -201,7 +201,7 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ino // out.SetAttrTimeout(dt time.Duration) n.fsys.setEntryOut(vfsNode, out) - return n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode}), 0 + return n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode, Ino: newNode.node.Inode()}), 0 } var _ = (fusefs.NodeLookuper)((*Node)(nil)) @@ -337,7 +337,7 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En } newNode := newNode(n.fsys, newDir) n.fsys.setEntryOut(newNode.node, out) - newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode}) + newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode, Ino: newNode.node.Inode()}) return newInode, 0 } @@ -379,7 +379,7 @@ func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint3 n.fsys.setEntryOut(vfsNode, out) newNode := newNode(n.fsys, vfsNode) fs.Debugf(nil, "attr=%#v", out.Attr) - newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode}) + newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode, Ino: newNode.node.Inode()}) return newInode, fh, 0, 0 } @@ -528,7 +528,7 @@ func (n *Node) Symlink(ctx context.Context, target, name string, out *fuse.Entry n.fsys.setEntryOut(vfsNode, out) newNode := newNode(n.fsys, vfsNode) - newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode}) + newInode := n.NewInode(ctx, newNode, fusefs.StableAttr{Mode: out.Attr.Mode, Ino: newNode.node.Inode()}) return newInode, 0 }