summaryrefslogtreecommitdiff
path: root/internal/fusefrontend/dircache.go
blob: 5ae6d6ba1945285732bf37d67565ba1a243e0cee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
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...)
	}
}