Skip to content

Commit c79e7d6

Browse files
committed
Fix _destinationOfSymbolicLink behaviour on Windows
_destinationOfSymbolic should return a relative path if the symlink is a relative symlink. Now instead of returning the resolved symlink path, we read the ReparsePoint to get the path it contains.
1 parent e75a099 commit c79e7d6

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

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

619619
#if TARGET_OS_WIN32
620620
CF_EXPORT void __CFSocketInitializeWinSock(void);
621+
622+
typedef struct _REPARSE_DATA_BUFFER {
623+
ULONG ReparseTag;
624+
USHORT ReparseDataLength;
625+
USHORT Reserved;
626+
union {
627+
struct {
628+
USHORT SubstituteNameOffset;
629+
USHORT SubstituteNameLength;
630+
USHORT PrintNameOffset;
631+
USHORT PrintNameLength;
632+
ULONG Flags;
633+
WCHAR PathBuffer[1];
634+
} SymbolicLinkReparseBuffer;
635+
struct {
636+
USHORT SubstituteNameOffset;
637+
USHORT SubstituteNameLength;
638+
USHORT PrintNameOffset;
639+
USHORT PrintNameLength;
640+
WCHAR PathBuffer[1];
641+
} MountPointReparseBuffer;
642+
struct {
643+
UCHAR DataBuffer[1];
644+
} GenericReparseBuffer;
645+
} DUMMYUNIONNAME;
646+
} REPARSE_DATA_BUFFER;
621647
#endif
622648

623649
_CF_EXPORT_SCOPE_END

Foundation/FileManager+Win32.swift

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,59 @@ extension FileManager {
301301
}
302302

303303
internal func _destinationOfSymbolicLink(atPath path: String) throws -> String {
304-
return try _canonicalizedPath(toFileAtPath: path)
304+
let handle = path.withCString(encodedAs: UTF16.self) { symlink in
305+
CreateFileW(symlink,
306+
GENERIC_READ,
307+
DWORD(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
308+
nil,
309+
DWORD(OPEN_EXISTING),
310+
DWORD(FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS),
311+
nil)
312+
}
313+
314+
guard handle != INVALID_HANDLE_VALUE else {
315+
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
316+
}
317+
defer { CloseHandle(handle) }
318+
319+
// Since REPARSE_DATA_BUFFER ends with an arbitrarily long buffer, we
320+
// have to manually get the path buffer out of it since binding it to a
321+
// type will truncate the path buffer
322+
let pathBufferOffset =
323+
MemoryLayout<ULONG>.size //ULONG ReparseTag
324+
+ MemoryLayout<USHORT>.size //ULONG ReparseDataLength
325+
+ MemoryLayout<USHORT>.size //ULONG Reserved
326+
+ MemoryLayout<USHORT>.size //ULONG SubstituteNameOffset
327+
+ MemoryLayout<USHORT>.size //ULONG SubstituteNameLength
328+
+ MemoryLayout<USHORT>.size //ULONG PrintNameOffset
329+
+ MemoryLayout<USHORT>.size //ULONG PrintNameLength
330+
+ MemoryLayout<ULONG>.size //ULONG Flags
331+
332+
var buff = Array(repeating: 0, count: MemoryLayout<REPARSE_DATA_BUFFER>.size + Int(2 * MAX_PATH))
333+
334+
return try buff.withUnsafeMutableBytes {
335+
var bytesWritten: DWORD = 0
336+
guard DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT, nil, 0,
337+
$0.baseAddress, DWORD($0.count), &bytesWritten, nil) else {
338+
throw _NSErrorWithWindowsError(GetLastError(), reading: true)
339+
}
340+
341+
let bound = $0.bindMemory(to: REPARSE_DATA_BUFFER.self)
342+
guard let reparseDataBuffer = bound.first else {
343+
fatalError()
344+
}
345+
346+
let pathBufferPtr = $0.baseAddress! + pathBufferOffset
347+
let printNameBytes = Int(reparseDataBuffer.SymbolicLinkReparseBuffer.PrintNameLength)
348+
let printNameOffset = Int(reparseDataBuffer.SymbolicLinkReparseBuffer.PrintNameOffset)
349+
let printNameBuff = Data(bytes: pathBufferPtr + printNameOffset, count: printNameBytes)
350+
guard let printPath = String(data: printNameBuff, encoding: .utf16LittleEndian) else {
351+
fatalError()
352+
}
353+
return printPath
354+
}
305355
}
306-
356+
307357
internal func _canonicalizedPath(toFileAtPath path: String) throws -> String {
308358
var hFile: HANDLE = INVALID_HANDLE_VALUE
309359
path.withCString(encodedAs: UTF16.self) { link in

0 commit comments

Comments
 (0)