From 3288d987032fde33ff3cd837b1a0feb612b9710b Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Mon, 27 Jun 2016 00:25:56 +0200 Subject: [PATCH] fusefrontend: fix PlaintextNames versions of Mkdir, Rmdir The "!fs.args.DirIV" special case was removed by b17f0465c7 but that, by accident, also removed the handling for PlaintextNames. Re-add it as an explicit PlaintextNames special case. Also adds support for removing directories that miss their gocryptfs.diriv file for some reason. --- internal/fusefrontend/fs_dir.go | 98 +++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index 80a66ca..9547ce6 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -4,6 +4,7 @@ package fusefrontend import ( "fmt" + "io" "os" "path/filepath" "syscall" @@ -46,6 +47,11 @@ func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fu if err != nil { return fuse.ToStatus(err) } + if fs.args.PlaintextNames { + err = os.Mkdir(cPath, os.FileMode(mode)) + return fuse.ToStatus(err) + } + // We need write and execute permissions to create gocryptfs.diriv origMode := mode mode = mode | 0300 @@ -95,7 +101,10 @@ func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { if err != nil { return fuse.ToStatus(err) } - + if fs.args.PlaintextNames { + err = syscall.Rmdir(cPath) + return fuse.ToStatus(err) + } parentDir := filepath.Dir(cPath) parentDirFd, err := os.Open(parentDir) if err != nil { @@ -146,49 +155,56 @@ func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { defer dirfd.Close() children, err := dirfd.Readdirnames(10) - if err != nil { + if err == nil { + // 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) + } + // Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ" + tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptocore.RandUint64()) + tlog.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 = syscall.Renameat(int(dirfd.Fd()), nametransform.DirIVFilename, + int(parentDirFd.Fd()), tmpName) + if err != nil { + tlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", + nametransform.DirIVFilename, tmpName, err) + return fuse.ToStatus(err) + } + // Actual Rmdir + // 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 := syscall.Renameat(int(parentDirFd.Fd()), tmpName, + int(dirfd.Fd()), nametransform.DirIVFilename) + if err != nil { + tlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2) + } + return fuse.ToStatus(err) + } + // Delete "gocryptfs.diriv.rmdir.XYZ" + err = syscall.Unlinkat(int(parentDirFd.Fd()), tmpName) + if err != nil { + tlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err) + } + } else if err == io.EOF { + // The directory is empty + tlog.Warn.Printf("Rmdir: %q: gocryptfs.diriv is missing", cPath) + err = syscall.Rmdir(cPath) + if err != nil { + return fuse.ToStatus(err) + } + } else { tlog.Warn.Printf("Rmdir: Readdirnames: %v", err) return fuse.ToStatus(err) } - // 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) - } - - // Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ" - tmpName := fmt.Sprintf("gocryptfs.diriv.rmdir.%d", cryptocore.RandUint64()) - tlog.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 = syscall.Renameat(int(dirfd.Fd()), nametransform.DirIVFilename, - int(parentDirFd.Fd()), tmpName) - if err != nil { - tlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", - nametransform.DirIVFilename, tmpName, err) - return fuse.ToStatus(err) - } - // Actual Rmdir - // 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 := syscall.Renameat(int(parentDirFd.Fd()), tmpName, - int(dirfd.Fd()), nametransform.DirIVFilename) - if err != nil { - tlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2) - } - return fuse.ToStatus(err) - } - // Delete "gocryptfs.diriv.rmdir.XYZ" - err = syscall.Unlinkat(int(parentDirFd.Fd()), tmpName) - if err != nil { - tlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err) - } // Delete .name file if nametransform.IsLongContent(cName) { nametransform.DeleteLongName(parentDirFd, cName)