aboutsummaryrefslogtreecommitdiff
path: root/internal/fusefrontend
diff options
context:
space:
mode:
Diffstat (limited to 'internal/fusefrontend')
-rw-r--r--internal/fusefrontend/file.go22
-rw-r--r--internal/fusefrontend/file_allocate_truncate.go24
-rw-r--r--internal/fusefrontend/file_holes.go10
-rw-r--r--internal/fusefrontend/node.go28
-rw-r--r--internal/fusefrontend/node_dir_ops.go11
-rw-r--r--internal/fusefrontend/node_helpers.go5
-rw-r--r--internal/fusefrontend/node_prepare_syscall.go3
-rw-r--r--internal/fusefrontend/prepare_syscall_test.go13
-rw-r--r--internal/fusefrontend/root_node.go5
-rw-r--r--internal/fusefrontend/xattr_unit_test.go6
10 files changed, 75 insertions, 52 deletions
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go
index 103c217..afee158 100644
--- a/internal/fusefrontend/file.go
+++ b/internal/fusefrontend/file.go
@@ -37,8 +37,6 @@ type File struct {
// Every FUSE entrypoint should RLock(). The only user of Lock() is
// Release(), which closes the fd and sets "released" to true.
fdLock sync.RWMutex
- // Content encryption helper
- contentEnc *contentenc.ContentEnc
// Device and inode number uniquely identify the backing file
qIno inomap.QIno
// Entry in the open file table
@@ -73,7 +71,6 @@ func NewFile(fd int, cName string, rn *RootNode) (f *File, st *syscall.Stat_t, e
f = &File{
fd: osFile,
- contentEnc: rn.contentEnc,
qIno: qi,
fileTableEntry: e,
rootNode: rn,
@@ -177,7 +174,7 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Er
log.Panicf("fileID=%v", fileID)
}
// Read the backing ciphertext in one go
- blocks := f.contentEnc.ExplodePlainRange(off, length)
+ blocks := f.rootNode.contentEnc.ExplodePlainRange(off, length)
alignedOffset, alignedLength := blocks[0].JointCiphertextRange(blocks)
// f.fd.ReadAt takes an int64!
if alignedOffset > math.MaxInt64 {
@@ -206,10 +203,10 @@ func (f *File) doRead(dst []byte, off uint64, length uint64) ([]byte, syscall.Er
tlog.Debug.Printf("ReadAt offset=%d bytes (%d blocks), want=%d, got=%d", alignedOffset, firstBlockNo, alignedLength, n)
// Decrypt it
- plaintext, err := f.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
+ plaintext, err := f.rootNode.contentEnc.DecryptBlocks(ciphertext, firstBlockNo, fileID)
f.rootNode.contentEnc.CReqPool.Put(ciphertext)
if err != nil {
- corruptBlockNo := firstBlockNo + f.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
+ corruptBlockNo := firstBlockNo + f.rootNode.contentEnc.PlainOffToBlockNo(uint64(len(plaintext)))
tlog.Warn.Printf("doRead %d: corrupt block #%d: %v", f.qIno.Ino, corruptBlockNo, err)
return nil, syscall.EIO
}
@@ -287,20 +284,20 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
}
// Handle payload data
dataBuf := bytes.NewBuffer(data)
- blocks := f.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data)))
+ blocks := f.rootNode.contentEnc.ExplodePlainRange(uint64(off), uint64(len(data)))
toEncrypt := make([][]byte, len(blocks))
for i, b := range blocks {
blockData := dataBuf.Next(int(b.Length))
// Incomplete block -> Read-Modify-Write
if b.IsPartial() {
// Read
- oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.contentEnc.PlainBS())
+ oldData, errno := f.doRead(nil, b.BlockPlainOff(), f.rootNode.contentEnc.PlainBS())
if errno != 0 {
tlog.Warn.Printf("ino%d fh%d: RMW read failed: errno=%d", f.qIno.Ino, f.intFd(), errno)
return 0, errno
}
// Modify
- blockData = f.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip))
+ blockData = f.rootNode.contentEnc.MergeBlocks(oldData, blockData, int(b.Skip))
tlog.Debug.Printf("len(oldData)=%d len(blockData)=%d", len(oldData), len(blockData))
}
tlog.Debug.Printf("ino%d: Writing %d bytes to block #%d",
@@ -309,7 +306,7 @@ func (f *File) doWrite(data []byte, off int64) (uint32, syscall.Errno) {
toEncrypt[i] = blockData
}
// Encrypt all blocks
- ciphertext := f.contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, f.fileTableEntry.ID)
+ ciphertext := f.rootNode.contentEnc.EncryptBlocks(toEncrypt, blocks[0].BlockNo, f.fileTableEntry.ID)
// Preallocate so we cannot run out of space in the middle of the write.
// This prevents partially written (=corrupt) blocks.
var err error
@@ -439,7 +436,10 @@ func (f *File) Getattr(ctx context.Context, a *fuse.AttrOut) syscall.Errno {
}
f.rootNode.inoMap.TranslateStat(&st)
a.FromStat(&st)
- a.Size = f.contentEnc.CipherSizeToPlainSize(a.Size)
+ if a.IsRegular() {
+ a.Size = f.rootNode.contentEnc.CipherSizeToPlainSize(a.Size)
+ }
+ // TODO: Handle symlink size similar to node.translateSize()
if f.rootNode.args.ForceOwner != nil {
a.Owner = *f.rootNode.args.ForceOwner
}
diff --git a/internal/fusefrontend/file_allocate_truncate.go b/internal/fusefrontend/file_allocate_truncate.go
index cae796e..a3decf9 100644
--- a/internal/fusefrontend/file_allocate_truncate.go
+++ b/internal/fusefrontend/file_allocate_truncate.go
@@ -54,7 +54,7 @@ func (f *File) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32)
f.fileTableEntry.ContentLock.Lock()
defer f.fileTableEntry.ContentLock.Unlock()
- blocks := f.contentEnc.ExplodePlainRange(off, sz)
+ blocks := f.rootNode.contentEnc.ExplodePlainRange(off, sz)
firstBlock := blocks[0]
lastBlock := blocks[len(blocks)-1]
@@ -63,7 +63,7 @@ func (f *File) Allocate(ctx context.Context, off uint64, sz uint64, mode uint32)
// the file.
cipherOff := firstBlock.BlockCipherOff()
cipherSz := lastBlock.BlockCipherOff() - cipherOff +
- f.contentEnc.BlockOverhead() + lastBlock.Skip + lastBlock.Length
+ f.rootNode.contentEnc.BlockOverhead() + lastBlock.Skip + lastBlock.Length
err := syscallcompat.Fallocate(f.intFd(), FALLOC_FL_KEEP_SIZE, int64(cipherOff), int64(cipherSz))
tlog.Debug.Printf("Allocate off=%d sz=%d mode=%x cipherOff=%d cipherSz=%d\n",
off, sz, mode, cipherOff, cipherSz)
@@ -113,8 +113,8 @@ func (f *File) truncate(newSize uint64) (errno syscall.Errno) {
return fs.ToErrno(err)
}
- oldB := float32(oldSize) / float32(f.contentEnc.PlainBS())
- newB := float32(newSize) / float32(f.contentEnc.PlainBS())
+ oldB := float32(oldSize) / float32(f.rootNode.contentEnc.PlainBS())
+ newB := float32(newSize) / float32(f.rootNode.contentEnc.PlainBS())
tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.qIno.Ino, oldB, newB, oldSize, newSize)
// File size stays the same - nothing to do
@@ -127,9 +127,9 @@ func (f *File) truncate(newSize uint64) (errno syscall.Errno) {
}
// File shrinks
- blockNo := f.contentEnc.PlainOffToBlockNo(newSize)
- cipherOff := f.contentEnc.BlockNoToCipherOff(blockNo)
- plainOff := f.contentEnc.BlockNoToPlainOff(blockNo)
+ blockNo := f.rootNode.contentEnc.PlainOffToBlockNo(newSize)
+ cipherOff := f.rootNode.contentEnc.BlockNoToCipherOff(blockNo)
+ plainOff := f.rootNode.contentEnc.BlockNoToPlainOff(blockNo)
lastBlockLen := newSize - plainOff
var data []byte
if lastBlockLen > 0 {
@@ -161,7 +161,7 @@ func (f *File) statPlainSize() (uint64, error) {
return 0, err
}
cipherSz := uint64(fi.Size())
- plainSz := uint64(f.contentEnc.CipherSizeToPlainSize(cipherSz))
+ plainSz := uint64(f.rootNode.contentEnc.CipherSizeToPlainSize(cipherSz))
return plainSz, nil
}
@@ -174,8 +174,8 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er
}
newEOFOffset := newPlainSz - 1
if oldPlainSz > 0 {
- n1 := f.contentEnc.PlainOffToBlockNo(oldPlainSz - 1)
- n2 := f.contentEnc.PlainOffToBlockNo(newEOFOffset)
+ n1 := f.rootNode.contentEnc.PlainOffToBlockNo(oldPlainSz - 1)
+ n2 := f.rootNode.contentEnc.PlainOffToBlockNo(newEOFOffset)
// The file is grown within one block, no need to pad anything.
// Write a single zero to the last byte and let doWrite figure out the RMW.
if n1 == n2 {
@@ -194,7 +194,7 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er
}
// The new size is block-aligned. In this case we can do everything ourselves
// and avoid the call to doWrite.
- if newPlainSz%f.contentEnc.PlainBS() == 0 {
+ if newPlainSz%f.rootNode.contentEnc.PlainBS() == 0 {
// The file was empty, so it did not have a header. Create one.
if oldPlainSz == 0 {
id, err := f.createHeader()
@@ -203,7 +203,7 @@ func (f *File) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) syscall.Er
}
f.fileTableEntry.ID = id
}
- cSz := int64(f.contentEnc.PlainSizeToCipherSize(newPlainSz))
+ cSz := int64(f.rootNode.contentEnc.PlainSizeToCipherSize(newPlainSz))
err := syscall.Ftruncate(f.intFd(), cSz)
if err != nil {
tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err)
diff --git a/internal/fusefrontend/file_holes.go b/internal/fusefrontend/file_holes.go
index f35fa70..fc58898 100644
--- a/internal/fusefrontend/file_holes.go
+++ b/internal/fusefrontend/file_holes.go
@@ -21,12 +21,12 @@ func (f *File) writePadHole(targetOff int64) syscall.Errno {
tlog.Warn.Printf("checkAndPadHole: Fstat failed: %v", err)
return fs.ToErrno(err)
}
- plainSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
+ plainSize := f.rootNode.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
// Appending a single byte to the file (equivalent to writing to
// offset=plainSize) would write to "nextBlock".
- nextBlock := f.contentEnc.PlainOffToBlockNo(plainSize)
+ nextBlock := f.rootNode.contentEnc.PlainOffToBlockNo(plainSize)
// targetBlock is the block the user wants to write to.
- targetBlock := f.contentEnc.PlainOffToBlockNo(uint64(targetOff))
+ targetBlock := f.rootNode.contentEnc.PlainOffToBlockNo(uint64(targetOff))
// The write goes into an existing block or (if the last block was full)
// starts a new one directly after the last block. Nothing to do.
if targetBlock <= nextBlock {
@@ -45,12 +45,12 @@ func (f *File) writePadHole(targetOff int64) syscall.Errno {
// Zero-pad the file of size plainSize to the next block boundary. This is a no-op
// if the file is already block-aligned.
func (f *File) zeroPad(plainSize uint64) syscall.Errno {
- lastBlockLen := plainSize % f.contentEnc.PlainBS()
+ lastBlockLen := plainSize % f.rootNode.contentEnc.PlainBS()
if lastBlockLen == 0 {
// Already block-aligned
return 0
}
- missing := f.contentEnc.PlainBS() - lastBlockLen
+ missing := f.rootNode.contentEnc.PlainBS() - lastBlockLen
pad := make([]byte, missing)
tlog.Debug.Printf("zeroPad: Writing %d bytes\n", missing)
_, errno := f.doWrite(pad, int64(plainSize))
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index 91c947e..95be48d 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -75,26 +75,50 @@ func (n *Node) Getattr(ctx context.Context, f fs.FileHandle, out *fuse.AttrOut)
return fga.Getattr(ctx, out)
}
}
+ rn := n.rootNode()
+ var st *syscall.Stat_t
+ var err error
dirfd, cName, errno := n.prepareAtSyscallMyself()
+ // Hack for deleted fifos. As OPEN on a fifo does not reach
+ // the filesystem, we have no fd to access it. To make "cat" and git's
+ // t9300-fast-import.sh happy, we fake it as best as we can.
+ // https://github.com/rfjakob/gocryptfs/issues/929
+ if errno == syscall.ENOENT && n.StableAttr().Mode == syscall.S_IFIFO {
+ out.Mode = syscall.S_IFIFO
+ out.Ino = n.StableAttr().Ino
+ // cat looks at this to determine the optimal io size. Seems to be always 4096 for fifos.
+ out.Blksize = 4096
+ // We don't know what the owner was. Set it to nobody which seems safer
+ // than leaving it at 0 (root).
+ out.Owner.Gid = 65534
+ out.Owner.Uid = 65534
+ // All the other fields stay 0. This is what cat sees (strace -v log):
+ //
+ // fstat(1, {st_dev=makedev(0, 0x4d), st_ino=3838227, st_mode=S_IFIFO|000, st_nlink=0,
+ // st_uid=65534, st_gid=65534, st_blksize=4096, st_blocks=0, st_size=0, st_atime=0,
+ // st_atime_nsec=0, st_mtime=0, st_mtime_nsec=0, st_ctime=0, st_ctime_nsec=0}) = 0
+ goto out
+ }
if errno != 0 {
return
}
+
defer syscall.Close(dirfd)
- st, err := syscallcompat.Fstatat2(dirfd, cName, unix.AT_SYMLINK_NOFOLLOW)
+ st, err = syscallcompat.Fstatat2(dirfd, cName, 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
n.translateSize(dirfd, cName, &out.Attr)
+out:
if rn.args.ForceOwner != nil {
out.Owner = *rn.args.ForceOwner
}
diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go
index 11ff83d..97327ce 100644
--- a/internal/fusefrontend/node_dir_ops.go
+++ b/internal/fusefrontend/node_dir_ops.go
@@ -136,12 +136,6 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
}
defer syscall.Close(fd)
- err = syscall.Fstat(fd, &st)
- if err != nil {
- tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err)
- return nil, fs.ToErrno(err)
- }
-
// Fix permissions
if origMode != mode {
// Preserve SGID bit if it was set due to inheritance.
@@ -150,7 +144,12 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
if err != nil {
tlog.Warn.Printf("Mkdir %q: Fchmod %#o -> %#o failed: %v", cName, mode, origMode, err)
}
+ }
+ err = syscall.Fstat(fd, &st)
+ if err != nil {
+ tlog.Warn.Printf("Mkdir %q: Fstat failed: %v", cName, err)
+ return nil, fs.ToErrno(err)
}
// Create child node & return
diff --git a/internal/fusefrontend/node_helpers.go b/internal/fusefrontend/node_helpers.go
index f5dfeb6..e8fca80 100644
--- a/internal/fusefrontend/node_helpers.go
+++ b/internal/fusefrontend/node_helpers.go
@@ -2,7 +2,6 @@ package fusefrontend
import (
"context"
- "sync/atomic"
"syscall"
"github.com/hanwen/go-fuse/v2/fs"
@@ -91,12 +90,12 @@ func (n *Node) newChild(ctx context.Context, st *syscall.Stat_t, out *fuse.Entry
if rn.args.SharedStorage || rn.quirks&syscallcompat.QuirkDuplicateIno1 != 0 {
// Make each directory entry a unique node by using a unique generation
// value - see the comment at RootNode.gen for details.
- gen = atomic.AddUint64(&rn.gen, 1)
+ gen = rn.gen.Add(1)
}
// Create child node
id := fs.StableAttr{
- Mode: uint32(st.Mode),
+ Mode: uint32(st.Mode), // go-fuse masks this with syscall.S_IFMT
Gen: gen,
Ino: st.Ino,
}
diff --git a/internal/fusefrontend/node_prepare_syscall.go b/internal/fusefrontend/node_prepare_syscall.go
index 2a4d6ab..9021350 100644
--- a/internal/fusefrontend/node_prepare_syscall.go
+++ b/internal/fusefrontend/node_prepare_syscall.go
@@ -1,7 +1,6 @@
package fusefrontend
import (
- "sync/atomic"
"syscall"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
@@ -24,7 +23,7 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy
// All filesystem operations go through here, so this is a good place
// to reset the idle marker.
- atomic.StoreUint32(&rn.IsIdle, 0)
+ rn.IsIdle.Store(false)
if n.IsRoot() && rn.isFiltered(child) {
return -1, "", syscall.EPERM
diff --git a/internal/fusefrontend/prepare_syscall_test.go b/internal/fusefrontend/prepare_syscall_test.go
index acddaf3..e2c7d08 100644
--- a/internal/fusefrontend/prepare_syscall_test.go
+++ b/internal/fusefrontend/prepare_syscall_test.go
@@ -1,6 +1,7 @@
package fusefrontend
import (
+ "context"
"strings"
"syscall"
"testing"
@@ -22,13 +23,13 @@ func TestPrepareAtSyscall(t *testing.T) {
rn := newTestFS(args)
out := &fuse.EntryOut{}
- child, errno := rn.Mkdir(nil, "dir1", 0700, out)
+ child, errno := rn.Mkdir(context.TODO(), "dir1", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
rn.AddChild("dir1", child, false)
dir1 := toNode(child.Operations())
- _, errno = dir1.Mkdir(nil, "dir2", 0700, out)
+ _, errno = dir1.Mkdir(context.TODO(), "dir2", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
@@ -43,7 +44,7 @@ func TestPrepareAtSyscall(t *testing.T) {
syscall.Close(dirfd)
// Again, but populate the cache for "" by looking up a non-existing file
- rn.Lookup(nil, "xyz1234", &fuse.EntryOut{})
+ rn.Lookup(context.TODO(), "xyz1234", &fuse.EntryOut{})
dirfd, cName, errno = rn.prepareAtSyscallMyself()
if errno != 0 {
t.Fatal(errno)
@@ -89,7 +90,7 @@ func TestPrepareAtSyscall(t *testing.T) {
syscall.Close(dirfd)
n255 := strings.Repeat("n", 255)
- dir1.Mkdir(nil, n255, 0700, out)
+ dir1.Mkdir(context.TODO(), n255, 0700, out)
dirfd, cName, errno = dir1.prepareAtSyscall(n255)
if errno != 0 {
t.Fatal(errno)
@@ -116,13 +117,13 @@ func TestPrepareAtSyscallPlaintextnames(t *testing.T) {
rn := newTestFS(args)
out := &fuse.EntryOut{}
- child, errno := rn.Mkdir(nil, "dir1", 0700, out)
+ child, errno := rn.Mkdir(context.TODO(), "dir1", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
rn.AddChild("dir1", child, false)
dir1 := toNode(child.Operations())
- _, errno = dir1.Mkdir(nil, "dir2", 0700, out)
+ _, errno = dir1.Mkdir(context.TODO(), "dir2", 0700, out)
if errno != 0 {
t.Fatal(errno)
}
diff --git a/internal/fusefrontend/root_node.go b/internal/fusefrontend/root_node.go
index ac814ad..8464c5f 100644
--- a/internal/fusefrontend/root_node.go
+++ b/internal/fusefrontend/root_node.go
@@ -4,6 +4,7 @@ import (
"os"
"strings"
"sync"
+ "sync/atomic"
"syscall"
"time"
@@ -44,7 +45,7 @@ type RootNode struct {
// (uint32 so that it can be reset with CompareAndSwapUint32).
// When -idle was used when mounting, idleMonitor() sets it to 1
// periodically.
- IsIdle uint32
+ IsIdle atomic.Bool
// dirCache caches directory fds
dirCache dirCache
// inoMap translates inode numbers from different devices to unique inode
@@ -55,7 +56,7 @@ type RootNode struct {
// This makes each directory entry unique (even hard links),
// makes go-fuse hand out separate FUSE Node IDs for each, and prevents
// bizarre problems when inode numbers are reused behind our back.
- gen uint64
+ gen atomic.Uint64
// quirks is a bitmap that enables workaround for quirks in the filesystem
// backing the cipherdir
quirks uint64
diff --git a/internal/fusefrontend/xattr_unit_test.go b/internal/fusefrontend/xattr_unit_test.go
index 86c87a7..7d8e32e 100644
--- a/internal/fusefrontend/xattr_unit_test.go
+++ b/internal/fusefrontend/xattr_unit_test.go
@@ -21,10 +21,10 @@ func newTestFS(args Args) *RootNode {
cEnc := contentenc.New(cCore, contentenc.DefaultBS)
n := nametransform.New(cCore.EMECipher, true, 0, true, nil, false)
rn := NewRootNode(args, cEnc, n)
- oneSec := time.Second
+ oneSecond := time.Second
options := &fs.Options{
- EntryTimeout: &oneSec,
- AttrTimeout: &oneSec,
+ EntryTimeout: &oneSecond,
+ AttrTimeout: &oneSecond,
}
fs.NewNodeFS(rn, options)
return rn