libgocryptfs/internal/fusefrontend_reverse/node.go
Jakob Unterwurzacher 1c1692c4d9 fusefrontend_reverse: fix GETATTR panic
We don't implement Getattr(), so don't try to call it.

Reported at https://github.com/rfjakob/gocryptfs/issues/519#issuecomment-718790790 :

15:22:53.414101 rx 3344: READ n2565 {Fh 7 [42143744 +131072)  L 0 RDONLY,0x8000}
15:22:53.414274 rx 3342: READ n2565 {Fh 7 [42012672 +131072)  L 0 RDONLY,0x8000}
15:22:53.414787 rx 3346: READ n2565 {Fh 7 [42274816 +131072)  L 0 RDONLY,0x8000}
15:22:53.414806 tx 3336:     OK,  131072b data "\xcb\xd3<\"!-\xa7\xc4"...
15:22:53.414874 rx 3348: GETATTR n1446 {Fh 0}
panic: interface conversion: *fusefrontend_reverse.File is not fs.FileGetattrer: missing method Getattr

goroutine 451 [running]:
github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse.(*Node).Getattr(0xc00034c880, 0x5620579784c0, 0xc000593e60, 0x562057939800, 0xc000218050, 0xc0000fc108, 0x0)
        github.com/rfjakob/gocryptfs/internal/fusefrontend_reverse/node.go:69 +0x273
github.com/hanwen/go-fuse/v2/fs.(*rawBridge).getattr(0xc00011e000, 0x5620579784c0, 0xc000593e60, 0xc00034c880, 0x562057939800, 0xc000218050, 0xc0000fc108, 0xbfded1ef58ba7b13)
        github.com/hanwen/go-fuse/v2@v2.0.4-0.20200908172753-0b6cbc515082/fs/bridge.go:500 +0x2d4
github.com/hanwen/go-fuse/v2/fs.(*rawBridge).GetAttr(0xc00011e000, 0xc0000e0000, 0xc0000fc198, 0xc0000fc108, 0x0)
        github.com/hanwen/go-fuse/v2@v2.0.4-0.20200908172753-0b6cbc515082/fs/bridge.go:488 +0x11c
github.com/hanwen/go-fuse/v2/fuse.doGetAttr(0xc000120000, 0xc0000fc000)
        github.com/hanwen/go-fuse/v2@v2.0.4-0.20200908172753-0b6cbc515082/fuse/opcode.go:287 +0x6f
github.com/hanwen/go-fuse/v2/fuse.(*Server).handleRequest(0xc000120000, 0xc0000fc000, 0xc000000000)
        github.com/hanwen/go-fuse/v2@v2.0.4-0.20200908172753-0b6cbc515082/fuse/server.go:472 +0x2c1
github.com/hanwen/go-fuse/v2/fuse.(*Server).loop(0xc000120000, 0xc000288001)
        github.com/hanwen/go-fuse/v2@v2.0.4-0.20200908172753-0b6cbc515082/fuse/server.go:445 +0x18c
created by github.com/hanwen/go-fuse/v2/fuse.(*Server).readRequest
        github.com/hanwen/go-fuse/v2@v2.0.4-0.20200908172753-0b6cbc515082/fuse/server.go:312 +0x419
2020-11-10 19:37:49 +01:00

189 lines
5.0 KiB
Go

package fusefrontend_reverse
import (
"context"
"fmt"
"os"
"path/filepath"
"syscall"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/pathiv"
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/internal/tlog"
)
// Node is a file or directory in the filesystem tree
// in a `gocryptfs -reverse` mount.
type Node struct {
fs.Inode
}
// Lookup - FUSE call for discovering a file.
func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
var d *dirfdPlus
t := n.lookupFileType(cName)
if t == typeDiriv {
// gocryptfs.diriv
return n.lookupDiriv(ctx, out)
} else if t == typeName {
// gocryptfs.longname.*.name
return n.lookupLongnameName(ctx, cName, out)
} else if t == typeConfig {
// gocryptfs.conf
return n.lookupConf(ctx, out)
} else if t == typeReal {
// real file
d, errno = n.prepareAtSyscall(cName)
//fmt.Printf("Lookup: prepareAtSyscall -> d=%#v, errno=%d\n", d, errno)
if errno != 0 {
return
}
defer syscall.Close(d.dirfd)
}
// Get device number and inode number into `st`
st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return nil, fs.ToErrno(err)
}
// Create new inode and fill `out`
ch = n.newChild(ctx, st, out)
// Translate ciphertext size in `out.Attr.Size` to plaintext size
if t == typeReal {
n.translateSize(d.dirfd, cName, d.pName, &out.Attr)
}
return ch, 0
}
// GetAttr - FUSE call for stat()ing a file.
//
// GetAttr is symlink-safe through use of openBackingDir() and Fstatat().
func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut) (errno syscall.Errno) {
d, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(d.dirfd)
st, err := syscallcompat.Fstatat2(d.dirfd, d.pName, unix.AT_SYMLINK_NOFOLLOW)
if err != nil {
return fs.ToErrno(err)
}
// Fix inode number
rn := n.rootNode()
rn.inoMap.TranslateStat(st)
out.Attr.FromStat(st)
// Translate ciphertext size in `out.Attr.Size` to plaintext size
cName := filepath.Base(n.Path())
n.translateSize(d.dirfd, cName, d.pName, &out.Attr)
if rn.args.ForceOwner != nil {
out.Owner = *rn.args.ForceOwner
}
return 0
}
// Readlink - FUSE call.
//
// Symlink-safe through openBackingDir() + Readlinkat().
func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
d, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(d.dirfd)
return n.readlink(d.dirfd, d.cName, d.pName)
}
// 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) {
d, errno := n.prepareAtSyscall("")
if errno != 0 {
return
}
defer syscall.Close(d.dirfd)
fd, err := syscallcompat.Openat(d.dirfd, d.pName, syscall.O_RDONLY|syscall.O_NOFOLLOW, 0)
if err != nil {
errno = fs.ToErrno(err)
return
}
// Reject access if the file descriptor does not refer to a regular file.
var st syscall.Stat_t
err = syscall.Fstat(fd, &st)
if err != nil {
tlog.Warn.Printf("Open: Fstat error: %v", err)
syscall.Close(fd)
errno = fs.ToErrno(err)
return
}
var a fuse.Attr
a.FromStat(&st)
if !a.IsRegular() {
tlog.Warn.Printf("ino%d: newFile: not a regular file", st.Ino)
syscall.Close(fd)
errno = syscall.EACCES
return
}
// See if we have that inode number already in the table
// (even if Nlink has dropped to 1)
var derivedIVs pathiv.FileIVs
v, found := inodeTable.Load(st.Ino)
if found {
tlog.Debug.Printf("ino%d: newFile: found in the inode table", st.Ino)
derivedIVs = v.(pathiv.FileIVs)
} else {
p := n.Path()
derivedIVs = pathiv.DeriveFile(p)
// Nlink > 1 means there is more than one path to this file.
// Store the derived values so we always return the same data,
// regardless of the path that is used to access the file.
// This means that the first path wins.
if st.Nlink > 1 {
v, found = inodeTable.LoadOrStore(st.Ino, derivedIVs)
if found {
// Another thread has stored a different value before we could.
derivedIVs = v.(pathiv.FileIVs)
} else {
tlog.Debug.Printf("ino%d: newFile: Nlink=%d, stored in the inode table", st.Ino, st.Nlink)
}
}
}
header := contentenc.FileHeader{
Version: contentenc.CurrentVersion,
ID: derivedIVs.ID,
}
fh = &File{
fd: os.NewFile(uintptr(fd), fmt.Sprintf("fd%d", fd)),
header: header,
block0IV: derivedIVs.Block0IV,
contentEnc: n.rootNode().contentEnc,
}
return
}
// StatFs - FUSE call. Returns information about the filesystem.
//
// Symlink-safe because the path is ignored.
func (n *Node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
p := n.rootNode().args.Cipherdir
var st syscall.Statfs_t
err := syscall.Statfs(p, &st)
if err != nil {
return fs.ToErrno(err)
}
out.FromStatfsT(&st)
return 0
}