diff options
| author | Jakob Unterwurzacher | 2020-06-21 13:57:04 +0200 | 
|---|---|---|
| committer | Jakob Unterwurzacher | 2020-06-21 13:57:04 +0200 | 
| commit | cc0b94a3c58c942250c0b2728c2d95123b0decf1 (patch) | |
| tree | 3de6e8bd29c3963d693183da9695799799196b2f /internal | |
| parent | 192a29075a7a567931959c2b4c8e4a9513742eee (diff) | |
v2api: implement Rmdir
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/fusefrontend/node_api_check.go | 1 | ||||
| -rw-r--r-- | internal/fusefrontend/node_dir_ops.go | 128 | 
2 files changed, 129 insertions, 0 deletions
| diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go index e6d1681..8b3eef1 100644 --- a/internal/fusefrontend/node_api_check.go +++ b/internal/fusefrontend/node_api_check.go @@ -9,3 +9,4 @@ var _ = (fs.NodeLookuper)((*Node)(nil))  var _ = (fs.NodeReaddirer)((*Node)(nil))  var _ = (fs.NodeCreater)((*Node)(nil))  var _ = (fs.NodeMkdirer)((*Node)(nil)) +var _ = (fs.NodeRmdirer)((*Node)(nil)) diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go index 0789c92..b4bce1b 100644 --- a/internal/fusefrontend/node_dir_ops.go +++ b/internal/fusefrontend/node_dir_ops.go @@ -2,7 +2,10 @@ package fusefrontend  import (  	"context" +	"fmt" +	"io"  	"path/filepath" +	"runtime"  	"syscall"  	"golang.org/x/sys/unix" @@ -11,6 +14,7 @@ import (  	"github.com/hanwen/go-fuse/v2/fuse"  	"github.com/rfjakob/gocryptfs/internal/configfile" +	"github.com/rfjakob/gocryptfs/internal/cryptocore"  	"github.com/rfjakob/gocryptfs/internal/nametransform"  	"github.com/rfjakob/gocryptfs/internal/syscallcompat"  	"github.com/rfjakob/gocryptfs/internal/tlog" @@ -216,3 +220,127 @@ func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {  	return fs.NewListDirStream(plain), 0  } + +// Rmdir - FUSE call. +// +// Symlink-safe through Unlinkat() + AT_REMOVEDIR. +func (n *Node) Rmdir(ctx context.Context, name string) (code syscall.Errno) { +	rn := n.rootNode() +	p := filepath.Join(n.path(), name) +	parentDirFd, cName, err := rn.openBackingDir(p) +	if err != nil { +		return fs.ToErrno(err) +	} +	defer syscall.Close(parentDirFd) +	if rn.args.PlaintextNames { +		// Unlinkat with AT_REMOVEDIR is equivalent to Rmdir +		err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) +		return fs.ToErrno(err) +	} +	// Unless we are running as root, we need read, write and execute permissions +	// to handle gocryptfs.diriv. +	permWorkaround := false +	var origMode uint32 +	if !rn.args.PreserveOwner { +		var st unix.Stat_t +		err = syscallcompat.Fstatat(parentDirFd, cName, &st, unix.AT_SYMLINK_NOFOLLOW) +		if err != nil { +			return fs.ToErrno(err) +		} +		if st.Mode&0700 != 0700 { +			tlog.Debug.Printf("Rmdir: permWorkaround") +			permWorkaround = true +			// This cast is needed on Darwin, where st.Mode is uint16. +			origMode = uint32(st.Mode) +			err = syscallcompat.FchmodatNofollow(parentDirFd, cName, origMode|0700) +			if err != nil { +				tlog.Debug.Printf("Rmdir: permWorkaround: chmod failed: %v", err) +				return fs.ToErrno(err) +			} +		} +	} +	dirfd, err := syscallcompat.Openat(parentDirFd, cName, +		syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0) +	if err != nil { +		tlog.Debug.Printf("Rmdir: Open: %v", err) +		return fs.ToErrno(err) +	} +	defer syscall.Close(dirfd) +	// Undo the chmod if removing the directory failed. This must run before +	// closing dirfd, so defer it after (defer is LIFO). +	if permWorkaround { +		defer func() { +			if code != 0 { +				err = unix.Fchmod(dirfd, origMode) +				if err != nil { +					tlog.Warn.Printf("Rmdir: permWorkaround: rollback failed: %v", err) +				} +			} +		}() +	} +retry: +	// Check directory contents +	children, err := syscallcompat.Getdents(dirfd) +	if err == io.EOF { +		// The directory is empty +		tlog.Warn.Printf("Rmdir: %q: %s is missing", cName, nametransform.DirIVFilename) +		err = unix.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) +		return fs.ToErrno(err) +	} +	if err != nil { +		tlog.Warn.Printf("Rmdir: Readdirnames: %v", err) +		return fs.ToErrno(err) +	} +	// 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) { +		err = unix.Unlinkat(dirfd, dsStoreName, 0) +		if err != nil { +			tlog.Warn.Printf("Rmdir: failed to delete blocking file %q: %v", dsStoreName, err) +			return fs.ToErrno(err) +		} +		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 +	// attempt the dance around gocryptfs.diriv. +	if len(children) > 1 { +		return fs.ToErrno(syscall.ENOTEMPTY) +	} +	// Move "gocryptfs.diriv" to the parent dir as "gocryptfs.diriv.rmdir.XYZ" +	tmpName := fmt.Sprintf("%s.rmdir.%d", nametransform.DirIVFilename, 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. +	rn.dirIVLock.Lock() +	defer rn.dirIVLock.Unlock() +	err = syscallcompat.Renameat(dirfd, nametransform.DirIVFilename, +		parentDirFd, tmpName) +	if err != nil { +		tlog.Warn.Printf("Rmdir: Renaming %s to %s failed: %v", +			nametransform.DirIVFilename, tmpName, err) +		return fs.ToErrno(err) +	} +	// Actual Rmdir +	err = syscallcompat.Unlinkat(parentDirFd, cName, unix.AT_REMOVEDIR) +	if err != nil { +		// This can happen if another file in the directory was created in the +		// meantime, undo the rename +		err2 := syscallcompat.Renameat(parentDirFd, tmpName, +			dirfd, nametransform.DirIVFilename) +		if err2 != nil { +			tlog.Warn.Printf("Rmdir: Rename rollback failed: %v", err2) +		} +		return fs.ToErrno(err) +	} +	// Delete "gocryptfs.diriv.rmdir.XYZ" +	err = syscallcompat.Unlinkat(parentDirFd, tmpName, 0) +	if err != nil { +		tlog.Warn.Printf("Rmdir: Could not clean up %s: %v", tmpName, err) +	} +	// Delete .name file +	if nametransform.IsLongContent(cName) { +		nametransform.DeleteLongNameAt(parentDirFd, cName) +	} +	return 0 +} | 
