aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/fusefrontend_reverse/node.go19
-rw-r--r--internal/fusefrontend_reverse/node_helpers.go26
-rw-r--r--internal/fusefrontend_reverse/root_node.go24
3 files changed, 54 insertions, 15 deletions
diff --git a/internal/fusefrontend_reverse/node.go b/internal/fusefrontend_reverse/node.go
index 170410f..22ad975 100644
--- a/internal/fusefrontend_reverse/node.go
+++ b/internal/fusefrontend_reverse/node.go
@@ -68,6 +68,25 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
if t == typeReal {
n.translateSize(d.dirfd, cName, d.pName, &out.Attr)
}
+
+ // Usually we always create a new Node ID by always incrementing the generation
+ // number.
+ //
+ // If we already have a child node that matches what we found on disk*
+ // (as reflected in `ch`), return it here.
+ //
+ // This keeps the Node ID for each directory entry stable
+ // (until forgotten), preventing extra FORGETs from the kernel.
+ //
+ // *We compare `cName`, `Ino`, `Mode` (but not `Gen`!)
+ old := n.Inode.GetChild(cName)
+ if old != nil &&
+ old.StableAttr().Ino == ch.StableAttr().Ino &&
+ // `Mode` has already been masked with syscall.S_IFMT by n.newChild()
+ old.StableAttr().Mode == ch.StableAttr().Mode {
+ return old, 0
+ }
+
return ch, 0
}
diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go
index 96c3c2d..6bba097 100644
--- a/internal/fusefrontend_reverse/node_helpers.go
+++ b/internal/fusefrontend_reverse/node_helpers.go
@@ -91,18 +91,17 @@ func (n *Node) prepareAtSyscall(child string) (d *dirfdPlus, errno syscall.Errno
// newChild attaches a new child inode to n.
// The passed-in `st` will be modified to get a unique inode number.
+//
+// This function is not used for virtual files. See lookupLongnameName(),
+// lookupDiriv() instead.
func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode {
- isOtherFilesystem := (uint64(st.Dev) != n.rootNode().rootDev)
- // Get unique inode number
rn := n.rootNode()
+ isOtherFilesystem := (uint64(st.Dev) != rn.rootDev)
+ // Get unique inode number
rn.inoMap.TranslateStat(st)
out.Attr.FromStat(st)
// Create child node
- id := fs.StableAttr{
- Mode: uint32(st.Mode),
- Gen: 1,
- Ino: st.Ino,
- }
+ id := rn.uniqueStableAttr(uint32(st.Mode), st.Ino)
node := &Node{
isOtherFilesystem: isOtherFilesystem,
}
@@ -153,7 +152,7 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
}
out.Attr = vf.attr
// Create child node
- id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino}
+ id := rn.uniqueStableAttr(uint32(vf.attr.Mode), vf.attr.Ino)
ch = n.NewInode(ctx, vf, id)
return
@@ -161,7 +160,8 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
// lookupDiriv returns a new Inode for a gocryptfs.diriv file inside `n`.
func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
- if rn := n.rootNode(); rn.args.DeterministicNames {
+ rn := n.rootNode()
+ if rn.args.DeterministicNames {
log.Panic("BUG: lookupDiriv called but DeterministicNames is set")
}
@@ -183,7 +183,7 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
}
out.Attr = vf.attr
// Create child node
- id := fs.StableAttr{Mode: uint32(vf.attr.Mode), Gen: 1, Ino: vf.attr.Ino}
+ id := rn.uniqueStableAttr(uint32(vf.attr.Mode), vf.attr.Ino)
ch = n.NewInode(ctx, vf, id)
return
}
@@ -202,11 +202,7 @@ func (n *Node) lookupConf(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode
rn.inoMap.TranslateStat(&st)
out.Attr.FromStat(&st)
// Create child node
- id := fs.StableAttr{
- Mode: uint32(st.Mode),
- Gen: 1,
- Ino: st.Ino,
- }
+ id := rn.uniqueStableAttr(uint32(st.Mode), st.Ino)
node := &VirtualConfNode{path: p}
ch = n.NewInode(ctx, node, id)
return
diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go
index 8a2afd9..1a68ffd 100644
--- a/internal/fusefrontend_reverse/root_node.go
+++ b/internal/fusefrontend_reverse/root_node.go
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
+ "sync/atomic"
"syscall"
"github.com/rfjakob/gocryptfs/v2/internal/exitcodes"
@@ -45,6 +46,13 @@ type RootNode struct {
// If a file name length is shorter than shortNameMax, there is no need to
// hash it.
shortNameMax int
+ // gen is the node generation number. Normally, it is always set to 1,
+ // but reverse mode, like -sharestorage, uses an incrementing counter for new nodes.
+ // This makes each directory entry unique (even hard links),
+ // makes go-fuse hand out separate FUSE Node IDs for each, and prevents
+ // bizarre problems when inode numbers are reused behind our back,
+ // like this one: https://github.com/rfjakob/gocryptfs/issues/802
+ gen uint64
}
// NewRootNode returns an encrypted FUSE overlay filesystem.
@@ -149,3 +157,19 @@ func (rn *RootNode) excludeDirEntries(d *dirfdPlus, entries []fuse.DirEntry) (fi
}
return filtered
}
+
+// uniqueStableAttr returns a fs.StableAttr struct with a unique generation number,
+// preventing files to appear hard-linked, even when they have the same inode number.
+//
+// This is good because inode numbers can be reused behind our back, which could make
+// unrelated files appear hard-linked.
+// Example: https://github.com/rfjakob/gocryptfs/issues/802
+func (rn *RootNode) uniqueStableAttr(mode uint32, ino uint64) fs.StableAttr {
+ return fs.StableAttr{
+ Mode: mode,
+ Ino: ino,
+ // Make each directory entry a unique node by using a unique generation
+ // value. Also see the comment at RootNode.gen for details.
+ Gen: atomic.AddUint64(&rn.gen, 1),
+ }
+}