Skip to content

Commit 407cbc8

Browse files
authored
Merge pull request #2340 from gmittert/ReadTheSymbols
Fix _destinationOfSymbolicLink behaviour on Windows
2 parents 738a647 + 806d267 commit 407cbc8

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,32 @@ static inline int _CF_renameat2(int olddirfd, const char *_Nonnull oldpath,
623623

624624
#if TARGET_OS_WIN32
625625
CF_EXPORT void __CFSocketInitializeWinSock(void);
626+
627+
typedef struct _REPARSE_DATA_BUFFER {
628+
ULONG ReparseTag;
629+
USHORT ReparseDataLength;
630+
USHORT Reserved;
631+
union {
632+
struct {
633+
USHORT SubstituteNameOffset;
634+
USHORT SubstituteNameLength;
635+
USHORT PrintNameOffset;
636+
USHORT PrintNameLength;
637+
ULONG Flags;
638+
WCHAR PathBuffer[1];
639+
} SymbolicLinkReparseBuffer;
640+
struct {
641+
USHORT SubstituteNameOffset;
642+
USHORT SubstituteNameLength;
643+
USHORT PrintNameOffset;
644+
USHORT PrintNameLength;
645+
WCHAR PathBuffer[1];
646+
} MountPointReparseBuffer;
647+
struct {
648+
UCHAR DataBuffer[1];
649+
} GenericReparseBuffer;
650+
} DUMMYUNIONNAME;
651+
} REPARSE_DATA_BUFFER;
626652
#endif
627653

628654
_CF_EXPORT_SCOPE_END

Foundation/FileManager+Win32.swift

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,9 +312,92 @@ extension FileManager {
312312
}
313313

314314
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
316399
}
317-
400+
318401
internal func _canonicalizedPath(toFileAtPath path: String) throws -> String {
319402
var hFile: HANDLE = INVALID_HANDLE_VALUE
320403
path.withCString(encodedAs: UTF16.self) { link in

0 commit comments

Comments
 (0)