aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/configfile/config_file.go5
-rw-r--r--internal/configfile/scrypt.go10
-rw-r--r--internal/configfile/validate.go6
-rw-r--r--internal/contentenc/bpool.go1
-rw-r--r--internal/contentenc/content.go2
-rw-r--r--internal/ctlsocksrv/ctlsock_serve.go4
-rw-r--r--internal/exitcodes/exitcodes.go4
-rw-r--r--internal/fusefrontend/file.go21
-rw-r--r--internal/fusefrontend/file_allocate_truncate.go24
-rw-r--r--internal/fusefrontend/file_dir_ops.go177
-rw-r--r--internal/fusefrontend/file_holes.go10
-rw-r--r--internal/fusefrontend/node.go32
-rw-r--r--internal/fusefrontend/node_api_check.go1
-rw-r--r--internal/fusefrontend/node_dir_ops.go86
-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
-rw-r--r--internal/fusefrontend_reverse/excluder.go3
-rw-r--r--internal/fusefrontend_reverse/excluder_test.go5
-rw-r--r--internal/fusefrontend_reverse/node_helpers.go3
-rw-r--r--internal/fusefrontend_reverse/root_node.go6
-rw-r--r--internal/fusefrontend_reverse/virtualnode.go9
-rw-r--r--internal/inomap/inomap.go11
-rw-r--r--internal/nametransform/diriv.go2
-rw-r--r--internal/nametransform/pad16.go12
-rw-r--r--internal/openfiletable/open_file_table.go6
-rw-r--r--internal/readpassword/read.go10
-rw-r--r--internal/siv_aead/correctness_test.go4
-rw-r--r--internal/speed/cpuinfo.go4
-rw-r--r--internal/syscallcompat/asuser.go59
-rw-r--r--internal/syscallcompat/asuser_darwin.go46
-rw-r--r--internal/syscallcompat/asuser_linux.go80
-rw-r--r--internal/syscallcompat/getdents_test.go5
-rw-r--r--internal/syscallcompat/main_test.go3
-rw-r--r--internal/syscallcompat/sys_darwin.go75
-rw-r--r--internal/syscallcompat/sys_linux.go135
-rw-r--r--internal/syscallcompat/thread_credentials_linux.go61
-rw-r--r--internal/syscallcompat/thread_credentials_linux_32.go40
-rw-r--r--internal/syscallcompat/thread_credentials_linux_other.go37
41 files changed, 642 insertions, 389 deletions
diff --git a/internal/configfile/config_file.go b/internal/configfile/config_file.go
index 995a0c8..28a1ca5 100644
--- a/internal/configfile/config_file.go
+++ b/internal/configfile/config_file.go
@@ -5,7 +5,6 @@ package configfile
import (
"encoding/json"
"fmt"
- "io/ioutil"
"syscall"
"os"
@@ -183,12 +182,12 @@ func Load(filename string) (*ConfFile, error) {
cf.filename = filename
// Read from disk
- js, err := ioutil.ReadFile(filename)
+ js, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
if len(js) == 0 {
- return nil, fmt.Errorf("Config file is empty")
+ return nil, fmt.Errorf("config file is empty")
}
// Unmarshal
diff --git a/internal/configfile/scrypt.go b/internal/configfile/scrypt.go
index 0ce8777..f6201ba 100644
--- a/internal/configfile/scrypt.go
+++ b/internal/configfile/scrypt.go
@@ -87,19 +87,19 @@ func (s *ScryptKDF) LogN() int {
func (s *ScryptKDF) validateParams() error {
minN := 1 << scryptMinLogN
if s.N < minN {
- return fmt.Errorf("Fatal: scryptn below 10 is too low to make sense")
+ return fmt.Errorf("fatal: scryptn below 10 is too low to make sense")
}
if s.R < scryptMinR {
- return fmt.Errorf("Fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
+ return fmt.Errorf("fatal: scrypt parameter R below minimum: value=%d, min=%d", s.R, scryptMinR)
}
if s.P < scryptMinP {
- return fmt.Errorf("Fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
+ return fmt.Errorf("fatal: scrypt parameter P below minimum: value=%d, min=%d", s.P, scryptMinP)
}
if len(s.Salt) < scryptMinSaltLen {
- return fmt.Errorf("Fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
+ return fmt.Errorf("fatal: scrypt salt length below minimum: value=%d, min=%d", len(s.Salt), scryptMinSaltLen)
}
if s.KeyLen < cryptocore.KeyLen {
- return fmt.Errorf("Fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
+ return fmt.Errorf("fatal: scrypt parameter KeyLen below minimum: value=%d, min=%d", s.KeyLen, cryptocore.KeyLen)
}
return nil
}
diff --git a/internal/configfile/validate.go b/internal/configfile/validate.go
index ab8917d..5428a7b 100644
--- a/internal/configfile/validate.go
+++ b/internal/configfile/validate.go
@@ -9,7 +9,7 @@ import (
// Validate that the combination of settings makes sense and is supported
func (cf *ConfFile) Validate() error {
if cf.Version != contentenc.CurrentVersion {
- return fmt.Errorf("Unsupported on-disk format %d", cf.Version)
+ return fmt.Errorf("unsupported on-disk format %d", cf.Version)
}
// scrypt params ok?
if err := cf.ScryptObject.validateParams(); err != nil {
@@ -18,13 +18,13 @@ func (cf *ConfFile) Validate() error {
// All feature flags that are in the config file are known?
for _, flag := range cf.FeatureFlags {
if !isFeatureFlagKnown(flag) {
- return fmt.Errorf("Unknown feature flag %q", flag)
+ return fmt.Errorf("unknown feature flag %q", flag)
}
}
// File content encryption
{
if cf.IsFeatureFlagSet(FlagXChaCha20Poly1305) && cf.IsFeatureFlagSet(FlagAESSIV) {
- return fmt.Errorf("Can't have both XChaCha20Poly1305 and AESSIV feature flags")
+ return fmt.Errorf("can't have both XChaCha20Poly1305 and AESSIV feature flags")
}
if cf.IsFeatureFlagSet(FlagAESSIV) && !cf.IsFeatureFlagSet(FlagGCMIV128) {
diff --git a/internal/contentenc/bpool.go b/internal/contentenc/bpool.go
index c4517d3..c62170d 100644
--- a/internal/contentenc/bpool.go
+++ b/internal/contentenc/bpool.go
@@ -26,6 +26,7 @@ func (b *bPool) Put(s []byte) {
if len(s) != b.sliceLen {
log.Panicf("wrong len=%d, want=%d", len(s), b.sliceLen)
}
+ //lint:ignore SA6002 We intentionally pass slice by value to avoid allocation overhead in this specific use case
b.Pool.Put(s)
}
diff --git a/internal/contentenc/content.go b/internal/contentenc/content.go
index 3005bf5..5bf0b3c 100644
--- a/internal/contentenc/content.go
+++ b/internal/contentenc/content.go
@@ -151,7 +151,7 @@ func (be *ContentEnc) DecryptBlock(ciphertext []byte, blockNo uint64, fileID []b
if len(ciphertext) < be.cryptoCore.IVLen {
tlog.Warn.Printf("DecryptBlock: Block is too short: %d bytes", len(ciphertext))
- return nil, errors.New("Block is too short")
+ return nil, errors.New("block is too short")
}
// Extract nonce
diff --git a/internal/ctlsocksrv/ctlsock_serve.go b/internal/ctlsocksrv/ctlsock_serve.go
index 85f5b65..25d1e44 100644
--- a/internal/ctlsocksrv/ctlsock_serve.go
+++ b/internal/ctlsocksrv/ctlsock_serve.go
@@ -101,7 +101,7 @@ func (ch *ctlSockHandler) handleRequest(in *ctlsock.RequestStruct, conn *net.Uni
}
// Neither encryption nor encryption has been requested, makes no sense
if in.DecryptPath == "" && in.EncryptPath == "" {
- err = errors.New("Empty input")
+ err = errors.New("empty input")
sendResponse(conn, err, "", "")
return
}
@@ -118,7 +118,7 @@ func (ch *ctlSockHandler) handleRequest(in *ctlsock.RequestStruct, conn *net.Uni
}
// Error out if the canonical path is now empty
if clean == "" {
- err = errors.New("Empty input after canonicalization")
+ err = errors.New("empty input after canonicalization")
sendResponse(conn, err, "", warnText)
return
}
diff --git a/internal/exitcodes/exitcodes.go b/internal/exitcodes/exitcodes.go
index 508ba38..881afda 100644
--- a/internal/exitcodes/exitcodes.go
+++ b/internal/exitcodes/exitcodes.go
@@ -3,7 +3,7 @@
package exitcodes
import (
- "fmt"
+ "errors"
"os"
)
@@ -83,7 +83,7 @@ type Err struct {
// NewErr returns an error containing "msg" and the exit code "code".
func NewErr(msg string, code int) Err {
return Err{
- error: fmt.Errorf(msg),
+ error: errors.New(msg),
code: code,
}
}
diff --git a/internal/fusefrontend/file.go b/internal/fusefrontend/file.go
index 0e25de3..bb9e539 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
@@ -50,6 +48,8 @@ type File struct {
lastOpCount uint64
// Parent filesystem
rootNode *RootNode
+ // If this open file is a directory, dirHandle will be set, otherwise it's nil.
+ dirHandle *DirHandle
}
// NewFile returns a new go-fuse File instance based on an already-open file
@@ -71,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,
@@ -175,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 {
@@ -204,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
}
@@ -285,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",
@@ -307,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
@@ -437,7 +436,7 @@ 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)
+ a.Size = f.rootNode.contentEnc.CipherSizeToPlainSize(a.Size)
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_dir_ops.go b/internal/fusefrontend/file_dir_ops.go
new file mode 100644
index 0000000..b69e7bc
--- /dev/null
+++ b/internal/fusefrontend/file_dir_ops.go
@@ -0,0 +1,177 @@
+package fusefrontend
+
+import (
+ "context"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fs"
+ "github.com/hanwen/go-fuse/v2/fuse"
+ "github.com/rfjakob/gocryptfs/v2/internal/configfile"
+ "github.com/rfjakob/gocryptfs/v2/internal/nametransform"
+ "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
+ "github.com/rfjakob/gocryptfs/v2/internal/tlog"
+)
+
+func (n *Node) OpendirHandle(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+ var fd int = -1
+ var fdDup int = -1
+ var file *File
+ var dirIV []byte
+ var ds fs.DirStream
+ rn := n.rootNode()
+
+ dirfd, cName, errno := n.prepareAtSyscallMyself()
+ if errno != 0 {
+ return
+ }
+ defer syscall.Close(dirfd)
+
+ // Open backing directory
+ fd, err := syscallcompat.Openat(dirfd, cName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
+ if err != nil {
+ errno = fs.ToErrno(err)
+ return
+ }
+
+ // NewLoopbackDirStreamFd gets its own fd to untangle Release vs Releasedir
+ fdDup, err = syscall.Dup(fd)
+
+ if err != nil {
+ errno = fs.ToErrno(err)
+ goto err_out
+ }
+
+ ds, errno = fs.NewLoopbackDirStreamFd(fdDup)
+ if errno != 0 {
+ goto err_out
+ }
+
+ if !rn.args.PlaintextNames {
+ // Read the DirIV from disk
+ dirIV, err = rn.nameTransform.ReadDirIVAt(fd)
+ if err != nil {
+ tlog.Warn.Printf("OpendirHandle: could not read %s: %v", nametransform.DirIVFilename, err)
+ errno = syscall.EIO
+ goto err_out
+ }
+ }
+
+ file, _, errno = NewFile(fd, cName, rn)
+ if errno != 0 {
+ goto err_out
+ }
+
+ file.dirHandle = &DirHandle{
+ ds: ds,
+ dirIV: dirIV,
+ isRootDir: n.IsRoot(),
+ }
+
+ return file, fuseFlags, errno
+
+err_out:
+ if fd >= 0 {
+ syscall.Close(fd)
+ }
+ if fdDup >= 0 {
+ syscall.Close(fdDup)
+ }
+ if errno == 0 {
+ tlog.Warn.Printf("BUG: OpendirHandle: err_out called with errno == 0")
+ errno = syscall.EIO
+ }
+ return nil, 0, errno
+}
+
+type DirHandle struct {
+ // Content of gocryptfs.diriv. nil if plaintextnames is used.
+ dirIV []byte
+
+ isRootDir bool
+
+ // fs.loopbackDirStream with a private dup of the file descriptor
+ ds fs.FileHandle
+}
+
+var _ = (fs.FileReleasedirer)((*File)(nil))
+
+func (f *File) Releasedir(ctx context.Context, flags uint32) {
+ // Does its own locking
+ f.dirHandle.ds.(fs.FileReleasedirer).Releasedir(ctx, flags)
+ // Does its own locking
+ f.Release(ctx)
+}
+
+var _ = (fs.FileSeekdirer)((*File)(nil))
+
+func (f *File) Seekdir(ctx context.Context, off uint64) syscall.Errno {
+ return f.dirHandle.ds.(fs.FileSeekdirer).Seekdir(ctx, off)
+}
+
+var _ = (fs.FileFsyncdirer)((*File)(nil))
+
+func (f *File) Fsyncdir(ctx context.Context, flags uint32) syscall.Errno {
+ return f.dirHandle.ds.(fs.FileFsyncdirer).Fsyncdir(ctx, flags)
+}
+
+var _ = (fs.FileReaddirenter)((*File)(nil))
+
+// This function is symlink-safe through use of openBackingDir() and
+// ReadDirIVAt().
+func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno syscall.Errno) {
+ f.fdLock.RLock()
+ defer f.fdLock.RUnlock()
+
+ for {
+ entry, errno = f.dirHandle.ds.(fs.FileReaddirenter).Readdirent(ctx)
+ if errno != 0 || entry == nil {
+ return
+ }
+
+ cName := entry.Name
+ if cName == "." || cName == ".." {
+ // We want these as-is
+ return
+ }
+ if f.dirHandle.isRootDir && cName == configfile.ConfDefaultName {
+ // silently ignore "gocryptfs.conf" in the top level dir
+ continue
+ }
+ if f.rootNode.args.PlaintextNames {
+ return
+ }
+ if !f.rootNode.args.DeterministicNames && cName == nametransform.DirIVFilename {
+ // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
+ continue
+ }
+ // Handle long file name
+ isLong := nametransform.LongNameNone
+ if f.rootNode.args.LongNames {
+ isLong = nametransform.NameType(cName)
+ }
+ if isLong == nametransform.LongNameContent {
+ cNameLong, err := nametransform.ReadLongNameAt(f.intFd(), cName)
+ if err != nil {
+ tlog.Warn.Printf("Readdirent: incomplete entry %q: Could not read .name: %v",
+ cName, err)
+ f.rootNode.reportMitigatedCorruption(cName)
+ continue
+ }
+ cName = cNameLong
+ } else if isLong == nametransform.LongNameFilename {
+ // ignore "gocryptfs.longname.*.name"
+ continue
+ }
+ name, err := f.rootNode.nameTransform.DecryptName(cName, f.dirHandle.dirIV)
+ if err != nil {
+ tlog.Warn.Printf("Readdirent: could not decrypt entry %q: %v",
+ cName, err)
+ f.rootNode.reportMitigatedCorruption(cName)
+ continue
+ }
+ // Override the ciphertext name with the plaintext name but reuse the rest
+ // of the structure
+ entry.Name = name
+ return
+ }
+}
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 687a386..95be48d 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -71,28 +71,54 @@ func (n *Node) Lookup(ctx context.Context, name string, out *fuse.EntryOut) (ch
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)
+ if fga, ok := f.(fs.FileGetattrer); ok {
+ 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_api_check.go b/internal/fusefrontend/node_api_check.go
index 0f60c74..37d4293 100644
--- a/internal/fusefrontend/node_api_check.go
+++ b/internal/fusefrontend/node_api_check.go
@@ -7,7 +7,6 @@ import (
// Check that we have implemented the fs.Node* interfaces
var _ = (fs.NodeGetattrer)((*Node)(nil))
var _ = (fs.NodeLookuper)((*Node)(nil))
-var _ = (fs.NodeReaddirer)((*Node)(nil))
var _ = (fs.NodeCreater)((*Node)(nil))
var _ = (fs.NodeMkdirer)((*Node)(nil))
var _ = (fs.NodeRmdirer)((*Node)(nil))
diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go
index 97e4caa..11ff83d 100644
--- a/internal/fusefrontend/node_dir_ops.go
+++ b/internal/fusefrontend/node_dir_ops.go
@@ -12,7 +12,6 @@ import (
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
- "github.com/rfjakob/gocryptfs/v2/internal/configfile"
"github.com/rfjakob/gocryptfs/v2/internal/cryptocore"
"github.com/rfjakob/gocryptfs/v2/internal/nametransform"
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
@@ -159,91 +158,6 @@ func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.En
return ch, 0
}
-// Readdir - FUSE call.
-//
-// This function is symlink-safe through use of openBackingDir() and
-// ReadDirIVAt().
-func (n *Node) Readdir(ctx context.Context) (fs.DirStream, syscall.Errno) {
- parentDirFd, cDirName, errno := n.prepareAtSyscallMyself()
- if errno != 0 {
- return nil, errno
- }
- defer syscall.Close(parentDirFd)
-
- // Read ciphertext directory
- fd, err := syscallcompat.Openat(parentDirFd, cDirName, syscall.O_RDONLY|syscall.O_DIRECTORY|syscall.O_NOFOLLOW, 0)
- if err != nil {
- return nil, fs.ToErrno(err)
- }
- defer syscall.Close(fd)
- cipherEntries, specialEntries, err := syscallcompat.GetdentsSpecial(fd)
- if err != nil {
- return nil, fs.ToErrno(err)
- }
- // Get DirIV (stays nil if PlaintextNames is used)
- var cachedIV []byte
- rn := n.rootNode()
- if !rn.args.PlaintextNames {
- // Read the DirIV from disk
- cachedIV, err = rn.nameTransform.ReadDirIVAt(fd)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: could not read %s: %v", cDirName, nametransform.DirIVFilename, err)
- return nil, syscall.EIO
- }
- }
- // Decrypted directory entries
- var plain []fuse.DirEntry
- // Add "." and ".."
- plain = append(plain, specialEntries...)
- // Filter and decrypt filenames
- for i := range cipherEntries {
- cName := cipherEntries[i].Name
- if n.IsRoot() && cName == configfile.ConfDefaultName {
- // silently ignore "gocryptfs.conf" in the top level dir
- continue
- }
- if rn.args.PlaintextNames {
- plain = append(plain, cipherEntries[i])
- continue
- }
- if !rn.args.DeterministicNames && cName == nametransform.DirIVFilename {
- // silently ignore "gocryptfs.diriv" everywhere if dirIV is enabled
- continue
- }
- // Handle long file name
- isLong := nametransform.LongNameNone
- if rn.args.LongNames {
- isLong = nametransform.NameType(cName)
- }
- if isLong == nametransform.LongNameContent {
- cNameLong, err := nametransform.ReadLongNameAt(fd, cName)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: invalid entry %q: Could not read .name: %v",
- cDirName, cName, err)
- rn.reportMitigatedCorruption(cName)
- continue
- }
- cName = cNameLong
- } else if isLong == nametransform.LongNameFilename {
- // ignore "gocryptfs.longname.*.name"
- continue
- }
- name, err := rn.nameTransform.DecryptName(cName, cachedIV)
- if err != nil {
- tlog.Warn.Printf("OpenDir %q: invalid entry %q: %v",
- cDirName, cName, err)
- rn.reportMitigatedCorruption(cName)
- continue
- }
- // Override the ciphertext name with the plaintext name but reuse the rest
- // of the structure
- cipherEntries[i].Name = name
- plain = append(plain, cipherEntries[i])
- }
-
- return fs.NewListDirStream(plain), 0
-}
-
// Rmdir - FUSE call.
//
// Symlink-safe through Unlinkat() + AT_REMOVEDIR.
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
diff --git a/internal/fusefrontend_reverse/excluder.go b/internal/fusefrontend_reverse/excluder.go
index 0faadfa..1cb4b80 100644
--- a/internal/fusefrontend_reverse/excluder.go
+++ b/internal/fusefrontend_reverse/excluder.go
@@ -1,7 +1,6 @@
package fusefrontend_reverse
import (
- "io/ioutil"
"log"
"os"
"strings"
@@ -50,7 +49,7 @@ func getExclusionPatterns(args fusefrontend.Args) []string {
// getLines reads a file and splits it into lines
func getLines(file string) ([]string, error) {
- buffer, err := ioutil.ReadFile(file)
+ buffer, err := os.ReadFile(file)
if err != nil {
return nil, err
}
diff --git a/internal/fusefrontend_reverse/excluder_test.go b/internal/fusefrontend_reverse/excluder_test.go
index bb041ce..b44ddce 100644
--- a/internal/fusefrontend_reverse/excluder_test.go
+++ b/internal/fusefrontend_reverse/excluder_test.go
@@ -1,7 +1,6 @@
package fusefrontend_reverse
import (
- "io/ioutil"
"os"
"reflect"
"testing"
@@ -23,7 +22,7 @@ func TestShouldPrefixExcludeValuesWithSlash(t *testing.T) {
}
func TestShouldReadExcludePatternsFromFiles(t *testing.T) {
- tmpfile1, err := ioutil.TempFile("", "excludetest")
+ tmpfile1, err := os.CreateTemp("", "excludetest")
if err != nil {
t.Fatal(err)
}
@@ -31,7 +30,7 @@ func TestShouldReadExcludePatternsFromFiles(t *testing.T) {
defer os.Remove(exclude1)
defer tmpfile1.Close()
- tmpfile2, err := ioutil.TempFile("", "excludetest")
+ tmpfile2, err := os.CreateTemp("", "excludetest")
if err != nil {
t.Fatal(err)
}
diff --git a/internal/fusefrontend_reverse/node_helpers.go b/internal/fusefrontend_reverse/node_helpers.go
index 30361bc..3165db6 100644
--- a/internal/fusefrontend_reverse/node_helpers.go
+++ b/internal/fusefrontend_reverse/node_helpers.go
@@ -24,7 +24,6 @@ const (
// * base64(192 bytes) = 256 bytes (over 255!)
// But the PKCS#7 padding is at least one byte. This means we can only use
// 175 bytes for the file name.
- shortNameMax = 175
)
// translateSize translates the ciphertext size in `out` into plaintext size.
@@ -175,7 +174,7 @@ func (n *Node) lookupDiriv(ctx context.Context, out *fuse.EntryOut) (ch *fs.Inod
errno = fs.ToErrno(err)
return
}
- content := pathiv.Derive(d.cPath, pathiv.PurposeDirIV)
+ content := rn.deriveDirIV(d.cPath)
var vf *VirtualMemNode
vf, errno = n.newVirtualMemNode(content, st, inoTagDirIV)
if errno != 0 {
diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go
index cb04151..9c2de28 100644
--- a/internal/fusefrontend_reverse/root_node.go
+++ b/internal/fusefrontend_reverse/root_node.go
@@ -21,7 +21,7 @@ import (
"github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
"github.com/rfjakob/gocryptfs/v2/internal/tlog"
- "github.com/sabhiram/go-gitignore"
+ ignore "github.com/sabhiram/go-gitignore"
)
// RootNode is the root directory in a `gocryptfs -reverse` mount
@@ -50,7 +50,7 @@ type RootNode struct {
// makes go-fuse hand out separate FUSE Node IDs for each, and prevents
// bizarre problems when inode numbers are reused behind our back,
// like this one: https://github.com/rfjakob/gocryptfs/issues/802
- gen uint64
+ gen atomic.Uint64
// rootIno is the inode number that we report for the root node on mount
rootIno uint64
}
@@ -175,7 +175,7 @@ func (rn *RootNode) uniqueStableAttr(mode uint32, ino uint64) fs.StableAttr {
Ino: ino,
// Make each directory entry a unique node by using a unique generation
// value. Also see the comment at RootNode.gen for details.
- Gen: atomic.AddUint64(&rn.gen, 1),
+ Gen: rn.gen.Add(1),
}
}
diff --git a/internal/fusefrontend_reverse/virtualnode.go b/internal/fusefrontend_reverse/virtualnode.go
index 732564a..95e71ab 100644
--- a/internal/fusefrontend_reverse/virtualnode.go
+++ b/internal/fusefrontend_reverse/virtualnode.go
@@ -100,6 +100,15 @@ func (n *Node) newVirtualMemNode(content []byte, parentStat *syscall.Stat_t, ino
st.Nlink = 1
var a fuse.Attr
a.FromStat(st)
+ // With inode number reuse and hard links, we could have returned
+ // wrong data for gocryptfs.diriv and gocryptfs.xyz.longname files, respectively
+ // (https://github.com/rfjakob/gocryptfs/issues/802).
+ //
+ // Now that this is fixed, ensure that rsync and similar tools pick up the new
+ // correct files by advancing mtime and ctime by 10 seconds, which should be more
+ // than any filesytems' timestamp granularity (FAT32 has 2 seconds).
+ a.Mtime += 10
+ a.Ctime += 10
if rn.args.ForceOwner != nil {
a.Owner = *rn.args.ForceOwner
}
diff --git a/internal/inomap/inomap.go b/internal/inomap/inomap.go
index b4dbf27..5749202 100644
--- a/internal/inomap/inomap.go
+++ b/internal/inomap/inomap.go
@@ -45,7 +45,7 @@ type InoMap struct {
// spill is used once the namespaces map is full
spillMap map[QIno]uint64
// spillNext is the next free inode number in the spill map
- spillNext uint64
+ spillNext atomic.Uint64
}
// New returns a new InoMap.
@@ -57,8 +57,9 @@ func New(rootDev uint64) *InoMap {
namespaceMap: make(map[namespaceData]uint16),
namespaceNext: 0,
spillMap: make(map[QIno]uint64),
- spillNext: spillSpaceStart,
}
+ m.spillNext.Store(spillSpaceStart)
+
if rootDev > 0 {
// Reserve namespace 0 for rootDev
m.namespaceMap[namespaceData{rootDev, 0}] = 0
@@ -74,10 +75,10 @@ var spillWarn sync.Once
// Reverse mode NextSpillIno() for gocryptfs.longname.*.name files where a stable
// mapping is not needed.
func (m *InoMap) NextSpillIno() (out uint64) {
- if m.spillNext == math.MaxUint64 {
- log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext)
+ if m.spillNext.Load() == math.MaxUint64 {
+ log.Panicf("spillMap overflow: spillNext = 0x%x", m.spillNext.Load())
}
- return atomic.AddUint64(&m.spillNext, 1) - 1
+ return m.spillNext.Add(1) - 1
}
func (m *InoMap) spill(in QIno) (out uint64) {
diff --git a/internal/nametransform/diriv.go b/internal/nametransform/diriv.go
index 7929c40..5dd4940 100644
--- a/internal/nametransform/diriv.go
+++ b/internal/nametransform/diriv.go
@@ -67,7 +67,7 @@ func fdReadDirIV(fd *os.File) (iv []byte, err error) {
func WriteDirIVAt(dirfd int) error {
iv := cryptocore.RandBytes(DirIVLen)
// 0400 permissions: gocryptfs.diriv should never be modified after creation.
- // Don't use "ioutil.WriteFile", it causes trouble on NFS:
+ // Don't use "os.WriteFile", it causes trouble on NFS:
// https://github.com/rfjakob/gocryptfs/commit/7d38f80a78644c8ec4900cc990bfb894387112ed
fd, err := syscallcompat.Openat(dirfd, DirIVFilename, os.O_WRONLY|os.O_CREATE|os.O_EXCL, dirivPerms)
if err != nil {
diff --git a/internal/nametransform/pad16.go b/internal/nametransform/pad16.go
index 833be0e..2c2466a 100644
--- a/internal/nametransform/pad16.go
+++ b/internal/nametransform/pad16.go
@@ -32,10 +32,10 @@ func pad16(orig []byte) (padded []byte) {
func unPad16(padded []byte) ([]byte, error) {
oldLen := len(padded)
if oldLen == 0 {
- return nil, errors.New("Empty input")
+ return nil, errors.New("empty input")
}
if oldLen%aes.BlockSize != 0 {
- return nil, errors.New("Unaligned size")
+ return nil, errors.New("unaligned size")
}
// The last byte is always a padding byte
padByte := padded[oldLen-1]
@@ -43,20 +43,20 @@ func unPad16(padded []byte) ([]byte, error) {
padLen := int(padByte)
// Padding must be at least 1 byte
if padLen == 0 {
- return nil, errors.New("Padding cannot be zero-length")
+ return nil, errors.New("padding cannot be zero-length")
}
// Padding more than 16 bytes make no sense
if padLen > aes.BlockSize {
- return nil, fmt.Errorf("Padding too long, padLen=%d > 16", padLen)
+ return nil, fmt.Errorf("padding too long, padLen=%d > 16", padLen)
}
// Padding cannot be as long as (or longer than) the whole string,
if padLen >= oldLen {
- return nil, fmt.Errorf("Padding too long, oldLen=%d >= padLen=%d", oldLen, padLen)
+ return nil, fmt.Errorf("padding too long, oldLen=%d >= padLen=%d", oldLen, padLen)
}
// All padding bytes must be identical
for i := oldLen - padLen; i < oldLen; i++ {
if padded[i] != padByte {
- return nil, fmt.Errorf("Padding byte at i=%d is invalid", i)
+ return nil, fmt.Errorf("padding byte at i=%d is invalid", i)
}
}
newLen := oldLen - padLen
diff --git a/internal/openfiletable/open_file_table.go b/internal/openfiletable/open_file_table.go
index ce8df76..420d070 100644
--- a/internal/openfiletable/open_file_table.go
+++ b/internal/openfiletable/open_file_table.go
@@ -29,7 +29,7 @@ type table struct {
// The variable is accessed without holding any locks so atomic operations
// must be used. It must be the first element of the struct to guarantee
// 64-bit alignment.
- writeOpCount uint64
+ writeOpCount atomic.Uint64
// Protects map access
sync.Mutex
// Table entries
@@ -85,13 +85,13 @@ type countingMutex struct {
func (c *countingMutex) Lock() {
c.RWMutex.Lock()
- atomic.AddUint64(&t.writeOpCount, 1)
+ t.writeOpCount.Add(1)
}
// WriteOpCount returns the write lock counter value. This value is incremented
// each time writeLock.Lock() on a file table entry is called.
func WriteOpCount() uint64 {
- return atomic.LoadUint64(&t.writeOpCount)
+ return t.writeOpCount.Load()
}
// CountOpenFiles returns how many entries are currently in the table
diff --git a/internal/readpassword/read.go b/internal/readpassword/read.go
index 582a104..9193ae8 100644
--- a/internal/readpassword/read.go
+++ b/internal/readpassword/read.go
@@ -58,7 +58,7 @@ func Twice(extpass []string, passfile []string) ([]byte, error) {
return nil, err
}
if !bytes.Equal(p1, p2) {
- return nil, fmt.Errorf("Passwords do not match")
+ return nil, fmt.Errorf("passwords do not match")
}
// Wipe the password duplicate from memory
for i := range p2 {
@@ -71,15 +71,15 @@ func Twice(extpass []string, passfile []string) ([]byte, error) {
// Exits on read error or empty result.
func readPasswordTerminal(prompt string) ([]byte, error) {
fd := int(os.Stdin.Fd())
- fmt.Fprintf(os.Stderr, prompt)
+ fmt.Fprint(os.Stderr, prompt)
// term.ReadPassword removes the trailing newline
p, err := term.ReadPassword(fd)
if err != nil {
- return nil, fmt.Errorf("Could not read password from terminal: %v\n", err)
+ return nil, fmt.Errorf("could not read password from terminal: %v", err)
}
fmt.Fprintf(os.Stderr, "\n")
if len(p) == 0 {
- return nil, fmt.Errorf("Password is empty")
+ return nil, fmt.Errorf("password is empty")
}
return p, nil
}
@@ -100,7 +100,7 @@ func readPasswordStdin(prompt string) ([]byte, error) {
return nil, err
}
if len(p) == 0 {
- return nil, fmt.Errorf("Got empty %s from stdin", prompt)
+ return nil, fmt.Errorf("got empty %s from stdin", prompt)
}
return p, nil
}
diff --git a/internal/siv_aead/correctness_test.go b/internal/siv_aead/correctness_test.go
index 0653e26..7be97bc 100644
--- a/internal/siv_aead/correctness_test.go
+++ b/internal/siv_aead/correctness_test.go
@@ -50,7 +50,7 @@ func TestK32(t *testing.T) {
expectedResult, _ := hex.DecodeString(
"02020202020202020202020202020202ad7a4010649a84d8c1dd5f752e935eed57d45b8b10008f3834")
if !bytes.Equal(aResult, expectedResult) {
- t.Errorf(hex.EncodeToString(aResult))
+ t.Error(hex.EncodeToString(aResult))
}
// Verify overhead
overhead := len(aResult) - len(plaintext) - len(nonce)
@@ -108,7 +108,7 @@ func TestK64(t *testing.T) {
expectedResult, _ := hex.DecodeString(
"02020202020202020202020202020202317b316f67c3ad336c01c9a01b4c5e552ba89e966bc4c1ade1")
if !bytes.Equal(aResult, expectedResult) {
- t.Errorf(hex.EncodeToString(aResult))
+ t.Error(hex.EncodeToString(aResult))
}
// Verify overhead
overhead := len(aResult) - len(plaintext) - len(nonce)
diff --git a/internal/speed/cpuinfo.go b/internal/speed/cpuinfo.go
index df3177d..636a4f0 100644
--- a/internal/speed/cpuinfo.go
+++ b/internal/speed/cpuinfo.go
@@ -1,7 +1,7 @@
package speed
import (
- "io/ioutil"
+ "io"
"os"
"runtime"
"strings"
@@ -33,7 +33,7 @@ func cpuModelName() string {
if err != nil {
return ""
}
- content, err := ioutil.ReadAll(f)
+ content, err := io.ReadAll(f)
if err != nil {
return ""
}
diff --git a/internal/syscallcompat/asuser.go b/internal/syscallcompat/asuser.go
new file mode 100644
index 0000000..0c083ec
--- /dev/null
+++ b/internal/syscallcompat/asuser.go
@@ -0,0 +1,59 @@
+package syscallcompat
+
+import (
+ "golang.org/x/sys/unix"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// OpenatUser runs the Openat syscall in the context of a different user.
+// It switches the current thread to the new user, performs the syscall,
+// and switches back.
+//
+// If `context` is nil, this function behaves like ordinary Openat (no
+// user switching).
+func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
+ f := func() (int, error) {
+ return Openat(dirfd, path, flags, mode)
+ }
+ return asUser(f, context)
+}
+
+// MknodatUser runs the Mknodat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Mknodat.
+//
+// See OpenatUser() for how this works.
+func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := Mknodat(dirfd, path, mode, dev)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
+
+// SymlinkatUser runs the Symlinkat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Symlinkat.
+//
+// See OpenatUser() for how this works.
+func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := unix.Symlinkat(oldpath, newdirfd, newpath)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
+
+// MkdiratUser runs the Mkdirat syscall in the context of a different user.
+// If `context` is nil, this function behaves like ordinary Mkdirat.
+//
+// See OpenatUser() for how this works.
+func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
+ f := func() (int, error) {
+ err := unix.Mkdirat(dirfd, path, mode)
+ return -1, err
+ }
+ _, err = asUser(f, context)
+ return err
+}
diff --git a/internal/syscallcompat/asuser_darwin.go b/internal/syscallcompat/asuser_darwin.go
new file mode 100644
index 0000000..5da2782
--- /dev/null
+++ b/internal/syscallcompat/asuser_darwin.go
@@ -0,0 +1,46 @@
+package syscallcompat
+
+import (
+ "runtime"
+ "syscall"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// asUser runs `f()` under the effective uid, gid, groups specified
+// in `context`.
+//
+// If `context` is nil, `f()` is executed directly without switching user id.
+func asUser(f func() (int, error), context *fuse.Context) (int, error) {
+ if context == nil {
+ return f()
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ err := pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
+ if err != nil {
+ return -1, err
+ }
+
+ const (
+ // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
+ // revert permissions to the process credentials.
+ KAUTH_UID_NONE = ^uint32(0) - 100
+ KAUTH_GID_NONE = ^uint32(0) - 100
+ )
+
+ defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
+
+ return f()
+}
+
+// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
+func pthread_setugid_np(uid uint32, gid uint32) (err error) {
+ _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/internal/syscallcompat/asuser_linux.go b/internal/syscallcompat/asuser_linux.go
new file mode 100644
index 0000000..39e3ff2
--- /dev/null
+++ b/internal/syscallcompat/asuser_linux.go
@@ -0,0 +1,80 @@
+package syscallcompat
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+ "strings"
+
+ "github.com/hanwen/go-fuse/v2/fuse"
+)
+
+// asUser runs `f()` under the effective uid, gid, groups specified
+// in `context`.
+//
+// If `context` is nil, `f()` is executed directly without switching user id.
+func asUser(f func() (int, error), context *fuse.Context) (int, error) {
+ if context == nil {
+ return f()
+ }
+
+ runtime.LockOSThread()
+ defer runtime.UnlockOSThread()
+
+ // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c ,
+ // https://go-review.googlesource.com/c/go/+/210639 )
+ // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which
+ // is exactly what we not want.
+ //
+ // And unix.{Setgroups,Setregid,Setreuid} also changed to this behavoir in
+ // v0.1.0 (commit d0df966e6959f00dc1c74363e537872647352d51 ,
+ // https://go-review.googlesource.com/c/sys/+/428174 ), so we use
+ // our own syscall wrappers.
+
+ err := Setgroups(getSupplementaryGroups(context.Pid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetgroupsPanic(nil)
+
+ err = Setregid(-1, int(context.Owner.Gid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetregidPanic(-1, 0)
+
+ err = Setreuid(-1, int(context.Owner.Uid))
+ if err != nil {
+ return -1, err
+ }
+ defer SetreuidPanic(-1, 0)
+
+ return f()
+}
+
+func getSupplementaryGroups(pid uint32) (gids []int) {
+ procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid)
+ blob, err := os.ReadFile(procPath)
+ if err != nil {
+ return nil
+ }
+
+ lines := strings.Split(string(blob), "\n")
+ for _, line := range lines {
+ if strings.HasPrefix(line, "Groups:") {
+ f := strings.Fields(line[7:])
+ gids = make([]int, len(f))
+ for i := range gids {
+ val, err := strconv.ParseInt(f[i], 10, 32)
+ if err != nil {
+ return nil
+ }
+ gids[i] = int(val)
+ }
+ return gids
+ }
+ }
+
+ return nil
+}
diff --git a/internal/syscallcompat/getdents_test.go b/internal/syscallcompat/getdents_test.go
index eb670d6..c9e6a99 100644
--- a/internal/syscallcompat/getdents_test.go
+++ b/internal/syscallcompat/getdents_test.go
@@ -4,7 +4,6 @@
package syscallcompat
import (
- "io/ioutil"
"os"
"runtime"
"strings"
@@ -49,13 +48,13 @@ func testGetdents(t *testing.T) {
getdentsUnderTest = emulateGetdents
}
// Fill a directory with filenames of length 1 ... 255
- testDir, err := ioutil.TempDir(tmpDir, "TestGetdents")
+ testDir, err := os.MkdirTemp(tmpDir, "TestGetdents")
if err != nil {
t.Fatal(err)
}
for i := 1; i <= unix.NAME_MAX; i++ {
n := strings.Repeat("x", i)
- err = ioutil.WriteFile(testDir+"/"+n, nil, 0600)
+ err = os.WriteFile(testDir+"/"+n, nil, 0600)
if err != nil {
t.Fatal(err)
}
diff --git a/internal/syscallcompat/main_test.go b/internal/syscallcompat/main_test.go
index ddf6bc4..7183f5a 100644
--- a/internal/syscallcompat/main_test.go
+++ b/internal/syscallcompat/main_test.go
@@ -2,7 +2,6 @@ package syscallcompat
import (
"fmt"
- "io/ioutil"
"os"
"testing"
)
@@ -23,7 +22,7 @@ func TestMain(m *testing.M) {
fmt.Println(err)
os.Exit(1)
}
- tmpDir, err = ioutil.TempDir(parent, "syscallcompat")
+ tmpDir, err = os.MkdirTemp(parent, "syscallcompat")
if err != nil {
fmt.Println(err)
os.Exit(1)
diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go
index 06f09f0..cf2f3f0 100644
--- a/internal/syscallcompat/sys_darwin.go
+++ b/internal/syscallcompat/sys_darwin.go
@@ -3,7 +3,6 @@ package syscallcompat
import (
"log"
"path/filepath"
- "runtime"
"syscall"
"time"
"unsafe"
@@ -26,22 +25,8 @@ const (
RENAME_NOREPLACE = 1
RENAME_EXCHANGE = 2
RENAME_WHITEOUT = 4
-
- // KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
- // revert permissions to the process credentials.
- KAUTH_UID_NONE = ^uint32(0) - 100
- KAUTH_GID_NONE = ^uint32(0) - 100
)
-// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
-func pthread_setugid_np(uid uint32, gid uint32) (err error) {
- _, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
- if e1 != 0 {
- err = e1
- }
- return
-}
-
// Unfortunately fsetattrlist does not have a syscall wrapper yet.
func fsetattrlist(fd int, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
_, _, e1 := syscall.Syscall6(syscall.SYS_FSETATTRLIST, uintptr(fd), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
@@ -84,74 +69,14 @@ func Dup3(oldfd int, newfd int, flags int) (err error) {
//// Emulated Syscalls (see emulate.go) ////////////////
////////////////////////////////////////////////////////
-func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return -1, err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return Openat(dirfd, path, flags, mode)
-}
-
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
return emulateMknodat(dirfd, path, mode, dev)
}
-func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return Mknodat(dirfd, path, mode, dev)
-}
-
func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
return unix.Fchmodat(dirfd, path, mode, unix.AT_SYMLINK_NOFOLLOW)
}
-func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return unix.Symlinkat(oldpath, newdirfd, newpath)
-}
-
-func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
- if context != nil {
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
- if err != nil {
- return err
- }
- defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
- }
-
- return unix.Mkdirat(dirfd, path, mode)
-}
-
type attrList struct {
bitmapCount uint16
_ uint16
diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go
index a64b27e..19d2c56 100644
--- a/internal/syscallcompat/sys_linux.go
+++ b/internal/syscallcompat/sys_linux.go
@@ -3,10 +3,6 @@ package syscallcompat
import (
"fmt"
- "io/ioutil"
- "runtime"
- "strconv"
- "strings"
"sync"
"syscall"
"time"
@@ -67,104 +63,11 @@ func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return syscall.Fallocate(fd, mode, off, len)
}
-func getSupplementaryGroups(pid uint32) (gids []int) {
- procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid)
- blob, err := ioutil.ReadFile(procPath)
- if err != nil {
- return nil
- }
-
- lines := strings.Split(string(blob), "\n")
- for _, line := range lines {
- if strings.HasPrefix(line, "Groups:") {
- f := strings.Fields(line[7:])
- gids = make([]int, len(f))
- for i := range gids {
- val, err := strconv.ParseInt(f[i], 10, 32)
- if err != nil {
- return nil
- }
- gids[i] = int(val)
- }
- return gids
- }
- }
-
- return nil
-}
-
-// asUser runs `f()` under the effective uid, gid, groups specified
-// in `context`.
-//
-// If `context` is nil, `f()` is executed directly without switching user id.
-func asUser(f func() (int, error), context *fuse.Context) (int, error) {
- if context == nil {
- return f()
- }
-
- runtime.LockOSThread()
- defer runtime.UnlockOSThread()
-
- // Since go1.16beta1 (commit d1b1145cace8b968307f9311ff611e4bb810710c ,
- // https://go-review.googlesource.com/c/go/+/210639 )
- // syscall.{Setgroups,Setregid,Setreuid} affects all threads, which
- // is exactly what we not want.
- //
- // We now use unix.{Setgroups,Setregid,Setreuid} instead.
-
- err := unix.Setgroups(getSupplementaryGroups(context.Pid))
- if err != nil {
- return -1, err
- }
- defer unix.Setgroups(nil)
-
- err = unix.Setregid(-1, int(context.Owner.Gid))
- if err != nil {
- return -1, err
- }
- defer unix.Setregid(-1, 0)
-
- err = unix.Setreuid(-1, int(context.Owner.Uid))
- if err != nil {
- return -1, err
- }
- defer unix.Setreuid(-1, 0)
-
- return f()
-}
-
-// OpenatUser runs the Openat syscall in the context of a different user.
-//
-// It switches the current thread to the new user, performs the syscall,
-// and switches back.
-//
-// If `context` is nil, this function behaves like ordinary Openat (no
-// user switching).
-func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
- f := func() (int, error) {
- return Openat(dirfd, path, flags, mode)
- }
- return asUser(f, context)
-}
-
// Mknodat wraps the Mknodat syscall.
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
return syscall.Mknodat(dirfd, path, mode, dev)
}
-// MknodatUser runs the Mknodat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Mknodat.
-//
-// See OpenatUser() for how this works.
-func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := Mknodat(dirfd, path, mode, dev)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
// Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2
// is not implemented on arm64.
func Dup3(oldfd int, newfd int, flags int) (err error) {
@@ -205,32 +108,6 @@ func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
return syscall.Chmod(procPath, mode)
}
-// SymlinkatUser runs the Symlinkat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Symlinkat.
-//
-// See OpenatUser() for how this works.
-func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := unix.Symlinkat(oldpath, newdirfd, newpath)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
-// MkdiratUser runs the Mkdirat syscall in the context of a different user.
-// If `context` is nil, this function behaves like ordinary Mkdirat.
-//
-// See OpenatUser() for how this works.
-func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
- f := func() (int, error) {
- err := unix.Mkdirat(dirfd, path, mode)
- return -1, err
- }
- _, err = asUser(f, context)
- return err
-}
-
// LsetxattrUser runs the Lsetxattr syscall in the context of a different user.
// This is useful when setting ACLs, as the result depends on the user running
// the operation (see fuse-xfstests generic/375).
@@ -247,8 +124,16 @@ func LsetxattrUser(path string, attr string, data []byte, flags int, context *fu
func timesToTimespec(a *time.Time, m *time.Time) []unix.Timespec {
ts := make([]unix.Timespec, 2)
- ts[0] = unix.Timespec(fuse.UtimeToTimespec(a))
- ts[1] = unix.Timespec(fuse.UtimeToTimespec(m))
+ if a == nil {
+ ts[0] = unix.Timespec{Nsec: unix.UTIME_OMIT}
+ } else {
+ ts[0], _ = unix.TimeToTimespec(*a)
+ }
+ if m == nil {
+ ts[1] = unix.Timespec{Nsec: unix.UTIME_OMIT}
+ } else {
+ ts[1], _ = unix.TimeToTimespec(*m)
+ }
return ts
}
diff --git a/internal/syscallcompat/thread_credentials_linux.go b/internal/syscallcompat/thread_credentials_linux.go
new file mode 100644
index 0000000..b5ec6cd
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux.go
@@ -0,0 +1,61 @@
+//go:build linux
+
+// golang.org/x/sys/unix commit
+// https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51
+// changed unix.Setreuid/unix.Setregid functions to affect the whole thread, which is
+// what gocryptfs does NOT want (https://github.com/rfjakob/gocryptfs/issues/893).
+// The functions Setreuid/Setegid are copy-pasted from one commit before
+// (9e1f76180b77a12eb07c82eb8e1ea8a7f8d202e7).
+//
+// Looking at the diff at https://github.com/golang/sys/commit/d0df966e6959f00dc1c74363e537872647352d51
+// we see that only two architectures, 386 and arm, use SYS_SETREUID32/SYS_SETREGID32
+// (see "man 2 setreuid" for why).
+// All the others architectures use SYS_SETREUID/SYS_SETREGID.
+//
+// As of golang.org/x/sys/unix v0.30.0, Setgroups/setgroups is still per-thread, but
+// it is likely that this will change, too. Setgroups/setgroups are copy-pasted from
+// v0.30.0. The SYS_SETGROUPS32/SYS_SETGROUPS split is the same as for Setreuid.
+//
+// Note: _Gid_t is always uint32 on linux, so we can directly use uint32 for setgroups.
+package syscallcompat
+
+import (
+ "log"
+)
+
+// Setgroups is like setgroups(2) but affects only the current thread
+func Setgroups(gids []int) (err error) {
+ if len(gids) == 0 {
+ return setgroups(0, nil)
+ }
+
+ a := make([]uint32, len(gids))
+ for i, v := range gids {
+ a[i] = uint32(v)
+ }
+ return setgroups(len(a), &a[0])
+}
+
+// SetgroupsPanic calls Setgroups and panics on error
+func SetgroupsPanic(gids []int) {
+ err := Setgroups(gids)
+ if err != nil {
+ log.Panic(err)
+ }
+}
+
+// SetregidPanic calls Setregid and panics on error
+func SetregidPanic(rgid int, egid int) {
+ err := Setregid(rgid, egid)
+ if err != nil {
+ log.Panic(err)
+ }
+}
+
+// SetreuidPanic calls Setreuid and panics on error
+func SetreuidPanic(ruid int, euid int) {
+ err := Setreuid(ruid, euid)
+ if err != nil {
+ log.Panic(err)
+ }
+}
diff --git a/internal/syscallcompat/thread_credentials_linux_32.go b/internal/syscallcompat/thread_credentials_linux_32.go
new file mode 100644
index 0000000..69fffca
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux_32.go
@@ -0,0 +1,40 @@
+//go:build (linux && 386) || (linux && arm)
+
+// Linux on i386 and 32-bit ARM has SYS_SETREUID and friends returning 16-bit values.
+// We need to use SYS_SETREUID32 instead.
+
+package syscallcompat
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// See thread_credentials_linux.go for docs
+
+// Setreuid is like setreuid(2) but affects only the current thread
+func Setreuid(ruid int, euid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID32, uintptr(ruid), uintptr(euid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+// Setreuid is like setregid(2) but affects only the current thread
+func Setregid(rgid int, egid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID32, uintptr(rgid), uintptr(egid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+func setgroups(n int, list *uint32) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS32, uintptr(n), uintptr(unsafe.Pointer(list)), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
diff --git a/internal/syscallcompat/thread_credentials_linux_other.go b/internal/syscallcompat/thread_credentials_linux_other.go
new file mode 100644
index 0000000..ab11c71
--- /dev/null
+++ b/internal/syscallcompat/thread_credentials_linux_other.go
@@ -0,0 +1,37 @@
+//go:build !((linux && 386) || (linux && arm))
+
+package syscallcompat
+
+import (
+ "unsafe"
+
+ "golang.org/x/sys/unix"
+)
+
+// See thread_credentials_linux.go for docs
+
+// Setreuid is like setreuid(2) but affects only the current thread
+func Setreuid(ruid int, euid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREUID, uintptr(ruid), uintptr(euid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+// Setreuid is like setregid(2) but affects only the current thread
+func Setregid(rgid int, egid int) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETREGID, uintptr(rgid), uintptr(egid), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}
+
+func setgroups(n int, list *uint32) (err error) {
+ _, _, e1 := unix.RawSyscall(unix.SYS_SETGROUPS, uintptr(n), uintptr(unsafe.Pointer(list)), 0)
+ if e1 != 0 {
+ err = e1
+ }
+ return
+}