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.NodeReaddirer)((*Node)(nil))
|
||||||
var _ = (fs.NodeCreater)((*Node)(nil))
|
var _ = (fs.NodeCreater)((*Node)(nil))
|
||||||
var _ = (fs.NodeMkdirer)((*Node)(nil))
|
var _ = (fs.NodeMkdirer)((*Node)(nil))
|
||||||
|
var _ = (fs.NodeRmdirer)((*Node)(nil))
|
||||||
|
@ -2,7 +2,10 @@ package fusefrontend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
@ -11,6 +14,7 @@ import (
|
|||||||
"github.com/hanwen/go-fuse/v2/fuse"
|
"github.com/hanwen/go-fuse/v2/fuse"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"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
|
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