fusefrontend: make Rmdir symlink-safe
Now uses Unlinkat.
This commit is contained in:
parent
2de3851abd
commit
436f918c21
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user