diff --git a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs index 39ba394842..1790cc72b5 100644 --- a/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs +++ b/src/System.Management.Automation/CoreCLR/CorePsPlatform.cs @@ -365,11 +365,6 @@ namespace System.Management.Automation // - only to be used with the IsWindows feature query, and only if // no other more specific feature query makes sense - internal static bool NonWindowsIsHardLink(ref IntPtr handle) - { - return Unix.IsHardLink(ref handle); - } - internal static bool NonWindowsIsHardLink(FileSystemInfo fileInfo) { return Unix.IsHardLink(fileInfo); @@ -716,15 +711,6 @@ namespace System.Management.Automation return (ErrorCategory)Unix.NativeMethods.GetErrorCategory(errno); } - /// Is this a hardlink. - /// The handle to a file. - /// A boolean that represents whether the item is a hardlink. - public static bool IsHardLink(ref IntPtr handle) - { - // TODO:PSL implement using fstat to query inode refcount to see if it is a hard link - return false; - } - /// Determine if the item is a hardlink. /// A FileSystemInfo to check to determine if it is a hardlink. /// A boolean that represents whether the item is a hardlink. diff --git a/src/System.Management.Automation/engine/Interop/Windows/CreateFile.cs b/src/System.Management.Automation/engine/Interop/Windows/CreateFile.cs new file mode 100644 index 0000000000..fa1552c91c --- /dev/null +++ b/src/System.Management.Automation/engine/Interop/Windows/CreateFile.cs @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#nullable enable + +using System; +using System.IO; +using System.Management.Automation; +using System.Runtime.InteropServices; + +using Microsoft.Win32.SafeHandles; + +internal static partial class Interop +{ + internal static unsafe partial class Windows + { + // dwDesiredAccess of CreateFile + [Flags] + internal enum FileDesiredAccess : uint + { + GenericZero = 0, + GenericRead = 0x80000000, + GenericWrite = 0x40000000, + GenericExecute = 0x20000000, + GenericAll = 0x10000000, + } + + // dwFlagsAndAttributes + [Flags] + internal enum FileAttributes : uint + { + Readonly = 0x00000001, + Hidden = 0x00000002, + System = 0x00000004, + Archive = 0x00000020, + Encrypted = 0x00004000, + Write_Through = 0x80000000, + Overlapped = 0x40000000, + NoBuffering = 0x20000000, + RandomAccess = 0x10000000, + SequentialScan = 0x08000000, + DeleteOnClose = 0x04000000, + BackupSemantics = 0x02000000, + PosixSemantics = 0x01000000, + OpenReparsePoint = 0x00200000, + OpenNoRecall = 0x00100000, + SessionAware = 0x00800000, + Normal = 0x00000080 + } + + // WARNING: This method does not implicitly handle long paths. Use CreateFile. + [LibraryImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + private static unsafe partial SafeFileHandle CreateFilePrivate( + string lpFileName, + uint dwDesiredAccess, + FileShare dwShareMode, + nint lpSecurityAttributes, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [LibraryImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] + private static unsafe partial nint CreateFileWithPipeHandlePrivate( + string lpFileName, + uint dwDesiredAccess, + FileShare dwShareMode, + nint lpSecurityAttributes, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + internal static unsafe SafeFileHandle CreateFileWithSafeFileHandle( + string lpFileName, + FileAccess dwDesiredAccess, + FileShare dwShareMode, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes) + { + lpFileName = Path.TrimEndingDirectorySeparator(lpFileName); + lpFileName = PathUtils.EnsureExtendedPrefixIfNeeded(lpFileName); + + return CreateFilePrivate(lpFileName, (uint)dwDesiredAccess, dwShareMode, nint.Zero, dwCreationDisposition, dwFlagsAndAttributes, nint.Zero); + } + + internal static unsafe nint CreateFileWithPipeHandle( + string lpFileName, + FileAccess dwDesiredAccess, + FileShare dwShareMode, + FileMode dwCreationDisposition, + FileAttributes dwFlagsAndAttributes) + { + return CreateFileWithPipeHandlePrivate(lpFileName, (uint)dwDesiredAccess, dwShareMode, nint.Zero, dwCreationDisposition, dwFlagsAndAttributes, nint.Zero); + } + } +} diff --git a/src/System.Management.Automation/engine/Interop/Windows/Errors.cs b/src/System.Management.Automation/engine/Interop/Windows/Errors.cs index d165493321..bef9e17219 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/Errors.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/Errors.cs @@ -9,6 +9,7 @@ internal static partial class Interop { // List of error constants https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes internal const int ERROR_SUCCESS = 0; + internal const int ERROR_FILE_NOT_FOUND = 2; internal const int ERROR_GEN_FAILURE = 31; internal const int ERROR_NOT_SUPPORTED = 50; internal const int ERROR_NO_NETWORK = 1222; diff --git a/src/System.Management.Automation/engine/Interop/Windows/FindExecutable.cs b/src/System.Management.Automation/engine/Interop/Windows/FindExecutable.cs index 7b52c317c8..2184e57a51 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/FindExecutable.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/FindExecutable.cs @@ -24,7 +24,6 @@ internal static partial class Interop // HINSTANCE == PVOID == nint nint resultCode = 0; - const int MAX_PATH = 260; Span buffer = stackalloc char[MAX_PATH]; unsafe { diff --git a/src/System.Management.Automation/engine/Interop/Windows/FindFirstFile.cs b/src/System.Management.Automation/engine/Interop/Windows/FindFirstFile.cs index b3cd84b028..1873ada4bf 100644 --- a/src/System.Management.Automation/engine/Interop/Windows/FindFirstFile.cs +++ b/src/System.Management.Automation/engine/Interop/Windows/FindFirstFile.cs @@ -4,6 +4,8 @@ #nullable enable using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Management.Automation; using System.Runtime.InteropServices; using Microsoft.Win32.SafeHandles; @@ -50,6 +52,14 @@ internal static partial class Interop // We use 'FindFirstFileW' instead of 'FindFirstFileExW' because the latter doesn't work correctly with Unicode file names on FAT32. // See https://github.com/PowerShell/PowerShell/issues/16804 [LibraryImport("api-ms-win-core-file-l1-1-0.dll", EntryPoint = "FindFirstFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] - internal static partial SafeFindHandle FindFirstFile(string lpFileName, ref WIN32_FIND_DATA lpFindFileData); + private static partial SafeFindHandle FindFirstFileW(string lpFileName, ref WIN32_FIND_DATA lpFindFileData); + + internal static SafeFindHandle FindFirstFile(string lpFileName, ref WIN32_FIND_DATA lpFindFileData) + { + lpFileName = Path.TrimEndingDirectorySeparator(lpFileName); + lpFileName = PathUtils.EnsureExtendedPrefixIfNeeded(lpFileName); + + return FindFirstFileW(lpFileName, ref lpFindFileData); + } } } diff --git a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs index 13ded67a04..e80c51fb81 100644 --- a/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs +++ b/src/System.Management.Automation/engine/remoting/common/RemoteSessionNamedPipe.cs @@ -190,26 +190,6 @@ namespace System.Management.Automation.Remoting internal const uint ERROR_IO_INCOMPLETE = 996; internal const uint ERROR_IO_PENDING = 997; - // File function constants - internal const uint GENERIC_READ = 0x80000000; - internal const uint GENERIC_WRITE = 0x40000000; - internal const uint GENERIC_EXECUTE = 0x20000000; - internal const uint GENERIC_ALL = 0x10000000; - - internal const uint CREATE_NEW = 1; - internal const uint CREATE_ALWAYS = 2; - internal const uint OPEN_EXISTING = 3; - internal const uint OPEN_ALWAYS = 4; - internal const uint TRUNCATE_EXISTING = 5; - - internal const uint SECURITY_IMPERSONATIONLEVEL_ANONYMOUS = 0; - internal const uint SECURITY_IMPERSONATIONLEVEL_IDENTIFICATION = 1; - internal const uint SECURITY_IMPERSONATIONLEVEL_IMPERSONATION = 2; - internal const uint SECURITY_IMPERSONATIONLEVEL_DELEGATION = 3; - - // Infinite timeout - internal const uint INFINITE = 0xFFFFFFFF; - #endregion #region Data structures @@ -265,15 +245,6 @@ namespace System.Management.Automation.Remoting return securityAttributes; } - [DllImport(PinvokeDllNames.CreateFileDllName, SetLastError = true, CharSet = CharSet.Unicode, CallingConvention = CallingConvention.StdCall)] - internal static extern SafePipeHandle CreateFile( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr SecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile); #endregion } @@ -1279,30 +1250,34 @@ namespace System.Management.Automation.Remoting /// protected override NamedPipeClientStream DoConnect(int timeout) { +#if UNIX + // TODO: `CreateFileWithSafePipeHandle` pinvoke below clearly says + // that the code is only for Windows and we could exclude + // a lot of code from compilation on Unix. + throw new NotSupportedException(nameof(DoConnect)); +#else // // WaitNamedPipe API is not supported by Windows Server container now, so we need to repeatedly // attempt connection to pipe server until timeout expires. // int startTime = Environment.TickCount; int elapsedTime = 0; - SafePipeHandle pipeHandle = null; + nint handle; do { // Get handle to pipe. - pipeHandle = NamedPipeNative.CreateFile( + handle = Interop.Windows.CreateFileWithPipeHandle( lpFileName: PipeName, - dwDesiredAccess: NamedPipeNative.GENERIC_READ | NamedPipeNative.GENERIC_WRITE, - dwShareMode: 0, - SecurityAttributes: IntPtr.Zero, - dwCreationDisposition: NamedPipeNative.OPEN_EXISTING, - dwFlagsAndAttributes: NamedPipeNative.FILE_FLAG_OVERLAPPED, - hTemplateFile: IntPtr.Zero); + FileAccess.ReadWrite, + FileShare.None, + FileMode.Open, + Interop.Windows.FileAttributes.Overlapped); - int lastError = Marshal.GetLastWin32Error(); - if (pipeHandle.IsInvalid) + if (handle == nint.Zero || handle == (nint)(-1)) { - if (lastError == NamedPipeNative.ERROR_FILE_NOT_FOUND) + int lastError = Marshal.GetLastPInvokeError(); + if (lastError == Interop.Windows.ERROR_FILE_NOT_FOUND) { elapsedTime = unchecked(Environment.TickCount - startTime); Thread.Sleep(100); @@ -1320,19 +1295,22 @@ namespace System.Management.Automation.Remoting } } while (elapsedTime < timeout); + SafePipeHandle pipeHandle = null; try { + pipeHandle = new SafePipeHandle(handle, ownsHandle: true); return new NamedPipeClientStream( PipeDirection.InOut, - true, - true, + isAsync: true, + isConnected: true, pipeHandle); } catch (Exception) { - pipeHandle.Dispose(); + pipeHandle?.Dispose(); throw; } +#endif } #endregion diff --git a/src/System.Management.Automation/namespaces/FileSystemProvider.cs b/src/System.Management.Automation/namespaces/FileSystemProvider.cs index 51d84f8f6a..7edf50b514 100644 --- a/src/System.Management.Automation/namespaces/FileSystemProvider.cs +++ b/src/System.Management.Automation/namespaces/FileSystemProvider.cs @@ -7497,13 +7497,6 @@ namespace Microsoft.PowerShell.Commands /// public static partial class InternalSymbolicLinkLinkCodeMethods { - // This size comes from measuring the size of the header of REPARSE_GUID_DATA_BUFFER - private const int REPARSE_GUID_DATA_BUFFER_HEADER_SIZE = 24; - - // Maximum reparse buffer info size. The max user defined reparse - // data is 16KB, plus there's a header. - private const int MAX_REPARSE_SIZE = (16 * 1024) + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE; - private const int FSCTL_GET_REPARSE_POINT = 0x000900A8; private const int FSCTL_SET_REPARSE_POINT = 0x000900A4; @@ -7518,62 +7511,6 @@ namespace Microsoft.PowerShell.Commands private const string NonInterpretedPathPrefix = @"\??\"; - private const int MAX_PATH = 260; - - [Flags] - // dwDesiredAccess of CreateFile - internal enum FileDesiredAccess : uint - { - GenericZero = 0, - GenericRead = 0x80000000, - GenericWrite = 0x40000000, - GenericExecute = 0x20000000, - GenericAll = 0x10000000, - } - - [Flags] - // dwShareMode of CreateFile - internal enum FileShareMode : uint - { - None = 0x00000000, - Read = 0x00000001, - Write = 0x00000002, - Delete = 0x00000004, - } - - // dwCreationDisposition of CreateFile - internal enum FileCreationDisposition : uint - { - New = 1, - CreateAlways = 2, - OpenExisting = 3, - OpenAlways = 4, - TruncateExisting = 5, - } - - [Flags] - // dwFlagsAndAttributes - internal enum FileAttributes : uint - { - Readonly = 0x00000001, - Hidden = 0x00000002, - System = 0x00000004, - Archive = 0x00000020, - Encrypted = 0x00004000, - Write_Through = 0x80000000, - Overlapped = 0x40000000, - NoBuffering = 0x20000000, - RandomAccess = 0x10000000, - SequentialScan = 0x08000000, - DeleteOnClose = 0x04000000, - BackupSemantics = 0x02000000, - PosixSemantics = 0x01000000, - OpenReparsePoint = 0x00200000, - OpenNoRecall = 0x00100000, - SessionAware = 0x00800000, - Normal = 0x00000080 - } - [StructLayout(LayoutKind.Sequential)] private struct REPARSE_DATA_BUFFER_SYMBOLICLINK { @@ -7626,29 +7563,6 @@ namespace Microsoft.PowerShell.Commands public uint dwHighDateTime; } - [StructLayout(LayoutKind.Sequential)] - private struct GUID - { - public uint Data1; - public ushort Data2; - public ushort Data3; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] - public char[] Data4; - } - - [StructLayout(LayoutKind.Sequential)] - private struct REPARSE_GUID_DATA_BUFFER - { - public uint ReparseTag; - public ushort ReparseDataLength; - public ushort Reserved; - public GUID ReparseGuid; - - [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_REPARSE_SIZE)] - public char[] DataBuffer; - } - [LibraryImport(PinvokeDllNames.DeviceIoControlDllName, StringMarshalling = StringMarshalling.Utf16, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static partial bool DeviceIoControl(IntPtr hDevice, uint dwIoControlCode, @@ -7662,16 +7576,6 @@ namespace Microsoft.PowerShell.Commands IntPtr hFile, out BY_HANDLE_FILE_INFORMATION lpFileInformation); - [LibraryImport(PinvokeDllNames.CreateFileDllName, EntryPoint = "CreateFileW", SetLastError = true, StringMarshalling = StringMarshalling.Utf16)] - internal static partial IntPtr CreateFile( - string lpFileName, - FileDesiredAccess dwDesiredAccess, - FileShareMode dwShareMode, - IntPtr lpSecurityAttributes, - FileCreationDisposition dwCreationDisposition, - FileAttributes dwFlagsAndAttributes, - IntPtr hTemplateFile); - /// /// Gets the target of the specified reparse point. /// @@ -7729,16 +7633,14 @@ namespace Microsoft.PowerShell.Commands private static string InternalGetLinkType(FileSystemInfo fileInfo) { - if (Platform.IsWindows) - { - return WinInternalGetLinkType(fileInfo.FullName); - } - else - { - return Platform.NonWindowsInternalGetLinkType(fileInfo); - } +#if UNIX + return Platform.NonWindowsInternalGetLinkType(fileInfo); +#else + return WinInternalGetLinkType(fileInfo.FullName); +#endif } +#if !UNIX [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] private static string WinInternalGetLinkType(string filePath) { @@ -7746,7 +7648,7 @@ namespace Microsoft.PowerShell.Commands // If this parameter is zero, the application can query certain metadata // such as file, directory, or device attributes without accessing // that file or device, even if GENERIC_READ access would have been denied. - using (SafeFileHandle handle = WinOpenReparsePoint(filePath, FileDesiredAccess.GenericZero)) + using (SafeFileHandle handle = WinOpenReparsePoint(filePath, (FileAccess)0)) { int outBufferSize = Marshal.SizeOf(); @@ -7778,7 +7680,7 @@ namespace Microsoft.PowerShell.Commands if (!result) { // It's not a reparse point or the file system doesn't support reparse points. - return IsHardLink(ref dangerousHandle) ? "HardLink" : null; + return WinIsHardLink(ref dangerousHandle) ? "HardLink" : null; } REPARSE_DATA_BUFFER_SYMBOLICLINK reparseDataBuffer = Marshal.PtrToStructure(outBuffer); @@ -7811,13 +7713,28 @@ namespace Microsoft.PowerShell.Commands } } } +#endif internal static bool IsHardLink(FileSystemInfo fileInfo) { #if UNIX return Platform.NonWindowsIsHardLink(fileInfo); #else - return WinIsHardLink(fileInfo); + bool isHardLink = false; + + // only check for hard link if the item is not directory + if ((fileInfo.Attributes & System.IO.FileAttributes.Directory) != System.IO.FileAttributes.Directory) + { + SafeFileHandle handle = Interop.Windows.CreateFileWithSafeFileHandle(fileInfo.FullName, FileAccess.Read, FileShare.Read, FileMode.Open, Interop.Windows.FileAttributes.Normal); + + using (handle) + { + var dangerousHandle = handle.DangerousGetHandle(); + isHardLink = InternalSymbolicLinkLinkCodeMethods.WinIsHardLink(ref dangerousHandle); + } + } + + return isHardLink; #endif } @@ -7838,13 +7755,7 @@ namespace Microsoft.PowerShell.Commands } Interop.Windows.WIN32_FIND_DATA data = default; - string fullPath = Path.TrimEndingDirectorySeparator(fileInfo.FullName); - if (fullPath.Length >= MAX_PATH) - { - fullPath = PathUtils.EnsureExtendedPrefix(fullPath); - } - - using (Interop.Windows.SafeFindHandle handle = Interop.Windows.FindFirstFile(fullPath, ref data)) + using (Interop.Windows.SafeFindHandle handle = Interop.Windows.FindFirstFile(fileInfo.FullName, ref data)) { if (handle.IsInvalid) { @@ -7877,43 +7788,6 @@ namespace Microsoft.PowerShell.Commands #endif } - internal static bool WinIsHardLink(FileSystemInfo fileInfo) - { - bool isHardLink = false; - - // only check for hard link if the item is not directory - if ((fileInfo.Attributes & System.IO.FileAttributes.Directory) != System.IO.FileAttributes.Directory) - { - IntPtr nativeHandle = InternalSymbolicLinkLinkCodeMethods.CreateFile( - fileInfo.FullName, - InternalSymbolicLinkLinkCodeMethods.FileDesiredAccess.GenericRead, - InternalSymbolicLinkLinkCodeMethods.FileShareMode.Read, - IntPtr.Zero, - InternalSymbolicLinkLinkCodeMethods.FileCreationDisposition.OpenExisting, - InternalSymbolicLinkLinkCodeMethods.FileAttributes.Normal, - IntPtr.Zero); - - using (SafeFileHandle handle = new SafeFileHandle(nativeHandle, true)) - { - bool success = false; - - try - { - handle.DangerousAddRef(ref success); - IntPtr dangerousHandle = handle.DangerousGetHandle(); - isHardLink = InternalSymbolicLinkLinkCodeMethods.IsHardLink(ref dangerousHandle); - } - finally - { - if (success) - handle.DangerousRelease(); - } - } - } - - return isHardLink; - } - internal static bool IsSameFileSystemItem(string pathOne, string pathTwo) { #if UNIX @@ -7926,13 +7800,10 @@ namespace Microsoft.PowerShell.Commands #if !UNIX private static bool WinIsSameFileSystemItem(string pathOne, string pathTwo) { - const FileAccess access = FileAccess.Read; - const FileShare share = FileShare.Read; - const FileMode creation = FileMode.Open; - const FileAttributes attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics; + const Interop.Windows.FileAttributes Attributes = Interop.Windows.FileAttributes.BackupSemantics | Interop.Windows.FileAttributes.PosixSemantics; - using (var sfOne = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathOne, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) - using (var sfTwo = AlternateDataStreamUtilities.NativeMethods.CreateFile(pathTwo, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) + using (var sfOne = Interop.Windows.CreateFileWithSafeFileHandle(pathOne, FileAccess.Read, FileShare.Read, FileMode.Open, Attributes)) + using (var sfTwo = Interop.Windows.CreateFileWithSafeFileHandle(pathTwo, FileAccess.Read, FileShare.Read, FileMode.Open, Attributes)) { if (!sfOne.IsInvalid && !sfTwo.IsInvalid) { @@ -7965,12 +7836,9 @@ namespace Microsoft.PowerShell.Commands #if !UNIX private static bool WinGetInodeData(string path, out System.ValueTuple inodeData) { - const FileAccess access = FileAccess.Read; - const FileShare share = FileShare.Read; - const FileMode creation = FileMode.Open; - const FileAttributes attributes = FileAttributes.BackupSemantics | FileAttributes.PosixSemantics; + const Interop.Windows.FileAttributes Attributes = Interop.Windows.FileAttributes.BackupSemantics | Interop.Windows.FileAttributes.PosixSemantics; - using (var sf = AlternateDataStreamUtilities.NativeMethods.CreateFile(path, access, share, IntPtr.Zero, creation, (int)attributes, IntPtr.Zero)) + using (var sf = Interop.Windows.CreateFileWithSafeFileHandle(path, FileAccess.Read, FileShare.Read, FileMode.Open, Attributes)) { if (!sf.IsInvalid) { @@ -7994,15 +7862,6 @@ namespace Microsoft.PowerShell.Commands } #endif - internal static bool IsHardLink(ref IntPtr handle) - { -#if UNIX - return Platform.NonWindowsIsHardLink(ref handle); -#else - return WinIsHardLink(ref handle); -#endif - } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] internal static bool WinIsHardLink(ref IntPtr handle) { @@ -8013,18 +7872,9 @@ namespace Microsoft.PowerShell.Commands internal static bool CreateJunction(string path, string target) { - // this is a purely Windows specific feature, no feature flag used for that reason. - if (Platform.IsWindows) - { - return WinCreateJunction(path, target); - } - +#if UNIX return false; - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods")] - private static bool WinCreateJunction(string path, string target) - { +#else if (string.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); @@ -8035,7 +7885,7 @@ namespace Microsoft.PowerShell.Commands throw new ArgumentNullException(nameof(target)); } - using (SafeHandle handle = WinOpenReparsePoint(path, FileDesiredAccess.GenericWrite)) + using (SafeHandle handle = WinOpenReparsePoint(path, FileAccess.Write)) { byte[] mountPointBytes = Encoding.Unicode.GetBytes(NonInterpretedPathPrefix + Path.GetFullPath(target)); @@ -8082,25 +7932,27 @@ namespace Microsoft.PowerShell.Commands } } } +#endif } - private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileDesiredAccess accessMode) +#if !UNIX + private static SafeFileHandle WinOpenReparsePoint(string reparsePoint, FileAccess accessMode) { - IntPtr nativeHandle = CreateFile(reparsePoint, accessMode, - FileShareMode.Read | FileShareMode.Write | FileShareMode.Delete, - IntPtr.Zero, FileCreationDisposition.OpenExisting, - FileAttributes.BackupSemantics | FileAttributes.OpenReparsePoint, - IntPtr.Zero); + const Interop.Windows.FileAttributes Attributes = Interop.Windows.FileAttributes.BackupSemantics | Interop.Windows.FileAttributes.OpenReparsePoint; - int lastError = Marshal.GetLastWin32Error(); + SafeFileHandle reparsePointHandle = Interop.Windows.CreateFileWithSafeFileHandle(reparsePoint, accessMode, FileShare.ReadWrite | FileShare.Delete, FileMode.Open, Attributes); - if (lastError != 0) + if (reparsePointHandle.IsInvalid) + { + // Save last error since Dispose() will do another pinvoke. + int lastError = Marshal.GetLastPInvokeError(); + reparsePointHandle.Dispose(); throw new Win32Exception(lastError); - - SafeFileHandle reparsePointHandle = new SafeFileHandle(nativeHandle, true); + } return reparsePointHandle; } +#endif } #endregion diff --git a/src/System.Management.Automation/utils/PathUtils.cs b/src/System.Management.Automation/utils/PathUtils.cs index 48d0acb5f4..558ca25f2f 100644 --- a/src/System.Management.Automation/utils/PathUtils.cs +++ b/src/System.Management.Automation/utils/PathUtils.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Management.Automation.Internal; @@ -473,11 +474,35 @@ namespace System.Management.Automation } #region Helpers for long paths from .Net Runtime - + // Code here is copied from .NET's internal path helper implementation: // https://github.com/dotnet/runtime/blob/dcce0f56e10f5ac9539354b049341a2d7c0cdebf/src/libraries/System.Private.CoreLib/src/System/IO/PathInternal.Windows.cs // It has been left as a verbatim copy. +#nullable enable + + /// + /// Adds the extended path prefix (\\?\) if not already a device path, IF the path is not relative, + /// AND the path is more than 259 characters. (> MAX_PATH + null). This will also insert the extended + /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten + /// away from paths during normalization, but if we see such a path at this point it should be + /// normalized and has retained the final characters. (Typically from one of the *Info classes). + /// + /// File path. + /// File path (with extended prefix if the path is long path). + [return: NotNullIfNotNull(nameof(path))] + internal static string? EnsureExtendedPrefixIfNeeded(string? path) + { + if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path))) + { + return EnsureExtendedPrefix(path); + } + else + { + return path; + } + } + internal static string EnsureExtendedPrefix(string path) { if (IsPartiallyQualified(path) || IsDevice(path)) @@ -495,10 +520,22 @@ namespace System.Management.Automation private const string UncDevicePrefixToInsert = @"?\UNC\"; private const string UncExtendedPathPrefix = @"\\?\UNC\"; private const string DevicePathPrefix = @"\\.\"; + private const int MaxShortPath = 260; // \\?\, \\.\, \??\ private const int DevicePrefixLength = 4; + private static bool EndsWithPeriodOrSpace(string? path) + { + if (string.IsNullOrEmpty(path)) + { + return false; + } + + char c = path[path.Length - 1]; + return c == ' ' || c == '.'; + } + /// /// Returns true if the given character is a valid drive letter ///