Skip to content

Commit e36a0eb

Browse files
committed
main: add "-sharedstorage" flag
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. Mitigates #156
1 parent 9ab6cdb commit e36a0eb

File tree

4 files changed

+82
-7
lines changed

4 files changed

+82
-7
lines changed

Documentation/MANPAGE.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,30 @@ These factors will limit throughput to below 70MB/s.
248248

249249
For more details visit https://github.com/rfjakob/gocryptfs/issues/92 .
250250

251+
#### -sharedstorage
252+
Enable work-arounds so gocryptfs works better when the backing
253+
storage directory is concurrently accessed by multiple gocryptfs
254+
instances.
255+
256+
At the moment, it does two things:
257+
258+
1. Disable stat() caching so changes to the backing storage show up
259+
immediately.
260+
2. Disable hard link tracking, as the inode numbers on the backing
261+
storage are not stable when files are deleted and re-created behind
262+
our back. This would otherwise produce strange "file does not exist"
263+
and other errors.
264+
265+
When "-sharedstorage" is active, performance is reduced and hard
266+
links cannot be created.
267+
268+
Even with this flag set, you may hit occasional problems. Running
269+
gocryptfs on shared storage does not receive as much testing as the
270+
usual (exclusive) use-case. Please test your workload in advance
271+
and report any problems you may hit.
272+
273+
More info: https://github.com/rfjakob/gocryptfs/issues/156
274+
251275
#### -speed
252276
Run crypto speed test. Benchmark Go's built-in GCM against OpenSSL
253277
(if available). The library that will be selected on "-openssl=auto"

Documentation/duplicate-inodes.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
ls: cannot access foo: No such file or directory
2+
ls: cannot access foo: No such file or directory
3+
ls: cannot access foo: No such file or directory
4+
ls: cannot access foo: No such file or directory
5+
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
6+
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
7+
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
8+
36962337 -rwxrwxrwx 1 u1026 users 0 Nov 11 18:00 foo
9+
10+
11+
u1026@d8min:/mnt/synology/public/tmp/g1$ strace -e lstat -p 8899 -f
12+
Process 8899 attached with 10 threads
13+
2017/11/11 18:12:21 Dispatch 238: LOOKUP, NodeId: 1. names: [foo] 4 bytes
14+
[pid 10539] lstat("/mnt/synology/public/tmp/g1/a/4DZNVle_txclugO7n_FRIg", 0xc4241adbe8) = -1 ENOENT (No such file or directory)
15+
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}}
16+
2017/11/11 18:12:22 Dispatch 239: LOOKUP, NodeId: 1. names: [foo] 4 bytes
17+
[pid 8903] lstat("/mnt/synology/public/tmp/g1/a/Xsy8mhdcIh0u9aiI7-iLiw", {st_mode=S_IFREG|0777, st_size=0, ...}) = 0
18+
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}}
19+
20+
21+
Call Trace:
22+
23+
nodefs/fsops.go (c *rawBridge) Lookup
24+
nodefs/fsops.go (c *FileSystemConnector) internalLookup
25+
nodefs/inode.go (n *Inode) GetChild
26+
pathfs/pathfs.go (n *pathInode) GetAttr
27+
pathfs/pathfs.go (n *pathInode) GetPath
28+
nodefs/inode.go (n *Inode) Parent()
29+
pathfs/loopback.go (fs *loopbackFileSystem) GetAttr
30+
31+
Call Trace 2 (new child):
32+
33+
nodefs/fsops.go (c *rawBridge) Lookup
34+
nodefs/fsops.go (c *FileSystemConnector) internalLookup
35+
pathfs/pathfs.go (n *pathInode) Lookup
36+
pathfs/pathfs.go (n *pathInode) findChild

cli_args.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ type argContainer struct {
2121
debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
2222
plaintextnames, quiet, nosyslog, wpanic,
2323
longnames, allow_other, ro, reverse, aessiv, nonempty, raw64,
24-
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info bool
24+
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
25+
sharedstorage bool
2526
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
2627
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
2728
// Configuration file name override
@@ -130,6 +131,7 @@ func parseCliOpts() (args argContainer) {
130131
" Requires gocryptfs to be compiled with openssl support and implies -openssl true")
131132
flagSet.BoolVar(&args.hh, "hh", false, "Show this long help text")
132133
flagSet.BoolVar(&args.info, "info", false, "Display information about CIPHERDIR")
134+
flagSet.BoolVar(&args.sharedstorage, "sharedstorage", false, "Make concurrent access to a shared CIPHERDIR safer")
133135
flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key")
134136
flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file")
135137
flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file")

mount.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile
230230
var ctlSockBackend ctlsock.Interface
231231
// pathFsOpts are passed into go-fuse/pathfs
232232
pathFsOpts := &pathfs.PathNodeFsOptions{ClientInodes: true}
233+
if args.sharedstorage {
234+
// shared storage mode disables hard link tracking as the backing inode
235+
// numbers may change behind our back:
236+
// https://github.com/rfjakob/gocryptfs/issues/156
237+
pathFsOpts.ClientInodes = false
238+
}
233239
if args.reverse {
234240
// The dance with the intermediate variables is because we need to
235241
// cast the FS into pathfs.FileSystem *and* ctlsock.Interface. This
@@ -257,12 +263,19 @@ func initFuseFrontend(masterkey []byte, args *argContainer, confFile *configfile
257263
go ctlsock.Serve(args._ctlsockFd, ctlSockBackend)
258264
}
259265
pathFs := pathfs.NewPathNodeFs(finalFs, pathFsOpts)
260-
fuseOpts := &nodefs.Options{
261-
// These options are to be compatible with libfuse defaults,
262-
// making benchmarking easier.
263-
NegativeTimeout: time.Second,
264-
AttrTimeout: time.Second,
265-
EntryTimeout: time.Second,
266+
var fuseOpts *nodefs.Options
267+
if args.sharedstorage {
268+
// sharedstorage mode sets all cache timeouts to zero so changes to the
269+
// backing shared storage show up immediately.
270+
fuseOpts = &nodefs.Options{}
271+
} else {
272+
fuseOpts = &nodefs.Options{
273+
// These options are to be compatible with libfuse defaults,
274+
// making benchmarking easier.
275+
NegativeTimeout: time.Second,
276+
AttrTimeout: time.Second,
277+
EntryTimeout: time.Second,
278+
}
266279
}
267280
conn := nodefs.NewFileSystemConnector(pathFs.Root(), fuseOpts)
268281
mOpts := fuse.MountOptions{

0 commit comments

Comments
 (0)