v2api/reverse: implement Read
This commit is contained in:
parent
5276092663
commit
6d4f1a6888
70
internal/fusefrontend_reverse/file.go
Normal file
70
internal/fusefrontend_reverse/file.go
Normal file
@ -0,0 +1,70 @@
|
||||
package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
// Backing FD
|
||||
fd *os.File
|
||||
// File header (contains the IV)
|
||||
header contentenc.FileHeader
|
||||
// IV for block 0
|
||||
block0IV []byte
|
||||
// Content encryption helper
|
||||
contentEnc *contentenc.ContentEnc
|
||||
}
|
||||
|
||||
// Read - FUSE call
|
||||
func (f *File) Read(ctx context.Context, buf []byte, ioff int64) (resultData fuse.ReadResult, errno syscall.Errno) {
|
||||
length := uint64(len(buf))
|
||||
off := uint64(ioff)
|
||||
out := bytes.NewBuffer(buf[:0])
|
||||
var header []byte
|
||||
|
||||
// Synthesize file header
|
||||
if off < contentenc.HeaderLen {
|
||||
header = f.header.Pack()
|
||||
// Truncate to requested part
|
||||
end := int(off) + len(buf)
|
||||
if end > len(header) {
|
||||
end = len(header)
|
||||
}
|
||||
header = header[off:end]
|
||||
// Write into output buffer and adjust offsets
|
||||
out.Write(header)
|
||||
hLen := uint64(len(header))
|
||||
off += hLen
|
||||
length -= hLen
|
||||
}
|
||||
|
||||
// Read actual file data
|
||||
if length > 0 {
|
||||
fileData, err := f.readBackingFile(off, length)
|
||||
if err != nil {
|
||||
return nil, fs.ToErrno(err)
|
||||
}
|
||||
if len(fileData) == 0 {
|
||||
// If we could not read any actual data, we also don't want to
|
||||
// return the file header. An empty file stays empty in encrypted
|
||||
// form.
|
||||
return nil, 0
|
||||
}
|
||||
out.Write(fileData)
|
||||
}
|
||||
|
||||
return fuse.ReadResultData(out.Bytes()), 0
|
||||
}
|
||||
|
||||
// Release - FUSE call, close file
|
||||
func (f *File) Release(context.Context) syscall.Errno {
|
||||
return fs.ToErrno(f.fd.Close())
|
||||
}
|
23
internal/fusefrontend_reverse/file_api_check.go
Normal file
23
internal/fusefrontend_reverse/file_api_check.go
Normal file
@ -0,0 +1,23 @@
|
||||
package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
)
|
||||
|
||||
// Check that we have implemented the fs.File* interfaces
|
||||
var _ = (fs.FileReader)((*File)(nil))
|
||||
var _ = (fs.FileReleaser)((*File)(nil))
|
||||
|
||||
/* TODO
|
||||
var _ = (fs.FileGetattrer)((*File2)(nil))
|
||||
var _ = (fs.FileSetattrer)((*File2)(nil))
|
||||
var _ = (fs.FileWriter)((*File2)(nil))
|
||||
var _ = (fs.FileFsyncer)((*File2)(nil))
|
||||
var _ = (fs.FileFlusher)((*File2)(nil))
|
||||
var _ = (fs.FileAllocater)((*File2)(nil))
|
||||
var _ = (fs.FileLseeker)((*File2)(nil))
|
||||
var _ = (fs.FileHandle)((*File2)(nil))
|
||||
var _ = (fs.FileGetlker)((*File2)(nil))
|
||||
var _ = (fs.FileSetlker)((*File2)(nil))
|
||||
var _ = (fs.FileSetlkwer)((*File2)(nil))
|
||||
*/
|
62
internal/fusefrontend_reverse/file_helpers.go
Normal file
62
internal/fusefrontend_reverse/file_helpers.go
Normal file
@ -0,0 +1,62 @@
|
||||
package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||
)
|
||||
|
||||
var inodeTable sync.Map
|
||||
|
||||
// encryptBlocks - encrypt "plaintext" into a number of ciphertext blocks.
|
||||
// "plaintext" must already be block-aligned.
|
||||
func (rf *File) encryptBlocks(plaintext []byte, firstBlockNo uint64, fileID []byte, block0IV []byte) []byte {
|
||||
inBuf := bytes.NewBuffer(plaintext)
|
||||
var outBuf bytes.Buffer
|
||||
bs := int(rf.contentEnc.PlainBS())
|
||||
for blockNo := firstBlockNo; inBuf.Len() > 0; blockNo++ {
|
||||
inBlock := inBuf.Next(bs)
|
||||
iv := pathiv.BlockIV(block0IV, blockNo)
|
||||
outBlock := rf.contentEnc.EncryptBlockNonce(inBlock, blockNo, fileID, iv)
|
||||
outBuf.Write(outBlock)
|
||||
}
|
||||
return outBuf.Bytes()
|
||||
}
|
||||
|
||||
// readBackingFile: read from the backing plaintext file, encrypt it, return the
|
||||
// ciphertext.
|
||||
// "off" ... ciphertext offset (must be >= HEADER_LEN)
|
||||
// "length" ... ciphertext length
|
||||
func (f *File) readBackingFile(off uint64, length uint64) (out []byte, err error) {
|
||||
blocks := f.contentEnc.ExplodeCipherRange(off, length)
|
||||
|
||||
// Read the backing plaintext in one go
|
||||
alignedOffset, alignedLength := contentenc.JointPlaintextRange(blocks)
|
||||
plaintext := make([]byte, int(alignedLength))
|
||||
n, err := f.fd.ReadAt(plaintext, int64(alignedOffset))
|
||||
if err != nil && err != io.EOF {
|
||||
tlog.Warn.Printf("readBackingFile: ReadAt: %s", err.Error())
|
||||
return nil, err
|
||||
}
|
||||
// Truncate buffer down to actually read bytes
|
||||
plaintext = plaintext[0:n]
|
||||
|
||||
// Encrypt blocks
|
||||
ciphertext := f.encryptBlocks(plaintext, blocks[0].BlockNo, f.header.ID, f.block0IV)
|
||||
|
||||
// Crop down to the relevant part
|
||||
lenHave := len(ciphertext)
|
||||
skip := blocks[0].Skip
|
||||
endWant := int(skip + length)
|
||||
if lenHave > endWant {
|
||||
out = ciphertext[skip:endWant]
|
||||
} else if lenHave > int(skip) {
|
||||
out = ciphertext[skip:lenHave]
|
||||
} // else: out stays empty, file was smaller than the requested offset
|
||||
|
||||
return out, nil
|
||||
}
|
@ -2,6 +2,8 @@ package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
@ -10,8 +12,10 @@ import (
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"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
|
||||
@ -33,15 +37,7 @@ func (n *Node) Lookup(ctx context.Context, cName string, out *fuse.EntryOut) (ch
|
||||
return n.lookupLongnameName(ctx, cName, out)
|
||||
} else if t == typeConfig {
|
||||
// gocryptfs.conf
|
||||
var err error
|
||||
pName = configfile.ConfReverseName
|
||||
rn := n.rootNode()
|
||||
dirfd, err = syscallcompat.OpenDirNofollow(rn.args.Cipherdir, "")
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
return n.lookupConf(ctx, out)
|
||||
} else if t == typeReal {
|
||||
// real file
|
||||
dirfd, pName, errno = n.prepareAtSyscall(cName)
|
||||
@ -112,3 +108,73 @@ func (n *Node) Readlink(ctx context.Context) (out []byte, errno syscall.Errno) {
|
||||
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
|
||||
}
|
||||
|
@ -9,11 +9,10 @@ var _ = (fs.NodeGetattrer)((*Node)(nil))
|
||||
var _ = (fs.NodeLookuper)((*Node)(nil))
|
||||
var _ = (fs.NodeReaddirer)((*Node)(nil))
|
||||
var _ = (fs.NodeReadlinker)((*Node)(nil))
|
||||
|
||||
/* TODO
|
||||
var _ = (fs.NodeOpener)((*Node)(nil))
|
||||
|
||||
/*
|
||||
var _ = (fs.NodeStatfser)((*Node)(nil))
|
||||
var _ = (fs.NodeMknoder)((*Node)(nil))
|
||||
var _ = (fs.NodeGetxattrer)((*Node)(nil))
|
||||
var _ = (fs.NodeListxattrer)((*Node)(nil))
|
||||
*/
|
||||
@ -23,6 +22,7 @@ var _ = (fs.NodeOpendirer)((*Node)(nil))
|
||||
*/
|
||||
|
||||
/* Will not implement these - reverse mode is read-only!
|
||||
var _ = (fs.NodeMknoder)((*Node)(nil))
|
||||
var _ = (fs.NodeCreater)((*Node)(nil))
|
||||
var _ = (fs.NodeMkdirer)((*Node)(nil))
|
||||
var _ = (fs.NodeRmdirer)((*Node)(nil))
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
|
||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||
"github.com/rfjakob/gocryptfs/internal/pathiv"
|
||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||
)
|
||||
@ -115,8 +116,8 @@ func (n *Node) lookupLongnameName(ctx context.Context, nameFile string, out *fus
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
}
|
||||
var vf *virtualFile
|
||||
vf, errno = n.newVirtualFile([]byte(cFullname), st, inoTagNameFile)
|
||||
var vf *VirtualMemNode
|
||||
vf, errno = n.newVirtualMemNode([]byte(cFullname), st, inoTagNameFile)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
@ -141,8 +142,8 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
|
||||
return
|
||||
}
|
||||
content := pathiv.Derive(n.Path(), pathiv.PurposeDirIV)
|
||||
var vf *virtualFile
|
||||
vf, errno = n.newVirtualFile(content, st, inoTagDirIV)
|
||||
var vf *VirtualMemNode
|
||||
vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
|
||||
if errno != 0 {
|
||||
return nil, errno
|
||||
}
|
||||
@ -153,6 +154,30 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
|
||||
return
|
||||
}
|
||||
|
||||
// lookupConf returns a new Inode for the gocryptfs.conf file
|
||||
func (n *Node) lookupConf(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inode, errno syscall.Errno) {
|
||||
rn := n.rootNode()
|
||||
p := filepath.Join(rn.args.Cipherdir, configfile.ConfReverseName)
|
||||
var st syscall.Stat_t
|
||||
err := syscall.Stat(p, &st)
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
}
|
||||
// Get unique inode number
|
||||
rn.inoMap.TranslateStat(&st)
|
||||
out.Attr.FromStat(&st)
|
||||
// Create child node
|
||||
id := fs.StableAttr{
|
||||
Mode: uint32(st.Mode),
|
||||
Gen: 1,
|
||||
Ino: st.Ino,
|
||||
}
|
||||
node := &VirtualConfNode{path: p}
|
||||
ch = n.NewInode(ctx, node, id)
|
||||
return
|
||||
}
|
||||
|
||||
// readlink reads and encrypts a symlink. Used by Readlink, Getattr, Lookup.
|
||||
func (n *Node) readlink(dirfd int, cName string, pName string) (out []byte, errno syscall.Errno) {
|
||||
plainTarget, err := syscallcompat.Readlinkat(dirfd, pName)
|
||||
|
55
internal/fusefrontend_reverse/virtualconf.go
Normal file
55
internal/fusefrontend_reverse/virtualconf.go
Normal file
@ -0,0 +1,55 @@
|
||||
package fusefrontend_reverse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/hanwen/go-fuse/v2/fs"
|
||||
"github.com/hanwen/go-fuse/v2/fuse"
|
||||
)
|
||||
|
||||
var _ = (fs.NodeOpener)((*VirtualConfNode)(nil))
|
||||
|
||||
type VirtualConfNode struct {
|
||||
fs.Inode
|
||||
|
||||
path string
|
||||
}
|
||||
|
||||
func (n *VirtualConfNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
fd, err := syscall.Open(n.path, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
errno = fs.ToErrno(err)
|
||||
return
|
||||
}
|
||||
fh = &VirtualConfFile{fd: fd}
|
||||
return
|
||||
}
|
||||
|
||||
// Check that we have implemented the fs.File* interfaces
|
||||
var _ = (fs.FileReader)((*VirtualConfFile)(nil))
|
||||
var _ = (fs.FileReleaser)((*VirtualConfFile)(nil))
|
||||
|
||||
type VirtualConfFile struct {
|
||||
mu sync.Mutex
|
||||
fd int
|
||||
}
|
||||
|
||||
func (f *VirtualConfFile) Read(ctx context.Context, buf []byte, off int64) (res fuse.ReadResult, errno syscall.Errno) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
res = fuse.ReadResultFd(uintptr(f.fd), off, len(buf))
|
||||
return
|
||||
}
|
||||
|
||||
func (f *VirtualConfFile) Release(ctx context.Context) syscall.Errno {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
if f.fd != -1 {
|
||||
err := syscall.Close(f.fd)
|
||||
f.fd = -1
|
||||
return fs.ToErrno(err)
|
||||
}
|
||||
return syscall.EBADF
|
||||
}
|
@ -60,7 +60,9 @@ func (n *Node) lookupFileType(cName string) fileType {
|
||||
return typeReal
|
||||
}
|
||||
|
||||
type virtualFile struct {
|
||||
// VirtualMemNode is an in-memory node that does not have a representation
|
||||
// on disk.
|
||||
type VirtualMemNode struct {
|
||||
fs.Inode
|
||||
|
||||
// file content
|
||||
@ -69,12 +71,12 @@ type virtualFile struct {
|
||||
attr fuse.Attr
|
||||
}
|
||||
|
||||
// newVirtualFile creates a new in-memory file that does not have a representation
|
||||
// newVirtualMemNode creates a new in-memory file that does not have a representation
|
||||
// on disk. "content" is the file content. Timestamps and file owner are copied
|
||||
// from "parentFile" (file descriptor).
|
||||
// For a "gocryptfs.diriv" file, you would use the parent directory as
|
||||
// "parentFile".
|
||||
func (n *Node) newVirtualFile(content []byte, parentStat *syscall.Stat_t, inoTag uint8) (vf *virtualFile, errno syscall.Errno) {
|
||||
func (n *Node) newVirtualMemNode(content []byte, parentStat *syscall.Stat_t, inoTag uint8) (vf *VirtualMemNode, errno syscall.Errno) {
|
||||
if inoTag == 0 {
|
||||
log.Panicf("BUG: inoTag for virtual file is zero - this will cause ino collisions!")
|
||||
}
|
||||
@ -90,23 +92,23 @@ func (n *Node) newVirtualFile(content []byte, parentStat *syscall.Stat_t, inoTag
|
||||
var a fuse.Attr
|
||||
a.FromStat(st)
|
||||
|
||||
vf = &virtualFile{content: content, attr: a}
|
||||
vf = &VirtualMemNode{content: content, attr: a}
|
||||
return
|
||||
}
|
||||
|
||||
// Open - FUSE call
|
||||
func (f *virtualFile) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
func (f *VirtualMemNode) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
|
||||
return nil, fuse.FOPEN_KEEP_CACHE, 0
|
||||
}
|
||||
|
||||
// GetAttr - FUSE call
|
||||
func (f *virtualFile) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
func (f *VirtualMemNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
|
||||
out.Attr = f.attr
|
||||
return 0
|
||||
}
|
||||
|
||||
// Read - FUSE call
|
||||
func (f *virtualFile) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
||||
func (f *VirtualMemNode) Read(ctx context.Context, fh fs.FileHandle, dest []byte, off int64) (fuse.ReadResult, syscall.Errno) {
|
||||
end := int(off) + len(dest)
|
||||
if end > len(f.content) {
|
||||
end = len(f.content)
|
Loading…
x
Reference in New Issue
Block a user