fusefrontend: make Rmdir symlink-safe

Now uses Unlinkat.
This commit is contained in:
Jakob Unterwurzacher 2018-11-04 22:25:32 +01:00
parent 2de3851abd
commit 436f918c21
2 changed files with 24 additions and 31 deletions

View File

@ -502,6 +502,8 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co
} }
// Rename - FUSE call. // Rename - FUSE call.
//
// Symlink-safe through Renameat().
func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) { func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) {
if fs.isFiltered(newPath) { if fs.isFiltered(newPath) {
return fuse.EPERM return fuse.EPERM

View File

@ -5,7 +5,6 @@ package fusefrontend
import ( import (
"fmt" "fmt"
"io" "io"
"os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"syscall" "syscall"
@ -134,52 +133,44 @@ func haveDsstore(entries []fuse.DirEntry) bool {
return false return false
} }
// Rmdir implements pathfs.FileSystem // Rmdir - FUSE call.
func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { //
cPath, err := fs.getBackingPath(path) // Symlink-safe through Unlinkat() + AT_REMOVEDIR.
if err != nil { func (fs *FS) Rmdir(relPath string, context *fuse.Context) (code fuse.Status) {
return fuse.ToStatus(err) parentDirFd, cName, err := fs.openBackingDir(relPath)
}
if fs.args.PlaintextNames {
err = syscall.Rmdir(cPath)
return fuse.ToStatus(err)
}
parentDir := filepath.Dir(cPath)
parentDirFd, err := syscall.Open(parentDir, syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
if err != nil { if err != nil {
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
defer syscall.Close(parentDirFd) defer syscall.Close(parentDirFd)
if fs.args.PlaintextNames {
cName := filepath.Base(cPath) // Unlinkat with AT_REMOVEDIR is equivalent to Rmdir
err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
return fuse.ToStatus(err)
}
dirfd, err := syscallcompat.Openat(parentDirFd, cName, dirfd, err := syscallcompat.Openat(parentDirFd, cName,
syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err == syscall.EACCES { if err == syscall.EACCES {
// We need permission to read and modify the directory // We need permission to read and modify the directory
tlog.Debug.Printf("Rmdir: handling EACCESS") tlog.Debug.Printf("Rmdir: handling EACCESS")
// TODO use syscall.Fstatat once it is available in Go var st unix.Stat_t
var fi os.FileInfo err = syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW)
fi, err = os.Lstat(cPath)
if err != nil { if err != nil {
tlog.Debug.Printf("Rmdir: Stat: %v", err) tlog.Debug.Printf("Rmdir: Stat: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
origMode := fi.Mode() origMode := st.Mode & 0777
// TODO use syscall.Chmodat once it is available in Go err = syscallcompat.Fchmodat(parentDirFd, cName, origMode|0700, unix.AT_SYMLINK_NOFOLLOW)
err = os.Chmod(cPath, origMode|0700)
if err != nil { if err != nil {
tlog.Debug.Printf("Rmdir: Chmod failed: %v", err) tlog.Debug.Printf("Rmdir: Fchmodat failed: %v", err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
// Retry open // Retry open
var st syscall.Stat_t
syscall.Lstat(cPath, &st)
dirfd, err = syscallcompat.Openat(parentDirFd, cName, dirfd, err = syscallcompat.Openat(parentDirFd, cName,
syscall.O_RDONLY|syscall.O_NOFOLLOW, 0) syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
// Undo the chmod if removing the directory failed // Undo the chmod if removing the directory failed
defer func() { defer func() {
if code != fuse.OK { if code != fuse.OK {
err = os.Chmod(cPath, origMode) err = syscallcompat.Fchmodat(parentDirFd, cName, origMode, unix.AT_SYMLINK_NOFOLLOW)
if err != nil { if err != nil {
tlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err) tlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err)
} }
@ -196,8 +187,9 @@ retry:
children, err := syscallcompat.Getdents(dirfd) children, err := syscallcompat.Getdents(dirfd)
if err == io.EOF { if err == io.EOF {
// The directory is empty // The directory is empty
tlog.Warn.Printf("Rmdir: %q: gocryptfs.diriv is missing", cPath) tlog.Warn.Printf("Rmdir: %q: gocryptfs.diriv is missing", cName)
return fuse.ToStatus(syscall.Rmdir(cPath)) err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)
return fuse.ToStatus(err)
} }
if err != nil { if err != nil {
tlog.Warn.Printf("Rmdir: Readdirnames: %v", err) tlog.Warn.Printf("Rmdir: Readdirnames: %v", err)
@ -206,13 +198,12 @@ retry:
// MacOS sprinkles .DS_Store files everywhere. This is hard to avoid for // MacOS sprinkles .DS_Store files everywhere. This is hard to avoid for
// users, so handle it transparently here. // users, so handle it transparently here.
if runtime.GOOS == "darwin" && len(children) <= 2 && haveDsstore(children) { if runtime.GOOS == "darwin" && len(children) <= 2 && haveDsstore(children) {
ds := filepath.Join(cPath, dsStoreName) err = unix.Unlinkat(dirfd, dsStoreName, 0)
err = syscall.Unlink(ds)
if err != nil { if err != nil {
tlog.Warn.Printf("Rmdir: failed to delete blocking file %q: %v", ds, err) tlog.Warn.Printf("Rmdir: failed to delete blocking file %q: %v", dsStoreName, err)
return fuse.ToStatus(err) return fuse.ToStatus(err)
} }
tlog.Warn.Printf("Rmdir: had to delete blocking file %q", ds) tlog.Warn.Printf("Rmdir: had to delete blocking file %q", dsStoreName)
goto retry goto retry
} }
// If the directory is not empty besides gocryptfs.diriv, do not even // If the directory is not empty besides gocryptfs.diriv, do not even