181 lines
4.7 KiB
Go
181 lines
4.7 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) {
|
|
dirfd := int(-1)
|
|
pName := ""
|
|
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
|
|
dirfd, pName, errno = n.prepareAtSyscall(cName)
|
|
if errno != 0 {
|
|
return
|
|
}
|
|
defer syscall.Close(dirfd)
|
|
}
|
|
// Get device number and inode number into `st`
|
|
st, err := syscallcompat.Fstatat2(dirfd, 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(dirfd, cName, 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) {
|
|
// If the kernel gives us a file handle, use it.
|
|
if f != nil {
|
|
return f.(fs.FileGetattrer).Getattr(ctx, out)
|
|
}
|
|
|
|
dirfd, pName, errno := n.prepareAtSyscall("")
|
|
if errno != 0 {
|
|
return
|
|
}
|
|
defer syscall.Close(dirfd)
|
|
|
|
st, err := syscallcompat.Fstatat2(dirfd, 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(dirfd, cName, 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) {
|
|
dirfd, pName, errno := n.prepareAtSyscall("")
|
|
if errno != 0 {
|
|
return
|
|
}
|
|
defer syscall.Close(dirfd)
|
|
|
|
cName := filepath.Base(n.Path())
|
|
return n.readlink(dirfd, cName, 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) {
|
|
dirfd, pName, errno := n.prepareAtSyscall("")
|
|
if errno != 0 {
|
|
return
|
|
}
|
|
defer syscall.Close(dirfd)
|
|
|
|
fd, err := syscallcompat.Openat(dirfd, 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
|
|
}
|