fs: add initial dirfd caching

dirfd caching was temporarily removed when moving
to the v2api. Add it back to gain back some lost speed.
This commit is contained in:
Jakob Unterwurzacher 2021-04-03 13:08:28 +02:00
parent 6aae2aad97
commit 24d5d39300
3 changed files with 43 additions and 13 deletions

View File

@ -21,13 +21,11 @@ const (
enableDebugMessages = false enableDebugMessages = false
// Enable hit rate statistics printing // Enable hit rate statistics printing
enableStats = false enableStats = false
pathFmt = "%-40q"
) )
type dirCacheEntryStruct struct { type dirCacheEntryStruct struct {
// relative plaintext path to the directory // pointer to the Node this entry belongs to
dirRelPath string node *Node
// fd to the directory (opened with O_PATH!) // fd to the directory (opened with O_PATH!)
fd int fd int
// content of gocryptfs.diriv in this directory // content of gocryptfs.diriv in this directory
@ -46,7 +44,7 @@ func (e *dirCacheEntryStruct) Clear() {
} }
} }
e.fd = -1 e.fd = -1
e.dirRelPath = "" e.node = nil
e.iv = nil e.iv = nil
} }
@ -76,7 +74,7 @@ func (d *dirCacheStruct) Clear() {
// Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller // Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller
// can close their copy at will. // can close their copy at will.
func (d *dirCacheStruct) Store(dirRelPath string, fd int, iv []byte) { func (d *dirCacheStruct) Store(node *Node, fd int, iv []byte) {
// Note: package ensurefds012, imported from main, guarantees that dirCache // Note: package ensurefds012, imported from main, guarantees that dirCache
// can never get fds 0,1,2. // can never get fds 0,1,2.
if fd <= 0 || len(iv) != nametransform.DirIVLen { if fd <= 0 || len(iv) != nametransform.DirIVLen {
@ -94,9 +92,9 @@ func (d *dirCacheStruct) Store(dirRelPath string, fd int, iv []byte) {
tlog.Warn.Printf("dirCache.Store: Dup failed: %v", err) tlog.Warn.Printf("dirCache.Store: Dup failed: %v", err)
return return
} }
d.dbg("Store "+pathFmt+" fd=%d iv=%x\n", dirRelPath, fd2, iv) d.dbg("dirCache.Store %p fd=%d iv=%x\n", node, fd2, iv)
e.fd = fd2 e.fd = fd2
e.dirRelPath = dirRelPath e.node = node
e.iv = iv e.iv = iv
// expireThread is started on the first Lookup() // expireThread is started on the first Lookup()
if !d.expireThreadRunning { if !d.expireThreadRunning {
@ -108,7 +106,7 @@ func (d *dirCacheStruct) Store(dirRelPath string, fd int, iv []byte) {
// Lookup checks if relPath is in the cache, and returns an (fd, iv) pair. // Lookup checks if relPath is in the cache, and returns an (fd, iv) pair.
// It returns (-1, nil) if not found. The fd is internally Dup()ed and the // It returns (-1, nil) if not found. The fd is internally Dup()ed and the
// caller must close it when done. // caller must close it when done.
func (d *dirCacheStruct) Lookup(dirRelPath string) (fd int, iv []byte) { func (d *dirCacheStruct) Lookup(node *Node) (fd int, iv []byte) {
d.Lock() d.Lock()
defer d.Unlock() defer d.Unlock()
if enableStats { if enableStats {
@ -121,7 +119,7 @@ func (d *dirCacheStruct) Lookup(dirRelPath string) (fd int, iv []byte) {
// Cache slot is empty // Cache slot is empty
continue continue
} }
if dirRelPath != e.dirRelPath { if node != e.node {
// Not the right path // Not the right path
continue continue
} }
@ -135,7 +133,7 @@ func (d *dirCacheStruct) Lookup(dirRelPath string) (fd int, iv []byte) {
break break
} }
if fd == 0 { if fd == 0 {
d.dbg("Lookup "+pathFmt+" miss\n", dirRelPath) d.dbg("dirCache.Lookup %p miss\n", node)
return -1, nil return -1, nil
} }
if enableStats { if enableStats {
@ -144,7 +142,7 @@ func (d *dirCacheStruct) Lookup(dirRelPath string) (fd int, iv []byte) {
if fd <= 0 || len(iv) != nametransform.DirIVLen { if fd <= 0 || len(iv) != nametransform.DirIVLen {
log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv)) log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
} }
d.dbg("Lookup "+pathFmt+" hit fd=%d dup=%d iv=%x\n", dirRelPath, e.fd, fd, iv) d.dbg("dirCache.Lookup %p hit fd=%d dup=%d iv=%x\n", node, e.fd, fd, iv)
return fd, iv return fd, iv
} }

View File

@ -9,6 +9,7 @@ import (
"github.com/hanwen/go-fuse/v2/fuse" "github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/internal/nametransform"
"github.com/rfjakob/gocryptfs/internal/syscallcompat" "github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog" "github.com/rfjakob/gocryptfs/internal/tlog"
) )
@ -83,11 +84,28 @@ func (n *Node) rootNode() *RootNode {
// a child of this node. // a child of this node.
// If `child` is empty, the (dirfd, cName) pair refers to this node itself. // If `child` is empty, the (dirfd, cName) pair refers to this node itself.
func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) { func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno syscall.Errno) {
rn := n.rootNode()
// Cache lookup
// TODO: also handle caching for root node & plaintextnames
cacheable := (child != "" && !rn.args.PlaintextNames)
if cacheable {
var iv []byte
dirfd, iv = rn.dirCache.Lookup(n)
if dirfd > 0 {
cName, err := rn.nameTransform.EncryptAndHashName(child, iv)
if err != nil {
return -1, "", fs.ToErrno(err)
}
return dirfd, cName, 0
}
}
// Slowpath
p := n.Path() p := n.Path()
if child != "" { if child != "" {
p = filepath.Join(p, child) p = filepath.Join(p, child)
} }
rn := n.rootNode()
if rn.isFiltered(p) { if rn.isFiltered(p) {
errno = syscall.EPERM errno = syscall.EPERM
return return
@ -96,6 +114,18 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy
if err != nil { if err != nil {
errno = fs.ToErrno(err) errno = fs.ToErrno(err)
} }
// Cache store
// TODO: also handle caching for root node & plaintextnames
if cacheable {
// TODO: openBackingDir already calls ReadDirIVAt(). Get the data out.
iv, err := nametransform.ReadDirIVAt(dirfd)
if err != nil {
syscall.Close(dirfd)
return -1, "", fs.ToErrno(err)
}
rn.dirCache.Store(n, dirfd, iv)
}
return return
} }

View File

@ -48,6 +48,8 @@ type RootNode struct {
// When -idle was used when mounting, idleMonitor() sets it to 1 // When -idle was used when mounting, idleMonitor() sets it to 1
// periodically. // periodically.
IsIdle uint32 IsIdle uint32
// dirCache caches directory fds
dirCache dirCacheStruct
// inoMap translates inode numbers from different devices to unique inode // inoMap translates inode numbers from different devices to unique inode
// numbers. // numbers.
inoMap inomap.TranslateStater inoMap inomap.TranslateStater