diff --git a/src/Framework/NativeMethods.cs b/src/Framework/NativeMethods.cs
index f871d073876..51b6302fe91 100644
--- a/src/Framework/NativeMethods.cs
+++ b/src/Framework/NativeMethods.cs
@@ -45,6 +45,8 @@ internal static class NativeMethods
internal const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
internal const int FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400;
+ internal const uint IO_REPARSE_TAG_SYMLINK = 0xA000000C;
+
///
/// Default buffer size to use when dealing with the Windows API.
///
@@ -208,6 +210,12 @@ internal enum SymbolicLink
AllowUnprivilegedCreate = 2,
}
+ // https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ne-minwinbase-file_info_by_handle_class
+ private enum FileInfoByHandleClass : int
+ {
+ FileAttributeTagInfo = 9
+ }
+
#endregion
#region Structs
@@ -575,6 +583,17 @@ private unsafe static int GetLogicalCoreCountOnWindows()
return -1;
}
+ ///
+ /// Receives the requested file attribute information. Used for any handles.
+ /// Use only when calling GetFileInformationByHandleEx.
+ ///
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FileAttributeTagInfo
+ {
+ internal int fileAttributes;
+ internal int reparseTag;
+ }
+
#endregion
#region Member data
@@ -1072,12 +1091,39 @@ internal static bool IsSymLink(FileInfo fileInfo)
WIN32_FILE_ATTRIBUTE_DATA data = new WIN32_FILE_ATTRIBUTE_DATA();
- return NativeMethods.GetFileAttributesEx(fileInfo.FullName, 0, ref data) &&
- (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0 &&
- (data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT;
+ return
+ NativeMethods.GetFileAttributesEx(fileInfo.FullName, 0, ref data) &&
+ (data.fileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) == 0 &&
+ // This is fast but unspecific check - there are multiple types of reparse points.
+ (data.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT &&
+ // Specific check for a symlink.
+ IsSymLinkFileInternal(fileInfo.FullName);
#endif
}
+ [SupportedOSPlatform("windows")]
+ private static bool IsSymLinkFileInternal(string path)
+ {
+ using SafeFileHandle handle = CreateFile(path,
+ FILE_READ_ATTRIBUTES,
+ FILE_SHARE_READ,
+ IntPtr.Zero,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OPEN_REPARSE_POINT,
+ IntPtr.Zero);
+
+ if (handle.IsInvalid)
+ {
+ // Link is broken. Details can be obtained via GetLastError.
+ return false;
+ }
+
+ return
+ GetFileAttributeTagInfoByHandle(handle, out FileAttributeTagInfo attributes) &&
+ (attributes.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT &&
+ (attributes.reparseTag & NativeMethods.IO_REPARSE_TAG_SYMLINK) == NativeMethods.IO_REPARSE_TAG_SYMLINK;
+ }
+
internal static bool IsSymLink(string path)
{
return IsSymLink(new FileInfo(path));
@@ -1625,6 +1671,26 @@ internal static void VerifyThrowWin32Result(int result)
[SupportedOSPlatform("windows")]
internal static extern bool GetFileAttributesEx(String name, int fileInfoLevel, ref WIN32_FILE_ATTRIBUTE_DATA lpFileInformation);
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ [SupportedOSPlatform("windows")]
+ private static extern bool GetFileInformationByHandleEx(
+ SafeFileHandle fileHandle,
+ FileInfoByHandleClass fileInfoByHandleClass,
+ [Out] IntPtr lpFileInformation,
+ int dwBufferSize);
+
+ [SupportedOSPlatform("windows")]
+ static unsafe bool GetFileAttributeTagInfoByHandle(SafeFileHandle fileHandle, out FileAttributeTagInfo fileAttributeTagInfo)
+ {
+ fileAttributeTagInfo = new FileAttributeTagInfo();
+ fixed (FileAttributeTagInfo* ptr = &fileAttributeTagInfo)
+ {
+ return GetFileInformationByHandleEx(fileHandle, FileInfoByHandleClass.FileAttributeTagInfo, (IntPtr)ptr, sizeof(FileAttributeTagInfo));
+ }
+ }
+
+
[DllImport("kernel32.dll", PreserveSig = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
[SupportedOSPlatform("windows")]
@@ -1734,6 +1800,7 @@ internal static bool SetCurrentDirectory(string path)
public static extern int CoWaitForMultipleHandles(COWAIT_FLAGS dwFlags, int dwTimeout, int cHandles, [MarshalAs(UnmanagedType.LPArray)] IntPtr[] pHandles, out int pdwIndex);
internal const uint GENERIC_READ = 0x80000000;
+ internal const uint FILE_READ_ATTRIBUTES = 0x80;
internal const uint FILE_SHARE_READ = 0x1;
internal const uint FILE_ATTRIBUTE_NORMAL = 0x80;
internal const uint FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000;