From f59e552e755f35c0278eaebd27a0dacb10914551 Mon Sep 17 00:00:00 2001 From: rafjaf Date: Sun, 20 Jul 2025 18:24:34 +0200 Subject: darwin: syscallcompat: add RenameatxNp plus flags Fix macos file saving problem by implementing RENAME_EXCHANGE flag. With test. The dummy value for RENAME_WHITEOUT collides with the new flags. Move it wayyy up. https://github.com/rfjakob/gocryptfs/issues/914 --- .gitignore | 3 ++ internal/syscallcompat/rename_exchange_test.go | 58 ++++++++++++++++++++++++++ internal/syscallcompat/sys_darwin.go | 19 ++++++--- 3 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 internal/syscallcompat/rename_exchange_test.go diff --git a/.gitignore b/.gitignore index 2e2743f..73b7357 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,6 @@ gocryptfs.1 # Source tarball version. Should never be committed to git. /VERSION + +# Ignore macos specific files +.DS_Store \ No newline at end of file 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)) } -- cgit v1.2.3