aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorJakob Unterwurzacher2020-06-21 13:46:08 +0200
committerJakob Unterwurzacher2020-06-21 13:46:08 +0200
commit192a29075a7a567931959c2b4c8e4a9513742eee (patch)
tree4cc2eb036fe60a1e66c6f704a69eadf7fd664d87 /internal
parentf6ded09e36a679695354f4b9bc74242ef399be09 (diff)
v2api: implement Mkdir
Diffstat (limited to 'internal')
-rw-r--r--internal/fusefrontend/fs_dir.go4
-rw-r--r--internal/fusefrontend/node_api_check.go11
-rw-r--r--internal/fusefrontend/node_dir_ops.go121
-rw-r--r--internal/fusefrontend/root_node.go5
-rw-r--r--internal/syscallcompat/sys_linux.go10
5 files changed, 144 insertions, 7 deletions
diff --git a/internal/fusefrontend/fs_dir.go b/internal/fusefrontend/fs_dir.go
index 8481d56..0d8adae 100644
--- a/internal/fusefrontend/fs_dir.go
+++ b/internal/fusefrontend/fs_dir.go
@@ -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)
}
diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go
new file mode 100644
index 0000000..e6d1681
--- /dev/null
+++ b/internal/fusefrontend/node_api_check.go
@@ -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))
diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go
index 38d0940..0789c92 100644
--- a/internal/fusefrontend/node_dir_ops.go
+++ b/internal/fusefrontend/node_dir_ops.go
@@ -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()
diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go
index c84ac93..be82851 100644
--- a/internal/fusefrontend/root_node.go
+++ b/internal/fusefrontend/root_node.go
@@ -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
diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go
index 02064ac..c82480e 100644
--- a/internal/syscallcompat/sys_linux.go
+++ b/internal/syscallcompat/sys_linux.go
@@ -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
}