@@ -312,9 +312,92 @@ extension FileManager {
312
312
}
313
313
314
314
internal func _destinationOfSymbolicLink( atPath path: String ) throws -> String {
315
- return try _canonicalizedPath ( toFileAtPath: path)
315
+ let faAttributes = try windowsFileAttributes ( atPath: path)
316
+ guard faAttributes. dwFileAttributes & DWORD ( FILE_ATTRIBUTE_REPARSE_POINT) == DWORD ( FILE_ATTRIBUTE_REPARSE_POINT) else {
317
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_BAD_ARGUMENTS) , reading: false )
318
+ }
319
+
320
+ let handle = path. withCString ( encodedAs: UTF16 . self) { symlink in
321
+ CreateFileW ( symlink, GENERIC_READ, DWORD ( FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE) ,
322
+ nil , DWORD ( OPEN_EXISTING) , DWORD ( FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS) ,
323
+ nil )
324
+ }
325
+
326
+ guard handle != INVALID_HANDLE_VALUE else {
327
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true )
328
+ }
329
+ defer { CloseHandle ( handle) }
330
+
331
+ // Since REPARSE_DATA_BUFFER ends with an arbitrarily long buffer, we
332
+ // have to manually get the path buffer out of it since binding it to a
333
+ // type will truncate the path buffer.
334
+ //
335
+ // 20 is the sum of the offsets of:
336
+ // ULONG ReparseTag
337
+ // USHORT ReparseDataLength
338
+ // USHORT Reserved
339
+ // USHORT SubstituteNameOffset
340
+ // USHORT SubstituteNameLength
341
+ // USHORT PrintNameOffset
342
+ // USHORT PrintNameLength
343
+ // ULONG Flags (Symlink only)
344
+ let symLinkPathBufferOffset = 20 // 4 + 2 + 2 + 2 + 2 + 2 + 2 + 4
345
+ let mountPointPathBufferOffset = 16 // 4 + 2 + 2 + 2 + 2 + 2 + 2
346
+ let buff = UnsafeMutableRawBufferPointer . allocate ( byteCount: Int ( MAXIMUM_REPARSE_DATA_BUFFER_SIZE) ,
347
+ alignment: 8 )
348
+
349
+ guard let buffBase = buff. baseAddress else {
350
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_INVALID_DATA) , reading: false )
351
+ }
352
+
353
+ var bytesWritten : DWORD = 0
354
+ guard DeviceIoControl ( handle, FSCTL_GET_REPARSE_POINT, nil , 0 ,
355
+ buffBase, DWORD ( MAXIMUM_REPARSE_DATA_BUFFER_SIZE) ,
356
+ & bytesWritten, nil ) else {
357
+ throw _NSErrorWithWindowsError ( GetLastError ( ) , reading: true )
358
+ }
359
+
360
+ guard bytesWritten >= MemoryLayout< REPARSE_DATA_BUFFER> . size else {
361
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_INVALID_DATA) , reading: false )
362
+ }
363
+
364
+ let bound = buff. bindMemory ( to: REPARSE_DATA_BUFFER . self)
365
+ guard let reparseDataBuffer = bound. first else {
366
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_INVALID_DATA) , reading: false )
367
+ }
368
+
369
+ guard reparseDataBuffer. ReparseTag == IO_REPARSE_TAG_SYMLINK
370
+ || reparseDataBuffer. ReparseTag == IO_REPARSE_TAG_MOUNT_POINT else {
371
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_BAD_ARGUMENTS) , reading: false )
372
+ }
373
+
374
+ let pathBufferPtr : UnsafeMutableRawPointer
375
+ let substituteNameBytes : Int
376
+ let substituteNameOffset : Int
377
+ switch reparseDataBuffer. ReparseTag {
378
+ case IO_REPARSE_TAG_SYMLINK:
379
+ pathBufferPtr = buffBase + symLinkPathBufferOffset
380
+ substituteNameBytes = Int ( reparseDataBuffer. SymbolicLinkReparseBuffer. SubstituteNameLength)
381
+ substituteNameOffset = Int ( reparseDataBuffer. SymbolicLinkReparseBuffer. SubstituteNameOffset)
382
+ case IO_REPARSE_TAG_MOUNT_POINT:
383
+ pathBufferPtr = buffBase + mountPointPathBufferOffset
384
+ substituteNameBytes = Int ( reparseDataBuffer. MountPointReparseBuffer. SubstituteNameLength)
385
+ substituteNameOffset = Int ( reparseDataBuffer. MountPointReparseBuffer. SubstituteNameOffset)
386
+ default :
387
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_BAD_ARGUMENTS) , reading: false )
388
+ }
389
+
390
+ guard substituteNameBytes + substituteNameOffset <= bytesWritten else {
391
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_INVALID_DATA) , reading: false )
392
+ }
393
+
394
+ let substituteNameBuff = Data ( bytes: pathBufferPtr + substituteNameOffset, count: substituteNameBytes)
395
+ guard let substitutePath = String ( data: substituteNameBuff, encoding: . utf16LittleEndian) else {
396
+ throw _NSErrorWithWindowsError ( DWORD ( ERROR_INVALID_DATA) , reading: false )
397
+ }
398
+ return substitutePath
316
399
}
317
-
400
+
318
401
internal func _canonicalizedPath( toFileAtPath path: String ) throws -> String {
319
402
var hFile : HANDLE = INVALID_HANDLE_VALUE
320
403
path. withCString ( encodedAs: UTF16 . self) { link in
0 commit comments