longnames: fix fsstress failure, use dirfd
Using dirfd-relative operations allows safe lockless handling of the ".name" files.
This commit is contained in:
parent
63d3e51734
commit
db72fcea41
@ -5,6 +5,7 @@ package fusefrontend
|
||||
import (
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
@ -111,18 +112,38 @@ func (fs *FS) Create(path string, flags uint32, mode uint32, context *fuse.Conte
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
// Create .name file to store the long file name if needed
|
||||
if !fs.args.PlaintextNames {
|
||||
err = fs.nameTransform.WriteLongName(cPath, path)
|
||||
|
||||
// Handle long file name
|
||||
cName := filepath.Base(cPath)
|
||||
if nametransform.IsLongContent(cName) {
|
||||
dirfd, err := os.Open(filepath.Dir(cPath))
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
defer dirfd.Close()
|
||||
|
||||
// Create ".name"
|
||||
err = fs.nameTransform.WriteLongName(dirfd, cName, path)
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
// Create content
|
||||
fdRaw, err := syscall.Openat(int(dirfd.Fd()), cName, iflags|os.O_CREATE, mode)
|
||||
if err != nil {
|
||||
nametransform.DeleteLongName(dirfd, cName)
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
fd := os.NewFile(uintptr(fdRaw), cName)
|
||||
|
||||
return NewFile(fd, writeOnly, fs.contentEnc), fuse.OK
|
||||
}
|
||||
f, err := os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode))
|
||||
|
||||
fd, err := os.OpenFile(cPath, iflags|os.O_CREATE, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return nil, fuse.ToStatus(err)
|
||||
}
|
||||
return NewFile(f, writeOnly, fs.contentEnc), fuse.OK
|
||||
return NewFile(fd, writeOnly, fs.contentEnc), fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) Chmod(path string, mode uint32, context *fuse.Context) (code fuse.Status) {
|
||||
@ -155,13 +176,31 @@ func (fs *FS) Mknod(path string, mode uint32, dev uint32, context *fuse.Context)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.PlaintextNames {
|
||||
// Create .name file to store the long file name if needed
|
||||
err = fs.nameTransform.WriteLongName(cPath, path)
|
||||
|
||||
// Handle long file name
|
||||
cName := filepath.Base(cPath)
|
||||
if nametransform.IsLongContent(cName) {
|
||||
dirfd, err := os.Open(filepath.Dir(cPath))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer dirfd.Close()
|
||||
|
||||
// Create ".name"
|
||||
err = fs.nameTransform.WriteLongName(dirfd, cName, path)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
// Create device node
|
||||
err = syscall.Mknodat(int(dirfd.Fd()), cName, uint32(mode), int(dev))
|
||||
if err != nil {
|
||||
nametransform.DeleteLongName(dirfd, cName)
|
||||
}
|
||||
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
return fs.FileSystem.Mknod(cPath, mode, dev, context)
|
||||
}
|
||||
|
||||
@ -227,11 +266,28 @@ func (fs *FS) Unlink(path string, context *fuse.Context) (code fuse.Status) {
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
err = syscall.Unlink(cPath)
|
||||
// Delete .name file
|
||||
if err == nil && !fs.args.PlaintextNames {
|
||||
nametransform.DeleteLongName(cPath)
|
||||
|
||||
cName := filepath.Base(cPath)
|
||||
if nametransform.IsLongContent(cName) {
|
||||
dirfd, err := os.Open(filepath.Dir(cPath))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer dirfd.Close()
|
||||
// Delete content
|
||||
err = syscall.Unlinkat(int(dirfd.Fd()), cName)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Delete ".name"
|
||||
err = nametransform.DeleteLongName(dirfd, cName)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("Unlink: could not delete .name file: %v", err)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
err = syscall.Unlink(cPath)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
@ -244,8 +300,8 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Old filesystem: symlinks are encrypted like paths (CBC)
|
||||
// TODO drop compatibility and simplify code
|
||||
// Before v0.5, symlinks were encrypted like paths (CBC)
|
||||
// TODO drop compatibility and simplify code?
|
||||
if !fs.args.DirIV {
|
||||
cTarget, err := fs.encryptPath(target)
|
||||
if err != nil {
|
||||
@ -255,18 +311,36 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co
|
||||
err = os.Symlink(cTarget, cPath)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Since gocryptfs v0.5 symlinks are encrypted like file contents (GCM)
|
||||
|
||||
cBinTarget := fs.contentEnc.EncryptBlock([]byte(target), 0, nil)
|
||||
cTarget := base64.URLEncoding.EncodeToString(cBinTarget)
|
||||
if !fs.args.PlaintextNames {
|
||||
// Create .name file to store the long file name if needed
|
||||
err = fs.nameTransform.WriteLongName(cPath, linkName)
|
||||
|
||||
// Handle long file name
|
||||
cName := filepath.Base(cPath)
|
||||
if nametransform.IsLongContent(cName) {
|
||||
dirfd, err := os.Open(filepath.Dir(cPath))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer dirfd.Close()
|
||||
|
||||
// Create ".name"
|
||||
err = fs.nameTransform.WriteLongName(dirfd, cName, linkName)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
// Create symlink
|
||||
// TODO use syscall.Symlinkat once it is available in Go
|
||||
err = syscall.Symlink(cTarget, cPath)
|
||||
if err != nil {
|
||||
nametransform.DeleteLongName(dirfd, cName)
|
||||
}
|
||||
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
err = os.Symlink(cTarget, cPath)
|
||||
toggledlog.Debug.Printf("Symlink: os.Symlink(%s, %s) = %v", cTarget, cPath, err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
@ -286,35 +360,61 @@ func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (cod
|
||||
// That directory may still be in the DirIV cache, clear it.
|
||||
fs.nameTransform.DirIVCache.Clear()
|
||||
|
||||
if !fs.args.PlaintextNames {
|
||||
// Create .name file to store the new long file name if needed
|
||||
err = fs.nameTransform.WriteLongName(cNewPath, newPath)
|
||||
// Handle long source file name
|
||||
var oldDirFd *os.File
|
||||
var finalOldDirFd int
|
||||
var finalOldPath = cOldPath
|
||||
cOldName := filepath.Base(cOldPath)
|
||||
if nametransform.IsLongContent(cOldName) {
|
||||
oldDirFd, err = os.Open(filepath.Dir(cOldPath))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer oldDirFd.Close()
|
||||
finalOldDirFd = int(oldDirFd.Fd())
|
||||
finalOldPath = cOldName
|
||||
}
|
||||
// Handle long destination file name
|
||||
var newDirFd *os.File
|
||||
var finalNewDirFd int
|
||||
var finalNewPath = cNewPath
|
||||
cNewName := filepath.Base(cNewPath)
|
||||
if nametransform.IsLongContent(cNewName) {
|
||||
newDirFd, err = os.Open(filepath.Dir(cNewPath))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer newDirFd.Close()
|
||||
finalNewDirFd = int(newDirFd.Fd())
|
||||
finalNewPath = cNewName
|
||||
// Create destination .name file
|
||||
err = fs.nameTransform.WriteLongName(newDirFd, cNewName, newPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Actual rename
|
||||
err = os.Rename(cOldPath, cNewPath)
|
||||
|
||||
if lerr, ok := err.(*os.LinkError); ok && lerr.Err == syscall.ENOTEMPTY {
|
||||
// If an empty directory is overwritten we will always get
|
||||
// ENOTEMPTY as the "empty" directory will still contain gocryptfs.diriv.
|
||||
err = syscall.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
|
||||
if err == syscall.ENOTEMPTY {
|
||||
// If an empty directory is overwritten we will always get ENOTEMPTY as
|
||||
// the "empty" directory will still contain gocryptfs.diriv.
|
||||
// Handle that case by removing the target directory and trying again.
|
||||
toggledlog.Debug.Printf("Rename: Handling ENOTEMPTY")
|
||||
if fs.Rmdir(newPath, context) == fuse.OK {
|
||||
err = os.Rename(cOldPath, cNewPath)
|
||||
err = syscall.Renameat(finalOldDirFd, finalOldPath, finalNewDirFd, finalNewPath)
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
// Rename succeeded - delete old long name file
|
||||
nametransform.DeleteLongName(cOldPath)
|
||||
} else {
|
||||
// Rename has failed - undo long name file creation
|
||||
nametransform.DeleteLongName(cNewPath)
|
||||
if err != nil {
|
||||
if newDirFd != nil {
|
||||
// Roll back .name creation
|
||||
nametransform.DeleteLongName(newDirFd, cNewName)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
return fuse.ToStatus(err)
|
||||
if oldDirFd != nil {
|
||||
nametransform.DeleteLongName(oldDirFd, cOldName)
|
||||
}
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) {
|
||||
@ -329,13 +429,28 @@ func (fs *FS) Link(oldPath string, newPath string, context *fuse.Context) (code
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.PlaintextNames {
|
||||
// Create .name file to store the long file name if needed
|
||||
err = fs.nameTransform.WriteLongName(cNewPath, newPath)
|
||||
|
||||
// Handle long file name
|
||||
cNewName := filepath.Base(cNewPath)
|
||||
if nametransform.IsLongContent(cNewName) {
|
||||
dirfd, err := os.Open(filepath.Dir(cNewPath))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer dirfd.Close()
|
||||
err = fs.nameTransform.WriteLongName(dirfd, cNewName, newPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// TODO Use syscall.Linkat once it is available in Go (it is not in Go
|
||||
// 1.6).
|
||||
err = syscall.Link(cOldPath, cNewPath)
|
||||
if err != nil {
|
||||
nametransform.DeleteLongName(dirfd, cNewName)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
}
|
||||
|
||||
return fuse.ToStatus(os.Link(cOldPath, cNewPath))
|
||||
}
|
||||
|
||||
|
@ -16,50 +16,74 @@ import (
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
)
|
||||
|
||||
func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fuse.Status) {
|
||||
if fs.isFiltered(relPath) {
|
||||
func (fs *FS) mkdirWithIv(cPath string, mode uint32) error {
|
||||
// Between the creation of the directory and the creation of gocryptfs.diriv
|
||||
// the directory is inconsistent. Take the lock to prevent other readers.
|
||||
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 := os.Mkdir(cPath, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Create gocryptfs.diriv
|
||||
err = nametransform.WriteDirIV(cPath)
|
||||
if err != nil {
|
||||
err2 := syscall.Rmdir(cPath)
|
||||
if err2 != nil {
|
||||
toggledlog.Warn.Printf("mkdirWithIv: rollback failed: %v", err2)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fuse.Status) {
|
||||
if fs.isFiltered(newPath) {
|
||||
return fuse.EPERM
|
||||
}
|
||||
encPath, err := fs.getBackingPath(relPath)
|
||||
cPath, err := fs.getBackingPath(newPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.DirIV {
|
||||
return fuse.ToStatus(os.Mkdir(encPath, os.FileMode(mode)))
|
||||
return fuse.ToStatus(os.Mkdir(cPath, os.FileMode(mode)))
|
||||
}
|
||||
|
||||
// We need write and execute permissions to create gocryptfs.diriv
|
||||
origMode := mode
|
||||
mode = mode | 0300
|
||||
// Create .name file to store the long file name if needed
|
||||
err = fs.nameTransform.WriteLongName(encPath, relPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// The new directory may take the place of an older one that is still in the cache
|
||||
fs.nameTransform.DirIVCache.Clear()
|
||||
// Create directory
|
||||
fs.dirIVLock.Lock()
|
||||
defer fs.dirIVLock.Unlock()
|
||||
err = os.Mkdir(encPath, os.FileMode(mode))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Create gocryptfs.diriv inside
|
||||
err = nametransform.WriteDirIV(encPath)
|
||||
if err != nil {
|
||||
// This should not happen
|
||||
toggledlog.Warn.Printf("Mkdir: WriteDirIV failed: %v", err)
|
||||
err2 := syscall.Rmdir(encPath)
|
||||
if err2 != nil {
|
||||
toggledlog.Warn.Printf("Mkdir: Rmdir rollback failed: %v", err2)
|
||||
|
||||
// Handle long file name
|
||||
cName := filepath.Base(cPath)
|
||||
if nametransform.IsLongContent(cName) {
|
||||
dirfd, err := os.Open(filepath.Dir(cPath))
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer dirfd.Close()
|
||||
|
||||
// Create ".name"
|
||||
err = fs.nameTransform.WriteLongName(dirfd, cName, newPath)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
// Create directory
|
||||
err = fs.mkdirWithIv(cPath, mode)
|
||||
if err != nil {
|
||||
nametransform.DeleteLongName(dirfd, cName)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
} else {
|
||||
err = fs.mkdirWithIv(cPath, mode)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
|
||||
// Set permissions back to what the user wanted
|
||||
if origMode != mode {
|
||||
err = os.Chmod(encPath, os.FileMode(origMode))
|
||||
err = os.Chmod(cPath, os.FileMode(origMode))
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("Mkdir: Chmod failed: %v", err)
|
||||
}
|
||||
@ -68,97 +92,111 @@ func (fs *FS) Mkdir(relPath string, mode uint32, context *fuse.Context) (code fu
|
||||
return fuse.OK
|
||||
}
|
||||
|
||||
func (fs *FS) Rmdir(name string, context *fuse.Context) (code fuse.Status) {
|
||||
encPath, err := fs.getBackingPath(name)
|
||||
func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) {
|
||||
cPath, err := fs.getBackingPath(path)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if !fs.args.DirIV {
|
||||
return fuse.ToStatus(syscall.Rmdir(encPath))
|
||||
return fuse.ToStatus(syscall.Rmdir(cPath))
|
||||
}
|
||||
|
||||
// If the directory is not empty besides gocryptfs.diriv, do not even
|
||||
// attempt the dance around gocryptfs.diriv.
|
||||
fd, err := os.Open(encPath)
|
||||
if perr, ok := err.(*os.PathError); ok && perr.Err == syscall.EACCES {
|
||||
parentDir := filepath.Dir(cPath)
|
||||
parentDirFd, err := os.Open(parentDir)
|
||||
if err != nil {
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
defer parentDirFd.Close()
|
||||
|
||||
cName := filepath.Base(cPath)
|
||||
dirfdRaw, err := syscall.Openat(int(parentDirFd.Fd()), cName,
|
||||
syscall.O_RDONLY, 0)
|
||||
if err == syscall.EACCES {
|
||||
// We need permission to read and modify the directory
|
||||
toggledlog.Debug.Printf("Rmdir: handling EACCESS")
|
||||
fi, err2 := os.Stat(encPath)
|
||||
if err2 != nil {
|
||||
toggledlog.Debug.Printf("Rmdir: Stat: %v", err2)
|
||||
return fuse.ToStatus(err2)
|
||||
}
|
||||
origMode := fi.Mode()
|
||||
newMode := origMode | 0700
|
||||
err2 = os.Chmod(encPath, newMode)
|
||||
if err2 != nil {
|
||||
toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err2)
|
||||
// TODO use syscall.Fstatat once it is available in Go
|
||||
var fi os.FileInfo
|
||||
fi, err = os.Lstat(cPath)
|
||||
if err != nil {
|
||||
toggledlog.Debug.Printf("Rmdir: Stat: %v", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
origMode := fi.Mode()
|
||||
// TODO use syscall.Chmodat once it is available in Go
|
||||
err = os.Chmod(cPath, origMode|0700)
|
||||
if err != nil {
|
||||
toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Retry open
|
||||
var st syscall.Stat_t
|
||||
syscall.Lstat(cPath, &st)
|
||||
dirfdRaw, err = syscall.Openat(int(parentDirFd.Fd()), cName,
|
||||
syscall.O_RDONLY, 0)
|
||||
// Undo the chmod if removing the directory failed
|
||||
defer func() {
|
||||
if code != fuse.OK {
|
||||
// Undo the chmod if removing the directory failed
|
||||
err3 := os.Chmod(encPath, origMode)
|
||||
if err3 != nil {
|
||||
toggledlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err2)
|
||||
err := os.Chmod(cPath, origMode)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Retry open
|
||||
fd, err = os.Open(encPath)
|
||||
}
|
||||
if err != nil {
|
||||
toggledlog.Debug.Printf("Rmdir: Open: %v", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
list, err := fd.Readdirnames(10)
|
||||
fd.Close()
|
||||
dirfd := os.NewFile(uintptr(dirfdRaw), cName)
|
||||
defer dirfd.Close()
|
||||
|
||||
children, err := dirfd.Readdirnames(10)
|
||||
if err != nil {
|
||||
toggledlog.Debug.Printf("Rmdir: Readdirnames: %v", err)
|
||||
toggledlog.Warn.Printf("Rmdir: Readdirnames: %v", err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
if len(list) > 1 {
|
||||
// If the directory is not empty besides gocryptfs.diriv, do not even
|
||||
// attempt the dance around gocryptfs.diriv.
|
||||
if len(children) > 1 {
|
||||
return fuse.ToStatus(syscall.ENOTEMPTY)
|
||||
} else if len(list) == 0 {
|
||||
toggledlog.Warn.Printf("Rmdir: gocryptfs.diriv missing, allowing deletion")
|
||||
return fuse.ToStatus(syscall.Rmdir(encPath))
|
||||
}
|
||||
|
||||
// Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ"
|
||||
dirivPath := filepath.Join(encPath, nametransform.DirIVFilename)
|
||||
parentDir := filepath.Dir(encPath)
|
||||
tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptocore.RandUint64())
|
||||
tmpDirivPath := filepath.Join(parentDir, tmpName)
|
||||
toggledlog.Debug.Printf("Rmdir: Renaming %s to %s", nametransform.DirIVFilename, tmpDirivPath)
|
||||
// The directory is in an inconsistent state between rename and rmdir. Protect against
|
||||
// concurrent readers.
|
||||
toggledlog.Debug.Printf("Rmdir: Renaming %s to %s", nametransform.DirIVFilename, tmpName)
|
||||
// The directory is in an inconsistent state between rename and rmdir.
|
||||
// Protect against concurrent readers.
|
||||
fs.dirIVLock.Lock()
|
||||
defer fs.dirIVLock.Unlock()
|
||||
err = os.Rename(dirivPath, tmpDirivPath)
|
||||
err = syscall.Renameat(int(dirfd.Fd()), nametransform.DirIVFilename,
|
||||
int(parentDirFd.Fd()), tmpName)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v",
|
||||
nametransform.DirIVFilename, tmpDirivPath, err)
|
||||
nametransform.DirIVFilename, tmpName, err)
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Actual Rmdir
|
||||
err = syscall.Rmdir(encPath)
|
||||
// TODO Use syscall.Unlinkat with the AT_REMOVEDIR flag once it is available
|
||||
// in Go
|
||||
err = syscall.Rmdir(cPath)
|
||||
if err != nil {
|
||||
// This can happen if another file in the directory was created in the
|
||||
// meantime, undo the rename
|
||||
err2 := os.Rename(tmpDirivPath, dirivPath)
|
||||
if err2 != nil {
|
||||
err2 := syscall.Renameat(int(parentDirFd.Fd()), tmpName,
|
||||
int(dirfd.Fd()), nametransform.DirIVFilename)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2)
|
||||
}
|
||||
return fuse.ToStatus(err)
|
||||
}
|
||||
// Delete "gocryptfs.diriv.rmdir.INODENUMBER"
|
||||
err = syscall.Unlink(tmpDirivPath)
|
||||
err = syscall.Unlinkat(int(parentDirFd.Fd()), tmpName)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err)
|
||||
}
|
||||
err = nametransform.DeleteLongName(encPath)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("Rmdir: Could not delete long name file: %v", err)
|
||||
// Delete .name file
|
||||
if nametransform.IsLongContent(cName) {
|
||||
nametransform.DeleteLongName(parentDirFd, cName)
|
||||
}
|
||||
// The now-deleted directory may have been in the DirIV cache. Clear it.
|
||||
fs.nameTransform.DirIVCache.Clear()
|
||||
@ -206,7 +244,7 @@ func (fs *FS) OpenDir(dirName string, context *fuse.Context) ([]fuse.DirEntry, f
|
||||
}
|
||||
|
||||
if fs.args.LongNames {
|
||||
isLong := nametransform.IsLongName(cName)
|
||||
isLong := nametransform.NameType(cName)
|
||||
if isLong == nametransform.LongNameContent {
|
||||
cNameLong, err := nametransform.ReadLongName(filepath.Join(cDirAbsPath, cName))
|
||||
if err != nil {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -11,11 +12,13 @@ import (
|
||||
"github.com/rfjakob/gocryptfs/internal/toggledlog"
|
||||
)
|
||||
|
||||
// Files with long names are stored in two files:
|
||||
// gocryptfs.longname.[sha256] <--- File content
|
||||
// gocryptfs.longname.[sha256].name <--- File name
|
||||
const longNamePrefix = "gocryptfs.longname."
|
||||
const longNameSuffix = ".name"
|
||||
const (
|
||||
// Files with long names are stored in two files:
|
||||
// gocryptfs.longname.[sha256] <--- File content, prefix = gocryptfs.longname.
|
||||
// gocryptfs.longname.[sha256].name <--- File name, suffix = .name
|
||||
LongNameSuffix = ".name"
|
||||
longNamePrefix = "gocryptfs.longname."
|
||||
)
|
||||
|
||||
// HashLongName - take the hash of a long string "name" and return
|
||||
// "gocryptfs.longname.[sha256]"
|
||||
@ -32,63 +35,68 @@ const (
|
||||
LongNameNone = iota
|
||||
)
|
||||
|
||||
// IsLongName - detect if cName is
|
||||
// NameType - detect if cName is
|
||||
// gocryptfs.longname.[sha256] ........ LongNameContent (content of a long name file)
|
||||
// gocryptfs.longname.[sha256].name .... LongNameFilename (full file name of a long name file)
|
||||
// else ................................ LongNameNone (normal file)
|
||||
func IsLongName(cName string) int {
|
||||
func NameType(cName string) int {
|
||||
if !strings.HasPrefix(cName, longNamePrefix) {
|
||||
return LongNameNone
|
||||
}
|
||||
if strings.HasSuffix(cName, longNameSuffix) {
|
||||
if strings.HasSuffix(cName, LongNameSuffix) {
|
||||
return LongNameFilename
|
||||
}
|
||||
return LongNameContent
|
||||
}
|
||||
|
||||
// IsLongContent returns true if "cName" is the content store of a long name file.
|
||||
func IsLongContent(cName string) bool {
|
||||
return NameType(cName) == LongNameContent
|
||||
}
|
||||
|
||||
// ReadLongName - read path.name
|
||||
func ReadLongName(path string) (string, error) {
|
||||
content, err := ioutil.ReadFile(path + longNameSuffix)
|
||||
content, err := ioutil.ReadFile(path + LongNameSuffix)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("ReadLongName: %v", err)
|
||||
}
|
||||
return string(content), err
|
||||
}
|
||||
|
||||
// DeleteLongName - if cPath ends in "gocryptfs.longname.[sha256]",
|
||||
// delete the "gocryptfs.longname.[sha256].name" file
|
||||
func DeleteLongName(cPath string) error {
|
||||
if IsLongName(filepath.Base(cPath)) == LongNameContent {
|
||||
err := syscall.Unlink(cPath + longNameSuffix)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("DeleteLongName: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteLongName - if cPath ends in "gocryptfs.longname.[sha256]", write the
|
||||
// "gocryptfs.longname.[sha256].name" file
|
||||
func (n *NameTransform) WriteLongName(cPath string, plainPath string) (err error) {
|
||||
cHashedName := filepath.Base(cPath)
|
||||
if IsLongName(cHashedName) != LongNameContent {
|
||||
// This is not a hashed file name, nothing to do
|
||||
return nil
|
||||
}
|
||||
// Encrypt (but do not hash) the plaintext name
|
||||
cDir := filepath.Dir(cPath)
|
||||
dirIV, err := ReadDirIV(cDir)
|
||||
// DeleteLongName deletes "hashName.name".
|
||||
func DeleteLongName(dirfd *os.File, hashName string) error {
|
||||
err := syscall.Unlinkat(int(dirfd.Fd()), hashName+LongNameSuffix)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("WriteLongName: %v", err)
|
||||
return err
|
||||
}
|
||||
plainName := filepath.Base(plainPath)
|
||||
cName := n.EncryptName(plainName, dirIV)
|
||||
// Write the encrypted name into gocryptfs.longname.[sha256].name
|
||||
err = ioutil.WriteFile(filepath.Join(cDir, cHashedName+longNameSuffix), []byte(cName), 0600)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("WriteLongName: %v", err)
|
||||
toggledlog.Warn.Printf("DeleteLongName: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteLongName encrypts plainName and writes it into "hashName.name".
|
||||
// For the convenience of the caller, plainName may also be a path and will be
|
||||
// converted internally.
|
||||
func (n *NameTransform) WriteLongName(dirfd *os.File, hashName string, plainName string) (err error) {
|
||||
plainName = filepath.Base(plainName)
|
||||
|
||||
// Encrypt the basename
|
||||
dirIV, err := ReadDirIVAt(dirfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cName := n.EncryptName(plainName, dirIV)
|
||||
|
||||
// Write the encrypted name into hashName.name
|
||||
fdRaw, err := syscall.Openat(int(dirfd.Fd()), hashName+LongNameSuffix,
|
||||
syscall.O_WRONLY|syscall.O_CREAT|syscall.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("WriteLongName: Openat: %v", err)
|
||||
return err
|
||||
}
|
||||
fd := os.NewFile(uintptr(fdRaw), hashName+LongNameSuffix)
|
||||
defer fd.Close()
|
||||
_, err = fd.Write([]byte(cName))
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("WriteLongName: Write: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -6,17 +6,17 @@ import (
|
||||
|
||||
func TestIsLongName(t *testing.T) {
|
||||
n := "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU=.name"
|
||||
if IsLongName(n) != LongNameFilename {
|
||||
if NameType(n) != LongNameFilename {
|
||||
t.Errorf("False negative")
|
||||
}
|
||||
|
||||
n = "gocryptfs.longname.LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU="
|
||||
if IsLongName(n) != LongNameContent {
|
||||
if NameType(n) != LongNameContent {
|
||||
t.Errorf("False negative")
|
||||
}
|
||||
|
||||
n = "LkwUdALvV_ANnzQN6ZZMYnxxfARD3IeZWCKnxGJjYmU="
|
||||
if IsLongName(n) != LongNameNone {
|
||||
if NameType(n) != LongNameNone {
|
||||
t.Errorf("False positive")
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package nametransform
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -21,25 +21,38 @@ const (
|
||||
)
|
||||
|
||||
// ReadDirIV - read the "gocryptfs.diriv" file from "dir" (absolute ciphertext path)
|
||||
// This function is exported because it allows for an efficient readdir implementation
|
||||
func ReadDirIV(dir string) (iv []byte, readErr error) {
|
||||
ivfile := filepath.Join(dir, DirIVFilename)
|
||||
toggledlog.Debug.Printf("ReadDirIV: reading %s\n", ivfile)
|
||||
iv, readErr = ioutil.ReadFile(ivfile)
|
||||
if readErr != nil {
|
||||
// The directory may have been concurrently deleted or moved. Failure to
|
||||
// read the diriv is not an error in that case.
|
||||
_, statErr := os.Stat(dir)
|
||||
if os.IsNotExist(statErr) {
|
||||
toggledlog.Debug.Printf("ReadDirIV: Dir %s was deleted under our feet", dir)
|
||||
} else {
|
||||
// This should not happen
|
||||
toggledlog.Warn.Printf("ReadDirIV: Dir exists but diriv does not: %v\n", readErr)
|
||||
}
|
||||
return nil, readErr
|
||||
// This function is exported because it allows for an efficient readdir implementation.
|
||||
func ReadDirIV(dir string) (iv []byte, err error) {
|
||||
dirfd, err := os.Open(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer dirfd.Close()
|
||||
|
||||
return ReadDirIVAt(dirfd)
|
||||
}
|
||||
|
||||
// ReadDirIVAt reads "gocryptfs.diriv" from the directory that is opened as "dirfd".
|
||||
// Using the dirfd makes it immune to concurrent renames of the directory.
|
||||
func ReadDirIVAt(dirfd *os.File) (iv []byte, err error) {
|
||||
fdRaw, err := syscall.Openat(int(dirfd.Fd()), DirIVFilename, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("ReadDirIVAt: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
fd := os.NewFile(uintptr(fdRaw), DirIVFilename)
|
||||
defer fd.Close()
|
||||
|
||||
iv = make([]byte, dirIVLen+1)
|
||||
n, err := fd.Read(iv)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("ReadDirIVAt: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
iv = iv[0:n]
|
||||
if len(iv) != dirIVLen {
|
||||
return nil, fmt.Errorf("ReadDirIV: Invalid length %d\n", len(iv))
|
||||
toggledlog.Warn.Printf("ReadDirIVAt: wanted %d bytes, got %d", dirIVLen, len(iv))
|
||||
return nil, errors.New("invalid iv length")
|
||||
}
|
||||
return iv, nil
|
||||
}
|
||||
@ -50,12 +63,15 @@ func ReadDirIV(dir string) (iv []byte, readErr error) {
|
||||
func WriteDirIV(dir string) error {
|
||||
iv := cryptocore.RandBytes(dirIVLen)
|
||||
file := filepath.Join(dir, DirIVFilename)
|
||||
// 0444 permissions: the file is not secret but should not be written to
|
||||
return ioutil.WriteFile(file, iv, 0444)
|
||||
err := ioutil.WriteFile(file, iv, 0400)
|
||||
if err != nil {
|
||||
toggledlog.Warn.Printf("WriteDirIV: %v", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// EncryptPathDirIV - encrypt relative plaintext path using EME with DirIV.
|
||||
// Components that are longer than 255 bytes are hashed.
|
||||
// Components that are longer than 255 bytes are hashed if be.longnames == true.
|
||||
func (be *NameTransform) EncryptPathDirIV(plainPath string, rootDir string) (cipherPath string, err error) {
|
||||
// Empty string means root directory
|
||||
if plainPath == "" {
|
||||
|
Loading…
Reference in New Issue
Block a user