aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorJakob Unterwurzacher2016-04-10 19:32:10 +0200
committerJakob Unterwurzacher2016-04-10 21:31:08 +0200
commitdb72fcea41f01f24ac3edb1cbf86d6b0be60f137 (patch)
treee65cc7c44b4c23bdee191015bfbbba1101370cd4 /internal
parent63d3e517349a6c4774a3f75f2fa039c780eaf5f9 (diff)
longnames: fix fsstress failure, use dirfd
Using dirfd-relative operations allows safe lockless handling of the ".name" files.
Diffstat (limited to 'internal')
-rw-r--r--internal/fusefrontend/fs.go195
-rw-r--r--internal/fusefrontend/fs_dir.go184
-rw-r--r--internal/nametransform/longnames.go78
-rw-r--r--internal/nametransform/longnames_test.go6
-rw-r--r--internal/nametransform/names_diriv.go58
5 files changed, 349 insertions, 172 deletions
diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go
index fb82c6b..9e67a6a 100644
--- a/internal/fusefrontend/fs.go
+++ b/internal/fusefrontend/fs.go
@@ -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))
}
diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go
index bcb93af..41e2101 100644
--- a/internal/fusefrontend/fs_dir.go
+++ b/internal/fusefrontend/fs_dir.go
@@ -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)
+ // 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()
- newMode := origMode | 0700
- err2 = os.Chmod(encPath, newMode)
- if err2 != nil {
- toggledlog.Debug.Printf("Rmdir: Chmod failed: %v", err2)
+ // 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 {
diff --git a/internal/nametransform/longnames.go b/internal/nametransform/longnames.go
index d048f95..0746cd6 100644
--- a/internal/nametransform/longnames.go
+++ b/internal/nametransform/longnames.go
@@ -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
+// 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("DeleteLongName: %v", err)
}
- return nil
+ return err
}
-// 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)
+// 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 {
- 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)
+
+ // 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: %v", err)
+ toggledlog.Warn.Printf("WriteLongName: Write: %v", err)
}
return err
}
diff --git a/internal/nametransform/longnames_test.go b/internal/nametransform/longnames_test.go
index 62073ec..8fa19fe 100644
--- a/internal/nametransform/longnames_test.go
+++ b/internal/nametransform/longnames_test.go
@@ -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")
}
}
diff --git a/internal/nametransform/names_diriv.go b/internal/nametransform/names_diriv.go
index 9336f5d..1beda3f 100644
--- a/internal/nametransform/names_diriv.go
+++ b/internal/nametransform/names_diriv.go
@@ -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 == "" {