From 1bc1db620b061aabf59469a5eb4fb60e3e1701a3 Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Sat, 31 Jul 2021 13:24:25 +0200 Subject: fusefrontend: -sharedstorage: present stable inode numbers Use the Gen field (inode generation) to distinguish hard links while passing the real inode numbers to userspace. Fixes https://github.com/rfjakob/gocryptfs/issues/584 --- internal/fusefrontend/node.go | 18 ++++++++++++++++++ internal/fusefrontend/node_helpers.go | 12 ++++++++++-- internal/fusefrontend/root_node.go | 13 +++++++------ internal/inomap/inomap.go | 11 ----------- 4 files changed, 35 insertions(+), 19 deletions(-) (limited to 'internal') diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index 5d3c178..8a3cfa2 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -40,6 +40,24 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch // Translate ciphertext size in `out.Attr.Size` to plaintext size n.translateSize(dirfd, cName, &out.Attr) + rn := n.rootNode() + if rn.args.SharedStorage { + // 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). + // + // *We compare `name`, `Ino`, `Mode` (but not `Gen`!) + old := n.Inode.GetChild(name) + 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/node_helpers.go b/internal/fusefrontend/node_helpers.go index ce2e8a9..31954f3 100644 --- a/internal/fusefrontend/node_helpers.go +++ b/internal/fusefrontend/node_helpers.go @@ -2,6 +2,7 @@ package fusefrontend import ( "context" + "sync/atomic" "syscall" "github.com/hanwen/go-fuse/v2/fs" @@ -82,13 +83,20 @@ func (n *Node) rootNode() *RootNode { func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode { rn := n.rootNode() // Get stable inode number based on underlying (device,ino) pair - // (or set to zero in case of `-sharestorage`) rn.inoMap.TranslateStat(st) out.Attr.FromStat(st) + + var gen uint64 = 1 + if rn.args.SharedStorage { + // Make each directory entry a unique node by using a unique generation + // value - see the comment at RootNode.gen for details. + gen = atomic.AddUint64(&rn.gen, 1) + } + // Create child node id := fs.StableAttr{ Mode: uint32(st.Mode), - Gen: 1, + Gen: gen, Ino: st.Ino, } node := &Node{} diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index c82078d..46bee4a 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -50,7 +50,13 @@ type RootNode struct { dirCache dirCache // inoMap translates inode numbers from different devices to unique inode // numbers. - inoMap inomap.TranslateStater + inoMap *inomap.InoMap + // gen is the node generation numbers. Normally, it is always set to 1, + // but -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. + gen uint64 } func NewRootNode(args Args, c *contentenc.ContentEnc, n *nametransform.NameTransform) *RootNode { @@ -71,11 +77,6 @@ func NewRootNode(args Args, c *contentenc.ContentEnc, n *nametransform.NameTrans inoMap: inomap.New(), dirCache: dirCache{ivLen: ivLen}, } - // In `-sharedstorage` mode we always set the inode number to zero. - // This makes go-fuse generate a new inode number for each lookup. - if args.SharedStorage { - rn.inoMap = &inomap.TranslateStatZero{} - } return rn } diff --git a/internal/inomap/inomap.go b/internal/inomap/inomap.go index 0977a46..82d50b0 100644 --- a/internal/inomap/inomap.go +++ b/internal/inomap/inomap.go @@ -104,14 +104,3 @@ func (m *InoMap) TranslateStat(st *syscall.Stat_t) { in := QInoFromStat(st) st.Ino = m.Translate(in) } - -type TranslateStater interface { - TranslateStat(st *syscall.Stat_t) -} - -// TranslateStatZero always sets st.Ino to zero. Used for `-sharedstorage`. -type TranslateStatZero struct{} - -func (z TranslateStatZero) TranslateStat(st *syscall.Stat_t) { - st.Ino = 0 -} -- cgit v1.2.3