Skip to content

Commit 84e7021

Browse files
committed
fusefrontend: implement recursive diriv caching
The new contrib/maxlen.bash showed that we have exponential runtime with respect to directory depth. The new recursive diriv caching is a lot smarter as it caches intermediate lookups. maxlen.bash now completes in a few seconds. xfstests results same as https://github.com/rfjakob/fuse-xfstests/blob/2d158e4c82be85c15269af77498e353f928f4fab/screenlog.0 : Failures: generic/035 generic/062 generic/080 generic/093 generic/099 generic/215 generic/285 generic/319 generic/426 generic/444 generic/467 generic/477 generic/523 Failed 13 of 580 tests benchmark.bash results are identical: $ ./benchmark.bash Testing gocryptfs at /tmp/benchmark.bash.BdQ: gocryptfs v2.0.1-17-g6b09bc0; go-fuse v2.1.1-0.20210611132105-24a1dfe6b4f8; 2021-06-25 go1.16.5 linux/amd64 /tmp/benchmark.bash.BdQ.mnt is a mountpoint WRITE: 262144000 bytes (262 MB, 250 MiB) copied, 0,4821 s, 544 MB/s READ: 262144000 bytes (262 MB, 250 MiB) copied, 0,266061 s, 985 MB/s UNTAR: 8,280 MD5: 4,564 LS: 1,745 RM: 2,244
1 parent 05b813f commit 84e7021

File tree

9 files changed

+144
-110
lines changed

9 files changed

+144
-110
lines changed

internal/fusefrontend/dircache.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"syscall"
88
"time"
99

10-
"github.com/rfjakob/gocryptfs/internal/nametransform"
1110
"github.com/rfjakob/gocryptfs/internal/tlog"
1211
)
1312

@@ -50,6 +49,9 @@ func (e *dirCacheEntry) Clear() {
5049

5150
type dirCache struct {
5251
sync.Mutex
52+
// Expected length of the stored IVs. Only used for sanity checks.
53+
// Usually set to 16, but 0 in plaintextnames mode.
54+
ivLen int
5355
// Cache entries
5456
entries [dirCacheSize]dirCacheEntry
5557
// Where to store the next entry (index into entries)
@@ -77,7 +79,7 @@ func (d *dirCache) Clear() {
7779
func (d *dirCache) Store(node *Node, fd int, iv []byte) {
7880
// Note: package ensurefds012, imported from main, guarantees that dirCache
7981
// can never get fds 0,1,2.
80-
if fd <= 0 || len(iv) != nametransform.DirIVLen {
82+
if fd <= 0 || len(iv) != d.ivLen {
8183
log.Panicf("Store sanity check failed: fd=%d len=%d", fd, len(iv))
8284
}
8385
d.Lock()
@@ -139,7 +141,7 @@ func (d *dirCache) Lookup(node *Node) (fd int, iv []byte) {
139141
if enableStats {
140142
d.hits++
141143
}
142-
if fd <= 0 || len(iv) != nametransform.DirIVLen {
144+
if fd <= 0 || len(iv) != d.ivLen {
143145
log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
144146
}
145147
d.dbg("dirCache.Lookup %p hit fd=%d dup=%d iv=%x\n", node, e.fd, fd, iv)

internal/fusefrontend/node.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
5252
return f.(fs.FileGetattrer).Getattr(ctx, out)
5353
}
5454

55-
dirfd, cName, errno := n.prepareAtSyscall("")
55+
dirfd, cName, errno := n.prepareAtSyscallMyself()
5656
if errno != 0 {
5757
return
5858
}
@@ -106,7 +106,7 @@ func (n *Node) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
106106
//
107107
// Symlink-safe through openBackingDir() + Readlinkat().
108108
func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
109-
dirfd, cName, errno := n.prepareAtSyscall("")
109+
dirfd, cName, errno := n.prepareAtSyscallMyself()
110110
if errno != 0 {
111111
return
112112
}
@@ -123,7 +123,7 @@ func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn,
123123
return f2.Setattr(ctx, in, out)
124124
}
125125

126-
dirfd, cName, errno := n.prepareAtSyscall("")
126+
dirfd, cName, errno := n.prepareAtSyscallMyself()
127127
if errno != 0 {
128128
return
129129
}
@@ -271,7 +271,7 @@ func (n *Node) Link(ctx context.Context, target fs.InodeEmbedder, name string, o
271271
defer syscall.Close(dirfd)
272272

273273
n2 := toNode(target)
274-
dirfd2, cName2, errno := n2.prepareAtSyscall("")
274+
dirfd2, cName2, errno := n2.prepareAtSyscallMyself()
275275
if errno != 0 {
276276
return
277277
}

internal/fusefrontend/node_dir_ops.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
154154
// This function is symlink-safe through use of openBackingDir() and
155155
// ReadDirIVAt().
156156
func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
157-
parentDirFd, cDirName, errno := n.prepareAtSyscall("")
157+
parentDirFd, cDirName, errno := n.prepareAtSyscallMyself()
158158
if errno != 0 {
159159
return nil, errno
160160
}
@@ -360,7 +360,7 @@ retry:
360360

361361
// Opendir is a FUSE call to check if the directory can be opened.
362362
func (n *Node) Opendir(ctx context.Context) (errno syscall.Errno) {
363-
dirfd, cName, errno := n.prepareAtSyscall("")
363+
dirfd, cName, errno := n.prepareAtSyscallMyself()
364364
if errno != 0 {
365365
return
366366
}

internal/fusefrontend/node_helpers.go

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,12 @@ package fusefrontend
22

33
import (
44
"context"
5-
"log"
6-
"path/filepath"
7-
"sync/atomic"
85
"syscall"
96

107
"github.com/hanwen/go-fuse/v2/fs"
118

129
"github.com/hanwen/go-fuse/v2/fuse"
1310

14-
"github.com/rfjakob/gocryptfs/internal/nametransform"
1511
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
1612
"github.com/rfjakob/gocryptfs/internal/tlog"
1713
)
@@ -80,91 +76,6 @@ func (n *Node) rootNode() *RootNode {
8076
return n.Root().Operations().(*RootNode)
8177
}
8278

83-
// prepareAtSyscall returns a (dirfd, cName) pair that can be used
84-
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to
85-
// access the backing encrypted directory.
86-
//
87-
// If you pass a `child` file name, the (dirfd, cName) pair will refer to
88-
// a child of this node.
89-
// If `child` is empty, the (dirfd, cName) pair refers to this node itself. For
90-
// the root node, that means (dirfd, ".").
91-
func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) {
92-
rn := n.rootNode()
93-
// all filesystem operations go through prepareAtSyscall(), so this is a
94-
// good place to reset the idle marker.
95-
atomic.StoreUint32(&rn.IsIdle, 0)
96-
97-
// root node itself is special
98-
if child == "" && n.IsRoot() {
99-
var err error
100-
dirfd, cName, err = rn.openBackingDir("")
101-
if err != nil {
102-
errno = fs.ToErrno(err)
103-
}
104-
return
105-
}
106-
107-
// normal node itself can be converted to child of parent node
108-
if child == "" {
109-
name, p1 := n.Parent()
110-
if p1 == nil || name == "" {
111-
return -1, "", syscall.ENOENT
112-
}
113-
p2 := toNode(p1.Operations())
114-
return p2.prepareAtSyscall(name)
115-
}
116-
117-
// Cache lookup
118-
// TODO make it work for plaintextnames as well?
119-
cacheable := (!rn.args.PlaintextNames)
120-
if cacheable {
121-
var iv []byte
122-
dirfd, iv = rn.dirCache.Lookup(n)
123-
if dirfd > 0 {
124-
var cName string
125-
var err error
126-
if rn.nameTransform.HaveBadnamePatterns() {
127-
//BadName allowed, try to determine filenames
128-
cName, err = rn.nameTransform.EncryptAndHashBadName(child, iv, dirfd)
129-
} else {
130-
cName, err = rn.nameTransform.EncryptAndHashName(child, iv)
131-
}
132-
133-
if err != nil {
134-
return -1, "", fs.ToErrno(err)
135-
}
136-
return dirfd, cName, 0
137-
}
138-
}
139-
140-
// Slowpath
141-
if child == "" {
142-
log.Panicf("BUG: child name is empty - this cannot happen")
143-
}
144-
p := filepath.Join(n.Path(), child)
145-
if rn.isFiltered(p) {
146-
errno = syscall.EPERM
147-
return
148-
}
149-
dirfd, cName, err := rn.openBackingDir(p)
150-
if err != nil {
151-
errno = fs.ToErrno(err)
152-
return
153-
}
154-
155-
// Cache store
156-
if cacheable {
157-
// TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work?
158-
iv, err := nametransform.ReadDirIVAt(dirfd)
159-
if err != nil {
160-
syscall.Close(dirfd)
161-
return -1, "", fs.ToErrno(err)
162-
}
163-
rn.dirCache.Store(n, dirfd, iv)
164-
}
165-
return
166-
}
167-
16879
// newChild attaches a new child inode to n.
16980
// The passed-in `st` will be modified to get a unique inode number
17081
// (or, in `-sharedstorage` mode, the inode number will be set to zero).

internal/fusefrontend/node_open_create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
//
1717
// Symlink-safe through Openat().
1818
func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
19-
dirfd, cName, errno := n.prepareAtSyscall("")
19+
dirfd, cName, errno := n.prepareAtSyscallMyself()
2020
if errno != 0 {
2121
return
2222
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package fusefrontend
2+
3+
import (
4+
"sync/atomic"
5+
"syscall"
6+
7+
"github.com/rfjakob/gocryptfs/internal/tlog"
8+
9+
"github.com/hanwen/go-fuse/v2/fs"
10+
11+
"github.com/rfjakob/gocryptfs/internal/nametransform"
12+
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
13+
)
14+
15+
// prepareAtSyscall returns a (dirfd, cName) pair that can be used
16+
// with the "___at" family of system calls (openat, fstatat, unlinkat...) to
17+
// access the backing encrypted child file.
18+
func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) {
19+
if child == "" {
20+
tlog.Warn.Printf("BUG: prepareAtSyscall: child=%q, should have called prepareAtSyscallMyself", child)
21+
return n.prepareAtSyscallMyself()
22+
}
23+
24+
rn := n.rootNode()
25+
26+
// All filesystem operations go through here, so this is a good place
27+
// to reset the idle marker.
28+
atomic.StoreUint32(&rn.IsIdle, 0)
29+
30+
if n.IsRoot() && rn.isFiltered(child) {
31+
return -1, "", syscall.EPERM
32+
}
33+
34+
var encryptName func(int, string, []byte) (string, error)
35+
if !rn.args.PlaintextNames {
36+
encryptName = func(dirfd int, child string, iv []byte) (cName string, err error) {
37+
// Badname allowed, try to determine filenames
38+
if rn.nameTransform.HaveBadnamePatterns() {
39+
return rn.nameTransform.EncryptAndHashBadName(child, iv, dirfd)
40+
}
41+
return rn.nameTransform.EncryptAndHashName(child, iv)
42+
}
43+
}
44+
45+
// Cache lookup
46+
var iv []byte
47+
dirfd, iv = rn.dirCache.Lookup(n)
48+
if dirfd > 0 {
49+
if rn.args.PlaintextNames {
50+
return dirfd, child, 0
51+
}
52+
var err error
53+
cName, err = encryptName(dirfd, child, iv)
54+
if err != nil {
55+
syscall.Close(dirfd)
56+
return -1, "", fs.ToErrno(err)
57+
}
58+
return
59+
}
60+
61+
// Slowpath: Open ourselves & read diriv
62+
parentDirfd, myCName, errno := n.prepareAtSyscallMyself()
63+
if errno != 0 {
64+
return
65+
}
66+
defer syscall.Close(parentDirfd)
67+
68+
dirfd, err := syscallcompat.Openat(parentDirfd, myCName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
69+
70+
// Cache store
71+
if !rn.args.PlaintextNames {
72+
var err error
73+
iv, err = nametransform.ReadDirIVAt(dirfd)
74+
if err != nil {
75+
syscall.Close(dirfd)
76+
return -1, "", fs.ToErrno(err)
77+
}
78+
}
79+
rn.dirCache.Store(n, dirfd, iv)
80+
81+
if rn.args.PlaintextNames {
82+
return dirfd, child, 0
83+
}
84+
85+
cName, err = encryptName(dirfd, child, iv)
86+
if err != nil {
87+
syscall.Close(dirfd)
88+
return -1, "", fs.ToErrno(err)
89+
}
90+
91+
return
92+
}
93+
94+
func (n *Node) prepareAtSyscallMyself() (dirfd int, cName string, errno syscall.Errno) {
95+
dirfd = -1
96+
97+
// Handle root node
98+
if n.IsRoot() {
99+
var err error
100+
rn := n.rootNode()
101+
dirfd, cName, err = rn.openBackingDir("")
102+
if err != nil {
103+
errno = fs.ToErrno(err)
104+
}
105+
return
106+
}
107+
108+
// Otherwise convert to prepareAtSyscall of parent node
109+
myName, p1 := n.Parent()
110+
if p1 == nil || myName == "" {
111+
errno = syscall.ENOENT
112+
return
113+
}
114+
parent := toNode(p1.Operations())
115+
return parent.prepareAtSyscall(myName)
116+
}

internal/fusefrontend/node_xattr_darwin.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func filterXattrSetFlags(flags int) int {
2020
}
2121

2222
func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
23-
dirfd, cName, errno := n.prepareAtSyscall("")
23+
dirfd, cName, errno := n.prepareAtSyscallMyself()
2424
if errno != 0 {
2525
return
2626
}
@@ -42,7 +42,7 @@ func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
4242
}
4343

4444
func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags uint32) (errno syscall.Errno) {
45-
dirfd, cName, errno := n.prepareAtSyscall("")
45+
dirfd, cName, errno := n.prepareAtSyscallMyself()
4646
if errno != 0 {
4747
return
4848
}
@@ -64,7 +64,7 @@ func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags
6464
}
6565

6666
func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
67-
dirfd, cName, errno := n.prepareAtSyscall("")
67+
dirfd, cName, errno := n.prepareAtSyscallMyself()
6868
if errno != 0 {
6969
return
7070
}
@@ -86,7 +86,7 @@ func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
8686
}
8787

8888
func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
89-
dirfd, cName, errno := n.prepareAtSyscall("")
89+
dirfd, cName, errno := n.prepareAtSyscallMyself()
9090
if errno != 0 {
9191
return
9292
}

internal/fusefrontend/node_xattr_linux.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func filterXattrSetFlags(flags int) int {
1717
}
1818

1919
func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
20-
dirfd, cName, errno := n.prepareAtSyscall("")
20+
dirfd, cName, errno := n.prepareAtSyscallMyself()
2121
if errno != 0 {
2222
return
2323
}
@@ -32,7 +32,7 @@ func (n *Node) getXAttr(cAttr string) (out []byte, errno syscall.Errno) {
3232
}
3333

3434
func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags uint32) (errno syscall.Errno) {
35-
dirfd, cName, errno := n.prepareAtSyscall("")
35+
dirfd, cName, errno := n.prepareAtSyscallMyself()
3636
if errno != 0 {
3737
return
3838
}
@@ -44,7 +44,7 @@ func (n *Node) setXAttr(context *fuse.Context, cAttr string, cData []byte, flags
4444
}
4545

4646
func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
47-
dirfd, cName, errno := n.prepareAtSyscall("")
47+
dirfd, cName, errno := n.prepareAtSyscallMyself()
4848
if errno != 0 {
4949
return
5050
}
@@ -55,7 +55,7 @@ func (n *Node) removeXAttr(cAttr string) (errno syscall.Errno) {
5555
}
5656

5757
func (n *Node) listXAttr() (out []string, errno syscall.Errno) {
58-
dirfd, cName, errno := n.prepareAtSyscall("")
58+
dirfd, cName, errno := n.prepareAtSyscallMyself()
5959
if errno != 0 {
6060
return
6161
}

0 commit comments

Comments
 (0)