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;