Update FUSE related stuff according to gocryptfs fusefrontend
This commit is contained in:
parent
89966b1aae
commit
71eb2bdf7c
@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"C"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
"libgocryptfs/v2/internal/nametransform"
|
||||
|
136
dircache.go
Normal file
136
dircache.go
Normal file
@ -0,0 +1,136 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Number of entries in the dirCache.
|
||||
// 20 entries work well for "git stat" on a small git repo on sshfs.
|
||||
// Keep in sync with test_helpers.maxCacheFds !
|
||||
// TODO: How to share this constant without causing an import cycle?
|
||||
dirCacheSize = 20
|
||||
// Enable Lookup/Store/Clear debug messages
|
||||
enableDebugMessages = false
|
||||
// Enable hit rate statistics printing
|
||||
enableStats = false
|
||||
)
|
||||
|
||||
type dirCacheEntry struct {
|
||||
path string
|
||||
// fd to the directory (opened with O_PATH!)
|
||||
fd int
|
||||
// content of gocryptfs.diriv in this directory
|
||||
iv []byte
|
||||
}
|
||||
|
||||
func (e *dirCacheEntry) Clear() {
|
||||
// An earlier clear may have already closed the fd, or the cache
|
||||
// has never been filled (fd is 0 in that case).
|
||||
// Note: package ensurefds012, imported from main, guarantees that dirCache
|
||||
// can never get fds 0,1,2.
|
||||
if e.fd > 0 {
|
||||
syscall.Close(e.fd)
|
||||
}
|
||||
e.fd = -1
|
||||
e.path = ""
|
||||
e.iv = nil
|
||||
}
|
||||
|
||||
type dirCache struct {
|
||||
// Expected length of the stored IVs. Only used for sanity checks.
|
||||
// Usually set to 16, but 0 in plaintextnames mode.
|
||||
ivLen int
|
||||
// Cache entries
|
||||
entries [dirCacheSize]dirCacheEntry
|
||||
// Where to store the next entry (index into entries)
|
||||
nextIndex int
|
||||
// On the first Lookup(), the expire thread is started, and this flag is set
|
||||
// to true.
|
||||
expireThreadRunning bool
|
||||
// Hit rate stats. Evaluated and reset by the expire thread.
|
||||
lookups uint64
|
||||
hits uint64
|
||||
}
|
||||
|
||||
// Clear clears the cache contents.
|
||||
func (d *dirCache) Clear() {
|
||||
for i := range d.entries {
|
||||
d.entries[i].Clear()
|
||||
}
|
||||
}
|
||||
|
||||
// Store the entry in the cache. The passed "fd" will be Dup()ed, and the caller
|
||||
// can close their copy at will.
|
||||
func (d *dirCache) Store(path string, fd int, iv []byte) {
|
||||
// Note: package ensurefds012, imported from main, guarantees that dirCache
|
||||
// can never get fds 0,1,2.
|
||||
if fd <= 0 || len(iv) != d.ivLen {
|
||||
log.Panicf("Store sanity check failed: fd=%d len=%d", fd, len(iv))
|
||||
}
|
||||
e := &d.entries[d.nextIndex]
|
||||
// Round-robin works well enough
|
||||
d.nextIndex = (d.nextIndex + 1) % dirCacheSize
|
||||
// Close the old fd
|
||||
e.Clear()
|
||||
fd2, err := syscall.Dup(fd)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
e.fd = fd2
|
||||
e.path = string([]byte(path[:]))
|
||||
e.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 an (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 *dirCache) Lookup(path string) (fd int, iv []byte) {
|
||||
if enableStats {
|
||||
d.lookups++
|
||||
}
|
||||
var e *dirCacheEntry
|
||||
for i := range d.entries {
|
||||
e = &d.entries[i]
|
||||
if e.fd <= 0 {
|
||||
// Cache slot is empty
|
||||
continue
|
||||
}
|
||||
if path != e.path {
|
||||
// Not the right path
|
||||
continue
|
||||
}
|
||||
var err error
|
||||
fd, err = syscall.Dup(e.fd)
|
||||
if err != nil {
|
||||
return -1, nil
|
||||
}
|
||||
iv = e.iv
|
||||
break
|
||||
}
|
||||
if fd == 0 {
|
||||
return -1, nil
|
||||
}
|
||||
if enableStats {
|
||||
d.hits++
|
||||
}
|
||||
if fd <= 0 || len(iv) != d.ivLen {
|
||||
log.Panicf("Lookup sanity check failed: fd=%d len=%d", fd, len(iv))
|
||||
}
|
||||
return fd, iv
|
||||
}
|
||||
|
||||
// expireThread is started on the first Lookup()
|
||||
func (d *dirCache) expireThread() {
|
||||
for {
|
||||
time.Sleep(60 * time.Second)
|
||||
d.Clear()
|
||||
}
|
||||
}
|
12
directory.go
12
directory.go
@ -41,7 +41,7 @@ func mkdirWithIv(dirfd int, cName string, mode uint32) error {
|
||||
//export gcf_list_dir
|
||||
func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
parentDirFd, cDirName, err := volume.prepareAtSyscall(dirName)
|
||||
parentDirFd, cDirName, err := volume.prepareAtSyscallMyself(dirName)
|
||||
if err != nil {
|
||||
return nil, nil, 0
|
||||
}
|
||||
@ -58,7 +58,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
|
||||
}
|
||||
// Get DirIV (stays nil if PlaintextNames is used)
|
||||
var cachedIV []byte
|
||||
if !OpenedVolumes[sessionID].plainTextNames {
|
||||
if !volume.plainTextNames {
|
||||
// Read the DirIV from disk
|
||||
cachedIV, err = volume.nameTransform.ReadDirIVAt(fd)
|
||||
if err != nil {
|
||||
@ -75,7 +75,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
|
||||
// silently ignore "gocryptfs.conf" in the top level dir
|
||||
continue
|
||||
}
|
||||
if OpenedVolumes[sessionID].plainTextNames {
|
||||
if volume.plainTextNames {
|
||||
plain.WriteString(cipherEntries[i].Name + "\x00")
|
||||
modes = append(modes, cipherEntries[i].Mode)
|
||||
continue
|
||||
@ -96,7 +96,7 @@ func gcf_list_dir(sessionID int, dirName string) (*C.char, *C.int, C.int) {
|
||||
// ignore "gocryptfs.longname.*.name"
|
||||
continue
|
||||
}
|
||||
name, err := OpenedVolumes[sessionID].nameTransform.DecryptName(cName, cachedIV)
|
||||
name, err := volume.nameTransform.DecryptName(cName, cachedIV)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@ -143,7 +143,7 @@ func gcf_mkdir(sessionID int, path string, mode uint32) bool {
|
||||
// Handle long file name
|
||||
if nametransform.IsLongContent(cName) {
|
||||
// Create ".name"
|
||||
err = OpenedVolumes[sessionID].nameTransform.WriteLongNameAt(dirfd, cName, path)
|
||||
err = volume.nameTransform.WriteLongNameAt(dirfd, cName, path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@ -188,7 +188,7 @@ func gcf_mkdir(sessionID int, path string, mode uint32) bool {
|
||||
//export gcf_rmdir
|
||||
func gcf_rmdir(sessionID int, relPath string) bool {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
parentDirFd, cName, err := volume.openBackingDir(relPath)
|
||||
parentDirFd, cName, err := volume.prepareAtSyscall(relPath)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
11
file.go
11
file.go
@ -370,7 +370,7 @@ func (volume *Volume) truncate(handleID int, newSize uint64) bool {
|
||||
//export gcf_open_read_mode
|
||||
func gcf_open_read_mode(sessionID int, path string) int {
|
||||
volume := OpenedVolumes[sessionID]
|
||||
dirfd, cName, err := volume.prepareAtSyscall(path)
|
||||
dirfd, cName, err := volume.prepareAtSyscallMyself(path)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
@ -455,13 +455,14 @@ func gcf_write_file(sessionID, handleID int, offset uint64, data []byte) uint32
|
||||
|
||||
//export gcf_close_file
|
||||
func gcf_close_file(sessionID, handleID int) {
|
||||
f, ok := OpenedVolumes[sessionID].file_handles[handleID]
|
||||
volume := OpenedVolumes[sessionID]
|
||||
f, ok := volume.file_handles[handleID]
|
||||
if ok {
|
||||
f.fd.Close()
|
||||
delete(OpenedVolumes[sessionID].file_handles, handleID)
|
||||
_, ok := OpenedVolumes[sessionID].fileIDs[handleID]
|
||||
delete(volume.file_handles, handleID)
|
||||
_, ok := volume.fileIDs[handleID]
|
||||
if ok {
|
||||
delete(OpenedVolumes[sessionID].fileIDs, handleID)
|
||||
delete(volume.fileIDs, handleID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
148
helpers.go
148
helpers.go
@ -2,14 +2,20 @@ package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"libgocryptfs/v2/internal/configfile"
|
||||
"libgocryptfs/v2/internal/nametransform"
|
||||
"libgocryptfs/v2/internal/syscallcompat"
|
||||
)
|
||||
|
||||
func getParentPath(path string) string {
|
||||
parent := filepath.Dir(path)
|
||||
if parent == "." {
|
||||
return ""
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
// isFiltered - check if plaintext "path" should be forbidden
|
||||
//
|
||||
// Prevents name clashes with internal files when file names are not encrypted
|
||||
@ -26,107 +32,99 @@ func (volume *Volume) isFiltered(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (volume *Volume) openBackingDir(relPath string) (dirfd int, cName string, err error) {
|
||||
dirRelPath := nametransform.Dir(relPath)
|
||||
// With PlaintextNames, we don't need to read DirIVs. Easy.
|
||||
if volume.plainTextNames {
|
||||
dirfd, err = syscallcompat.OpenDirNofollow(volume.rootCipherDir, dirRelPath)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
// If relPath is empty, cName is ".".
|
||||
cName = filepath.Base(relPath)
|
||||
return dirfd, cName, nil
|
||||
}
|
||||
// Open cipherdir (following symlinks)
|
||||
dirfd, err = syscallcompat.Open(volume.rootCipherDir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
// If relPath is empty, cName is ".".
|
||||
if relPath == "" {
|
||||
return dirfd, ".", nil
|
||||
}
|
||||
// Walk the directory tree
|
||||
parts := strings.Split(relPath, "/")
|
||||
for i, name := range parts {
|
||||
iv, err := volume.nameTransform.ReadDirIVAt(dirfd)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
cName, err = volume.nameTransform.EncryptAndHashName(name, iv)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
// Last part? We are done.
|
||||
if i == len(parts)-1 {
|
||||
break
|
||||
}
|
||||
// Not the last part? Descend into next directory.
|
||||
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
syscall.Close(dirfd)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
dirfd = dirfd2
|
||||
}
|
||||
return dirfd, cName, nil
|
||||
func (volume *Volume) prepareAtSyscall(path string) (dirfd int, cName string, err error) {
|
||||
if path == "" {
|
||||
return volume.prepareAtSyscallMyself(path)
|
||||
}
|
||||
|
||||
func (volume *Volume) prepareAtSyscall(path string) (dirfd int, cName string, err error) {
|
||||
// root node itself is special
|
||||
if path == "" {
|
||||
return volume.openBackingDir(path)
|
||||
if volume.isFiltered(path) {
|
||||
return -1, "", nil
|
||||
}
|
||||
|
||||
var encryptName func(int, string, []byte) (string, error)
|
||||
if !volume.plainTextNames {
|
||||
encryptName = func(dirfd int, child string, iv []byte) (cName string, err error) {
|
||||
// Badname allowed, try to determine filenames
|
||||
if volume.nameTransform.HaveBadnamePatterns() {
|
||||
return volume.nameTransform.EncryptAndHashBadName(child, iv, dirfd)
|
||||
}
|
||||
return volume.nameTransform.EncryptAndHashName(child, iv)
|
||||
}
|
||||
}
|
||||
|
||||
child := filepath.Base(path)
|
||||
parentPath := getParentPath(path)
|
||||
|
||||
// Cache lookup
|
||||
// TODO make it work for plaintextnames as well?
|
||||
if !volume.plainTextNames {
|
||||
directory, ok := volume.dirCache[path]
|
||||
if ok {
|
||||
if directory.fd > 0 {
|
||||
cName, err := volume.nameTransform.EncryptAndHashName(filepath.Base(path), directory.iv)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
var iv []byte
|
||||
dirfd, iv = volume.dirCache.Lookup(parentPath)
|
||||
if dirfd > 0 {
|
||||
if volume.plainTextNames {
|
||||
return dirfd, child, nil
|
||||
}
|
||||
dirfd, err = syscall.Dup(directory.fd)
|
||||
var err error
|
||||
cName, err = encryptName(dirfd, child, iv)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
return dirfd, cName, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Slowpath
|
||||
if volume.isFiltered(path) {
|
||||
return -1, "", syscall.EPERM
|
||||
// Slowpath: Open ourselves & read diriv
|
||||
parentDirfd, myCName, err := volume.prepareAtSyscallMyself(parentPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
dirfd, cName, err = volume.openBackingDir(path)
|
||||
defer syscall.Close(parentDirfd)
|
||||
|
||||
dirfd, err = syscallcompat.Openat(parentDirfd, myCName, syscall.O_NOFOLLOW|syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
// Cache store
|
||||
if !volume.plainTextNames {
|
||||
// TODO: openBackingDir already calls ReadDirIVAt(). Avoid duplicate work?
|
||||
iv, err := volume.nameTransform.ReadDirIVAt(dirfd)
|
||||
var err error
|
||||
iv, err = volume.nameTransform.ReadDirIVAt(dirfd)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
dirfdDup, err := syscall.Dup(dirfd)
|
||||
if err == nil {
|
||||
var pathCopy strings.Builder
|
||||
pathCopy.WriteString(path)
|
||||
volume.dirCache[pathCopy.String()] = Directory{dirfdDup, iv}
|
||||
}
|
||||
volume.dirCache.Store(parentPath, dirfd, iv)
|
||||
|
||||
if volume.plainTextNames {
|
||||
return dirfd, child, nil
|
||||
}
|
||||
|
||||
cName, err = encryptName(dirfd, child, iv)
|
||||
if err != nil {
|
||||
syscall.Close(dirfd)
|
||||
return -1, "", err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (volume *Volume) prepareAtSyscallMyself(path string) (dirfd int, cName string, err error) {
|
||||
dirfd = -1
|
||||
|
||||
// Handle root node
|
||||
if path == "" {
|
||||
var err error
|
||||
// Open cipherdir (following symlinks)
|
||||
dirfd, err = syscallcompat.Open(volume.rootCipherDir, syscall.O_DIRECTORY|syscallcompat.O_PATH, 0)
|
||||
if err != nil {
|
||||
return -1, "", err
|
||||
}
|
||||
return dirfd, ".", nil
|
||||
}
|
||||
|
||||
// Otherwise convert to prepareAtSyscall of parent node
|
||||
return volume.prepareAtSyscall(path)
|
||||
}
|
||||
|
||||
// decryptSymlinkTarget: "cData64" is base64-decoded and decrypted
|
||||
// like file contents (GCM).
|
||||
// The empty string decrypts to the empty string.
|
||||
|
37
volume.go
37
volume.go
@ -7,7 +7,6 @@ import (
|
||||
"C"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"libgocryptfs/v2/internal/configfile"
|
||||
@ -18,11 +17,6 @@ import (
|
||||
"libgocryptfs/v2/internal/syscallcompat"
|
||||
)
|
||||
|
||||
type Directory struct {
|
||||
fd int
|
||||
iv []byte
|
||||
}
|
||||
|
||||
type File struct {
|
||||
fd *os.File
|
||||
path string
|
||||
@ -35,12 +29,12 @@ type Volume struct {
|
||||
nameTransform *nametransform.NameTransform
|
||||
cryptoCore *cryptocore.CryptoCore
|
||||
contentEnc *contentenc.ContentEnc
|
||||
dirCache map[string]Directory
|
||||
dirCache dirCache
|
||||
file_handles map[int]File
|
||||
fileIDs map[int][]byte
|
||||
}
|
||||
|
||||
var OpenedVolumes map[int]Volume
|
||||
var OpenedVolumes map[int]*Volume
|
||||
|
||||
func wipe(d []byte) {
|
||||
for i := range d {
|
||||
@ -49,12 +43,6 @@ func wipe(d []byte) {
|
||||
d = nil
|
||||
}
|
||||
|
||||
func clearDirCache(volumeID int) {
|
||||
for k := range OpenedVolumes[volumeID].dirCache {
|
||||
delete(OpenedVolumes[volumeID].dirCache, k)
|
||||
}
|
||||
}
|
||||
|
||||
func errToBool(err error) bool {
|
||||
return err == nil
|
||||
}
|
||||
@ -85,12 +73,14 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co
|
||||
)
|
||||
|
||||
//copying rootCipherDir
|
||||
var grcd strings.Builder
|
||||
grcd.WriteString(rootCipherDir)
|
||||
newVolume.rootCipherDir = grcd.String()
|
||||
newVolume.rootCipherDir = string([]byte(rootCipherDir[:]))
|
||||
|
||||
ivLen := nametransform.DirIVLen
|
||||
if newVolume.plainTextNames {
|
||||
ivLen = 0
|
||||
}
|
||||
// New empty caches
|
||||
newVolume.dirCache = make(map[string]Directory)
|
||||
newVolume.dirCache = dirCache{ivLen: ivLen}
|
||||
newVolume.file_handles = make(map[int]File)
|
||||
newVolume.fileIDs = make(map[int][]byte)
|
||||
|
||||
@ -105,9 +95,9 @@ func registerNewVolume(rootCipherDir string, masterkey []byte, cf *configfile.Co
|
||||
c++
|
||||
}
|
||||
if OpenedVolumes == nil {
|
||||
OpenedVolumes = make(map[int]Volume)
|
||||
OpenedVolumes = make(map[int]*Volume)
|
||||
}
|
||||
OpenedVolumes[volumeID] = newVolume
|
||||
OpenedVolumes[volumeID] = &newVolume
|
||||
return volumeID
|
||||
}
|
||||
|
||||
@ -127,11 +117,12 @@ func gcf_init(rootCipherDir string, password, givenScryptHash, returnedScryptHas
|
||||
|
||||
//export gcf_close
|
||||
func gcf_close(volumeID int) {
|
||||
OpenedVolumes[volumeID].cryptoCore.Wipe()
|
||||
for handleID := range OpenedVolumes[volumeID].file_handles {
|
||||
volume := OpenedVolumes[volumeID]
|
||||
volume.cryptoCore.Wipe()
|
||||
for handleID := range volume.file_handles {
|
||||
gcf_close_file(volumeID, handleID)
|
||||
}
|
||||
clearDirCache(volumeID)
|
||||
volume.dirCache.Clear()
|
||||
delete(OpenedVolumes, volumeID)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user