v2api: implement Rmdir
This commit is contained in:
parent
192a29075a
commit
cc0b94a3c5
@ -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))
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user