diff options
| -rw-r--r-- | internal/fusefrontend/args.go | 3 | ||||
| -rw-r--r-- | internal/fusefrontend/node_helpers.go | 3 | ||||
| -rw-r--r-- | internal/fusefrontend/root_node.go | 10 | ||||
| -rw-r--r-- | internal/inomap/inomap.go | 11 | ||||
| -rw-r--r-- | mount.go | 10 | ||||
| -rw-r--r-- | tests/cli/cli_test.go | 47 | 
6 files changed, 79 insertions, 5 deletions
| diff --git a/internal/fusefrontend/args.go b/internal/fusefrontend/args.go index 5eb6bff..ae1c30c 100644 --- a/internal/fusefrontend/args.go +++ b/internal/fusefrontend/args.go @@ -46,4 +46,7 @@ type Args struct {  	Suid bool  	// Enable the FUSE kernel_cache option  	KernelCache bool +	// SharedStorage disables caching & hard link tracking, +	// enabled via cli flag "-sharedstorage" +	SharedStorage bool  } diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go index ad92043..f99e6df 100644 --- a/internal/fusefrontend/node_helpers.go +++ b/internal/fusefrontend/node_helpers.go @@ -102,8 +102,9 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy  // newChild attaches a new child inode to n.  // The passed-in `st` will be modified to get a unique inode number.  func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.EntryOut) *fs.Inode { -	// Get unique inode number  	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)  	// Create child node diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index e03e250..bdefafa 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -50,7 +50,7 @@ type RootNode struct {  	IsIdle uint32  	// inoMap translates inode numbers from different devices to unique inode  	// numbers. -	inoMap *inomap.InoMap +	inoMap inomap.TranslateStater  }  func NewRootNode(args Args, c *contentenc.ContentEnc, n nametransform.NameTransformer) *RootNode { @@ -60,12 +60,18 @@ func NewRootNode(args Args, c *contentenc.ContentEnc, n nametransform.NameTransf  	if len(args.Exclude) > 0 {  		tlog.Warn.Printf("Forward mode does not support -exclude")  	} -	return &RootNode{ +	rn := &RootNode{  		args:          args,  		nameTransform: n,  		contentEnc:    c,  		inoMap:        inomap.New(),  	} +	// 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  }  // mangleOpenFlags is used by Create() and Open() to convert the open flags the user diff --git a/internal/inomap/inomap.go b/internal/inomap/inomap.go index 82d50b0..0977a46 100644 --- a/internal/inomap/inomap.go +++ b/internal/inomap/inomap.go @@ -104,3 +104,14 @@ 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 +} @@ -280,6 +280,7 @@ func initFuseFrontend(args *argContainer) (rootNode fs.InodeEmbedder, wipeKeys f  		ExcludeFrom:     args.excludeFrom,  		Suid:            args.suid,  		KernelCache:     args.kernel_cache, +		SharedStorage:   args.sharedstorage,  	}  	// confFile is nil when "-zerokey" or "-masterkey" was used  	if confFile != nil { @@ -349,7 +350,11 @@ func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server {  	if args.sharedstorage {  		// sharedstorage mode sets all cache timeouts to zero so changes to the  		// backing shared storage show up immediately. -		fuseOpts = &fs.Options{} +		// Hard links are disabled by using automatically incrementing +		// inode numbers provided by go-fuse. +		fuseOpts = &fs.Options{ +			FirstAutomaticIno: 1000, +		}  	} else {  		fuseOpts = &fs.Options{  			// These options are to be compatible with libfuse defaults, @@ -421,7 +426,8 @@ func initGoFuse(rootNode fs.InodeEmbedder, args *argContainer) *fuse.Server {  	} else if args.rw {  		mOpts.Options = append(mOpts.Options, "rw")  	} -	// If both "nosuid" and "suid" were passed, the safer option wins. +	// If both "nosuid" & "suid", "nodev" & "dev", etc were passed, the safer +	// option wins.  	if args.nosuid {  		mOpts.Options = append(mOpts.Options, "nosuid")  	} else if args.suid { diff --git a/tests/cli/cli_test.go b/tests/cli/cli_test.go index 2872592..23cea05 100644 --- a/tests/cli/cli_test.go +++ b/tests/cli/cli_test.go @@ -822,3 +822,50 @@ func TestInitNotEmpty(t *testing.T) {  		t.Fatalf("wrong exit code: have=%d, want=%d", exitCode, exitcodes.CipherDir)  	}  } + +// TestSharedstorage checks that `-sharedstorage` hands out arbitrary inode +// numbers (no hard link tracking) +func TestSharedstorage(t *testing.T) { +	dir := test_helpers.InitFS(t) +	mnt := dir + ".mnt" +	err := os.Mkdir(mnt, 0700) +	if err != nil { +		t.Fatal(err) +	} +	test_helpers.MountOrFatal(t, dir, mnt, "-extpass=echo test", "-sharedstorage") +	defer test_helpers.UnmountPanic(mnt) +	foo1 := mnt + "/foo1" +	foo2 := mnt + "/foo2" +	if err := ioutil.WriteFile(foo1, nil, 0755); err != nil { +		t.Fatal(err) +	} +	if err := os.Link(foo1, foo2); err != nil { +		t.Fatal(err) +	} +	var st1, st2, st3 syscall.Stat_t +	if err := syscall.Stat(foo1, &st1); err != nil { +		t.Fatal(err) +	} +	// The link show show a new inode number +	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) +	} +	// Check that we we don't have stat caching. New length should show up +	// on the hard link immediately. +	if err := ioutil.WriteFile(foo1, []byte("xxxxxx"), 0755); err != nil { +		t.Fatal(err) +	} +	if err := syscall.Stat(foo2, &st2); err != nil { +		t.Fatal(err) +	} +	if st2.Size != 6 { +		t.Fatal(st2.Size) +	} +} | 
