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