summaryrefslogtreecommitdiff
path: root/internal/fusefrontend/dircache.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/fusefrontend/dircache.go')
-rw-r--r--internal/fusefrontend/dircache.go117
1 files changed, 117 insertions, 0 deletions
diff --git a/internal/fusefrontend/dircache.go b/internal/fusefrontend/dircache.go
new file mode 100644
index 0000000..5ae6d6b
--- /dev/null
+++ b/internal/fusefrontend/dircache.go
@@ -0,0 +1,117 @@
+package fusefrontend
+
+import (
+ "fmt"
+ "log"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/rfjakob/gocryptfs/internal/nametransform"
+ "github.com/rfjakob/gocryptfs/internal/tlog"
+)
+
+type dirCacheStruct struct {
+ sync.Mutex
+ // relative plaintext path to the directory
+ dirRelPath string
+ // fd to the directory (opened with O_PATH!)
+ fd int
+ // content of gocryptfs.diriv in this directory
+ iv []byte
+ // on the first Lookup(), the expire thread is stared, and this is set
+ // to true.
+ expireThreadRunning bool
+}
+
+// Clear clears the cache contents.
+func (d *dirCacheStruct) Clear() {
+ d.Lock()
+ defer d.Unlock()
+ d.doClear()
+}
+
+// doClear closes the fd and clears the cache contents.
+// Caller must hold d.Lock()!
+func (d *dirCacheStruct) doClear() {
+ // An earlier clear may have already closed the fd, or the cache
+ // has never been filled (fd is 0 in that case).
+ if d.fd > 0 {
+ err := syscall.Close(d.fd)
+ if err != nil {
+ tlog.Warn.Printf("dirCache.Clear: Close failed: %v", err)
+ }
+ }
+ d.fd = -1
+ d.dirRelPath = ""
+ d.iv = nil
+}
+
+// Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller
+// can close their copy at will.
+func (d *dirCacheStruct) Store(dirRelPath string, fd int, iv []byte) {
+ if fd <= 0 || len(iv) != nametransform.DirIVLen {
+ log.Panicf("Store sanity check failed: fd=%d len=%d", fd, len(iv))
+ }
+ d.Lock()
+ defer d.Unlock()
+ // Close the old fd
+ d.doClear()
+ fd2, err := syscall.Dup(fd)
+ if err != nil {
+ tlog.Warn.Printf("dirCache.Store: Dup failed: %v", err)
+ return
+ }
+ d.fd = fd2
+ d.dbg("Store: %q %d %x\n", dirRelPath, fd2, iv)
+ d.dirRelPath = dirRelPath
+ d.iv = iv
+ // expireThread is started on the first Lookup()
+ if !d.expireThreadRunning {
+ d.expireThreadRunning = true
+ go d.expireThread()
+ }
+}
+
+// Lookup checks if relPath is in the cache, and returns and (fd, iv) pair.
+// It returns (-1, nil) if not found. The fd is internally Dup()ed and the
+// caller must close it when done.
+func (d *dirCacheStruct) Lookup(dirRelPath string) (fd int, iv []byte) {
+ d.Lock()
+ defer d.Unlock()
+ if d.fd <= 0 {
+ // Cache is empty
+ d.dbg("Lookup %q: empty\n", dirRelPath)
+ return -1, nil
+ }
+ if dirRelPath != d.dirRelPath {
+ d.dbg("Lookup %q: miss\n", dirRelPath)
+ return -1, nil
+ }
+ fd, err := syscall.Dup(d.fd)
+ if err != nil {
+ tlog.Warn.Printf("dirCache.Lookup: Dup failed: %v", err)
+ return -1, nil
+ }
+ if fd <= 0 || len(d.iv) != nametransform.DirIVLen {
+ log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(d.iv))
+ }
+ d.dbg("Lookup %q: hit %d %x\n", dirRelPath, fd, d.iv)
+ return fd, d.iv
+}
+
+// expireThread is started on the first Lookup()
+func (d *dirCacheStruct) expireThread() {
+ for {
+ time.Sleep(1 * time.Second)
+ d.Clear()
+ }
+}
+
+// dbg prints a debug message. Usually disabled.
+func (d *dirCacheStruct) dbg(format string, a ...interface{}) {
+ const EnableDebugMessages = false
+ if EnableDebugMessages {
+ fmt.Printf(format, a...)
+ }
+}