diff options
| -rw-r--r-- | internal/fusefrontend/node.go | 18 | ||||
| -rw-r--r-- | internal/fusefrontend/node_helpers.go | 12 | ||||
| -rw-r--r-- | internal/fusefrontend/root_node.go | 13 | ||||
| -rw-r--r-- | internal/inomap/inomap.go | 11 | ||||
| -rw-r--r-- | tests/cli/cli_test.go | 14 | 
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. | 
