[llvm] [llvm][support] Refactor symlink handling and add readlink (PR #184256)
Michael Spencer via llvm-commits
llvm-commits at lists.llvm.org
Wed Mar 4 14:47:32 PST 2026
================
@@ -1649,6 +1683,96 @@ std::error_code real_path(const Twine &path, SmallVectorImpl<char> &dest,
return std::error_code();
}
+// This struct is normally only available in the Windows Driver Kit (WDK)
+// headers, not in the standard Windows SDK.
+struct LLVM_REPARSE_DATA_BUFFER {
+ unsigned long ReparseTag;
+ unsigned short ReparseDataLength;
+ unsigned short Reserved;
+ union {
+ struct {
+ unsigned short SubstituteNameOffset;
+ unsigned short SubstituteNameLength;
+ unsigned short PrintNameOffset;
+ unsigned short PrintNameLength;
+ unsigned long Flags;
+ wchar_t PathBuffer[1];
+ } SymbolicLinkReparseBuffer;
+ struct {
+ unsigned short SubstituteNameOffset;
+ unsigned short SubstituteNameLength;
+ unsigned short PrintNameOffset;
+ unsigned short PrintNameLength;
+ wchar_t PathBuffer[1];
+ } MountPointReparseBuffer;
+ struct {
+ unsigned char DataBuffer[1];
+ } GenericReparseBuffer;
+ };
+};
+
+std::error_code readlink(const Twine &path, SmallVectorImpl<char> &dest) {
+ dest.clear();
+
+ SmallVector<wchar_t, 128> PathUTF16;
+ if (std::error_code EC = widenPath(path, PathUTF16))
+ return EC;
+
+ // Open the symlink without following it.
+ ScopedFileHandle H(::CreateFileW(
+ c_str(PathUTF16), FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
+ OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+ NULL));
+ if (!H)
+ return mapWindowsError(::GetLastError());
+
+ // Read the reparse point data.
+ union {
+ LLVM_REPARSE_DATA_BUFFER RDB;
+ char Buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
+ };
+ DWORD BytesReturned;
+ if (!::DeviceIoControl(H, FSCTL_GET_REPARSE_POINT, NULL, 0, &RDB,
+ sizeof(Buffer), &BytesReturned, NULL)) {
+ DWORD Err = ::GetLastError();
+ if (Err == ERROR_NOT_A_REPARSE_POINT)
+ return make_error_code(errc::invalid_argument);
+ return mapWindowsError(Err);
+ }
+
+ if (RDB.ReparseTag != IO_REPARSE_TAG_SYMLINK)
+ return make_error_code(errc::invalid_argument);
+
+ const auto &SLB = RDB.SymbolicLinkReparseBuffer;
+ size_t PathBufOffset =
+ offsetof(LLVM_REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer);
+
+ // Prefer PrintName (user-friendly, e.g. "C:\foo") over SubstituteName
+ // (NT-internal, e.g. "\??\C:\foo").
+ USHORT NameOffset, NameLength;
+ if (SLB.PrintNameLength != 0) {
+ NameOffset = SLB.PrintNameOffset;
+ NameLength = SLB.PrintNameLength;
+ } else {
+ NameOffset = SLB.SubstituteNameOffset;
+ NameLength = SLB.SubstituteNameLength;
+ }
+
+ // Validate that the returned data is large enough to contain the name.
+ if (PathBufOffset + NameOffset + NameLength > BytesReturned)
+ return make_error_code(errc::invalid_argument);
+
+ const wchar_t *Target = SLB.PathBuffer + NameOffset / sizeof(wchar_t);
+ USHORT TargetLen = NameLength / sizeof(wchar_t);
+
+ if (std::error_code EC = UTF16ToUTF8(Target, TargetLen, dest))
+ return EC;
+
+ llvm::sys::path::make_preferred(dest);
----------------
Bigcheese wrote:
I originally was doing this because we do it elsewhere, but we're actually kinda inconsistent about it. I don't feel too strongly either way; path separators in LLVM/Clang on Windows end up as a mix a lot of the time anyway.
I'll remove it to be consistent with Unix and not remove information.
https://github.com/llvm/llvm-project/pull/184256
More information about the llvm-commits
mailing list