diff options
author | Jakob Unterwurzacher | 2016-04-10 19:32:10 +0200 |
---|---|---|
committer | Jakob Unterwurzacher | 2016-04-10 21:31:08 +0200 |
commit | db72fcea41f01f24ac3edb1cbf86d6b0be60f137 (patch) | |
tree | e65cc7c44b4c23bdee191015bfbbba1101370cd4 /internal/fusefrontend/fs.go | |
parent | 63d3e517349a6c4774a3f75f2fa039c780eaf5f9 (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.go | 195 |
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)) } |