aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2021-03-07 17:22:29 +0100
committerJakob Unterwurzacher2021-03-07 17:22:29 +0100
commite2dc52a9657e530820b2d5b49ad7425b529029ce (patch)
tree75683d1194615dc2774a1b0f99850b798ade2b83
parenteaca820e876bfcdc67323eac6dd43ecc420968f2 (diff)
v2api: -sharestorage: disable hard link tracking & add tests
Hard link tracking was not correctly disabled since the migration to the go-fuse v2 api. Add a test to ensure it stays off. Fixes https://github.com/rfjakob/gocryptfs/issues/525
-rw-r--r--internal/fusefrontend/args.go3
-rw-r--r--internal/fusefrontend/node_helpers.go3
-rw-r--r--internal/fusefrontend/root_node.go10
-rw-r--r--internal/inomap/inomap.go11
-rw-r--r--mount.go10
-rw-r--r--tests/cli/cli_test.go47
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
+}
diff --git a/mount.go b/mount.go
index e4a36d1..571b51b 100644
--- a/mount.go
+++ b/mount.go
@@ -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)
+ }
+}