aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorrafjaf2025-07-20 17:04:41 +0200
committerJakob Unterwurzacher2026-01-14 21:05:20 +0100
commita753e0075e854f57689b2b6efa00a447149df8b9 (patch)
treecb9ba4cbca88eb4d81c13198d11e6489cb6cea4c /internal
parentc9cf6f1f8a5b90c9cb70ed19f8c8426dc2655c9d (diff)
macOS: Fix Unicode normalization issues in forward & reverse moderafjaf-rebase-onto-2.6.0
This commit resolves https://github.com/rfjakob/gocryptfs/issues/850 by addressing Unicode normalization mismatches on macOS between NFC (used by CLI tools) and NFD (used by GUI apps). The solution is inspired by Cryptomator's approach ( https://github.com/cryptomator/cryptomator/issues/264 ). Forward mode on MacOS now enforces NFC for storage but presents NFD as recommended by https://developer.apple.com/library/archive/qa/qa1173/_index.html . See https://github.com/rfjakob/gocryptfs/pull/949 for more info.
Diffstat (limited to 'internal')
-rw-r--r--internal/fusefrontend/file_dir_ops.go4
-rw-r--r--internal/fusefrontend/node.go7
-rw-r--r--internal/fusefrontend/node_dir_ops.go19
-rw-r--r--internal/fusefrontend/node_open_create.go1
4 files changed, 30 insertions, 1 deletions
diff --git a/internal/fusefrontend/file_dir_ops.go b/internal/fusefrontend/file_dir_ops.go
index b69e7bc..df14410 100644
--- a/internal/fusefrontend/file_dir_ops.go
+++ b/internal/fusefrontend/file_dir_ops.go
@@ -138,6 +138,8 @@ func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno sysc
continue
}
if f.rootNode.args.PlaintextNames {
+ // Even in plaintext mode, normalize for macOS display
+ entry.Name = normalizeFilenameForDisplay(cName)
return
}
if !f.rootNode.args.DeterministicNames && cName == nametransform.DirIVFilename {
@@ -171,7 +173,7 @@ func (f *File) Readdirent(ctx context.Context) (entry *fuse.DirEntry, errno sysc
}
// Override the ciphertext name with the plaintext name but reuse the rest
// of the structure
- entry.Name = name
+ entry.Name = normalizeFilenameForDisplay(name)
return
}
}
diff --git a/internal/fusefrontend/node.go b/internal/fusefrontend/node.go
index 95be48d..56f9452 100644
--- a/internal/fusefrontend/node.go
+++ b/internal/fusefrontend/node.go
@@ -140,6 +140,7 @@ func (n *Node) Access(ctx context.Context, mode uint32) syscall.Errno {
//
// Symlink-safe through use of Unlinkat().
func (n *Node) Unlink(ctx context.Context, name string) (errno syscall.Errno) {
+ name = normalizeFilename(name) // Always store as NFC
dirfd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return
@@ -274,6 +275,7 @@ func (n *Node) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
//
// Symlink-safe through use of Mknodat().
func (n *Node) Mknod(ctx context.Context, name string, mode, rdev uint32, out *fuse.EntryOut) (inode *fs.Inode, errno syscall.Errno) {
+ name = normalizeFilename(name) // Always store as NFC
dirfd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return
@@ -329,6 +331,7 @@ func (n *Node) Mknod(ctx context.Context, name string, mode, rdev uint32, out *f
//
// Symlink-safe through use of Linkat().
func (n *Node) Link(ctx context.Context, target fs.InodeEmbedder, name string, out *fuse.EntryOut) (inode *fs.Inode, errno syscall.Errno) {
+ name = normalizeFilename(name) // Always store as NFC
dirfd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return
@@ -379,6 +382,7 @@ func (n *Node) Link(ctx context.Context, target fs.InodeEmbedder, name string, o
//
// Symlink-safe through use of Symlinkat.
func (n *Node) Symlink(ctx context.Context, target, name string, out *fuse.EntryOut) (inode *fs.Inode, errno syscall.Errno) {
+ name = normalizeFilename(name) // Always store as NFC
dirfd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return
@@ -451,6 +455,9 @@ func (n *Node) Rename(ctx context.Context, name string, newParent fs.InodeEmbedd
return errno
}
+ name = normalizeFilename(name) // Always store as NFC
+ newName = normalizeFilename(newName) // Always store as NFC
+
dirfd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return
diff --git a/internal/fusefrontend/node_dir_ops.go b/internal/fusefrontend/node_dir_ops.go
index 97327ce..ba78ac0 100644
--- a/internal/fusefrontend/node_dir_ops.go
+++ b/internal/fusefrontend/node_dir_ops.go
@@ -6,8 +6,10 @@ import (
"io"
"runtime"
"syscall"
+ "unicode/utf8"
"golang.org/x/sys/unix"
+ "golang.org/x/text/unicode/norm"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
@@ -20,6 +22,22 @@ import (
const dsStoreName = ".DS_Store"
+// normalizeFilename converts filenames to NFC for consistent internal storage
+func normalizeFilename(name string) string {
+ if runtime.GOOS == "darwin" && utf8.ValidString(name) {
+ return norm.NFC.String(name)
+ }
+ return name
+}
+
+// normalizeFilenameForDisplay converts NFC to NFD for macOS GUI compatibility
+func normalizeFilenameForDisplay(name string) string {
+ if runtime.GOOS == "darwin" && utf8.ValidString(name) {
+ return norm.NFD.String(name)
+ }
+ return name
+}
+
// haveDsstore return true if one of the entries in "names" is ".DS_Store".
func haveDsstore(entries []fuse.DirEntry) bool {
for _, e := range entries {
@@ -70,6 +88,7 @@ func (n *Node) mkdirWithIv(dirfd int, cName string, mode uint32, context *fuse.C
//
// Symlink-safe through use of Mkdirat().
func (n *Node) Mkdir(ctx context.Context, name string, mode uint32, out *fuse.EntryOut) (*fs.Inode, syscall.Errno) {
+ name = normalizeFilename(name) // Always store as NFC
dirfd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return nil, errno
diff --git a/internal/fusefrontend/node_open_create.go b/internal/fusefrontend/node_open_create.go
index 9598559..24f3e21 100644
--- a/internal/fusefrontend/node_open_create.go
+++ b/internal/fusefrontend/node_open_create.go
@@ -58,6 +58,7 @@ func (n *Node) Open(ctx context.Context, flags uint32) (fh fs.FileHandle, fuseFl
//
// Symlink-safe through the use of Openat().
func (n *Node) Create(ctx context.Context, name string, flags uint32, mode uint32, out *fuse.EntryOut) (inode *fs.Inode, fh fs.FileHandle, fuseFlags uint32, errno syscall.Errno) {
+ name = normalizeFilename(name) // Always store as NFC
dirfd, cName, errno := n.prepareAtSyscall(name)
if errno != 0 {
return