summaryrefslogtreecommitdiff
path: root/internal/fusefrontend/fs.go
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/fusefrontend/fs.go
parent63d3e517349a6c4774a3f75f2fa039c780eaf5f9 (diff)
longnames: fix fsstress failure, use dirfd
Using dirfd-relative operations allows safe lockless handling of the ".name" files.
Diffstat (limited to 'internal/fusefrontend/fs.go')
-rw-r--r--internal/fusefrontend/fs.go195
1 files changed, 155 insertions, 40 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))
}