v2api: implement Mkdir

This commit is contained in:
Jakob Unterwurzacher 2020-06-21 13:46:08 +02:00
parent f6ded09e36
commit 192a29075a
5 changed files with 144 additions and 7 deletions

View File

@ -30,7 +30,7 @@ func (fs *FS) mkdirWithIv(dirfd int, cName string, mode uint32, context *fuse.Co
// from seeing it.
fs.dirIVLock.Lock()
defer fs.dirIVLock.Unlock()
err := syscallcompat.MkdiratUser(dirfd, cName, mode, context)
err := syscallcompat.MkdiratUser(dirfd, cName, mode, &context.Caller)
if err != nil {
return err
}
@ -67,7 +67,7 @@ func (fs *FS) Mkdir(newPath string, mode uint32, context *fuse.Context) (code fu
context = nil
}
if fs.args.PlaintextNames {
err = syscallcompat.MkdiratUser(dirfd, cName, mode, context)
err = syscallcompat.MkdiratUser(dirfd, cName, mode, &context.Caller)
return fuse.ToStatus(err)
}

View File

@ -0,0 +1,11 @@
package fusefrontend
import (
"github.com/hanwen/go-fuse/v2/fs"
)
var _ = (fs.NodeGetattrer)((*Node)(nil))
var _ = (fs.NodeLookuper)((*Node)(nil))
var _ = (fs.NodeReaddirer)((*Node)(nil))
var _ = (fs.NodeCreater)((*Node)(nil))
var _ = (fs.NodeMkdirer)((*Node)(nil))

View File

@ -5,6 +5,8 @@ import (
"path/filepath"
"syscall"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
@ -14,6 +16,125 @@ import (
"github.com/rfjakob/gocryptfs/internal/tlog"
)
// mkdirWithIv - create a new directory and corresponding diriv file. dirfd
// should be a handle to the parent directory, cName is the name of the new
// directory and mode specifies the access permissions to use.
func (n *Node) mkdirWithIv(dirfd int, cName string, mode uint32, caller *fuse.Caller) error {
rn := n.rootNode()
// Between the creation of the directory and the creation of gocryptfs.diriv
// the directory is inconsistent. Take the lock to prevent other readers
// from seeing it.
rn.dirIVLock.Lock()
defer rn.dirIVLock.Unlock()
err := syscallcompat.MkdiratUser(dirfd, cName, mode, caller)
if err != nil {
return err
}
dirfd2, err := syscallcompat.Openat(dirfd, cName, syscall.O_DIRECTORY|syscall.O_NOFOLLOW|syscallcompat.O_PATH, 0)
if err == nil {
// Create gocryptfs.diriv
err = nametransform.WriteDirIVAt(dirfd2)
syscall.Close(dirfd2)
}
if err != nil {
// Delete inconsistent directory (missing gocryptfs.diriv!)
err2 := syscallcompat.Unlinkat(dirfd, cName, unix.AT_REMOVEDIR)
if err2 != nil {
tlog.Warn.Printf("mkdirWithIv: rollback failed: %v", err2)
}
}
return err
}
// Mkdir - FUSE call. Create a directory at "newPath" with permissions "mode".
//
// Symlink-safe through use of Mkdirat().
func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
rn := n.rootNode()
newPath := filepath.Join(n.path(), name)
if rn.isFiltered(newPath) {
return nil, syscall.EPERM
}
dirfd, cName, err := rn.openBackingDir(newPath)
if err != nil {
return nil, fs.ToErrno(err)
}
defer syscall.Close(dirfd)
var caller *fuse.Caller
if rn.args.PreserveOwner {
caller, _ = fuse.FromContext(ctx)
}
if rn.args.PlaintextNames {
err = syscallcompat.MkdiratUser(dirfd, cName, mode, caller)
return nil, fs.ToErrno(err)
}
// We need write and execute permissions to create gocryptfs.diriv.
// Also, we need read permissions to open the directory (to avoid
// race-conditions between getting and setting the mode).
origMode := mode
mode = mode | 0700
// Handle long file name
if nametransform.IsLongContent(cName) {
// Create ".name"
err = rn.nameTransform.WriteLongNameAt(dirfd, cName, newPath)
if err != nil {
return nil, fs.ToErrno(err)
}
// Create directory
err = rn.mkdirWithIv(dirfd, cName, mode, caller)
if err != nil {
nametransform.DeleteLongNameAt(dirfd, cName)
return nil, fs.ToErrno(err)
}
} else {
err = rn.mkdirWithIv(dirfd, cName, mode, caller)
if err != nil {
return nil, fs.ToErrno(err)
}
}
fd, err := syscallcompat.Openat(dirfd, cName,
syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
if err != nil {
tlog.Warn.Printf("Mkdir %q: Openat failed: %v", cName, err)
return nil, fs.ToErrno(err)
}
defer syscall.Close(fd)
// Get unique inode number
var st syscall.Stat_t
err = syscall.Fstat(fd, &st)
if err != nil {
tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err)
return nil, fs.ToErrno(err)
}
rn.inoMap.TranslateStat(&st)
out.Attr.FromStat(&st)
// Create child node
id := fs.StableAttr{
Mode: uint32(st.Mode),
Gen: 1,
Ino: st.Ino,
}
node := &Node{}
ch := n.NewInode(ctx, node, id)
// Set mode
if origMode != mode {
// Preserve SGID bit if it was set due to inheritance.
origMode = uint32(st.Mode&^0777) | origMode
err = syscall.Fchmod(fd, origMode)
if err != nil {
tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err)
}
}
return ch, 0
}
func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
rn := n.rootNode()
p := n.path()

View File

@ -2,6 +2,7 @@ package fusefrontend
import (
"os"
"sync"
"sync/atomic"
"syscall"
"time"
@ -19,6 +20,10 @@ type RootNode struct {
Node
// args stores configuration arguments
args Args
// dirIVLock: Lock()ed if any "gocryptfs.diriv" file is modified
// Readers must RLock() it to prevent them from seeing intermediate
// states
dirIVLock sync.RWMutex
// Filename encryption helper
nameTransform nametransform.NameTransformer
// Content encryption helper

View File

@ -233,24 +233,24 @@ func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.C
}
// MkdiratUser runs the Mkdirat syscall in the context of a different user.
func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
if context != nil {
func MkdiratUser(dirfd int, path string, mode uint32, caller *fuse.Caller) (err error) {
if caller != nil {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
err = syscall.Setgroups(getSupplementaryGroups(context.Pid))
err = syscall.Setgroups(getSupplementaryGroups(caller.Pid))
if err != nil {
return err
}
defer syscall.Setgroups(nil)
err = syscall.Setregid(-1, int(context.Owner.Gid))
err = syscall.Setregid(-1, int(caller.Gid))
if err != nil {
return err
}
defer syscall.Setregid(-1, 0)
err = syscall.Setreuid(-1, int(context.Owner.Uid))
err = syscall.Setreuid(-1, int(caller.Uid))
if err != nil {
return err
}