v2api: implement Open()

This commit is contained in:
Jakob Unterwurzacher 2020-07-04 21:37:44 +02:00
parent d73e4b3f7c
commit d2139e18ef
3 changed files with 102 additions and 5 deletions

View File

@ -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
}

View File

@ -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))

View File

@ -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)
}