@@ -461,3 +461,78 @@ extension SystemString {
461
461
return lexer. current
462
462
}
463
463
}
464
+
465
+ #if os(Windows)
466
+ import WinSDK
467
+
468
+ // FIXME: Rather than canonicalizing the path at every call site to a Win32 API,
469
+ // we should consider always storing absolute paths with the \\?\ prefix applied,
470
+ // for better performance.
471
+ extension UnsafePointer where Pointee == CInterop . PlatformChar {
472
+ /// Invokes `body` with a resolved and potentially `\\?\`-prefixed version of the pointee,
473
+ /// to ensure long paths greater than MAX_PATH (260) characters are handled correctly.
474
+ ///
475
+ /// - seealso: https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
476
+ internal func withCanonicalPathRepresentation< Result> ( _ body: ( Self ) throws -> Result ) throws -> Result {
477
+ // 1. Normalize the path first.
478
+ // Contrary to the documentation, this works on long paths independently
479
+ // of the registry or process setting to enable long paths (but it will also
480
+ // not add the \\?\ prefix required by other functions under these conditions).
481
+ let dwLength : DWORD = GetFullPathNameW ( self , 0 , nil , nil )
482
+ return try withUnsafeTemporaryAllocation ( of: WCHAR . self, capacity: Int ( dwLength) ) { pwszFullPath in
483
+ guard ( 1 ..< dwLength) . contains ( GetFullPathNameW ( self , DWORD ( pwszFullPath. count) , pwszFullPath. baseAddress, nil ) ) else {
484
+ throw Errno ( rawValue: _mapWindowsErrorToErrno ( GetLastError ( ) ) )
485
+ }
486
+
487
+ // 1.5 Leave \\.\ prefixed paths alone since device paths are already an exact representation and PathCchCanonicalizeEx will mangle these.
488
+ if let base = pwszFullPath. baseAddress,
489
+ base [ 0 ] == UInt8 ( ascii: " \\ " ) ,
490
+ base [ 1 ] == UInt8 ( ascii: " \\ " ) ,
491
+ base [ 2 ] == UInt8 ( ascii: " . " ) ,
492
+ base [ 3 ] == UInt8 ( ascii: " \\ " ) {
493
+ return try body ( base)
494
+ }
495
+
496
+ // 2. Canonicalize the path.
497
+ // This will add the \\?\ prefix if needed based on the path's length.
498
+ var pwszCanonicalPath : LPWSTR ?
499
+ let flags : ULONG = numericCast ( PATHCCH_ALLOW_LONG_PATHS . rawValue)
500
+ let result = PathAllocCanonicalize ( pwszFullPath. baseAddress, flags, & pwszCanonicalPath)
501
+ if let pwszCanonicalPath {
502
+ defer { LocalFree ( pwszCanonicalPath) }
503
+ if result == S_OK {
504
+ // 3. Perform the operation on the normalized path.
505
+ return try body ( pwszCanonicalPath)
506
+ }
507
+ }
508
+ throw Errno ( rawValue: _mapWindowsErrorToErrno ( WIN32_FROM_HRESULT ( result) ) )
509
+ }
510
+ }
511
+ }
512
+
513
+ @inline ( __always)
514
+ fileprivate func HRESULT_CODE( _ hr: HRESULT ) -> DWORD {
515
+ DWORD ( hr) & 0xffff
516
+ }
517
+
518
+ @inline ( __always)
519
+ fileprivate func HRESULT_FACILITY( _ hr: HRESULT ) -> DWORD {
520
+ DWORD ( hr << 16 ) & 0x1fff
521
+ }
522
+
523
+ @inline ( __always)
524
+ fileprivate func SUCCEEDED( _ hr: HRESULT ) -> Bool {
525
+ hr >= 0
526
+ }
527
+
528
+ // This is a non-standard extension to the Windows SDK that allows us to convert
529
+ // an HRESULT to a Win32 error code.
530
+ @inline ( __always)
531
+ fileprivate func WIN32_FROM_HRESULT( _ hr: HRESULT ) -> DWORD {
532
+ if SUCCEEDED ( hr) { return DWORD ( ERROR_SUCCESS) }
533
+ if HRESULT_FACILITY ( hr) == FACILITY_WIN32 {
534
+ return HRESULT_CODE ( hr)
535
+ }
536
+ return DWORD ( hr)
537
+ }
538
+ #endif
0 commit comments