Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 70 additions & 3 deletions src/Framework/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Default buffer size to use when dealing with the Windows API.
/// </summary>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -575,6 +583,17 @@ private unsafe static int GetLogicalCoreCountOnWindows()
return -1;
}

/// <summary>
/// Receives the requested file attribute information. Used for any handles.
/// Use only when calling GetFileInformationByHandleEx.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
internal struct FileAttributeTagInfo
{
internal int fileAttributes;
internal int reparseTag;
}

#endregion

#region Member data
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -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;
Expand Down