diff options
Diffstat (limited to 'internal/fusefrontend/node_dir_ops.go')
-rw-r--r-- | internal/fusefrontend/node_dir_ops.go | 121 |
1 files changed, 121 insertions, 0 deletions
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() |