@@ -461,3 +461,78 @@ extension SystemString {
461461 return lexer. current
462462 }
463463}
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