aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2021-07-31 13:24:25 +0200
committerJakob Unterwurzacher2021-07-31 13:24:25 +0200
commit1bc1db620b061aabf59469a5eb4fb60e3e1701a3 (patch)
treed569e213c3a046cdb1fa01fe089fbab048a6fdfe
parenteecbcbb0905320fc8a030fb716bee259bf6dd00f (diff)
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
-rw-r--r--internal/fusefrontend/node.go18
-rw-r--r--internal/fusefrontend/node_helpers.go12
-rw-r--r--internal/fusefrontend/root_node.go13
-rw-r--r--internal/inomap/inomap.go11
-rw-r--r--tests/cli/cli_test.go14
5 files changed, 40 insertions, 28 deletions
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
-}
diff --git a/tests/cli/cli_test.go b/tests/cli/cli_test.go
index df36822..33c9e7b 100644
--- a/tests/cli/cli_test.go
+++ b/tests/cli/cli_test.go
@@ -929,8 +929,8 @@ func TestInitNotEmpty(t *testing.T) {
}
}
-// TestSharedstorage checks that `-sharedstorage` hands out arbitrary inode
-// numbers (no hard link tracking)
+// TestSharedstorage checks that `-sharedstorage` shows stable inode numbers to
+// userpsace despite having hard link tracking disabled
func TestSharedstorage(t *testing.T) {
dir := test_helpers.InitFS(t)
mnt := dir + ".mnt"
@@ -944,7 +944,7 @@ func TestSharedstorage(t *testing.T) {
if err := os.Link(foo1, foo2); err != nil {
t.Fatal(err)
}
- var st1, st2, st3 syscall.Stat_t
+ var st1, st2 syscall.Stat_t
if err := syscall.Stat(foo1, &st1); err != nil {
t.Fatal(err)
}
@@ -952,12 +952,8 @@ func TestSharedstorage(t *testing.T) {
if err := syscall.Stat(foo2, &st2); err != nil {
t.Fatal(err)
}
- // Stat()'ing again should give us again a new inode number
- if err := syscall.Stat(foo2, &st3); err != nil {
- t.Fatal(err)
- }
- if st1.Ino == st2.Ino || st2.Ino == st3.Ino || st1.Ino == st3.Ino {
- t.Error(st1.Ino, st2.Ino, st3.Ino)
+ if st1.Ino != st2.Ino {
+ t.Errorf("unstable inode number: changed from %d to %d", st1.Ino, st2.Ino)
}
// Check that we we don't have stat caching. New length should show up
// on the hard link immediately.