11package fusefrontend
22
33import (
4+ "runtime"
45 "syscall"
6+ "unicode/utf8"
7+
8+ "golang.org/x/text/unicode/norm"
59
610 "github.com/rfjakob/gocryptfs/v2/internal/tlog"
711
@@ -10,12 +14,10 @@ import (
1014 "github.com/rfjakob/gocryptfs/v2/internal/syscallcompat"
1115)
1216
13- // prepareAtSyscall returns a (dirfd, cName) pair that can be used
14- // with the "___at" family of system calls (openat, fstatat, unlinkat...) to
15- // access the backing encrypted child file.
16- func (n * Node ) prepareAtSyscall (child string ) (dirfd int , cName string , errno syscall.Errno ) {
17+ // prepareAtSyscallDirect is the direct version without Unicode normalization fallback
18+ func (n * Node ) prepareAtSyscallDirect (child string ) (dirfd int , cName string , errno syscall.Errno ) {
1719 if child == "" {
18- tlog .Warn .Printf ("BUG: prepareAtSyscall : child=%q, should have called prepareAtSyscallMyself" , child )
20+ tlog .Warn .Printf ("BUG: prepareAtSyscallDirect : child=%q, should have called prepareAtSyscallMyself" , child )
1921 return n .prepareAtSyscallMyself ()
2022 }
2123
@@ -92,6 +94,116 @@ func (n *Node) prepareAtSyscall(child string) (dirfd int, cName string, errno sy
9294 return
9395}
9496
97+ // migrateFilename migrates a filename from NFD to NFC form
98+ func (n * Node ) migrateFilename (oldName , newName string ) syscall.Errno {
99+ if oldName == newName {
100+ return 0 // Nothing to do
101+ }
102+
103+ rn := n .rootNode ()
104+
105+ // Get directory file descriptor
106+ dirfd , _ , errno := n .prepareAtSyscallMyself ()
107+ if errno != 0 {
108+ return errno
109+ }
110+ defer syscall .Close (dirfd )
111+
112+ // For plaintext names: simple rename
113+ if rn .args .PlaintextNames {
114+ err := syscallcompat .Renameat (dirfd , oldName , dirfd , newName )
115+ return fs .ToErrno (err )
116+ }
117+
118+ // For encrypted names: encrypt both names and rename
119+ iv , err := rn .nameTransform .ReadDirIVAt (dirfd )
120+ if err != nil {
121+ return fs .ToErrno (err )
122+ }
123+
124+ var encryptName func (int , string , []byte ) (string , error )
125+ if rn .nameTransform .HaveBadnamePatterns () {
126+ encryptName = func (dirfd int , child string , iv []byte ) (string , error ) {
127+ return rn .nameTransform .EncryptAndHashBadName (child , iv , dirfd )
128+ }
129+ } else {
130+ encryptName = func (dirfd int , child string , iv []byte ) (string , error ) {
131+ return rn .nameTransform .EncryptAndHashName (child , iv )
132+ }
133+ }
134+
135+ oldCName , err := encryptName (dirfd , oldName , iv )
136+ if err != nil {
137+ return fs .ToErrno (err )
138+ }
139+
140+ newCName , err := encryptName (dirfd , newName , iv )
141+ if err != nil {
142+ return fs .ToErrno (err )
143+ }
144+
145+ err = syscallcompat .Renameat (dirfd , oldCName , dirfd , newCName )
146+ return fs .ToErrno (err )
147+ }
148+
149+ // prepareAtSyscall returns a (dirfd, cName) pair that can be used
150+ // with the "___at" family of system calls (openat, fstatat, unlinkat...) to
151+ // access the backing encrypted child file.
152+ func (n * Node ) prepareAtSyscall (child string ) (dirfd int , cName string , errno syscall.Errno ) {
153+ if child == "" {
154+ tlog .Warn .Printf ("BUG: prepareAtSyscall: child=%q, should have called prepareAtSyscallMyself" , child )
155+ return n .prepareAtSyscallMyself ()
156+ }
157+
158+ // On macOS, implement Unicode normalization with fallback and migration
159+ if runtime .GOOS == "darwin" && utf8 .ValidString (child ) {
160+ // Step 1: Always try NFC first (canonical storage form)
161+ normalizedChild := norm .NFC .String (child )
162+ dirfd , cName , errno = n .prepareAtSyscallDirect (normalizedChild )
163+ if errno == 0 {
164+ return dirfd , cName , 0 // Found NFC version
165+ }
166+
167+ // Step 2: Try alternate form if input was different
168+ if normalizedChild != child {
169+ // Input was NFD, try original NFD form
170+ dirfdNFD , cNameNFD , errnoNFD := n .prepareAtSyscallDirect (child )
171+ if errnoNFD == 0 {
172+ // Found NFD file - migrate it to NFC
173+ if errno := n .migrateFilename (child , normalizedChild ); errno == 0 {
174+ // Migration successful, use NFC
175+ syscall .Close (dirfdNFD ) // Close the NFD dirfd
176+ return n .prepareAtSyscallDirect (normalizedChild )
177+ } else {
178+ // Migration failed, use NFD
179+ return dirfdNFD , cNameNFD , 0
180+ }
181+ }
182+ }
183+
184+ // Step 3: If input was NFC, also try NFD as fallback
185+ if normalizedChild == child {
186+ nfdChild := norm .NFD .String (child )
187+ if nfdChild != child {
188+ dirfdNFD , cNameNFD , errnoNFD := n .prepareAtSyscallDirect (nfdChild )
189+ if errnoNFD == 0 {
190+ // Found NFD file - migrate it to NFC
191+ if errno := n .migrateFilename (nfdChild , normalizedChild ); errno == 0 {
192+ // Migration successful, use NFC
193+ syscall .Close (dirfdNFD ) // Close the NFD dirfd
194+ return n .prepareAtSyscallDirect (normalizedChild )
195+ } else {
196+ // Migration failed, use NFD
197+ return dirfdNFD , cNameNFD , 0
198+ }
199+ }
200+ }
201+ }
202+ }
203+
204+ return n .prepareAtSyscallDirect (child ) // Non-macOS or fallback
205+ }
206+
95207func (n * Node ) prepareAtSyscallMyself () (dirfd int , cName string , errno syscall.Errno ) {
96208 dirfd = - 1
97209
0 commit comments