From 0fa824933cfc8788450138a2d2e52c3dea2e592f Mon Sep 17 00:00:00 2001
From: Jakob Unterwurzacher
Date: Sun, 12 Jul 2020 20:17:15 +0200
Subject: v2api: properly implement Node.Setattr

We used to always open a *File2 and letting the *File2
handle Setattr. This does not work it we cannot open the file!

Before:

    $ go test
    2020/07/12 20:14:57 writer: Write/Writev failed, err: 2=no such file or directory. opcode: INTERRUPT
    2020/07/12 20:14:57 writer: Write/Writev failed, err: 2=no such file or directory. opcode: INTERRUPT
    --- FAIL: TestLchown (0.00s)
        matrix_test.go:634: lchown /tmp/gocryptfs-test-parent-1026/014500839/default-plain/symlink: too many levels of symbolic links
    touch: setting times of '/tmp/gocryptfs-test-parent-1026/014500839/default-plain/utimesnano_symlink': Too many levels of symbolic links
    --- FAIL: TestUtimesNanoSymlink (0.00s)
        matrix_test.go:655: exit status 1
    --- FAIL: TestMkfifo (0.00s)
        matrix_test.go:755: file exists
    --- FAIL: TestMagicNames (0.00s)
        matrix_test.go:773: Testing n="gocryptfs.longname.QhUr5d9FHerwEs--muUs6_80cy6JRp89c1otLwp92Cs"
        matrix_test.go:773: Testing n="gocryptfs.diriv"
        matrix_test.go:815: open /tmp/gocryptfs-test-parent-1026/014500839/default-plain/linktarget: permission denied
    --- FAIL: TestChmod (0.00s)
        matrix_test.go:840: chmod 444 -> 000 failed: permission denied
        matrix_test.go:840: chmod 444 -> 111 failed: permission denied
        matrix_test.go:840: chmod 444 -> 123 failed: permission denied
        matrix_test.go:840: chmod 444 -> 321 failed: permission denied
    FAIL
    exit status 1
    FAIL	github.com/rfjakob/gocryptfs/tests/matrix	0.790s

After:

    $ go test
    --- FAIL: TestMkfifo (0.00s)
        matrix_test.go:755: file exists
    --- FAIL: TestMagicNames (0.00s)
        matrix_test.go:773: Testing n="gocryptfs.longname.QhUr5d9FHerwEs--muUs6_80cy6JRp89c1otLwp92Cs"
        matrix_test.go:773: Testing n="gocryptfs.diriv"
        matrix_test.go:815: open /tmp/gocryptfs-test-parent-1026/501766059/default-plain/linktarget: permission denied
    --- FAIL: TestChmod (0.00s)
        matrix_test.go:849: modeHave 0644 != modeWant 0
    FAIL
    exit status 1
    FAIL    github.com/rfjakob/gocryptfs/tests/matrix   0.787s
---
 internal/fusefrontend/file2_setattr.go |  4 +-
 internal/fusefrontend/node.go          | 71 +++++++++++++++++++++++++++++++---
 2 files changed, 67 insertions(+), 8 deletions(-)

(limited to 'internal/fusefrontend')

diff --git a/internal/fusefrontend/file2_setattr.go b/internal/fusefrontend/file2_setattr.go
index 697e0d9..1385f3f 100644
--- a/internal/fusefrontend/file2_setattr.go
+++ b/internal/fusefrontend/file2_setattr.go
@@ -29,7 +29,7 @@ func (f *File2) setAttr(ctx context.Context, in *fuse.SetAttrIn) (errno syscall.
 	f.fileTableEntry.ContentLock.Lock()
 	defer f.fileTableEntry.ContentLock.Unlock()
 
-	// chmod(2) & fchmod(2)
+	// fchmod(2)
 	if mode, ok := in.GetMode(); ok {
 		errno = fs.ToErrno(syscall.Fchmod(f.intFd(), mode))
 		if errno != 0 {
@@ -37,7 +37,7 @@ func (f *File2) setAttr(ctx context.Context, in *fuse.SetAttrIn) (errno syscall.
 		}
 	}
 
-	// chown(2) & fchown(2)
+	// fchown(2)
 	uid32, uOk := in.GetUID()
 	gid32, gOk := in.GetGID()
 	if uOk || gOk {
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index a6272b5..b03829e 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -296,18 +296,77 @@ func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFl
 
 // Setattr - FUSE call. Called for chmod, truncate, utimens, ...
 func (n *Node) Setattr(ctx context.Context, f fs.FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) (errno syscall.Errno) {
-	var f2 *File2
+	// Use the fd if the kernel gave us one
 	if f != nil {
-		f2 = f.(*File2)
-	} else {
+		f2 := f.(*File2)
+		return f2.Setattr(ctx, in, out)
+	}
+
+	dirfd, cName, errno := n.prepareAtSyscall("")
+	if errno != 0 {
+		return
+	}
+	defer syscall.Close(dirfd)
+
+	// chmod(2)
+	if mode, ok := in.GetMode(); ok {
+		errno = fs.ToErrno(syscallcompat.FchmodatNofollow(dirfd, cName, mode))
+		if errno != 0 {
+			return errno
+		}
+	}
+
+	// chown(2)
+	uid32, uOk := in.GetUID()
+	gid32, gOk := in.GetGID()
+	if uOk || gOk {
+		uid := -1
+		gid := -1
+
+		if uOk {
+			uid = int(uid32)
+		}
+		if gOk {
+			gid = int(gid32)
+		}
+		errno = fs.ToErrno(syscallcompat.Fchownat(dirfd, cName, uid, gid, unix.AT_SYMLINK_NOFOLLOW))
+		if errno != 0 {
+			return errno
+		}
+	}
+
+	// utimens(2)
+	mtime, mok := in.GetMTime()
+	atime, aok := in.GetATime()
+	if mok || aok {
+		ap := &atime
+		mp := &mtime
+		if !aok {
+			ap = nil
+		}
+		if !mok {
+			mp = nil
+		}
+		errno = fs.ToErrno(syscallcompat.UtimesNanoAtNofollow(dirfd, cName, ap, mp))
+		if errno != 0 {
+			return errno
+		}
+	}
+
+	// For truncate, the user has to have write permissions. That means we can
+	// depend on opening a RDWR fd and letting the File handle truncate.
+	if sz, ok := in.GetSize(); ok {
 		f, _, errno := n.Open(ctx, syscall.O_RDWR)
 		if errno != 0 {
 			return errno
 		}
-		f2 = f.(*File2)
-		defer f2.Release(ctx)
+		f2 := f.(*File2)
+		errno = syscall.Errno(f2.truncate(sz))
+		if errno != 0 {
+			return errno
+		}
 	}
-	return f2.Setattr(ctx, in, out)
+	return 0
 }
 
 // StatFs - FUSE call. Returns information about the filesystem.
-- 
cgit v1.2.3