diff options
-rw-r--r-- | internal/fusefrontend/node.go | 58 | ||||
-rw-r--r-- | internal/fusefrontend/node_api_check.go | 1 | ||||
-rw-r--r-- | internal/fusefrontend/root_node.go | 48 |
3 files changed, 102 insertions, 5 deletions
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go index 1fa6137..239ec54 100644 --- a/internal/fusefrontend/node.go +++ b/internal/fusefrontend/node.go @@ -147,13 +147,15 @@ func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint3 } // Get device number and inode number into `st` - st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW) + var st syscall.Stat_t + err = syscall.Fstat(fd, &st) if err != nil { - return nil, nil, 0, fs.ToErrno(err) + errno = fs.ToErrno(err) + return } // Get unique inode number - rn.inoMap.TranslateStat(st) - out.Attr.FromStat(st) + rn.inoMap.TranslateStat(&st) + out.Attr.FromStat(&st) // Create child node id := fs.StableAttr{ Mode: uint32(st.Mode), @@ -164,7 +166,7 @@ func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint3 ch := n.NewInode(ctx, node, id) f := os.NewFile(uintptr(fd), cName) - return ch, NewFile2(f, rn, st), 0, 0 + return ch, NewFile2(f, rn, &st), 0, 0 } // Unlink - FUSE call. Delete a file. @@ -218,3 +220,49 @@ func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) { } return []byte(target), 0 } + +// Open - FUSE call. Open already-existing file. +// +// Symlink-safe through Openat(). +func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) { + dirfd, cName, errno := n.prepareAtSyscall("") + if errno != 0 { + return + } + defer syscall.Close(dirfd) + + rn := n.rootNode() + newFlags := rn.mangleOpenFlags(flags) + // Taking this lock makes sure we don't race openWriteOnlyFile() + rn.openWriteOnlyLock.RLock() + defer rn.openWriteOnlyLock.RUnlock() + + // Open backing file + fd, err := syscallcompat.Openat(dirfd, cName, newFlags, 0) + // Handle a few specific errors + if err != nil { + if err == syscall.EMFILE { + var lim syscall.Rlimit + syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim) + tlog.Warn.Printf("Open %q: too many open files. Current \"ulimit -n\": %d", cName, lim.Cur) + } + if err == syscall.EACCES && (int(flags)&syscall.O_ACCMODE) == syscall.O_WRONLY { + fd, err = rn.openWriteOnlyFile(dirfd, cName, newFlags) + } + } + // Could not handle the error? Bail out + if err != nil { + errno = fs.ToErrno(err) + return + } + + var st syscall.Stat_t + err = syscall.Fstat(fd, &st) + if err != nil { + errno = fs.ToErrno(err) + return + } + f := os.NewFile(uintptr(fd), cName) + fh = NewFile2(f, rn, &st) + return +} diff --git a/internal/fusefrontend/node_api_check.go b/internal/fusefrontend/node_api_check.go index dd2fd88..9b5746a 100644 --- a/internal/fusefrontend/node_api_check.go +++ b/internal/fusefrontend/node_api_check.go @@ -13,3 +13,4 @@ var _ = (fs.NodeMkdirer)((*Node)(nil)) var _ = (fs.NodeRmdirer)((*Node)(nil)) var _ = (fs.NodeUnlinker)((*Node)(nil)) var _ = (fs.NodeReadlinker)((*Node)(nil)) +var _ = (fs.NodeOpener)((*Node)(nil)) diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go index 7565018..2689e05 100644 --- a/internal/fusefrontend/root_node.go +++ b/internal/fusefrontend/root_node.go @@ -28,6 +28,9 @@ type RootNode struct { nameTransform nametransform.NameTransformer // Content encryption helper contentEnc *contentenc.ContentEnc + // This lock is used by openWriteOnlyFile() to block concurrent opens while + // it relaxes the permissions on a file. + openWriteOnlyLock sync.RWMutex // MitigatedCorruptions is used to report data corruption that is internally // mitigated by ignoring the corrupt item. For example, when OpenDir() finds // a corrupt filename, we still return the other valid filenames. @@ -137,3 +140,48 @@ func (rn *RootNode) decryptSymlinkTarget(cData64 string) (string, error) { } return string(data), nil } + +// Due to RMW, we always need read permissions on the backing file. This is a +// problem if the file permissions do not allow reading (i.e. 0200 permissions). +// This function works around that problem by chmod'ing the file, obtaining a fd, +// and chmod'ing it back. +func (rn *RootNode) openWriteOnlyFile(dirfd int, cName string, newFlags int) (rwFd int, err error) { + woFd, err := syscallcompat.Openat(dirfd, cName, syscall.O_WRONLY|syscall.O_NOFOLLOW, 0) + if err != nil { + return + } + defer syscall.Close(woFd) + var st syscall.Stat_t + err = syscall.Fstat(woFd, &st) + if err != nil { + return + } + // The cast to uint32 fixes a build failure on Darwin, where st.Mode is uint16. + perms := uint32(st.Mode) + // Verify that we don't have read permissions + if perms&0400 != 0 { + tlog.Warn.Printf("openWriteOnlyFile: unexpected permissions %#o, returning EPERM", perms) + err = syscall.EPERM + return + } + // Upgrade the lock to block other Open()s and downgrade again on return + rn.openWriteOnlyLock.RUnlock() + rn.openWriteOnlyLock.Lock() + defer func() { + rn.openWriteOnlyLock.Unlock() + rn.openWriteOnlyLock.RLock() + }() + // Relax permissions and revert on return + err = syscall.Fchmod(woFd, perms|0400) + if err != nil { + tlog.Warn.Printf("openWriteOnlyFile: changing permissions failed: %v", err) + return + } + defer func() { + err2 := syscall.Fchmod(woFd, perms) + if err2 != nil { + tlog.Warn.Printf("openWriteOnlyFile: reverting permissions failed: %v", err2) + } + }() + return syscallcompat.Openat(dirfd, cName, newFlags, 0) +} |