@@ -211,14 +211,83 @@ func isMaybeWin83(absPath string) bool {
211
211
return strings .Contains (strings .TrimPrefix (filepath .Base (absPath ), WindowsTempPrefix ), "~" )
212
212
}
213
213
214
+ func getFinalPathName (in string ) (string , error ) {
215
+ // Return the normalized path
216
+ // Wrap the call to GetFinalPathNameByHandleW
217
+ // The string returned by this function uses the \?\ syntax
218
+ // Implies GetFullPathName + GetLongPathName
219
+ kernel32 , err := syscall .LoadDLL ("kernel32.dll" )
220
+ if err != nil {
221
+ return "" , err
222
+ }
223
+ GetFinalPathNameByHandleW , err := kernel32 .FindProc ("GetFinalPathNameByHandleW" )
224
+ // https://github.com/golang/go/blob/ff048033e4304898245d843e79ed1a0897006c6d/src/internal/syscall/windows/syscall_windows.go#L303
225
+ if err != nil {
226
+ return "" , err
227
+ }
228
+ inPath , err := syscall .UTF16PtrFromString (in )
229
+ if err != nil {
230
+ return "" , err
231
+ }
232
+ // Get a file handler
233
+ h , err := syscall .CreateFile (inPath ,
234
+ syscall .GENERIC_READ ,
235
+ syscall .FILE_SHARE_READ ,
236
+ nil ,
237
+ syscall .OPEN_EXISTING ,
238
+ uint32 (syscall .FILE_FLAG_BACKUP_SEMANTICS ),
239
+ 0 )
240
+ if err != nil {
241
+ return "" , err
242
+ }
243
+ defer syscall .CloseHandle (h )
244
+ // Call GetFinalPathNameByHandleW
245
+ var VOLUME_NAME_DOS uint32 = 0x0 // not yet defined in syscall
246
+ var bufSize uint32 = syscall .MAX_PATH // 260
247
+ for i := 0 ; i < 2 ; i ++ {
248
+ buf := make ([]uint16 , bufSize )
249
+ var ret uintptr
250
+ ret , _ , err = GetFinalPathNameByHandleW .Call (
251
+ uintptr (h ), // HANDLE hFile
252
+ uintptr (unsafe .Pointer (& buf [0 ])), // LPWSTR lpszFilePath
253
+ uintptr (bufSize ), // DWORD cchFilePath
254
+ uintptr (VOLUME_NAME_DOS ), // DWORD dwFlags
255
+ )
256
+ // The returned value is the actual length of the norm path
257
+ // After Win 10 build 1607, MAX_PATH limitations have been removed
258
+ // so it is necessary to check newBufSize
259
+ newBufSize := uint32 (ret ) + 1
260
+ if ret == 0 || newBufSize > bufSize * 100 {
261
+ break
262
+ }
263
+ if newBufSize <= bufSize {
264
+ return syscall .UTF16ToString (buf ), nil
265
+ }
266
+ bufSize = newBufSize
267
+ }
268
+ return "" , err
269
+ }
270
+
214
271
func evalSymlinks (in string ) (string , error ) {
215
272
out , err := filepath .EvalSymlinks (in )
216
273
if err != nil && strings .HasPrefix (in , `\\?\` ) {
217
274
// Try again without the `\\?\` prefix
218
275
out , err = filepath .EvalSymlinks (in [4 :])
219
276
}
220
277
if err != nil {
221
- return "" , err
278
+ // Try to get a normalized path from Win-API
279
+ var err1 error
280
+ out , err1 = getFinalPathName (in )
281
+ if err1 != nil {
282
+ return "" , err // return the prior error
283
+ }
284
+ // Trim UNC prefix, equivalent to
285
+ // https://github.com/golang/go/blob/2396101e0590cb7d77556924249c26af0ccd9eff/src/os/file_windows.go#L470
286
+ if strings .HasPrefix (out , `\\?\UNC\` ) {
287
+ out = `\` + out [7 :] // path like \\server\share\...
288
+ } else {
289
+ out = strings .TrimPrefix (out , `\\?\` )
290
+ }
222
291
}
223
292
return longFilenameSupport (out ), nil
224
293
}
0 commit comments