diff options
| author | Jakob Unterwurzacher | 2018-11-04 22:25:32 +0100 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2019-01-01 16:24:25 +0100 | 
| commit | 436f918c21a0858c769e1bf7d86502a05132b4e7 (patch) | |
| tree | 3ba09841716df16e0ff83a984ab7bc86de710a12 /internal/fusefrontend | |
| parent | 2de3851abddd26949b72b9b255ce836fc93ed284 (diff) | |
fusefrontend: make Rmdir symlink-safe
Now uses Unlinkat.
Diffstat (limited to 'internal/fusefrontend')
| -rw-r--r-- | internal/fusefrontend/fs.go | 2 | ||||
| -rw-r--r-- | internal/fusefrontend/fs_dir.go | 49 | 
2 files changed, 22 insertions, 29 deletions
| diff --git a/internal/fusefrontend/fs.go b/internal/fusefrontend/fs.go index 2108ea4..444a34a 100644 --- a/internal/fusefrontend/fs.go +++ b/internal/fusefrontend/fs.go @@ -502,6 +502,8 @@ func (fs *FS) Symlink(target string, linkName string, context *fuse.Context) (co  }  // Rename - FUSE call. +// +// Symlink-safe through Renameat().  func (fs *FS) Rename(oldPath string, newPath string, context *fuse.Context) (code fuse.Status) {  	if fs.isFiltered(newPath) {  		return fuse.EPERM diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go index 5a6bc9d..d4117fb 100644 --- a/internal/fusefrontend/fs_dir.go +++ b/internal/fusefrontend/fs_dir.go @@ -5,7 +5,6 @@ package fusefrontend  import (  	"fmt"  	"io" -	"os"  	"path/filepath"  	"runtime"  	"syscall" @@ -134,52 +133,44 @@ func haveDsstore(entries []fuse.DirEntry) bool {  	return false  } -// Rmdir implements pathfs.FileSystem -func (fs *FS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { -	cPath, err := fs.getBackingPath(path) +// Rmdir - FUSE call. +// +// Symlink-safe through Unlinkat() + AT_REMOVEDIR. +func (fs *FS) Rmdir(relPath string, context *fuse.Context) (code fuse.Status) { +	parentDirFd, cName, err := fs.openBackingDir(relPath)  	if err != nil {  		return fuse.ToStatus(err)  	} +	defer syscall.Close(parentDirFd)  	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 { +		// Unlinkat with AT_REMOVEDIR is equivalent to Rmdir +		err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR)  		return fuse.ToStatus(err)  	} -	defer syscall.Close(parentDirFd) - -	cName := filepath.Base(cPath)  	dirfd, err := syscallcompat.Openat(parentDirFd, cName,  		syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)  	if err == syscall.EACCES {  		// We need permission to read and modify the directory  		tlog.Debug.Printf("Rmdir: handling EACCESS") -		// TODO use syscall.Fstatat once it is available in Go -		var fi os.FileInfo -		fi, err = os.Lstat(cPath) +		var st unix.Stat_t +		err = syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW)  		if err != nil {  			tlog.Debug.Printf("Rmdir: Stat: %v", err)  			return fuse.ToStatus(err)  		} -		origMode := fi.Mode() -		// TODO use syscall.Chmodat once it is available in Go -		err = os.Chmod(cPath, origMode|0700) +		origMode := st.Mode & 0777 +		err = syscallcompat.Fchmodat(parentDirFd, cName, origMode|0700, unix.AT_SYMLINK_NOFOLLOW)  		if err != nil { -			tlog.Debug.Printf("Rmdir: Chmod failed: %v", err) +			tlog.Debug.Printf("Rmdir: Fchmodat failed: %v", err)  			return fuse.ToStatus(err)  		}  		// Retry open -		var st syscall.Stat_t -		syscall.Lstat(cPath, &st)  		dirfd, err = syscallcompat.Openat(parentDirFd, cName,  			syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)  		// Undo the chmod if removing the directory failed  		defer func() {  			if code != fuse.OK { -				err = os.Chmod(cPath, origMode) +				err = syscallcompat.Fchmodat(parentDirFd, cName, origMode, unix.AT_SYMLINK_NOFOLLOW)  				if err != nil {  					tlog.Warn.Printf("Rmdir: Chmod rollback failed: %v", err)  				} @@ -196,8 +187,9 @@ retry:  	children, err := syscallcompat.Getdents(dirfd)  	if err == io.EOF {  		// The directory is empty -		tlog.Warn.Printf("Rmdir: %q: gocryptfs.diriv is missing", cPath) -		return fuse.ToStatus(syscall.Rmdir(cPath)) +		tlog.Warn.Printf("Rmdir: %q: gocryptfs.diriv is missing", cName) +		err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) +		return fuse.ToStatus(err)  	}  	if err != nil {  		tlog.Warn.Printf("Rmdir: Readdirnames: %v", err) @@ -206,13 +198,12 @@ retry:  	// MacOS sprinkles .DS_Store files everywhere. This is hard to avoid for  	// users, so handle it transparently here.  	if runtime.GOOS == "darwin" && len(children) <= 2 && haveDsstore(children) { -		ds := filepath.Join(cPath, dsStoreName) -		err = syscall.Unlink(ds) +		err = unix.Unlinkat(dirfd, dsStoreName, 0)  		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)  		} -		tlog.Warn.Printf("Rmdir: had to delete blocking file %q", ds) +		tlog.Warn.Printf("Rmdir: had to delete blocking file %q", dsStoreName)  		goto retry  	}  	// If the directory is not empty besides gocryptfs.diriv, do not even | 
