diff options
Diffstat (limited to 'internal')
47 files changed, 221 insertions, 138 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/cryptocore/nonce.go b/internal/cryptocore/nonce.go index 9df094c..c800807 100644 --- a/internal/cryptocore/nonce.go +++ b/internal/cryptocore/nonce.go @@ -11,6 +11,8 @@ func RandBytes(n int) []byte { b := make([]byte, n) _, err := rand.Read(b) if err != nil { + // crypto/rand.Read() is documented to never return an + // error, so this should never happen. Still, better safe than sorry. log.Panic("Failed to read random bytes: " + err.Error()) } return b 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 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 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 898587b..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. diff --git a/internal/fusefrontend_reverse/root_node.go b/internal/fusefrontend_reverse/root_node.go index c0ef814..9c2de28 100644 --- a/internal/fusefrontend_reverse/root_node.go +++ b/internal/fusefrontend_reverse/root_node.go @@ -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/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/stupidgcm/chacha.go b/internal/stupidgcm/chacha.go index de0c2e8..c500ea5 100644 --- a/internal/stupidgcm/chacha.go +++ b/internal/stupidgcm/chacha.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl package stupidgcm diff --git a/internal/stupidgcm/chacha_test.go b/internal/stupidgcm/chacha_test.go index 542ff15..356c813 100644 --- a/internal/stupidgcm/chacha_test.go +++ b/internal/stupidgcm/chacha_test.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl package stupidgcm diff --git a/internal/stupidgcm/common.go b/internal/stupidgcm/common.go index d88dc62..03698b9 100644 --- a/internal/stupidgcm/common.go +++ b/internal/stupidgcm/common.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl package stupidgcm diff --git a/internal/stupidgcm/common_test.go b/internal/stupidgcm/common_test.go index 7f38e90..633f279 100644 --- a/internal/stupidgcm/common_test.go +++ b/internal/stupidgcm/common_test.go @@ -1,5 +1,4 @@ //go:build cgo && !without_openssl -// +build cgo,!without_openssl package stupidgcm diff --git a/internal/stupidgcm/gcm.go b/internal/stupidgcm/gcm.go index 2e5aac4..274d3df 100644 --- a/internal/stupidgcm/gcm.go +++ b/internal/stupidgcm/gcm.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl package stupidgcm diff --git a/internal/stupidgcm/gcm_test.go b/internal/stupidgcm/gcm_test.go index c730a87..041dcab 100644 --- a/internal/stupidgcm/gcm_test.go +++ b/internal/stupidgcm/gcm_test.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl // We compare against Go's built-in GCM implementation. Since stupidgcm only // supports 128-bit IVs and Go only supports that from 1.5 onward, we cannot diff --git a/internal/stupidgcm/locking.go b/internal/stupidgcm/locking.go index 04cf232..00cc361 100644 --- a/internal/stupidgcm/locking.go +++ b/internal/stupidgcm/locking.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl package stupidgcm diff --git a/internal/stupidgcm/openssl.go b/internal/stupidgcm/openssl.go index 8c950f8..3a72f9c 100644 --- a/internal/stupidgcm/openssl.go +++ b/internal/stupidgcm/openssl.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl package stupidgcm diff --git a/internal/stupidgcm/openssl_aead.c b/internal/stupidgcm/openssl_aead.c index e02466f..a7f4f59 100644 --- a/internal/stupidgcm/openssl_aead.c +++ b/internal/stupidgcm/openssl_aead.c @@ -1,4 +1,4 @@ -// +build !without_openssl +//go:build cgo && !without_openssl #include "openssl_aead.h" #include <openssl/evp.h> diff --git a/internal/stupidgcm/without_openssl.go b/internal/stupidgcm/without_openssl.go index fcef793..c59ebe6 100644 --- a/internal/stupidgcm/without_openssl.go +++ b/internal/stupidgcm/without_openssl.go @@ -1,5 +1,4 @@ -//go:build without_openssl -// +build without_openssl +//go:build !cgo || without_openssl package stupidgcm diff --git a/internal/stupidgcm/xchacha.go b/internal/stupidgcm/xchacha.go index 3c121ba..cfd69d7 100644 --- a/internal/stupidgcm/xchacha.go +++ b/internal/stupidgcm/xchacha.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl // Copyright 2018 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -7,7 +6,7 @@ // // Copied from // https://github.com/golang/crypto/blob/32db794688a5a24a23a43f2a984cecd5b3d8da58/chacha20poly1305/xchacha20poly1305.go -// and adapted for stupidgcm by @rfjakob. +// and adapted to use OpenSSL ChaCha20 (see chacha.go) instaed of stdlib by @rfjakob. package stupidgcm diff --git a/internal/stupidgcm/xchacha_test.go b/internal/stupidgcm/xchacha_test.go index 676a023..a448f20 100644 --- a/internal/stupidgcm/xchacha_test.go +++ b/internal/stupidgcm/xchacha_test.go @@ -1,5 +1,4 @@ -//go:build !without_openssl -// +build !without_openssl +//go:build cgo && !without_openssl package stupidgcm diff --git a/internal/syscallcompat/asuser_linux.go b/internal/syscallcompat/asuser_linux.go index 804a898..39e3ff2 100644 --- a/internal/syscallcompat/asuser_linux.go +++ b/internal/syscallcompat/asuser_linux.go @@ -2,7 +2,7 @@ package syscallcompat import ( "fmt" - "io/ioutil" + "os" "runtime" "strconv" "strings" @@ -55,7 +55,7 @@ func asUser(f func() (int, error), context *fuse.Context) (int, error) { func getSupplementaryGroups(pid uint32) (gids []int) { procPath := fmt.Sprintf("/proc/%d/task/%d/status", pid, pid) - blob, err := ioutil.ReadFile(procPath) + blob, err := os.ReadFile(procPath) if err != nil { 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/rename_exchange_test.go b/internal/syscallcompat/rename_exchange_test.go new file mode 100644 index 0000000..97a95f8 --- /dev/null +++ b/internal/syscallcompat/rename_exchange_test.go @@ -0,0 +1,58 @@ +package syscallcompat + +import ( + "os" + "path/filepath" + "testing" + + "golang.org/x/sys/unix" +) + +func TestRenameExchange(t *testing.T) { + // Create a temporary directory for testing + tmpDir, err := os.MkdirTemp("", "renameat2_test") + if err != nil { + t.Fatalf("Failed to create temp dir: %v", err) + } + defer os.RemoveAll(tmpDir) + + // Test basic exchange functionality + file1 := filepath.Join(tmpDir, "file1.txt") + file2 := filepath.Join(tmpDir, "file2.txt") + + content1 := []byte("content of file 1") + content2 := []byte("content of file 2") + + if err := os.WriteFile(file1, content1, 0644); err != nil { + t.Fatalf("Failed to create file1: %v", err) + } + + if err := os.WriteFile(file2, content2, 0644); err != nil { + t.Fatalf("Failed to create file2: %v", err) + } + + // Test RENAME_EXCHANGE - this is the core functionality for issue #914 + err = Renameat2(unix.AT_FDCWD, file1, unix.AT_FDCWD, file2, RENAME_EXCHANGE) + if err != nil { + t.Fatalf("RENAME_EXCHANGE failed: %v", err) + } + + // Verify that the files have been swapped + newContent1, err := os.ReadFile(file1) + if err != nil { + t.Fatalf("Failed to read file1 after exchange: %v", err) + } + + newContent2, err := os.ReadFile(file2) + if err != nil { + t.Fatalf("Failed to read file2 after exchange: %v", err) + } + + if string(newContent1) != string(content2) { + t.Errorf("file1 content after exchange. Expected: %s, Got: %s", content2, newContent1) + } + + if string(newContent2) != string(content1) { + t.Errorf("file2 content after exchange. Expected: %s, Got: %s", content1, newContent2) + } +} diff --git a/internal/syscallcompat/sys_darwin.go b/internal/syscallcompat/sys_darwin.go index cf2f3f0..0ebdd3b 100644 --- a/internal/syscallcompat/sys_darwin.go +++ b/internal/syscallcompat/sys_darwin.go @@ -20,11 +20,13 @@ const ( // O_PATH is only defined on Linux O_PATH = 0 + // Same meaning, different name + RENAME_NOREPLACE = unix.RENAME_EXCL + RENAME_EXCHANGE = unix.RENAME_SWAP + // Only exists on Linux. Define here to fix build failure, even though - // we will never see the flags. - RENAME_NOREPLACE = 1 - RENAME_EXCHANGE = 2 - RENAME_WHITEOUT = 4 + // we will never see this flag. + RENAME_WHITEOUT = 1 << 30 ) // Unfortunately fsetattrlist does not have a syscall wrapper yet. @@ -152,7 +154,12 @@ func GetdentsSpecial(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.Dir return emulateGetdents(fd) } -// Renameat2 does not exist on Darwin, so we call Renameat and ignore the flags. +// Renameat2 does not exist on Darwin, but RenameatxNp does. func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) { - return unix.Renameat(olddirfd, oldpath, newdirfd, newpath) + // If no flags are set, use tried and true renameat + if flags == 0 { + return unix.Renameat(olddirfd, oldpath, newdirfd, newpath) + } + // Let RenameatxNp handle everything else + return unix.RenameatxNp(olddirfd, oldpath, newdirfd, newpath, uint32(flags)) } diff --git a/internal/syscallcompat/sys_linux.go b/internal/syscallcompat/sys_linux.go index 5a4a4ab..19d2c56 100644 --- a/internal/syscallcompat/sys_linux.go +++ b/internal/syscallcompat/sys_linux.go @@ -124,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 } |
