diff options
| author | Jakob Unterwurzacher | 2019-01-02 22:32:21 +0100 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2019-01-03 15:31:13 +0100 | 
| commit | 4f66d66755da63c78b09201c6c72353009251cf2 (patch) | |
| tree | 9eb4e937419e7255c56df04d3c30d36185e1a337 /internal/fusefrontend | |
| parent | f6dad8d0fae25b5d88ad036b841fea10b7296ccb (diff) | |
fusefrontend: add dirCache
Diffstat (limited to 'internal/fusefrontend')
| -rw-r--r-- | internal/fusefrontend/dircache.go | 117 | ||||
| -rw-r--r-- | internal/fusefrontend/fs.go | 6 | ||||
| -rw-r--r-- | internal/fusefrontend/fs_dir.go | 8 | ||||
| -rw-r--r-- | internal/fusefrontend/openbackingdir.go | 15 | ||||
| -rw-r--r-- | internal/fusefrontend/openbackingdir_test.go | 7 | 
5 files changed, 142 insertions, 11 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...) +	} +} diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 5adade6..c0a275f 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -53,6 +53,8 @@ type FS struct {  	// which is called as part of every filesystem operation.  	// (This flag uses a uint32 so that it can be reset with CompareAndSwapUint32.)  	AccessedSinceLastCheck uint32 + +	dirCache dirCacheStruct  }  var _ pathfs.FileSystem = &FS{} // Verify that interface is implemented. @@ -533,6 +535,7 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co  //  // Symlink-safe through Renameat().  func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) { +	defer fs.dirCache.Clear()  	if fs.isFiltered(newPath) {  		return fuse.EPERM  	} @@ -546,9 +549,6 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod  		return fuse.ToStatus(err)  	}  	defer syscall.Close(newDirfd) -	// The Rename may cause a directory to take the place of another directory. -	// That directory may still be in the DirIV cache, clear it. -	fs.nameTransform.DirIVCache.Clear()  	// Easy case.  	if fs.args.PlaintextNames {  		return fuse.ToStatus(syscallcompat.Renameat(oldDirfd, oldCName, newDirfd, newCName)) diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index 302fe38..3c71e45 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -30,8 +30,6 @@ func (fs *FS) mkdirWithIv(dirfd int, cName string, mode uint32) error {  	// the directory is inconsistent. Take the lock to prevent other readers  	// from seeing it.  	fs.dirIVLock.Lock() -	// The new directory may take the place of an older one that is still in the cache -	fs.nameTransform.DirIVCache.Clear()  	defer fs.dirIVLock.Unlock()  	err := syscallcompat.Mkdirat(dirfd, cName, mode)  	if err != nil { @@ -57,6 +55,7 @@ func (fs *FS) mkdirWithIv(dirfd int, cName string, mode uint32) error {  //  // Symlink-safe through use of Mkdirat().  func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fuse.Status) { +	defer fs.dirCache.Clear()  	if fs.isFiltered(newPath) {  		return fuse.EPERM  	} @@ -142,6 +141,7 @@ func haveDsstore(entries []fuse.DirEntry) bool {  //  // Symlink-safe through Unlinkat() + AT_REMOVEDIR.  func (fs *FS) Rmdir(relPath string, context *fuse.Context) (code fuse.Status) { +	defer fs.dirCache.Clear()  	parentDirFd, cName, err := fs.openBackingDir(relPath)  	if err != nil {  		return fuse.ToStatus(err) @@ -252,8 +252,8 @@ retry:  	if nametransform.IsLongContent(cName) {  		nametransform.DeleteLongNameAt(parentDirFd, cName)  	} -	// The now-deleted directory may have been in the DirIV cache. Clear it. -	fs.nameTransform.DirIVCache.Clear() +	// The now-deleted directory may have been in the dirCache. Clear it. +	fs.dirCache.Clear()  	return fuse.OK  } diff --git a/internal/fusefrontend/openbackingdir.go b/internal/fusefrontend/openbackingdir.go index 849a486..4da7fd6 100644 --- a/internal/fusefrontend/openbackingdir.go +++ b/internal/fusefrontend/openbackingdir.go @@ -10,7 +10,8 @@ import (  )  // openBackingDir opens the parent ciphertext directory of plaintext path -// "relPath" and returns the dirfd and the encrypted basename. +// "relPath". It returns the dirfd (opened with O_PATH) and the encrypted +// basename.  //  // The caller should then use Openat(dirfd, cName, ...) and friends.  // For convenience, if relPath is "", cName is going to be ".". @@ -18,10 +19,10 @@ import (  // openBackingDir is secure against symlink races by using Openat and  // ReadDirIVAt.  func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error) { +	dirRelPath := nametransform.Dir(relPath)  	// With PlaintextNames, we don't need to read DirIVs. Easy.  	if fs.args.PlaintextNames { -		dir := nametransform.Dir(relPath) -		dirfd, err = syscallcompat.OpenDirNofollow(fs.args.Cipherdir, dir) +		dirfd, err = syscallcompat.OpenDirNofollow(fs.args.Cipherdir, dirRelPath)  		if err != nil {  			return -1, "", err  		} @@ -29,6 +30,13 @@ func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error  		cName = filepath.Base(relPath)  		return dirfd, cName, nil  	} +	// Cache lookup +	dirfd, iv := fs.dirCache.Lookup(dirRelPath) +	if dirfd > 0 { +		name := filepath.Base(relPath) +		cName = fs.nameTransform.EncryptAndHashName(name, iv) +		return dirfd, cName, nil +	}  	// Open cipherdir (following symlinks)  	dirfd, err = syscall.Open(fs.args.Cipherdir, syscall.O_RDONLY|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)  	if err != nil { @@ -49,6 +57,7 @@ func (fs *FS) openBackingDir(relPath string) (dirfd int, cName string, err error  		cName = fs.nameTransform.EncryptAndHashName(name, iv)  		// Last part? We are done.  		if i == len(parts)-1 { +			fs.dirCache.Store(dirRelPath, dirfd, iv)  			break  		}  		// Not the last part? Descend into next directory. diff --git a/internal/fusefrontend/openbackingdir_test.go b/internal/fusefrontend/openbackingdir_test.go index 8453e52..f784989 100644 --- a/internal/fusefrontend/openbackingdir_test.go +++ b/internal/fusefrontend/openbackingdir_test.go @@ -1,9 +1,12 @@  package fusefrontend  import ( +	"fmt" +	"os"  	"strings"  	"syscall"  	"testing" +	"time"  	"golang.org/x/sys/unix" @@ -53,7 +56,9 @@ func TestOpenBackingDir(t *testing.T) {  	}  	err = syscallcompat.Faccessat(dirfd, cName, unix.R_OK)  	if err != nil { -		t.Error(err) +		fmt.Printf("pid=%d dirfd=%d dir1->cName=%q: %v\n", os.Getpid(), dirfd, cName, err) +		time.Sleep(600 * time.Second) +		t.Errorf("dirfd=%d cName=%q: %v", dirfd, cName, err)  	}  	syscall.Close(dirfd) | 
