-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add symbolic link APIs #54253
Add symbolic link APIs #54253
Changes from all commits
b63c7b7
b223304
2e822fd
32e196c
8ed2772
abbecf0
fc49855
86cbd19
480d3df
a6cd9da
6952eb7
797ace5
8639e3b
6bb1aa9
88bcadb
8701118
d10e00c
fa5ff40
6788db7
45392b4
38b6e88
a70cdef
cbcf7af
c43664a
9460264
bbaa35a
7e2eaa9
0d4250b
1a05a56
d656aed
adcca16
35165b0
9d2694b
c79df26
fc4241d
4881f2b
b377cb0
496487f
beaf701
a5986fb
b7ec269
7f37a86
3e36f72
6f5b99a
2d09f7e
4b5e133
82dc88a
48e1af1
0495f59
40cb737
995ead5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
internal static partial class Interop | ||
{ | ||
// Unix max paths are typically 1K or 4K UTF-8 bytes, 256 should handle the majority of paths | ||
// without putting too much pressure on the stack. | ||
internal const int DefaultPathBufferSize = 256; | ||
} |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -9,16 +9,12 @@ internal static partial class Interop | |||
{ | ||||
internal static partial class Sys | ||||
{ | ||||
// Unix max paths are typically 1K or 4K UTF-8 bytes, 256 should handle the majority of paths | ||||
// without putting too much pressure on the stack. | ||||
private const int StackBufferSize = 256; | ||||
|
||||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Stat", SetLastError = true)] | ||||
internal static extern int Stat(ref byte path, out FileStatus output); | ||||
|
||||
internal static int Stat(ReadOnlySpan<char> path, out FileStatus output) | ||||
{ | ||||
var converter = new ValueUtf8Converter(stackalloc byte[StackBufferSize]); | ||||
var converter = new ValueUtf8Converter(stackalloc byte[DefaultPathBufferSize]); | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: can this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, @GrabYourPitchforks Do you know why it was decided not to make this type disposable? Is there anything special about its
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting, thanks for showing me @hoyosjs . Didn't know We have a list of to-dos for changes that are unrelated to this PR. We'll add this suggestion to the list. |
||||
int result = Stat(ref MemoryMarshal.GetReference(converter.ConvertAndTerminateString(path)), out output); | ||||
converter.Dispose(); | ||||
return result; | ||||
|
@@ -29,7 +25,7 @@ internal static int Stat(ReadOnlySpan<char> path, out FileStatus output) | |||
|
||||
internal static int LStat(ReadOnlySpan<char> path, out FileStatus output) | ||||
{ | ||||
var converter = new ValueUtf8Converter(stackalloc byte[StackBufferSize]); | ||||
var converter = new ValueUtf8Converter(stackalloc byte[DefaultPathBufferSize]); | ||||
int result = LStat(ref MemoryMarshal.GetReference(converter.ConvertAndTerminateString(path)), out output); | ||||
converter.Dispose(); | ||||
return result; | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class Sys | ||
{ | ||
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_SymLink", SetLastError = true)] | ||
internal static extern int SymLink(string target, string linkPath); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class Kernel32 | ||
{ | ||
/// <summary> | ||
/// The link target is a directory. | ||
/// </summary> | ||
internal const int SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1; | ||
|
||
/// <summary> | ||
/// Allows creation of symbolic links from a process that is not elevated. Requires Windows 10 Insiders build 14972 or later. | ||
/// Developer Mode must first be enabled on the machine before this option will function. | ||
adamsitnik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// </summary> | ||
internal const int SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2; | ||
|
||
[DllImport(Libraries.Kernel32, EntryPoint = "CreateSymbolicLinkW", SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)] | ||
private static extern bool CreateSymbolicLinkPrivate(string lpSymlinkFileName, string lpTargetFileName, int dwFlags); | ||
|
||
/// <summary> | ||
/// Creates a symbolic link. | ||
/// </summary> | ||
/// <param name="symlinkFileName">The symbolic link to be created.</param> | ||
/// <param name="targetFileName">The name of the target for the symbolic link to be created. | ||
/// If it has a device name associated with it, the link is treated as an absolute link; otherwise, the link is treated as a relative link.</param> | ||
/// <param name="isDirectory"><see langword="true" /> if the link target is a directory; <see langword="false" /> otherwise.</param> | ||
internal static void CreateSymbolicLink(string symlinkFileName, string targetFileName, bool isDirectory) | ||
{ | ||
string originalPath = symlinkFileName; | ||
symlinkFileName = PathInternal.EnsureExtendedPrefixIfNeeded(symlinkFileName); | ||
targetFileName = PathInternal.EnsureExtendedPrefixIfNeeded(targetFileName); | ||
|
||
int flags = 0; | ||
|
||
bool isAtLeastWin10Build14972 = | ||
Environment.OSVersion.Version.Major == 10 && Environment.OSVersion.Version.Build >= 14972 || | ||
Environment.OSVersion.Version.Major >= 11; | ||
|
||
if (isAtLeastWin10Build14972) | ||
{ | ||
flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; | ||
} | ||
|
||
if (isDirectory) | ||
{ | ||
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY; | ||
} | ||
|
||
bool success = CreateSymbolicLinkPrivate(symlinkFileName, targetFileName, flags); | ||
|
||
int error; | ||
if (!success) | ||
{ | ||
throw Win32Marshal.GetExceptionForLastWin32Error(originalPath); | ||
} | ||
// In older versions we need to check GetLastWin32Error regardless of the return value of CreateSymbolicLink, | ||
// e.g: if the user doesn't have enough privileges to create a symlink the method returns success which we can consider as a silent failure. | ||
else if (!isAtLeastWin10Build14972 && (error = Marshal.GetLastWin32Error()) != 0) | ||
{ | ||
throw Win32Marshal.GetExceptionForWin32Error(error, originalPath); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class Kernel32 | ||
{ | ||
// https://docs.microsoft.com/windows/win32/api/winioctl/ni-winioctl-fsctl_get_reparse_point | ||
internal const int FSCTL_GET_REPARSE_POINT = 0x000900a8; | ||
jozkee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[DllImport(Libraries.Kernel32, EntryPoint = "DeviceIoControl", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)] | ||
adamsitnik marked this conversation as resolved.
Show resolved
Hide resolved
|
||
internal static extern bool DeviceIoControl( | ||
SafeHandle hDevice, | ||
uint dwIoControlCode, | ||
IntPtr lpInBuffer, | ||
uint nInBufferSize, | ||
byte[] lpOutBuffer, | ||
uint nOutBufferSize, | ||
out uint lpBytesReturned, | ||
IntPtr lpOverlapped); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.IO; | ||
using System.Runtime.InteropServices; | ||
using Microsoft.Win32.SafeHandles; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class Kernel32 | ||
{ | ||
internal const uint FILE_NAME_NORMALIZED = 0x0; | ||
|
||
// https://docs.microsoft.com/windows/desktop/api/fileapi/nf-fileapi-getfinalpathnamebyhandlew (kernel32) | ||
[DllImport(Libraries.Kernel32, EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)] | ||
internal static unsafe extern uint GetFinalPathNameByHandle( | ||
SafeFileHandle hFile, | ||
char* lpszFilePath, | ||
uint cchFilePath, | ||
uint dwFlags); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Runtime.InteropServices; | ||
|
||
internal static partial class Interop | ||
{ | ||
internal static partial class Kernel32 | ||
{ | ||
// https://docs.microsoft.com/windows-hardware/drivers/ifs/fsctl-get-reparse-point | ||
internal const int MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024; | ||
|
||
internal const uint SYMLINK_FLAG_RELATIVE = 1; | ||
|
||
// https://msdn.microsoft.com/library/windows/hardware/ff552012.aspx | ||
// We don't need all the struct fields; omitting the rest. | ||
[StructLayout(LayoutKind.Sequential)] | ||
internal unsafe struct REPARSE_DATA_BUFFER | ||
{ | ||
internal uint ReparseTag; | ||
internal ushort ReparseDataLength; | ||
internal ushort Reserved; | ||
internal SymbolicLinkReparseBuffer ReparseBufferSymbolicLink; | ||
|
||
[StructLayout(LayoutKind.Sequential)] | ||
internal struct SymbolicLinkReparseBuffer | ||
{ | ||
internal ushort SubstituteNameOffset; | ||
internal ushort SubstituteNameLength; | ||
internal ushort PrintNameOffset; | ||
internal ushort PrintNameLength; | ||
internal uint Flags; | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if the team has taken any measurements on this? What is typical path size?
I would choose half of the maximum i.e. 512 so as to always have no more than one fallback (bufferSize *= 2).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question, I don't know if we have data around this. Since this number is used in some other APIs (and this const is now consumed in those places) I would rather not make that change now. Feel free to open an issue though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: This will give us a new const for Unix, while we already have
Interop.Kernel32.MAX_PATH
for Windows. It would be nice to have one const for both platforms.