aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Unterwurzacher2020-07-04 21:37:44 +0200
committerJakob Unterwurzacher2020-07-04 21:37:44 +0200
commitd2139e18ef184afbd99759d07cc28c0642cdc4ae (patch)
tree9885bb8c343e79e6bbb8cccc8a83c60966cfd058
parentd73e4b3f7c97493b7dcf76c2160a6fe80d991f45 (diff)
v2api: implement Open()
-rw-r--r--internal/fusefrontend/node.go58
-rw-r--r--internal/fusefrontend/node_api_check.go1
-rw-r--r--internal/fusefrontend/root_node.go48
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)
+}