diff --git a/Documentation/MANPAGE.md b/Documentation/MANPAGE.md index cfe27a5..efb8565 100644 --- a/Documentation/MANPAGE.md +++ b/Documentation/MANPAGE.md @@ -248,6 +248,30 @@ These factors will limit throughput to below 70MB/s. For more details visit https://github.com/rfjakob/gocryptfs/issues/92 . +#### -sharedstorage +Enable work-arounds so gocryptfs works better when the backing +storage directory is concurrently accessed by multiple gocryptfs +instances. + +At the moment, it does two things: + +1. Disable stat() caching so changes to the backing storage show up + immediately. +2. Disable hard link tracking, as the inode numbers on the backing + storage are not stable when files are deleted and re-created behind + our back. This would otherwise produce strange "file does not exist" + and other errors. + +When "-sharedstorage" is active, performance is reduced and hard +links cannot be created. + +Even with this flag set, you may hit occasional problems. Running +gocryptfs on shared storage does not receive as much testing as the +usual (exclusive) use-case. Please test your workload in advance +and report any problems you may hit. + +More info: https://github.com/rfjakob/gocryptfs/issues/156 + #### -speed Run crypto speed test. Benchmark Go's built-in GCM against OpenSSL (if available). The library that will be selected on "-openssl=auto" diff --git a/Documentation/duplicate-inodes.txt b/Documentation/duplicate-inodes.txt new file mode 100644 index 0000000..36fb89c --- /dev/null +++ b/Documentation/duplicate-inodes.txt @@ -0,0 +1,36 @@ +ls: cannot access foo: No such file or directory +ls: cannot access foo: No such file or directory +ls: cannot access foo: No such file or directory +ls: cannot access foo: No such file or directory +36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo +36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo +36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo +36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo + + +u1026@d8min:/mnt/synology/public/tmp/g1$ strace -e lstat -p 8899 -f +Process 8899 attached with 10 threads +2017/11/11 18:12:21 Dispatch 238: LOOKUP, NodeId: 1. names: [foo] 4 bytes +[pid 10539] lstat("/mnt/synology/public/tmp/g1/a/4DZNVle_txclugO7n_FRIg", 0xc4241adbe8) = -1 ENOENT (No such file or directory) +2017/11/11 18:12:21 Serialize 238: LOOKUP code: OK value: {NodeId: 0 Generation=0 EntryValid=1.000 AttrValid=0.000 Attr={M00 SZ=0 L=0 0:0 B0*0 i0:0 A 0.000000000 M 0.000000000 C 0.000000000}} +2017/11/11 18:12:22 Dispatch 239: LOOKUP, NodeId: 1. names: [foo] 4 bytes +[pid 8903] lstat("/mnt/synology/public/tmp/g1/a/Xsy8mhdcIh0u9aiI7-iLiw", {st_mode=S_IFREG|0777, st_size=0, ...}) = 0 +2017/11/11 18:12:22 Serialize 239: LOOKUP code: OK value: {NodeId: 3 Generation=4 EntryValid=1.000 AttrValid=1.000 Attr={M0100777 SZ=0 L=1 1026:100 B0*16384 i0:36962337 A 1510419642.457639700 M 1510419642.457639700 C 1510419702.353712800}} + + +Call Trace: + +nodefs/fsops.go (c *rawBridge) Lookup + nodefs/fsops.go (c *FileSystemConnector) internalLookup + nodefs/inode.go (n *Inode) GetChild + pathfs/pathfs.go (n *pathInode) GetAttr + pathfs/pathfs.go (n *pathInode) GetPath + nodefs/inode.go (n *Inode) Parent() + pathfs/loopback.go (fs *loopbackFileSystem) GetAttr + +Call Trace 2 (new child): + +nodefs/fsops.go (c *rawBridge) Lookup + nodefs/fsops.go (c *FileSystemConnector) internalLookup + pathfs/pathfs.go (n *pathInode) Lookup + pathfs/pathfs.go (n *pathInode) findChild diff --git a/cli_args.go b/cli_args.go index 836d29c..71ad6bd 100644 --- a/cli_args.go +++ b/cli_args.go @@ -21,7 +21,8 @@ type argContainer struct { debug, init, zerokey, fusedebug, openssl, passwd, fg, version, plaintextnames, quiet, nosyslog, wpanic, longnames, allow_other, ro, reverse, aessiv, nonempty, raw64, - noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info bool + noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info, + sharedstorage bool masterkey, mountpoint, cipherdir, cpuprofile, extpass, memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string // Configuration file name override @@ -130,6 +131,7 @@ func parseCliOpts() (args argContainer) { " Requires gocryptfs to be compiled with openssl support and implies -openssl true") flagSet.BoolVar(&args.hh, "hh", false, "Show this long help text") flagSet.BoolVar(&args.info, "info", false, "Display information about CIPHERDIR") + flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file") diff --git a/mount.go b/mount.go index 007cc46..c3fda80 100644 --- a/mount.go +++ b/mount.go @@ -230,6 +230,12 @@ func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile var ctlSockBackend ctlsock.Interface // pathFsOpts are passed into go-fuse/pathfs pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true} + if args.sharedstorage { + // shared storage mode disables hard link tracking as the backing inode + // numbers may change behind our back: + // https://github.com/rfjakob/gocryptfs/issues/156 + pathFsOpts.ClientInodes = false + } if args.reverse { // The dance with the intermediate variables is because we need to // cast the FS into pathfs.FileSystem *and* ctlsock.Interface. This @@ -257,12 +263,19 @@ func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile go ctlsock.Serve(args._ctlsockFd, ctlSockBackend) } pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts) - fuseOpts := &nodefs.Options{ - // These options are to be compatible with libfuse defaults, - // making benchmarking easier. - NegativeTimeout: time.Second, - AttrTimeout: time.Second, - EntryTimeout: time.Second, + var fuseOpts *nodefs.Options + if args.sharedstorage { + // sharedstorage mode sets all cache timeouts to zero so changes to the + // backing shared storage show up immediately. + fuseOpts = &nodefs.Options{} + } else { + fuseOpts = &nodefs.Options{ + // These options are to be compatible with libfuse defaults, + // making benchmarking easier. + NegativeTimeout: time.Second, + AttrTimeout: time.Second, + EntryTimeout: time.Second, + } } conn := nodefs.NewFileSystemConnector(pathFs.Root(), fuseOpts) mOpts := fuse.MountOptions{