summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--clr.coreclr.props2
-rw-r--r--src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs74
-rw-r--r--src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs21
-rw-r--r--src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs17
-rw-r--r--src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs73
-rw-r--r--src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs26
-rw-r--r--src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs19
-rw-r--r--src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs18
-rw-r--r--src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs18
-rw-r--r--src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs16
-rw-r--r--src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs16
-rw-r--r--src/mscorlib/corefx/SR.cs30
-rw-r--r--src/mscorlib/corefx/System/IO/Path.Unix.cs256
-rw-r--r--src/mscorlib/corefx/System/IO/Path.Win32.cs36
-rw-r--r--src/mscorlib/corefx/System/IO/Path.Windows.cs153
-rw-r--r--src/mscorlib/corefx/System/IO/Path.cs578
-rw-r--r--src/mscorlib/corefx/System/IO/PathHelper.Windows.cs (renamed from src/mscorlib/src/System/IO/LongPathHelper.cs)204
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs75
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.Unix.cs122
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs89
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.Windows.cs482
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.cs230
-rw-r--r--src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs157
-rw-r--r--src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs109
-rw-r--r--src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs97
-rw-r--r--src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs355
-rw-r--r--src/mscorlib/mscorlib.shared.sources.props48
-rw-r--r--src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs43
-rw-r--r--src/mscorlib/src/System/AppDomain.cs13
-rw-r--r--src/mscorlib/src/System/AppDomainSetup.cs2
-rw-r--r--src/mscorlib/src/System/CfgParser.cs2
-rw-r--r--src/mscorlib/src/System/IO/Directory.cs61
-rw-r--r--src/mscorlib/src/System/IO/DirectoryInfo.cs12
-rw-r--r--src/mscorlib/src/System/IO/DriveInfo.cs2
-rw-r--r--src/mscorlib/src/System/IO/File.cs42
-rw-r--r--src/mscorlib/src/System/IO/FileInfo.cs6
-rw-r--r--src/mscorlib/src/System/IO/FileSecurityState.cs6
-rw-r--r--src/mscorlib/src/System/IO/FileStream.cs2695
-rw-r--r--src/mscorlib/src/System/IO/FileSystemEnumerable.cs28
-rw-r--r--src/mscorlib/src/System/IO/FileSystemInfo.cs4
-rw-r--r--src/mscorlib/src/System/IO/Path.cs1433
-rw-r--r--src/mscorlib/src/System/IO/PathHelper.cs448
-rw-r--r--src/mscorlib/src/System/IO/PathInternal.cs822
-rw-r--r--src/mscorlib/src/System/IO/__Error.cs4
-rw-r--r--src/mscorlib/src/System/Reflection/Assembly.cs12
-rw-r--r--src/mscorlib/src/System/Reflection/AssemblyName.cs2
-rw-r--r--src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs4
-rw-r--r--src/mscorlib/src/System/Reflection/Module.cs2
-rw-r--r--src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs12
-rw-r--r--src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs12
-rw-r--r--src/mscorlib/src/System/Security/Util/StringExpressionSet.cs6
-rw-r--r--src/mscorlib/src/System/Security/Util/URLString.cs14
-rw-r--r--src/mscorlib/src/System/Threading/Mutex.cs4
-rw-r--r--src/mscorlib/src/mscorlib.txt1
54 files changed, 3271 insertions, 5742 deletions
diff --git a/clr.coreclr.props b/clr.coreclr.props
index 8ff0f0b263..516f4ea311 100644
--- a/clr.coreclr.props
+++ b/clr.coreclr.props
@@ -82,6 +82,8 @@
<FeatureCoreFxFileStream>true</FeatureCoreFxFileStream>
<FeatureCoreFxShim>true</FeatureCoreFxShim>
<FeatureCoreFxOverlapped>true</FeatureCoreFxOverlapped>
+ <FeatureCoreFxPath>true</FeatureCoreFxPath>
+ <FeatureCoreFxInteropServices>true</FeatureCoreFxInteropServices>
<!-- We only want to use the CoreFx implementation of SecureString when we are not building the legacy surface area -->
<FeatureCoreFxSecureString Condition="'$(FeatureLegacySurface)'==''" >true</FeatureCoreFxSecureString>
diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs
new file mode 100644
index 0000000000..724e342342
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs
@@ -0,0 +1,74 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetCwd", SetLastError = true)]
+ private static unsafe extern byte* GetCwd(byte* buffer, int bufferLength);
+
+ internal static unsafe string GetCwd()
+ {
+ const int StackLimit = 256;
+
+ // First try to get the path into a buffer on the stack
+ byte* stackBuf = stackalloc byte[StackLimit];
+ string result = GetCwdHelper(stackBuf, StackLimit);
+ if (result != null)
+ {
+ return result;
+ }
+
+ // If that was too small, try increasing large buffer sizes
+ // until we get one that works or until we hit MaxPath.
+ int maxPath = Interop.Sys.MaxPath;
+ if (StackLimit < maxPath)
+ {
+ int bufferSize = StackLimit;
+ do
+ {
+ checked { bufferSize *= 2; }
+ var buf = new byte[Math.Min(bufferSize, maxPath)];
+ fixed (byte* ptr = buf)
+ {
+ result = GetCwdHelper(ptr, buf.Length);
+ if (result != null)
+ {
+ return result;
+ }
+ }
+ }
+ while (bufferSize < maxPath);
+ }
+
+ // If we couldn't get the cwd with a MaxPath-sized buffer, something's wrong.
+ throw Interop.GetExceptionForIoErrno(new ErrorInfo(Interop.Error.ENAMETOOLONG));
+ }
+
+ private static unsafe string GetCwdHelper(byte* ptr, int bufferSize)
+ {
+ // Call the real getcwd
+ byte* result = GetCwd(ptr, bufferSize);
+
+ // If it returned non-null, the null-terminated path is in the buffer
+ if (result != null)
+ {
+ return Marshal.PtrToStringAnsi((IntPtr)ptr);
+ }
+
+ // Otherwise, if it failed due to the buffer being too small, return null;
+ // for anything else, throw.
+ ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+ if (errorInfo.Error == Interop.Error.ERANGE)
+ {
+ return null;
+ }
+ throw Interop.GetExceptionForIoErrno(errorInfo);
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs
new file mode 100644
index 0000000000..33664c4d39
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetUnixName")]
+ private static extern IntPtr GetUnixNamePrivate();
+
+ internal static string GetUnixName()
+ {
+ IntPtr ptr = GetUnixNamePrivate();
+ return Marshal.PtrToStringAnsi(ptr);
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs
new file mode 100644
index 0000000000..b8694d9007
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_MksTemps", SetLastError = true)]
+ internal static extern IntPtr MksTemps(
+ byte[] template,
+ int suffixlen);
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs
new file mode 100644
index 0000000000..4a1fcf67d0
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs
@@ -0,0 +1,73 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal static partial class Interop
+{
+ internal static partial class Sys
+ {
+ internal static int DEFAULT_PC_NAME_MAX = 255;
+
+ internal enum PathConfName : int
+ {
+ PC_LINK_MAX = 1,
+ PC_MAX_CANON = 2,
+ PC_MAX_INPUT = 3,
+ PC_NAME_MAX = 4,
+ PC_PATH_MAX = 5,
+ PC_PIPE_BUF = 6,
+ PC_CHOWN_RESTRICTED = 7,
+ PC_NO_TRUNC = 8,
+ PC_VDISABLE = 9,
+ }
+
+ /// <summary>The maximum path length for the system. -1 if it hasn't yet been initialized.</summary>
+ private static int s_maxPath = -1;
+
+ /// <summary>The maximum name length for the system. -1 if it hasn't yet been initialized.</summary>
+ private static int s_maxName = -1;
+
+ internal static int MaxPath
+ {
+ get
+ {
+ // Benign race condition on cached value
+ if (s_maxPath < 0)
+ {
+ // GetMaximumPath returns a long from PathConf
+ // but our callers expect an int so we need to convert.
+ long temp = GetMaximumPath();
+ if (temp > int.MaxValue)
+ s_maxPath = int.MaxValue;
+ else
+ s_maxPath = Convert.ToInt32(temp);
+ }
+ return s_maxPath;
+ }
+ }
+
+ internal static int MaxName
+ {
+ get
+ {
+ // Benign race condition on cached value
+ if (s_maxName < 0)
+ {
+ int result = PathConf("/", PathConfName.PC_NAME_MAX);
+ s_maxName = result >= 0 ? result : DEFAULT_PC_NAME_MAX;
+ }
+
+ return s_maxName;
+ }
+ }
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_PathConf", SetLastError = true)]
+ private static extern int PathConf(string path, PathConfName name);
+
+ [DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_GetMaximumPath")]
+ private static extern long GetMaximumPath();
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs
new file mode 100644
index 0000000000..d2ce4131b0
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs
@@ -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.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class BCrypt
+ {
+ internal static unsafe NTSTATUS BCryptGenRandom(byte* pbBuffer, int count)
+ {
+ Debug.Assert(pbBuffer != null);
+ Debug.Assert(count >= 0);
+
+ return BCryptGenRandom(IntPtr.Zero, pbBuffer, count, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ }
+
+ private const int BCRYPT_USE_SYSTEM_PREFERRED_RNG = 0x00000002;
+
+ [DllImport(Libraries.BCrypt, CharSet = CharSet.Unicode)]
+ private static unsafe extern NTSTATUS BCryptGenRandom(IntPtr hAlgorithm, byte* pbBuffer, int cbBuffer, int dwFlags);
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs
new file mode 100644
index 0000000000..49d674f399
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+
+internal partial class Interop
+{
+ internal partial class BCrypt
+ {
+ internal enum NTSTATUS : uint
+ {
+ STATUS_SUCCESS = 0x0,
+ STATUS_NOT_FOUND = 0xc0000225,
+ STATUS_INVALID_PARAMETER = 0xc000000d,
+ STATUS_NO_MEMORY = 0xc0000017,
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs
new file mode 100644
index 0000000000..a34cc33db3
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+internal partial class Interop
+{
+ internal partial class mincore
+ {
+ /// <summary>
+ /// WARNING: This method does not implicitly handle long paths. Use GetFullPathName or PathHelper.
+ /// </summary>
+ [DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
+ unsafe internal static extern uint GetFullPathNameW(char* path, uint numBufferChars, SafeHandle buffer, IntPtr mustBeZero);
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs
new file mode 100644
index 0000000000..d50db6650b
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs
@@ -0,0 +1,18 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Runtime.InteropServices;
+
+partial class Interop
+{
+ partial class mincore
+ {
+ /// <summary>
+ /// WARNING: This method does not implicitly handle long paths. Use GetFullPath/PathHelper.
+ /// </summary>
+ [DllImport(Libraries.CoreFile_L1, SetLastError = true, CharSet = CharSet.Unicode, BestFitMapping = false, ExactSpelling = true)]
+ internal static extern uint GetLongPathNameW(SafeHandle lpszShortPath, SafeHandle lpszLongPath, uint cchBuffer);
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs
new file mode 100644
index 0000000000..f06d11be52
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Text;
+using System.Runtime.InteropServices;
+
+partial class Interop
+{
+ partial class mincore
+ {
+ [DllImport(Libraries.CoreFile_L1, CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false)]
+ internal static extern uint GetTempFileNameW(string tmpPath, string prefix, uint uniqueIdOrZero, [Out]StringBuilder tmpFileName);
+ }
+}
diff --git a/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs
new file mode 100644
index 0000000000..0ccc27c9ec
--- /dev/null
+++ b/src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Text;
+using System.Runtime.InteropServices;
+
+partial class Interop
+{
+ partial class mincore
+ {
+ [DllImport(Libraries.CoreFile_L1_2, CharSet = CharSet.Unicode, BestFitMapping = false)]
+ internal static extern uint GetTempPathW(int bufferLen, [Out]StringBuilder buffer);
+ }
+}
diff --git a/src/mscorlib/corefx/SR.cs b/src/mscorlib/corefx/SR.cs
index d234c9d5f3..d820613f7e 100644
--- a/src/mscorlib/corefx/SR.cs
+++ b/src/mscorlib/corefx/SR.cs
@@ -551,6 +551,36 @@ internal static class SR
get { return Environment.GetResourceString("ArgumentException_BufferNotFromPool"); }
}
+ public static string Argument_InvalidPathChars
+ {
+ get { return Environment.GetResourceString("Argument_InvalidPathChars"); }
+ }
+
+ public static string Argument_PathFormatNotSupported
+ {
+ get { return Environment.GetResourceString("Argument_PathFormatNotSupported"); }
+ }
+
+ public static string Arg_PathIllegal
+ {
+ get { return Environment.GetResourceString("Arg_PathIllegal"); }
+ }
+
+ public static string Arg_PathIllegalUNC
+ {
+ get { return Environment.GetResourceString("Arg_PathIllegalUNC"); }
+ }
+
+ public static string Arg_InvalidSearchPattern
+ {
+ get { return Environment.GetResourceString("Arg_InvalidSearchPattern"); }
+ }
+
+ public static string InvalidOperation_Cryptography
+ {
+ get { return Environment.GetResourceString("InvalidOperation_Cryptography"); }
+ }
+
public static string Format(string formatString, params object[] args)
{
return string.Format(CultureInfo.CurrentCulture, formatString, args);
diff --git a/src/mscorlib/corefx/System/IO/Path.Unix.cs b/src/mscorlib/corefx/System/IO/Path.Unix.cs
new file mode 100644
index 0000000000..2dd1907007
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/Path.Unix.cs
@@ -0,0 +1,256 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace System.IO
+{
+ public static partial class Path
+ {
+ public static readonly char DirectorySeparatorChar = '/';
+ public static readonly char VolumeSeparatorChar = '/';
+ public static readonly char PathSeparator = ':';
+
+ private const string DirectorySeparatorCharAsString = "/";
+
+ public static char[] GetInvalidFileNameChars() => new char[] { '\0', '/' };
+
+ internal static readonly int MaxPath = Interop.Sys.MaxPath;
+ private static readonly int MaxLongPath = MaxPath;
+
+ private static readonly bool s_isMac = Interop.Sys.GetUnixName() == "OSX";
+
+ // Expands the given path to a fully qualified path.
+ public static string GetFullPath(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ if (path.Length == 0)
+ throw new ArgumentException(SR.Arg_PathIllegal);
+
+ PathInternal.CheckInvalidPathChars(path);
+
+ // Expand with current directory if necessary
+ if (!IsPathRooted(path))
+ {
+ path = Combine(Interop.Sys.GetCwd(), path);
+ }
+
+ // We would ideally use realpath to do this, but it resolves symlinks, requires that the file actually exist,
+ // and turns it into a full path, which we only want if fullCheck is true.
+ string collapsedString = RemoveRelativeSegments(path);
+
+ Debug.Assert(collapsedString.Length < path.Length || collapsedString.ToString() == path,
+ "Either we've removed characters, or the string should be unmodified from the input path.");
+
+ if (collapsedString.Length > MaxPath)
+ {
+ throw new PathTooLongException(SR.IO_PathTooLong);
+ }
+
+ string result = collapsedString.Length == 0 ? DirectorySeparatorCharAsString : collapsedString;
+
+ return result;
+ }
+
+ /// <summary>
+ /// Try to remove relative segments from the given path (without combining with a root).
+ /// </summary>
+ /// <param name="skip">Skip the specified number of characters before evaluating.</param>
+ private static string RemoveRelativeSegments(string path, int skip = 0)
+ {
+ bool flippedSeparator = false;
+
+ // Remove "//", "/./", and "/../" from the path by copying each character to the output,
+ // except the ones we're removing, such that the builder contains the normalized path
+ // at the end.
+ var sb = StringBuilderCache.Acquire(path.Length);
+ if (skip > 0)
+ {
+ sb.Append(path, 0, skip);
+ }
+
+ int componentCharCount = 0;
+ for (int i = skip; i < path.Length; i++)
+ {
+ char c = path[i];
+
+ if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length)
+ {
+ componentCharCount = 0;
+
+ // Skip this character if it's a directory separator and if the next character is, too,
+ // e.g. "parent//child" => "parent/child"
+ if (PathInternal.IsDirectorySeparator(path[i + 1]))
+ {
+ continue;
+ }
+
+ // Skip this character and the next if it's referring to the current directory,
+ // e.g. "parent/./child" =? "parent/child"
+ if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) &&
+ path[i + 1] == '.')
+ {
+ i++;
+ continue;
+ }
+
+ // Skip this character and the next two if it's referring to the parent directory,
+ // e.g. "parent/child/../grandchild" => "parent/grandchild"
+ if (i + 2 < path.Length &&
+ (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) &&
+ path[i + 1] == '.' && path[i + 2] == '.')
+ {
+ // Unwind back to the last slash (and if there isn't one, clear out everything).
+ int s;
+ for (s = sb.Length - 1; s >= 0; s--)
+ {
+ if (PathInternal.IsDirectorySeparator(sb[s]))
+ {
+ sb.Length = s;
+ break;
+ }
+ }
+ if (s < 0)
+ {
+ sb.Length = 0;
+ }
+
+ i += 2;
+ continue;
+ }
+ }
+
+ if (++componentCharCount > PathInternal.MaxComponentLength)
+ {
+ throw new PathTooLongException(SR.IO_PathTooLong);
+ }
+
+ // Normalize the directory separator if needed
+ if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar)
+ {
+ c = Path.DirectorySeparatorChar;
+ flippedSeparator = true;
+ }
+
+ sb.Append(c);
+ }
+
+ if (flippedSeparator || sb.Length != path.Length)
+ {
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+ else
+ {
+ // We haven't changed the source path, return the original
+ StringBuilderCache.Release(sb);
+ return path;
+ }
+ }
+
+ private static string RemoveLongPathPrefix(string path)
+ {
+ return path; // nop. There's nothing special about "long" paths on Unix.
+ }
+
+ public static string GetTempPath()
+ {
+ const string TempEnvVar = "TMPDIR";
+ const string DefaultTempPath = "/tmp/";
+
+ // Get the temp path from the TMPDIR environment variable.
+ // If it's not set, just return the default path.
+ // If it is, return it, ensuring it ends with a slash.
+ string path = Environment.GetEnvironmentVariable(TempEnvVar);
+ return
+ string.IsNullOrEmpty(path) ? DefaultTempPath :
+ PathInternal.IsDirectorySeparator(path[path.Length - 1]) ? path :
+ path + DirectorySeparatorChar;
+ }
+
+ public static string GetTempFileName()
+ {
+ const string Suffix = ".tmp";
+ const int SuffixByteLength = 4;
+
+ // mkstemps takes a char* and overwrites the XXXXXX with six characters
+ // that'll result in a unique file name.
+ string template = GetTempPath() + "tmpXXXXXX" + Suffix + "\0";
+ byte[] name = Encoding.UTF8.GetBytes(template);
+
+ // Create, open, and close the temp file.
+ IntPtr fd = Interop.CheckIo(Interop.Sys.MksTemps(name, SuffixByteLength));
+ Interop.Sys.Close(fd); // ignore any errors from close; nothing to do if cleanup isn't possible
+
+ // 'name' is now the name of the file
+ Debug.Assert(name[name.Length - 1] == '\0');
+ return Encoding.UTF8.GetString(name, 0, name.Length - 1); // trim off the trailing '\0'
+ }
+
+ public static bool IsPathRooted(string path)
+ {
+ if (path == null)
+ return false;
+
+ PathInternal.CheckInvalidPathChars(path);
+ return path.Length > 0 && path[0] == DirectorySeparatorChar;
+ }
+
+ public static string GetPathRoot(string path)
+ {
+ if (path == null) return null;
+ return IsPathRooted(path) ? DirectorySeparatorCharAsString : String.Empty;
+ }
+
+ private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount)
+ {
+#if FEATURE_CORECLR
+ // We want to avoid dependencies on the Crypto library when compiling in CoreCLR. This
+ // will use the existing PAL implementation.
+ byte[] buffer = new byte[KeyLength];
+ Microsoft.Win32.Win32Native.Random(bStrong: true, buffer: buffer, length: KeyLength);
+ Runtime.InteropServices.Marshal.Copy(buffer, 0, (IntPtr)bytes, KeyLength);
+#else
+ if (s_isMac)
+ {
+ GetCryptoRandomBytesApple(bytes, byteCount);
+ }
+ else
+ {
+ GetCryptoRandomBytesOpenSsl(bytes, byteCount);
+ }
+#endif
+ }
+
+#if !FEATURE_CORECLR
+ private static unsafe void GetCryptoRandomBytesApple(byte* bytes, int byteCount)
+ {
+ Debug.Assert(bytes != null);
+ Debug.Assert(byteCount >= 0);
+
+ if (Interop.CommonCrypto.CCRandomGenerateBytes(bytes, byteCount) != 0)
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_Cryptography);
+ }
+ }
+
+ private static unsafe void GetCryptoRandomBytesOpenSsl(byte* bytes, int byteCount)
+ {
+ Debug.Assert(bytes != null);
+ Debug.Assert(byteCount >= 0);
+
+ if (!Interop.Crypto.GetRandomBytes(bytes, byteCount))
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_Cryptography);
+ }
+ }
+#endif
+
+ /// <summary>Gets whether the system is case-sensitive.</summary>
+ internal static bool IsCaseSensitive { get { return !s_isMac; } }
+ }
+}
diff --git a/src/mscorlib/corefx/System/IO/Path.Win32.cs b/src/mscorlib/corefx/System/IO/Path.Win32.cs
new file mode 100644
index 0000000000..8a9e62e6e5
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/Path.Win32.cs
@@ -0,0 +1,36 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.IO
+{
+ public static partial class Path
+ {
+ private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount)
+ {
+ // We need to fill a byte array with cryptographically-strong random bytes, but we can't reference
+ // System.Security.Cryptography.RandomNumberGenerator.dll due to layering. Instead, we just
+ // call to BCryptGenRandom directly, which is all that RandomNumberGenerator does.
+
+ Debug.Assert(bytes != null);
+ Debug.Assert(byteCount >= 0);
+
+ Interop.BCrypt.NTSTATUS status = Interop.BCrypt.BCryptGenRandom(bytes, byteCount);
+ if (status == Interop.BCrypt.NTSTATUS.STATUS_SUCCESS)
+ {
+ return;
+ }
+ else if (status == Interop.BCrypt.NTSTATUS.STATUS_NO_MEMORY)
+ {
+ throw new OutOfMemoryException();
+ }
+ else
+ {
+ Debug.Fail("BCryptGenRandom should only fail due to OOM or invalid args / handle inputs.");
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/IO/Path.Windows.cs b/src/mscorlib/corefx/System/IO/Path.Windows.cs
new file mode 100644
index 0000000000..b597efc54e
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/Path.Windows.cs
@@ -0,0 +1,153 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.IO
+{
+ public static partial class Path
+ {
+ public static readonly char DirectorySeparatorChar = '\\';
+ public static readonly char VolumeSeparatorChar = ':';
+ public static readonly char PathSeparator = ';';
+
+ private const string DirectorySeparatorCharAsString = "\\";
+
+ public static char[] GetInvalidFileNameChars() => new char[]
+ {
+ '\"', '<', '>', '|', '\0',
+ (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
+ (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
+ (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
+ (char)31, ':', '*', '?', '\\', '/'
+ };
+
+ // The max total path is 260, and the max individual component length is 255.
+ // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars.
+ internal static readonly int MaxPath = 260;
+ internal static readonly int MaxLongPath = short.MaxValue;
+
+ // Expands the given path to a fully qualified path.
+ public static string GetFullPath(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ // Embedded null characters are the only invalid character case we want to check up front.
+ // This is because the nulls will signal the end of the string to Win32 and therefore have
+ // unpredictable results. Other invalid characters we give a chance to be normalized out.
+ if (path.IndexOf('\0') != -1)
+ throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
+
+ if (PathInternal.IsExtended(path))
+ {
+ // We can't really know what is valid for all cases of extended paths.
+ //
+ // - object names can include other characters as well (':', '/', etc.)
+ // - even file objects have different rules (pipe names can contain most characters)
+ //
+ // As such we will do no further analysis of extended paths to avoid blocking known and unknown
+ // scenarios as well as minimizing compat breaks should we block now and need to unblock later.
+ return path;
+ }
+
+ bool isDevice = PathInternal.IsDevice(path);
+ if (!isDevice)
+ {
+ // Toss out paths with colons that aren't a valid drive specifier.
+ // Cannot start with a colon and can only be of the form "C:".
+ // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
+ int startIndex = PathInternal.PathStartSkip(path);
+
+ // Move past the colon
+ startIndex += 2;
+
+ if ((path.Length > 0 && path[0] == VolumeSeparatorChar)
+ || (path.Length >= startIndex && path[startIndex - 1] == VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2]))
+ || (path.Length > startIndex && path.IndexOf(VolumeSeparatorChar, startIndex) != -1))
+ {
+ throw new NotSupportedException(SR.Argument_PathFormatNotSupported);
+ }
+ }
+
+ // Technically this doesn't matter but we used to throw for this case
+ if (string.IsNullOrWhiteSpace(path))
+ throw new ArgumentException(SR.Arg_PathIllegal);
+
+ // We don't want to check invalid characters for device format- see comments for extended above
+ string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true);
+
+ if (!isDevice)
+ {
+ // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked)
+ if (PathInternal.HasWildCardCharacters(fullPath))
+ throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
+ }
+
+ return fullPath;
+ }
+
+ public static string GetTempPath()
+ {
+ StringBuilder sb = StringBuilderCache.Acquire(MaxPath);
+ uint r = Interop.mincore.GetTempPathW(MaxPath, sb);
+ if (r == 0)
+ throw Win32Marshal.GetExceptionForLastWin32Error();
+ return GetFullPath(StringBuilderCache.GetStringAndRelease(sb));
+ }
+
+ // Returns a unique temporary file name, and creates a 0-byte file by that
+ // name on disk.
+ public static string GetTempFileName()
+ {
+ string path = GetTempPath();
+
+ StringBuilder sb = StringBuilderCache.Acquire(MaxPath);
+ uint r = Interop.mincore.GetTempFileNameW(path, "tmp", 0, sb);
+ if (r == 0)
+ throw Win32Marshal.GetExceptionForLastWin32Error();
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
+ // Tests if the given path contains a root. A path is considered rooted
+ // if it starts with a backslash ("\") or a drive letter and a colon (":").
+ public static bool IsPathRooted(string path)
+ {
+ if (path != null)
+ {
+ PathInternal.CheckInvalidPathChars(path);
+
+ int length = path.Length;
+ if ((length >= 1 && PathInternal.IsDirectorySeparator(path[0])) ||
+ (length >= 2 && path[1] == VolumeSeparatorChar))
+ return true;
+ }
+ return false;
+ }
+
+ // Returns the root portion of the given path. The resulting string
+ // consists of those rightmost characters of the path that constitute the
+ // root of the path. Possible patterns for the resulting string are: An
+ // empty string (a relative path on the current drive), "\" (an absolute
+ // path on the current drive), "X:" (a relative path on a given drive,
+ // where X is the drive letter), "X:\" (an absolute path on a given drive),
+ // and "\\server\share" (a UNC path for a given server and share name).
+ // The resulting string is null if path is null.
+ public static string GetPathRoot(string path)
+ {
+ if (path == null) return null;
+ PathInternal.CheckInvalidPathChars(path);
+
+ // Need to return the normalized directory separator
+ path = PathInternal.NormalizeDirectorySeparators(path);
+
+ int pathRoot = PathInternal.GetRootLength(path);
+ return pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot);
+ }
+
+ /// <summary>Gets whether the system is case-sensitive.</summary>
+ internal static bool IsCaseSensitive { get { return false; } }
+ }
+}
diff --git a/src/mscorlib/corefx/System/IO/Path.cs b/src/mscorlib/corefx/System/IO/Path.cs
new file mode 100644
index 0000000000..3b1ba6b07d
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/Path.cs
@@ -0,0 +1,578 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Text;
+
+namespace System.IO
+{
+ // Provides methods for processing file system strings in a cross-platform manner.
+ // Most of the methods don't do a complete parsing (such as examining a UNC hostname),
+ // but they will handle most string operations.
+ public static partial class Path
+ {
+ // Platform specific alternate directory separator character.
+ // There is only one directory separator char on Unix, which is the same
+ // as the alternate separator on Windows, so same definition is used for both.
+ public static readonly char AltDirectorySeparatorChar = '/';
+
+ // For generating random file names
+ // 8 random bytes provides 12 chars in our encoding for the 8.3 name.
+ const int KeyLength = 8;
+
+ [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")]
+ public static readonly char[] InvalidPathChars = GetInvalidPathChars();
+
+ // Changes the extension of a file path. The path parameter
+ // specifies a file path, and the extension parameter
+ // specifies a file extension (with a leading period, such as
+ // ".exe" or ".cs").
+ //
+ // The function returns a file path with the same root, directory, and base
+ // name parts as path, but with the file extension changed to
+ // the specified extension. If path is null, the function
+ // returns null. If path does not contain a file extension,
+ // the new file extension is appended to the path. If extension
+ // is null, any existing extension is removed from path.
+ public static string ChangeExtension(string path, string extension)
+ {
+ if (path != null)
+ {
+ PathInternal.CheckInvalidPathChars(path);
+
+ string s = path;
+ for (int i = path.Length - 1; i >= 0; i--)
+ {
+ char ch = path[i];
+ if (ch == '.')
+ {
+ s = path.Substring(0, i);
+ break;
+ }
+ if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) break;
+ }
+
+ if (extension != null && path.Length != 0)
+ {
+ s = (extension.Length == 0 || extension[0] != '.') ?
+ s + "." + extension :
+ s + extension;
+ }
+
+ return s;
+ }
+ return null;
+ }
+
+ // Returns the directory path of a file path. This method effectively
+ // removes the last element of the given file path, i.e. it returns a
+ // string consisting of all characters up to but not including the last
+ // backslash ("\") in the file path. The returned value is null if the file
+ // path is null or if the file path denotes a root (such as "\", "C:", or
+ // "\\server\share").
+ public static string GetDirectoryName(string path)
+ {
+ if (path != null)
+ {
+ PathInternal.CheckInvalidPathChars(path);
+ path = PathInternal.NormalizeDirectorySeparators(path);
+ int root = PathInternal.GetRootLength(path);
+
+ int i = path.Length;
+ if (i > root)
+ {
+ while (i > root && !PathInternal.IsDirectorySeparator(path[--i])) ;
+ return path.Substring(0, i);
+ }
+ }
+ return null;
+ }
+
+ public static char[] GetInvalidPathChars()
+ {
+ return PathInternal.GetInvalidPathChars();
+ }
+
+ // Returns the extension of the given path. The returned value includes the
+ // period (".") character of the extension except when you have a terminal period when you get string.Empty, such as ".exe" or
+ // ".cpp". The returned value is null if the given path is
+ // null or if the given path does not include an extension.
+ [Pure]
+ public static string GetExtension(string path)
+ {
+ if (path == null)
+ return null;
+
+ PathInternal.CheckInvalidPathChars(path);
+ int length = path.Length;
+ for (int i = length - 1; i >= 0; i--)
+ {
+ char ch = path[i];
+ if (ch == '.')
+ {
+ if (i != length - 1)
+ return path.Substring(i, length - i);
+ else
+ return string.Empty;
+ }
+ if (PathInternal.IsDirectoryOrVolumeSeparator(ch))
+ break;
+ }
+ return string.Empty;
+ }
+
+ // Returns the name and extension parts of the given path. The resulting
+ // string contains the characters of path that follow the last
+ // separator in path. The resulting string is null if path is null.
+ [Pure]
+ public static string GetFileName(string path)
+ {
+ if (path == null)
+ return null;
+
+ int offset = PathInternal.FindFileNameIndex(path);
+ int count = path.Length - offset;
+ return path.Substring(offset, count);
+ }
+
+ [Pure]
+ public static string GetFileNameWithoutExtension(string path)
+ {
+ if (path == null)
+ return null;
+
+ int length = path.Length;
+ int offset = PathInternal.FindFileNameIndex(path);
+
+ int end = path.LastIndexOf('.', length - 1, length - offset);
+ return end == -1 ?
+ path.Substring(offset) : // No extension was found
+ path.Substring(offset, end - offset);
+ }
+
+ // Returns a cryptographically strong random 8.3 string that can be
+ // used as either a folder name or a file name.
+ public static unsafe string GetRandomFileName()
+ {
+
+ byte* pKey = stackalloc byte[KeyLength];
+ GetCryptoRandomBytes(pKey, KeyLength);
+
+ const int RandomFileNameLength = 12;
+ char* pRandomFileName = stackalloc char[RandomFileNameLength];
+ Populate83FileNameFromRandomBytes(pKey, KeyLength, pRandomFileName, RandomFileNameLength);
+ return new string(pRandomFileName, 0, RandomFileNameLength);
+ }
+
+ // Tests if a path includes a file extension. The result is
+ // true if the characters that follow the last directory
+ // separator ('\\' or '/') or volume separator (':') in the path include
+ // a period (".") other than a terminal period. The result is false otherwise.
+ [Pure]
+ public static bool HasExtension(string path)
+ {
+ if (path != null)
+ {
+ PathInternal.CheckInvalidPathChars(path);
+
+ for (int i = path.Length - 1; i >= 0; i--)
+ {
+ char ch = path[i];
+ if (ch == '.')
+ {
+ return i != path.Length - 1;
+ }
+ if (PathInternal.IsDirectoryOrVolumeSeparator(ch)) break;
+ }
+ }
+ return false;
+ }
+
+ public static string Combine(string path1, string path2)
+ {
+ if (path1 == null || path2 == null)
+ throw new ArgumentNullException((path1 == null) ? nameof(path1): nameof(path2));
+ Contract.EndContractBlock();
+
+ PathInternal.CheckInvalidPathChars(path1);
+ PathInternal.CheckInvalidPathChars(path2);
+
+ return CombineNoChecks(path1, path2);
+ }
+
+ public static string Combine(string path1, string path2, string path3)
+ {
+ if (path1 == null || path2 == null || path3 == null)
+ throw new ArgumentNullException((path1 == null) ? nameof(path1): (path2 == null) ? nameof(path2): nameof(path3));
+ Contract.EndContractBlock();
+
+ PathInternal.CheckInvalidPathChars(path1);
+ PathInternal.CheckInvalidPathChars(path2);
+ PathInternal.CheckInvalidPathChars(path3);
+
+ return CombineNoChecks(path1, path2, path3);
+ }
+
+ public static string Combine(string path1, string path2, string path3, string path4)
+ {
+ if (path1 == null || path2 == null || path3 == null || path4 == null)
+ throw new ArgumentNullException((path1 == null) ? nameof(path1): (path2 == null) ? nameof(path2): (path3 == null) ? nameof(path3): nameof(path4));
+ Contract.EndContractBlock();
+
+ PathInternal.CheckInvalidPathChars(path1);
+ PathInternal.CheckInvalidPathChars(path2);
+ PathInternal.CheckInvalidPathChars(path3);
+ PathInternal.CheckInvalidPathChars(path4);
+
+ return CombineNoChecks(path1, path2, path3, path4);
+ }
+
+ public static string Combine(params string[] paths)
+ {
+ if (paths == null)
+ {
+ throw new ArgumentNullException(nameof(paths));
+ }
+ Contract.EndContractBlock();
+
+ int finalSize = 0;
+ int firstComponent = 0;
+
+ // We have two passes, the first calculates how large a buffer to allocate and does some precondition
+ // checks on the paths passed in. The second actually does the combination.
+
+ for (int i = 0; i < paths.Length; i++)
+ {
+ if (paths[i] == null)
+ {
+ throw new ArgumentNullException(nameof(paths));
+ }
+
+ if (paths[i].Length == 0)
+ {
+ continue;
+ }
+
+ PathInternal.CheckInvalidPathChars(paths[i]);
+
+ if (IsPathRooted(paths[i]))
+ {
+ firstComponent = i;
+ finalSize = paths[i].Length;
+ }
+ else
+ {
+ finalSize += paths[i].Length;
+ }
+
+ char ch = paths[i][paths[i].Length - 1];
+ if (!PathInternal.IsDirectoryOrVolumeSeparator(ch))
+ finalSize++;
+ }
+
+ StringBuilder finalPath = StringBuilderCache.Acquire(finalSize);
+
+ for (int i = firstComponent; i < paths.Length; i++)
+ {
+ if (paths[i].Length == 0)
+ {
+ continue;
+ }
+
+ if (finalPath.Length == 0)
+ {
+ finalPath.Append(paths[i]);
+ }
+ else
+ {
+ char ch = finalPath[finalPath.Length - 1];
+ if (!PathInternal.IsDirectoryOrVolumeSeparator(ch))
+ {
+ finalPath.Append(DirectorySeparatorChar);
+ }
+
+ finalPath.Append(paths[i]);
+ }
+ }
+
+ return StringBuilderCache.GetStringAndRelease(finalPath);
+ }
+
+ private static string CombineNoChecks(string path1, string path2)
+ {
+ if (path2.Length == 0)
+ return path1;
+
+ if (path1.Length == 0)
+ return path2;
+
+ if (IsPathRooted(path2))
+ return path2;
+
+ char ch = path1[path1.Length - 1];
+ return PathInternal.IsDirectoryOrVolumeSeparator(ch) ?
+ path1 + path2 :
+ path1 + DirectorySeparatorCharAsString + path2;
+ }
+
+ private static string CombineNoChecks(string path1, string path2, string path3)
+ {
+ if (path1.Length == 0)
+ return CombineNoChecks(path2, path3);
+ if (path2.Length == 0)
+ return CombineNoChecks(path1, path3);
+ if (path3.Length == 0)
+ return CombineNoChecks(path1, path2);
+
+ if (IsPathRooted(path3))
+ return path3;
+ if (IsPathRooted(path2))
+ return CombineNoChecks(path2, path3);
+
+ bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]);
+ bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]);
+
+ if (hasSep1 && hasSep2)
+ {
+ return path1 + path2 + path3;
+ }
+ else if (hasSep1)
+ {
+ return path1 + path2 + DirectorySeparatorCharAsString + path3;
+ }
+ else if (hasSep2)
+ {
+ return path1 + DirectorySeparatorCharAsString + path2 + path3;
+ }
+ else
+ {
+ // string.Concat only has string-based overloads up to four arguments; after that requires allocating
+ // a params string[]. Instead, try to use a cached StringBuilder.
+ StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + 2);
+ sb.Append(path1)
+ .Append(DirectorySeparatorChar)
+ .Append(path2)
+ .Append(DirectorySeparatorChar)
+ .Append(path3);
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+ }
+
+ private static string CombineNoChecks(string path1, string path2, string path3, string path4)
+ {
+ if (path1.Length == 0)
+ return CombineNoChecks(path2, path3, path4);
+ if (path2.Length == 0)
+ return CombineNoChecks(path1, path3, path4);
+ if (path3.Length == 0)
+ return CombineNoChecks(path1, path2, path4);
+ if (path4.Length == 0)
+ return CombineNoChecks(path1, path2, path3);
+
+ if (IsPathRooted(path4))
+ return path4;
+ if (IsPathRooted(path3))
+ return CombineNoChecks(path3, path4);
+ if (IsPathRooted(path2))
+ return CombineNoChecks(path2, path3, path4);
+
+ bool hasSep1 = PathInternal.IsDirectoryOrVolumeSeparator(path1[path1.Length - 1]);
+ bool hasSep2 = PathInternal.IsDirectoryOrVolumeSeparator(path2[path2.Length - 1]);
+ bool hasSep3 = PathInternal.IsDirectoryOrVolumeSeparator(path3[path3.Length - 1]);
+
+ if (hasSep1 && hasSep2 && hasSep3)
+ {
+ // Use string.Concat overload that takes four strings
+ return path1 + path2 + path3 + path4;
+ }
+ else
+ {
+ // string.Concat only has string-based overloads up to four arguments; after that requires allocating
+ // a params string[]. Instead, try to use a cached StringBuilder.
+ StringBuilder sb = StringBuilderCache.Acquire(path1.Length + path2.Length + path3.Length + path4.Length + 3);
+
+ sb.Append(path1);
+ if (!hasSep1)
+ {
+ sb.Append(DirectorySeparatorChar);
+ }
+
+ sb.Append(path2);
+ if (!hasSep2)
+ {
+ sb.Append(DirectorySeparatorChar);
+ }
+
+ sb.Append(path3);
+ if (!hasSep3)
+ {
+ sb.Append(DirectorySeparatorChar);
+ }
+
+ sb.Append(path4);
+
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+ }
+
+ private static readonly char[] s_base32Char = {
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5'};
+
+ private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, char* chars, int charCount)
+ {
+ Debug.Assert(bytes != null);
+ Debug.Assert(chars != null);
+
+ // This method requires bytes of length 8 and chars of length 12.
+ Debug.Assert(byteCount == 8, $"Unexpected {nameof(byteCount)}");
+ Debug.Assert(charCount == 12, $"Unexpected {nameof(charCount)}");
+
+ byte b0 = bytes[0];
+ byte b1 = bytes[1];
+ byte b2 = bytes[2];
+ byte b3 = bytes[3];
+ byte b4 = bytes[4];
+
+ // Consume the 5 Least significant bits of the first 5 bytes
+ chars[0] = s_base32Char[b0 & 0x1F];
+ chars[1] = s_base32Char[b1 & 0x1F];
+ chars[2] = s_base32Char[b2 & 0x1F];
+ chars[3] = s_base32Char[b3 & 0x1F];
+ chars[4] = s_base32Char[b4 & 0x1F];
+
+ // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
+ chars[5] = s_base32Char[(
+ ((b0 & 0xE0) >> 5) |
+ ((b3 & 0x60) >> 2))];
+
+ chars[6] = s_base32Char[(
+ ((b1 & 0xE0) >> 5) |
+ ((b4 & 0x60) >> 2))];
+
+ // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
+ b2 >>= 5;
+
+ Debug.Assert(((b2 & 0xF8) == 0), "Unexpected set bits");
+
+ if ((b3 & 0x80) != 0)
+ b2 |= 0x08;
+ if ((b4 & 0x80) != 0)
+ b2 |= 0x10;
+
+ chars[7] = s_base32Char[b2];
+
+ // Set the file extension separator
+ chars[8] = '.';
+
+ // Consume the 5 Least significant bits of the remaining 3 bytes
+ chars[9] = s_base32Char[(bytes[5] & 0x1F)];
+ chars[10] = s_base32Char[(bytes[6] & 0x1F)];
+ chars[11] = s_base32Char[(bytes[7] & 0x1F)];
+ }
+
+ /// <summary>
+ /// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
+ /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
+ /// </summary>
+ /// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param>
+ /// <param name="path">The destination path.</param>
+ /// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
+ public static string GetRelativePath(string relativeTo, string path)
+ {
+ return GetRelativePath(relativeTo, path, StringComparison);
+ }
+
+ private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
+ {
+ if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo));
+ if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path));
+ Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
+
+ relativeTo = GetFullPath(relativeTo);
+ path = GetFullPath(path);
+
+ // Need to check if the roots are different- if they are we need to return the "to" path.
+ if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
+ return path;
+
+ int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);
+
+ // If there is nothing in common they can't share the same root, return the "to" path as is.
+ if (commonLength == 0)
+ return path;
+
+ // Trailing separators aren't significant for comparison
+ int relativeToLength = relativeTo.Length;
+ if (PathInternal.EndsInDirectorySeparator(relativeTo))
+ relativeToLength--;
+
+ bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path);
+ int pathLength = path.Length;
+ if (pathEndsInSeparator)
+ pathLength--;
+
+ // If we have effectively the same path, return "."
+ if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";
+
+ // We have the same root, we need to calculate the difference now using the
+ // common Length and Segment count past the length.
+ //
+ // Some examples:
+ //
+ // C:\Foo C:\Bar L3, S1 -> ..\Bar
+ // C:\Foo C:\Foo\Bar L6, S0 -> Bar
+ // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
+ // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
+
+ StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
+
+ // Add parent segments for segments past the common on the "from" path
+ if (commonLength < relativeToLength)
+ {
+ sb.Append(PathInternal.ParentDirectoryPrefix);
+
+ for (int i = commonLength; i < relativeToLength; i++)
+ {
+ if (PathInternal.IsDirectorySeparator(relativeTo[i]))
+ {
+ sb.Append(PathInternal.ParentDirectoryPrefix);
+ }
+ }
+ }
+ else if (PathInternal.IsDirectorySeparator(path[commonLength]))
+ {
+ // No parent segments and we need to eat the initial separator
+ // (C:\Foo C:\Foo\Bar case)
+ commonLength++;
+ }
+
+ // Now add the rest of the "to" path, adding back the trailing separator
+ int count = pathLength - commonLength;
+ if (pathEndsInSeparator)
+ count++;
+
+ sb.Append(path, commonLength, count);
+ return StringBuilderCache.GetStringAndRelease(sb);
+ }
+
+ // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are
+ // too low in System.Runtime.Extensions to use it (no FileStream, etc.)
+
+ /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
+ internal static StringComparison StringComparison
+ {
+ get
+ {
+ return IsCaseSensitive ?
+ StringComparison.Ordinal :
+ StringComparison.OrdinalIgnoreCase;
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/src/System/IO/LongPathHelper.cs b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs
index 9746fdc0aa..4c2cdff45e 100644
--- a/src/mscorlib/src/System/IO/LongPathHelper.cs
+++ b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs
@@ -2,17 +2,16 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Diagnostics.Contracts;
+using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using Microsoft.Win32;
namespace System.IO
{
/// <summary>
/// Wrapper to help with path normalization.
/// </summary>
- internal class LongPathHelper
+ unsafe internal class PathHelper
{
// Can't be over 8.3 and be a short name
private const int MaxShortName = 12;
@@ -42,8 +41,7 @@ namespace System.IO
/// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception>
/// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception>
/// <returns>Normalized path</returns>
- [System.Security.SecurityCritical]
- unsafe internal static string Normalize(string path, uint maxPathLength, bool checkInvalidCharacters, bool expandShortPaths)
+ internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths)
{
// Get the full path
StringBuffer fullPath = t_fullPathBuffer ?? (t_fullPathBuffer = new StringBuffer(PathInternal.MaxShortPath));
@@ -52,12 +50,12 @@ namespace System.IO
GetFullPathName(path, fullPath);
// Trim whitespace off the end of the string. Win32 normalization trims only U+0020.
- fullPath.TrimEnd(Path.TrimEndChars);
+ fullPath.TrimEnd(PathInternal.s_trimEndChars);
- if (fullPath.Length >= maxPathLength)
+ if (fullPath.Length >= PathInternal.MaxLongPath)
{
// Fullpath is genuinely too long
- throw new PathTooLongException();
+ throw new PathTooLongException(SR.IO_PathTooLong);
}
// Checking path validity used to happen before getting the full path name. To avoid additional input allocation
@@ -73,7 +71,7 @@ namespace System.IO
// - Invalid UNC paths like \\, \\server, \\server\.
// - Segments that are too long (over MaxComponentLength)
- // As the path could be > 60K, we'll combine the validity scan. None of these checks are performed by the Win32
+ // As the path could be > 30K, we'll combine the validity scan. None of these checks are performed by the Win32
// GetFullPathName() API.
bool possibleShortPath = false;
@@ -105,8 +103,7 @@ namespace System.IO
case '>':
case '<':
case '\"':
- if (checkInvalidCharacters) throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
- // No point in expanding a bad path
+ if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars);
foundTilde = false;
break;
case '~':
@@ -115,7 +112,7 @@ namespace System.IO
case '\\':
segmentLength = index - lastSeparator - 1;
if (segmentLength > (uint)PathInternal.MaxComponentLength)
- throw new PathTooLongException();
+ throw new PathTooLongException(SR.IO_PathTooLong + fullPath.ToString());
lastSeparator = index;
if (foundTilde)
@@ -134,7 +131,7 @@ namespace System.IO
// If we're at the end of the path and this is the first separator, we're missing the share.
// Otherwise we're good, so ignore UNC tracking from here.
if (index == fullPath.Length - 1)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
+ throw new ArgumentException(SR.Arg_PathIllegalUNC);
else
possibleBadUnc = false;
}
@@ -142,7 +139,7 @@ namespace System.IO
break;
default:
- if (checkInvalidCharacters && current < ' ') throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
+ if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
break;
}
}
@@ -151,11 +148,11 @@ namespace System.IO
}
if (possibleBadUnc)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
+ throw new ArgumentException(SR.Arg_PathIllegalUNC);
segmentLength = fullPath.Length - lastSeparator - 1;
if (segmentLength > (uint)PathInternal.MaxComponentLength)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
+ throw new PathTooLongException(SR.IO_PathTooLong);
if (foundTilde && segmentLength <= MaxShortName)
possibleShortPath = true;
@@ -186,12 +183,17 @@ namespace System.IO
}
}
- [System.Security.SecurityCritical]
- unsafe private static void GetFullPathName(string path, StringBuffer fullPath)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static bool IsDosUnc(StringBuffer buffer)
+ {
+ return !PathInternal.IsDevice(buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\';
+ }
+
+ private static void GetFullPathName(string path, StringBuffer fullPath)
{
// If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as
// it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here.
- Contract.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path));
+ Debug.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path));
// Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\"
int startIndex = PathInternal.PathStartSkip(path);
@@ -199,7 +201,7 @@ namespace System.IO
fixed (char* pathStart = path)
{
uint result = 0;
- while ((result = Win32Native.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity)
+ while ((result = Interop.mincore.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity)
{
// Reported size (which does not include the null) is greater than the buffer size. Increase the capacity.
fullPath.EnsureCharCapacity(result);
@@ -210,150 +212,14 @@ namespace System.IO
// Failure, get the error and throw
int errorCode = Marshal.GetLastWin32Error();
if (errorCode == 0)
- errorCode = Win32Native.ERROR_BAD_PATHNAME;
- __Error.WinIOError(errorCode, path);
+ errorCode = Interop.mincore.Errors.ERROR_BAD_PATHNAME;
+ throw Win32Marshal.GetExceptionForWin32Error(errorCode, path);
}
fullPath.Length = result;
}
}
- [System.Security.SecurityCritical]
- unsafe internal static string GetLongPathName(StringBuffer path)
- {
- using (StringBuffer outputBuffer = new StringBuffer(path.Length))
- {
- uint result = 0;
- while ((result = Win32Native.GetLongPathNameW(path.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity)
- {
- // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity.
- outputBuffer.EnsureCharCapacity(result);
- }
-
- if (result == 0)
- {
- // Failure, get the error and throw
- GetErrorAndThrow(path.ToString());
- }
-
- outputBuffer.Length = result;
- return outputBuffer.ToString();
- }
- }
-
- [System.Security.SecurityCritical]
- unsafe internal static string GetLongPathName(string path)
- {
- using (StringBuffer outputBuffer = new StringBuffer((uint)path.Length))
- {
- uint result = 0;
- while ((result = Win32Native.GetLongPathNameW(path, outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity)
- {
- // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity.
- outputBuffer.EnsureCharCapacity(result);
- }
-
- if (result == 0)
- {
- // Failure, get the error and throw
- GetErrorAndThrow(path);
- }
-
- outputBuffer.Length = result;
- return outputBuffer.ToString();
- }
- }
-
- [System.Security.SecurityCritical]
- private static void GetErrorAndThrow(string path)
- {
- int errorCode = Marshal.GetLastWin32Error();
- if (errorCode == 0)
- errorCode = Win32Native.ERROR_BAD_PATHNAME;
- __Error.WinIOError(errorCode, path);
- }
-
- // It is significantly more complicated to get the long path with minimal allocations if we're injecting the extended dos path prefix. The implicit version
- // should match up with what is in CoreFx System.Runtime.Extensions.
-#if !FEATURE_IMPLICIT_LONGPATH
- [System.Security.SecuritySafeCritical]
- private unsafe static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath)
- {
- // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
- // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.
-
- Contract.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now");
-
- using (StringBuffer inputBuffer = new StringBuffer(outputBuffer))
- {
- bool success = false;
- uint lastIndex = outputBuffer.Length - 1;
- uint foundIndex = lastIndex;
- uint rootLength = PathInternal.GetRootLength(outputBuffer);
-
- while (!success)
- {
- uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
-
- // Replace any temporary null we added
- if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\';
-
- if (result == 0)
- {
- // Look to see if we couldn't find the file
- int error = Marshal.GetLastWin32Error();
- if (error != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.ERROR_PATH_NOT_FOUND)
- {
- // Some other failure, give up
- break;
- }
-
- // We couldn't find the path at the given index, start looking further back in the string.
- foundIndex--;
-
- for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ;
- if (foundIndex == rootLength)
- {
- // Can't trim the path back any further
- break;
- }
- else
- {
- // Temporarily set a null in the string to get Windows to look further up the path
- inputBuffer[foundIndex] = '\0';
- }
- }
- else if (result > outputBuffer.CharCapacity)
- {
- // Not enough space. The result count for this API does not include the null terminator.
- outputBuffer.EnsureCharCapacity(result);
- }
- else
- {
- // Found the path
- success = true;
- outputBuffer.Length = result;
- if (foundIndex < lastIndex)
- {
- // It was a partial find, put the non-existant part of the path back
- outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
- }
- }
- }
-
- StringBuffer bufferToUse = success ? outputBuffer : inputBuffer;
-
- if (bufferToUse.SubstringEquals(originalPath))
- {
- // Use the original path to avoid allocating
- return originalPath;
- }
-
- return bufferToUse.ToString();
- }
- }
-#else // !FEATURE_IMPLICIT_LONGPATH
-
private static uint GetInputBuffer(StringBuffer content, bool isDosUnc, out StringBuffer buffer)
{
uint length = content.Length;
@@ -388,9 +254,13 @@ namespace System.IO
}
}
- [System.Security.SecuritySafeCritical]
private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath)
{
+ // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
+ // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.
+
+ Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now");
+
// We'll have one of a few cases by now (the normalized path will have already:
//
// 1. Dos path (C:\)
@@ -398,6 +268,8 @@ namespace System.IO
// 3. Dos device path (\\.\C:\, \\?\C:\)
//
// We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\.
+ //
+ // Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).
uint rootLength = PathInternal.GetRootLength(outputBuffer);
bool isDevice = PathInternal.IsDevice(outputBuffer);
@@ -411,7 +283,6 @@ namespace System.IO
if (isDevice)
{
// We have one of the following (\\?\ or \\.\)
- // We will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).
inputBuffer = new StringBuffer();
inputBuffer.Append(outputBuffer);
@@ -423,9 +294,7 @@ namespace System.IO
}
else
{
- // \\Server\Share, but not \\.\ or \\?\.
- // We need to know this to be able to push \\?\UNC\ on if required
- isDosUnc = outputBuffer.Length > 1 && outputBuffer[0] == '\\' && outputBuffer[1] == '\\' && !PathInternal.IsDevice(outputBuffer);
+ isDosUnc = IsDosUnc(outputBuffer);
rootDifference = GetInputBuffer(outputBuffer, isDosUnc, out inputBuffer);
}
@@ -437,7 +306,7 @@ namespace System.IO
while (!success)
{
- uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
+ uint result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
// Replace any temporary null we added
if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\';
@@ -446,7 +315,7 @@ namespace System.IO
{
// Look to see if we couldn't find the file
int error = Marshal.GetLastWin32Error();
- if (error != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.ERROR_PATH_NOT_FOUND)
+ if (error != Interop.mincore.Errors.ERROR_FILE_NOT_FOUND && error != Interop.mincore.Errors.ERROR_PATH_NOT_FOUND)
{
// Some other failure, give up
break;
@@ -471,7 +340,7 @@ namespace System.IO
{
// Not enough space. The result count for this API does not include the null terminator.
outputBuffer.EnsureCharCapacity(result);
- result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
+ result = Interop.mincore.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity);
}
else
{
@@ -516,6 +385,5 @@ namespace System.IO
inputBuffer.Dispose();
return returnValue;
}
-#endif // FEATURE_IMPLICIT_LONGPATH
}
-} \ No newline at end of file
+}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs b/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs
new file mode 100644
index 0000000000..bea2df93b9
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ private enum Tristate : byte
+ {
+ NotInitialized,
+ True,
+ False,
+ }
+
+ private static Tristate s_isCaseSensitive = Tristate.NotInitialized;
+
+ /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
+ internal static StringComparison StringComparison
+ {
+ get
+ {
+ return IsCaseSensitive ?
+ StringComparison.Ordinal :
+ StringComparison.OrdinalIgnoreCase;
+ }
+ }
+
+ /// <summary>Gets whether the system is case-sensitive.</summary>
+ internal static bool IsCaseSensitive
+ {
+ get
+ {
+ // This must be lazily initialized as there are dependencies on PathInternal's static constructor
+ // being fully initialized. (GetIsCaseSensitive() calls GetFullPath() which needs to use PathInternal)
+ if (s_isCaseSensitive == Tristate.NotInitialized)
+ s_isCaseSensitive = GetIsCaseSensitive() ? Tristate.True : Tristate.False;
+
+ return s_isCaseSensitive == Tristate.True;
+ }
+ }
+
+ /// <summary>
+ /// Determines whether the file system is case sensitive.
+ /// </summary>
+ /// <remarks>
+ /// Ideally we'd use something like pathconf with _PC_CASE_SENSITIVE, but that is non-portable,
+ /// not supported on Windows or Linux, etc. For now, this function creates a tmp file with capital letters
+ /// and then tests for its existence with lower-case letters. This could return invalid results in corner
+ /// cases where, for example, different file systems are mounted with differing sensitivities.
+ /// </remarks>
+ private static bool GetIsCaseSensitive()
+ {
+ try
+ {
+ string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N"));
+ using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose))
+ {
+ string lowerCased = pathWithUpperCase.ToLowerInvariant();
+ return !File.Exists(lowerCased);
+ }
+ }
+ catch (Exception exc)
+ {
+ // In case something goes terribly wrong, we don't want to fail just because
+ // of a casing test, so we assume case-insensitive-but-preserving.
+ Debug.Fail("Casing test failed: " + exc);
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs b/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs
new file mode 100644
index 0000000000..6c39f99556
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs
@@ -0,0 +1,122 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ // There is only one invalid path character in Unix
+ private const char InvalidPathChar = '\0';
+ internal static char[] GetInvalidPathChars() => new char[] { InvalidPathChar };
+
+ internal static readonly int MaxComponentLength = Interop.Sys.MaxName;
+
+ internal const string ParentDirectoryPrefix = @"../";
+
+ /// <summary>Returns a value indicating if the given path contains invalid characters.</summary>
+ internal static bool HasIllegalCharacters(string path)
+ {
+ Debug.Assert(path != null);
+ return path.IndexOf(InvalidPathChar) >= 0;
+ }
+
+ internal static int GetRootLength(string path)
+ {
+ return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0;
+ }
+
+ internal static bool IsDirectorySeparator(char c)
+ {
+ // The alternate directory separator char is the same as the directory separator,
+ // so we only need to check one.
+ Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar);
+ return c == Path.DirectorySeparatorChar;
+ }
+
+ /// <summary>
+ /// Returns true if the path is too long
+ /// </summary>
+ internal static bool IsPathTooLong(string fullPath)
+ {
+ return fullPath.Length >= Interop.Sys.MaxPath;
+ }
+
+ /// <summary>
+ /// Returns true if the directory is too long
+ /// </summary>
+ internal static bool IsDirectoryTooLong(string fullPath)
+ {
+ return fullPath.Length >= Interop.Sys.MaxPath;
+ }
+
+ /// <summary>
+ /// Normalize separators in the given path. Compresses forward slash runs.
+ /// </summary>
+ internal static string NormalizeDirectorySeparators(string path)
+ {
+ if (string.IsNullOrEmpty(path)) return path;
+
+ // Make a pass to see if we need to normalize so we can potentially skip allocating
+ bool normalized = true;
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ if (IsDirectorySeparator(path[i])
+ && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
+ {
+ normalized = false;
+ break;
+ }
+ }
+
+ if (normalized) return path;
+
+ StringBuilder builder = new StringBuilder(path.Length);
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ char current = path[i];
+
+ // Skip if we have another separator following
+ if (IsDirectorySeparator(current)
+ && (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])))
+ continue;
+
+ builder.Append(current);
+ }
+
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Returns true if the character is a directory or volume separator.
+ /// </summary>
+ /// <param name="ch">The character to test.</param>
+ internal static bool IsDirectoryOrVolumeSeparator(char ch)
+ {
+ // The directory separator, volume separator, and the alternate directory
+ // separator should be the same on Unix, so we only need to check one.
+ Debug.Assert(Path.DirectorySeparatorChar == Path.AltDirectorySeparatorChar);
+ Debug.Assert(Path.DirectorySeparatorChar == Path.VolumeSeparatorChar);
+ return ch == Path.DirectorySeparatorChar;
+ }
+
+ internal static bool HasInvalidVolumeSeparator(string path)
+ {
+ // This is only ever true for Windows
+ return false;
+ }
+
+ internal static bool IsPartiallyQualified(string path)
+ {
+ // This is much simpler than Windows where paths can be rooted, but not fully qualified (such as Drive Relative)
+ // As long as the path is rooted in Unix it doesn't use the current directory and therefore is fully qualified.
+ return !Path.IsPathRooted(path);
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs b/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs
new file mode 100644
index 0000000000..fec2218844
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs
@@ -0,0 +1,89 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ /// <summary>
+ /// Returns true if the path uses the extended syntax (\\?\)
+ /// </summary>
+ internal static bool IsExtended(StringBuffer path)
+ {
+ // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
+ // Skipping of normalization will *only* occur if back slashes ('\') are used.
+ return path.Length >= DevicePrefixLength
+ && path[0] == '\\'
+ && (path[1] == '\\' || path[1] == '?')
+ && path[2] == '?'
+ && path[3] == '\\';
+ }
+
+ /// <summary>
+ /// Gets the length of the root of the path (drive, share, etc.).
+ /// </summary>
+ internal unsafe static uint GetRootLength(StringBuffer path)
+ {
+ if (path.Length == 0) return 0;
+ return GetRootLength(path.CharPointer, path.Length);
+ }
+
+ /// <summary>
+ /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
+ /// </summary>
+ internal static bool IsDevice(StringBuffer path)
+ {
+ // If the path begins with any two separators is will be recognized and normalized and prepped with
+ // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
+ return IsExtended(path)
+ ||
+ (
+ path.Length >= DevicePrefixLength
+ && IsDirectorySeparator(path[0])
+ && IsDirectorySeparator(path[1])
+ && (path[2] == '.' || path[2] == '?')
+ && IsDirectorySeparator(path[3])
+ );
+ }
+
+ /// <summary>
+ /// Returns true if the path specified is relative to the current drive or working directory.
+ /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
+ /// validation of the path (URIs will be returned as relative as a result).
+ /// </summary>
+ /// <remarks>
+ /// Handles paths that use the alternate directory separator. It is a frequent mistake to
+ /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
+ /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+ /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+ /// will not be used to modify the path).
+ /// </remarks>
+ internal static bool IsPartiallyQualified(StringBuffer path)
+ {
+ if (path.Length < 2)
+ {
+ // It isn't fixed, it must be relative. There is no way to specify a fixed
+ // path with one character (or less).
+ return true;
+ }
+
+ if (IsDirectorySeparator(path[0]))
+ {
+ // There is no valid way to specify a relative path with two initial slashes or
+ // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
+ return !(path[1] == '?' || IsDirectorySeparator(path[1]));
+ }
+
+ // The only way to specify a fixed path that doesn't begin with two slashes
+ // is the drive, colon, slash format- i.e. C:\
+ return !((path.Length >= 3)
+ && (path[1] == Path.VolumeSeparatorChar)
+ && IsDirectorySeparator(path[2]));
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs b/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs
new file mode 100644
index 0000000000..bd7f1eae41
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs
@@ -0,0 +1,482 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ // All paths in Win32 ultimately end up becoming a path to a File object in the Windows object manager. Passed in paths get mapped through
+ // DosDevice symbolic links in the object tree to actual File objects under \Devices. To illustrate, this is what happens with a typical
+ // path "Foo" passed as a filename to any Win32 API:
+ //
+ // 1. "Foo" is recognized as a relative path and is appended to the current directory (say, "C:\" in our example)
+ // 2. "C:\Foo" is prepended with the DosDevice namespace "\??\"
+ // 3. CreateFile tries to create an object handle to the requested file "\??\C:\Foo"
+ // 4. The Object Manager recognizes the DosDevices prefix and looks
+ // a. First in the current session DosDevices ("\Sessions\1\DosDevices\" for example, mapped network drives go here)
+ // b. If not found in the session, it looks in the Global DosDevices ("\GLOBAL??\")
+ // 5. "C:" is found in DosDevices (in our case "\GLOBAL??\C:", which is a symbolic link to "\Device\HarddiskVolume6")
+ // 6. The full path is now "\Device\HarddiskVolume6\Foo", "\Device\HarddiskVolume6" is a File object and parsing is handed off
+ // to the registered parsing method for Files
+ // 7. The registered open method for File objects is invoked to create the file handle which is then returned
+ //
+ // There are multiple ways to directly specify a DosDevices path. The final format of "\??\" is one way. It can also be specified
+ // as "\\.\" (the most commonly documented way) and "\\?\". If the question mark syntax is used the path will skip normalization
+ // (essentially GetFullPathName()) and path length checks.
+
+ // Windows Kernel-Mode Object Manager
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff565763.aspx
+ // https://channel9.msdn.com/Shows/Going+Deep/Windows-NT-Object-Manager
+ //
+ // Introduction to MS-DOS Device Names
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff548088.aspx
+ //
+ // Local and Global MS-DOS Device Names
+ // https://msdn.microsoft.com/en-us/library/windows/hardware/ff554302.aspx
+
+ internal const string ExtendedPathPrefix = @"\\?\";
+ internal const string UncPathPrefix = @"\\";
+ internal const string UncExtendedPrefixToInsert = @"?\UNC\";
+ internal const string UncExtendedPathPrefix = @"\\?\UNC\";
+ internal const string DevicePathPrefix = @"\\.\";
+ internal const string ParentDirectoryPrefix = @"..\";
+
+ internal const int MaxShortPath = 260;
+ internal const int MaxShortDirectoryPath = 248;
+ internal const int MaxLongPath = short.MaxValue;
+ // \\?\, \\.\, \??\
+ internal const int DevicePrefixLength = 4;
+ // \\
+ internal const int UncPrefixLength = 2;
+ // \\?\UNC\, \\.\UNC\
+ internal const int UncExtendedPrefixLength = 8;
+ internal const int MaxComponentLength = 255;
+
+ internal static char[] GetInvalidPathChars() => new char[]
+ {
+ '|', '\0',
+ (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
+ (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
+ (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
+ (char)31
+ };
+
+ // [MS - FSA] 2.1.4.4 Algorithm for Determining if a FileName Is in an Expression
+ // https://msdn.microsoft.com/en-us/library/ff469270.aspx
+ private static readonly char[] s_wildcardChars =
+ {
+ '\"', '<', '>', '*', '?'
+ };
+
+ /// <summary>
+ /// Returns true if the given character is a valid drive letter
+ /// </summary>
+ internal static bool IsValidDriveChar(char value)
+ {
+ return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
+ }
+
+ /// <summary>
+ /// Returns true if the path is too long
+ /// </summary>
+ internal static bool IsPathTooLong(string fullPath)
+ {
+ // We'll never know precisely what will fail as paths get changed internally in Windows and
+ // may grow to exceed MaxLongPath.
+ return fullPath.Length >= MaxLongPath;
+ }
+
+ /// <summary>
+ /// Returns true if the directory is too long
+ /// </summary>
+ internal static bool IsDirectoryTooLong(string fullPath)
+ {
+ return IsPathTooLong(fullPath);
+ }
+
+ /// <summary>
+ /// 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)
+ /// </summary>
+ internal static string EnsureExtendedPrefixOverMaxPath(string path)
+ {
+ if (path != null && path.Length >= MaxShortPath)
+ {
+ return EnsureExtendedPrefix(path);
+ }
+ else
+ {
+ return path;
+ }
+ }
+
+ /// <summary>
+ /// Adds the extended path prefix (\\?\) if not relative or already a device path.
+ /// </summary>
+ internal static string EnsureExtendedPrefix(string path)
+ {
+ // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which
+ // means adding to relative paths will prevent them from getting the appropriate current directory inserted.
+
+ // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it
+ // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly
+ // in the future we wouldn't want normalization to come back and break existing code.
+
+ // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this
+ // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a
+ // normalized base path.)
+ if (IsPartiallyQualified(path) || IsDevice(path))
+ return path;
+
+ // Given \\server\share in longpath becomes \\?\UNC\server\share
+ if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase))
+ return path.Insert(2, UncExtendedPrefixToInsert);
+
+ return ExtendedPathPrefix + path;
+ }
+
+ /// <summary>
+ /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
+ /// </summary>
+ internal static bool IsDevice(string path)
+ {
+ // If the path begins with any two separators is will be recognized and normalized and prepped with
+ // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
+ return IsExtended(path)
+ ||
+ (
+ path.Length >= DevicePrefixLength
+ && IsDirectorySeparator(path[0])
+ && IsDirectorySeparator(path[1])
+ && (path[2] == '.' || path[2] == '?')
+ && IsDirectorySeparator(path[3])
+ );
+ }
+
+ /// <summary>
+ /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
+ /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
+ /// and path length checks.
+ /// </summary>
+ internal static bool IsExtended(string path)
+ {
+ // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
+ // Skipping of normalization will *only* occur if back slashes ('\') are used.
+ return path.Length >= DevicePrefixLength
+ && path[0] == '\\'
+ && (path[1] == '\\' || path[1] == '?')
+ && path[2] == '?'
+ && path[3] == '\\';
+ }
+
+ /// <summary>
+ /// Returns a value indicating if the given path contains invalid characters (", &lt;, &gt;, |
+ /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31).
+ /// Does not check for wild card characters ? and *.
+ /// </summary>
+ internal static bool HasIllegalCharacters(string path)
+ {
+ // This is equivalent to IndexOfAny(InvalidPathChars) >= 0,
+ // except faster since IndexOfAny grows slower as the input
+ // array grows larger.
+ // Since we know that some of the characters we're looking
+ // for are contiguous in the alphabet-- the path cannot contain
+ // characters 0-31-- we can optimize this for our specific use
+ // case and use simple comparison operations.
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ char c = path[i];
+
+ if (c <= '\u001f' || c == '|')
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Check for known wildcard characters. '*' and '?' are the most common ones.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal unsafe static bool HasWildCardCharacters(string path)
+ {
+ // Question mark is part of dos device syntax so we have to skip if we are
+ int startIndex = IsDevice(path) ? ExtendedPathPrefix.Length : 0;
+
+ return path.IndexOfAny(s_wildcardChars, startIndex) >= 0;
+ }
+
+ /// <summary>
+ /// Gets the length of the root of the path (drive, share, etc.).
+ /// </summary>
+ internal unsafe static int GetRootLength(string path)
+ {
+ fixed(char* value = path)
+ {
+ return (int)GetRootLength(value, (uint)path.Length);
+ }
+ }
+
+ private unsafe static uint GetRootLength(char* path, uint pathLength)
+ {
+ uint i = 0;
+ uint volumeSeparatorLength = 2; // Length to the colon "C:"
+ uint uncRootLength = 2; // Length to the start of the server name "\\"
+
+ bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix);
+ bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix);
+ if (extendedSyntax)
+ {
+ // Shift the position we look for the root from to account for the extended prefix
+ if (extendedUncSyntax)
+ {
+ // "\\" -> "\\?\UNC\"
+ uncRootLength = (uint)UncExtendedPathPrefix.Length;
+ }
+ else
+ {
+ // "C:" -> "\\?\C:"
+ volumeSeparatorLength += (uint)ExtendedPathPrefix.Length;
+ }
+ }
+
+ if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0]))
+ {
+ // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
+
+ i = 1; // Drive rooted (\foo) is one character
+ if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1])))
+ {
+ // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
+ // (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
+ i = uncRootLength;
+ int n = 2; // Maximum separators to skip
+ while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
+ }
+ }
+ else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar)
+ {
+ // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
+ // If the colon is followed by a directory separator, move past it
+ i = volumeSeparatorLength;
+ if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
+ }
+ return i;
+ }
+
+ private unsafe static bool StartsWithOrdinal(char* source, uint sourceLength, string value)
+ {
+ if (sourceLength < (uint)value.Length) return false;
+ for (int i = 0; i < value.Length; i++)
+ {
+ if (value[i] != source[i]) return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Returns true if the path specified is relative to the current drive or working directory.
+ /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
+ /// validation of the path (URIs will be returned as relative as a result).
+ /// </summary>
+ /// <remarks>
+ /// Handles paths that use the alternate directory separator. It is a frequent mistake to
+ /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
+ /// "C:a" is drive relative- meaning that it will be resolved against the current directory
+ /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
+ /// will not be used to modify the path).
+ /// </remarks>
+ internal static bool IsPartiallyQualified(string path)
+ {
+ if (path.Length < 2)
+ {
+ // It isn't fixed, it must be relative. There is no way to specify a fixed
+ // path with one character (or less).
+ return true;
+ }
+
+ if (IsDirectorySeparator(path[0]))
+ {
+ // There is no valid way to specify a relative path with two initial slashes or
+ // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
+ return !(path[1] == '?' || IsDirectorySeparator(path[1]));
+ }
+
+ // The only way to specify a fixed path that doesn't begin with two slashes
+ // is the drive, colon, slash format- i.e. C:\
+ return !((path.Length >= 3)
+ && (path[1] == Path.VolumeSeparatorChar)
+ && IsDirectorySeparator(path[2])
+ // To match old behavior we'll check the drive character for validity as the path is technically
+ // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
+ && IsValidDriveChar(path[0]));
+ }
+
+ /// <summary>
+ /// Returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator.
+ /// (examples are " C:", " \")
+ /// This is a legacy behavior of Path.GetFullPath().
+ /// </summary>
+ /// <remarks>
+ /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip.
+ /// </remarks>
+ internal static int PathStartSkip(string path)
+ {
+ int startIndex = 0;
+ while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
+
+ if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex]))
+ || (startIndex + 1 < path.Length && path[startIndex + 1] == ':' && IsValidDriveChar(path[startIndex])))
+ {
+ // Go ahead and skip spaces as we're either " C:" or " \"
+ return startIndex;
+ }
+
+ return 0;
+ }
+
+ /// <summary>
+ /// True if the given character is a directory separator.
+ /// </summary>
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static bool IsDirectorySeparator(char c)
+ {
+ return c == Path.DirectorySeparatorChar || c == Path.AltDirectorySeparatorChar;
+ }
+
+ /// <summary>
+ /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present.
+ /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip).
+ ///
+ /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false.
+ /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as
+ /// such can't be used here (and is overkill for our uses).
+ ///
+ /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments.
+ /// </summary>
+ /// <remarks>
+ /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do
+ /// not need trimming of trailing whitespace here.
+ ///
+ /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization.
+ ///
+ /// For legacy desktop behavior with ExpandShortPaths:
+ /// - It has no impact on GetPathRoot() so doesn't need consideration.
+ /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share).
+ ///
+ /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was
+ /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you
+ /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by
+ /// this undocumented behavior.
+ ///
+ /// We won't match this old behavior because:
+ ///
+ /// 1. It was undocumented
+ /// 2. It was costly (extremely so if it actually contained '~')
+ /// 3. Doesn't play nice with string logic
+ /// 4. Isn't a cross-plat friendly concept/behavior
+ /// </remarks>
+ internal static string NormalizeDirectorySeparators(string path)
+ {
+ if (string.IsNullOrEmpty(path)) return path;
+
+ char current;
+ int start = PathStartSkip(path);
+
+ if (start == 0)
+ {
+ // Make a pass to see if we need to normalize so we can potentially skip allocating
+ bool normalized = true;
+
+ for (int i = 0; i < path.Length; i++)
+ {
+ current = path[i];
+ if (IsDirectorySeparator(current)
+ && (current != Path.DirectorySeparatorChar
+ // Check for sequential separators past the first position (we need to keep initial two for UNC/extended)
+ || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))))
+ {
+ normalized = false;
+ break;
+ }
+ }
+
+ if (normalized) return path;
+ }
+
+ StringBuilder builder = new StringBuilder(path.Length);
+
+ if (IsDirectorySeparator(path[start]))
+ {
+ start++;
+ builder.Append(Path.DirectorySeparatorChar);
+ }
+
+ for (int i = start; i < path.Length; i++)
+ {
+ current = path[i];
+
+ // If we have a separator
+ if (IsDirectorySeparator(current))
+ {
+ // If the next is a separator, skip adding this
+ if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))
+ {
+ continue;
+ }
+
+ // Ensure it is the primary separator
+ current = Path.DirectorySeparatorChar;
+ }
+
+ builder.Append(current);
+ }
+
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Returns true if the character is a directory or volume separator.
+ /// </summary>
+ /// <param name="ch">The character to test.</param>
+ internal static bool IsDirectoryOrVolumeSeparator(char ch)
+ {
+ return IsDirectorySeparator(ch) || Path.VolumeSeparatorChar == ch;
+ }
+
+ /// <summary>
+ /// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams.
+ /// </summary>
+ /// <returns>True if the path has an invalid volume separator.</returns>
+ internal static bool HasInvalidVolumeSeparator(string path)
+ {
+ // Toss out paths with colons that aren't a valid drive specifier.
+ // Cannot start with a colon and can only be of the form "C:" or "\\?\C:".
+ // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
+
+ // We don't care about skipping starting space for extended paths. Assume no knowledge of extended paths if we're forcing old path behavior.
+ int startIndex = IsExtended(path) ? ExtendedPathPrefix.Length : PathStartSkip(path);
+
+ // If we start with a colon
+ if ((path.Length > startIndex && path[startIndex] == Path.VolumeSeparatorChar)
+ // Or have an invalid drive letter and colon
+ || (path.Length >= startIndex + 2 && path[startIndex + 1] == Path.VolumeSeparatorChar && !IsValidDriveChar(path[startIndex]))
+ // Or have any colons beyond the drive colon
+ || (path.Length > startIndex + 2 && path.IndexOf(Path.VolumeSeparatorChar, startIndex + 2) != -1))
+ {
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.cs b/src/mscorlib/corefx/System/IO/PathInternal.cs
new file mode 100644
index 0000000000..ee67680df5
--- /dev/null
+++ b/src/mscorlib/corefx/System/IO/PathInternal.cs
@@ -0,0 +1,230 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Text;
+
+namespace System.IO
+{
+ /// <summary>Contains internal path helpers that are shared between many projects.</summary>
+ internal static partial class PathInternal
+ {
+ // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space.
+ // string.WhitespaceChars will trim more aggressively than what the underlying FS does (for ex, NTFS, FAT).
+ //
+ // (This is for compatibility with old behavior.)
+ internal static readonly char[] s_trimEndChars =
+ {
+ (char)0x9, // Horizontal tab
+ (char)0xA, // Line feed
+ (char)0xB, // Vertical tab
+ (char)0xC, // Form feed
+ (char)0xD, // Carriage return
+ (char)0x20, // Space
+ (char)0x85, // Next line
+ (char)0xA0 // Non breaking space
+ };
+
+ /// <summary>
+ /// Checks for invalid path characters in the given path.
+ /// </summary>
+ /// <exception cref="System.ArgumentNullException">Thrown if the path is null.</exception>
+ /// <exception cref="System.ArgumentException">Thrown if the path has invalid characters.</exception>
+ /// <param name="path">The path to check for invalid characters.</param>
+ internal static void CheckInvalidPathChars(string path)
+ {
+ if (path == null)
+ throw new ArgumentNullException(nameof(path));
+
+ if (HasIllegalCharacters(path))
+ throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
+ }
+
+
+ /// <summary>
+ /// Returns true if the given StringBuilder starts with the given value.
+ /// </summary>
+ /// <param name="value">The string to compare against the start of the StringBuilder.</param>
+ internal static bool StartsWithOrdinal(this StringBuilder builder, string value)
+ {
+ if (value == null || builder.Length < value.Length)
+ return false;
+
+ for (int i = 0; i < value.Length; i++)
+ {
+ if (builder[i] != value[i]) return false;
+ }
+ return true;
+ }
+
+ /// <summary>
+ /// Returns true if the given string starts with the given value.
+ /// </summary>
+ /// <param name="value">The string to compare against the start of the source string.</param>
+ internal static bool StartsWithOrdinal(this string source, string value)
+ {
+ if (value == null || source.Length < value.Length)
+ return false;
+
+ return source.StartsWith(value, StringComparison.Ordinal);
+ }
+
+ /// <summary>
+ /// Trims the specified characters from the end of the StringBuilder.
+ /// </summary>
+ internal static StringBuilder TrimEnd(this StringBuilder builder, params char[] trimChars)
+ {
+ if (trimChars == null || trimChars.Length == 0)
+ return builder;
+
+ int end = builder.Length - 1;
+
+ for (; end >= 0; end--)
+ {
+ int i = 0;
+ char ch = builder[end];
+ for (; i < trimChars.Length; i++)
+ {
+ if (trimChars[i] == ch) break;
+ }
+ if (i == trimChars.Length)
+ {
+ // Not a trim char
+ break;
+ }
+ }
+
+ builder.Length = end + 1;
+ return builder;
+ }
+
+ /// <summary>
+ /// Returns the start index of the filename
+ /// in the given path, or 0 if no directory
+ /// or volume separator is found.
+ /// </summary>
+ /// <param name="path">The path in which to find the index of the filename.</param>
+ /// <remarks>
+ /// This method returns path.Length for
+ /// inputs like "/usr/foo/" on Unix. As such,
+ /// it is not safe for being used to index
+ /// the string without additional verification.
+ /// </remarks>
+ internal static int FindFileNameIndex(string path)
+ {
+ Debug.Assert(path != null);
+ CheckInvalidPathChars(path);
+
+ for (int i = path.Length - 1; i >= 0; i--)
+ {
+ char ch = path[i];
+ if (IsDirectoryOrVolumeSeparator(ch))
+ return i + 1;
+ }
+
+ return 0; // the whole path is the filename
+ }
+
+ /// <summary>
+ /// Returns true if the path ends in a directory separator.
+ /// </summary>
+ internal static bool EndsInDirectorySeparator(string path) =>
+ !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]);
+
+ /// <summary>
+ /// Get the common path length from the start of the string.
+ /// </summary>
+ internal static int GetCommonPathLength(string first, string second, bool ignoreCase)
+ {
+ int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase);
+
+ // If nothing matches
+ if (commonChars == 0)
+ return commonChars;
+
+ // Or we're a full string and equal length or match to a separator
+ if (commonChars == first.Length
+ && (commonChars == second.Length || IsDirectorySeparator(second[commonChars])))
+ return commonChars;
+
+ if (commonChars == second.Length && IsDirectorySeparator(first[commonChars]))
+ return commonChars;
+
+ // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar.
+ while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1]))
+ commonChars--;
+
+ return commonChars;
+ }
+
+ /// <summary>
+ /// Gets the count of common characters from the left optionally ignoring case
+ /// </summary>
+ unsafe internal static int EqualStartingCharacterCount(string first, string second, bool ignoreCase)
+ {
+ if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0;
+
+ int commonChars = 0;
+
+ fixed (char* f = first)
+ fixed (char* s = second)
+ {
+ char* l = f;
+ char* r = s;
+ char* leftEnd = l + first.Length;
+ char* rightEnd = r + second.Length;
+
+ while (l != leftEnd && r != rightEnd
+ && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r)))))
+ {
+ commonChars++;
+ l++;
+ r++;
+ }
+ }
+
+ return commonChars;
+ }
+
+ /// <summary>
+ /// Returns true if the two paths have the same root
+ /// </summary>
+ internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType)
+ {
+ int firstRootLength = GetRootLength(first);
+ int secondRootLength = GetRootLength(second);
+
+ return firstRootLength == secondRootLength
+ && string.Compare(
+ strA: first,
+ indexA: 0,
+ strB: second,
+ indexB: 0,
+ length: firstRootLength,
+ comparisonType: comparisonType) == 0;
+ }
+
+ /// <summary>
+ /// Returns false for ".." unless it is specified as a part of a valid File/Directory name.
+ /// (Used to avoid moving up directories.)
+ ///
+ /// Valid: a..b abc..d
+ /// Invalid: ..ab ab.. .. abc..d\abc..
+ /// </summary>
+ internal static void CheckSearchPattern(string searchPattern)
+ {
+ int index;
+ while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1)
+ {
+ // Terminal ".." . Files names cannot end in ".."
+ if (index + 2 == searchPattern.Length
+ || IsDirectorySeparator(searchPattern[index + 2]))
+ throw new ArgumentException(SR.Arg_InvalidSearchPattern);
+
+ searchPattern = searchPattern.Substring(index + 2);
+ }
+
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs
new file mode 100644
index 0000000000..875009aee2
--- /dev/null
+++ b/src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs
@@ -0,0 +1,157 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices
+{
+ /// <summary>
+ /// Wrapper for access to the native heap. Dispose to free the memory. Try to use with using statements.
+ /// Does not allocate zero size buffers, and will free the existing native buffer if capacity is dropped to zero.
+ ///
+ /// NativeBuffer utilizes a cache of heap buffers.
+ /// </summary>
+ /// <remarks>
+ /// Suggested use through P/Invoke: define DllImport arguments that take a byte buffer as SafeHandle.
+ ///
+ /// Using SafeHandle will ensure that the buffer will not get collected during a P/Invoke.
+ /// (Notably AddRef and ReleaseRef will be called by the interop layer.)
+ ///
+ /// This class is not threadsafe, changing the capacity or disposing on multiple threads risks duplicate heap
+ /// handles or worse.
+ /// </remarks>
+ internal class NativeBuffer : IDisposable
+ {
+ private readonly static SafeHeapHandleCache s_handleCache = new SafeHeapHandleCache();
+ private readonly static SafeHandle s_emptyHandle = new EmptySafeHandle();
+ private SafeHeapHandle _handle;
+ private ulong _capacity;
+
+ /// <summary>
+ /// Create a buffer with at least the specified initial capacity in bytes.
+ /// </summary>
+ public NativeBuffer(ulong initialMinCapacity = 0)
+ {
+ EnsureByteCapacity(initialMinCapacity);
+ }
+
+ protected unsafe void* VoidPointer
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ return _handle == null ? null : _handle.DangerousGetHandle().ToPointer();
+ }
+ }
+
+ protected unsafe byte* BytePointer
+ {
+ get
+ {
+ return (byte*)VoidPointer;
+ }
+ }
+
+ /// <summary>
+ /// Get the handle for the buffer.
+ /// </summary>
+ public SafeHandle GetHandle()
+ {
+ // Marshalling code will throw on null for SafeHandle
+ return _handle ?? s_emptyHandle;
+ }
+
+ /// <summary>
+ /// The capacity of the buffer in bytes.
+ /// </summary>
+ public ulong ByteCapacity
+ {
+ get { return _capacity; }
+ }
+
+ /// <summary>
+ /// Ensure capacity in bytes is at least the given minimum.
+ /// </summary>
+ /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to set <paramref name="nameof(minCapacity)"/> to a value that is larger than the maximum addressable memory.</exception>
+ public void EnsureByteCapacity(ulong minCapacity)
+ {
+ if (_capacity < minCapacity)
+ {
+ Resize(minCapacity);
+ _capacity = minCapacity;
+ }
+ }
+
+ public unsafe byte this[ulong index]
+ {
+ get
+ {
+ if (index >= _capacity) throw new ArgumentOutOfRangeException();
+ return BytePointer[index];
+ }
+ set
+ {
+ if (index >= _capacity) throw new ArgumentOutOfRangeException();
+ BytePointer[index] = value;
+ }
+ }
+
+ private unsafe void Resize(ulong byteLength)
+ {
+ if (byteLength == 0)
+ {
+ ReleaseHandle();
+ return;
+ }
+
+ if (_handle == null)
+ {
+ _handle = s_handleCache.Acquire(byteLength);
+ }
+ else
+ {
+ _handle.Resize(byteLength);
+ }
+ }
+
+ private void ReleaseHandle()
+ {
+ if (_handle != null)
+ {
+ s_handleCache.Release(_handle);
+ _capacity = 0;
+ _handle = null;
+ }
+ }
+
+ /// <summary>
+ /// Release the backing buffer
+ /// </summary>
+ public virtual void Free()
+ {
+ ReleaseHandle();
+ }
+
+ public void Dispose()
+ {
+ Free();
+ }
+
+ private sealed class EmptySafeHandle : SafeHandle
+ {
+ public EmptySafeHandle() : base(IntPtr.Zero, true) { }
+
+ public override bool IsInvalid
+ {
+ get { return true; }
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ return true;
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs
new file mode 100644
index 0000000000..92b3d980db
--- /dev/null
+++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs
@@ -0,0 +1,109 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ /// <summary>
+ /// Handle for heap memory that allows tracking of capacity and reallocating.
+ /// </summary>
+ internal sealed class SafeHeapHandle : SafeBuffer
+ {
+ /// <summary>
+ /// Allocate a buffer of the given size if requested.
+ /// </summary>
+ /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param>
+ /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception>
+ public SafeHeapHandle(ulong byteLength) : base(ownsHandle: true)
+ {
+ Resize(byteLength);
+ }
+
+ public override bool IsInvalid
+ {
+ get { return handle == IntPtr.Zero; }
+ }
+
+ /// <summary>
+ /// Resize the buffer to the given size if requested.
+ /// </summary>
+ /// <param name="byteLength">Required size in bytes. Must be less than UInt32.MaxValue for 32 bit or UInt64.MaxValue for 64 bit.</param>
+ /// <exception cref="OutOfMemoryException">Thrown if the requested memory size cannot be allocated.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Thrown if size is greater than the maximum memory size.</exception>
+ public void Resize(ulong byteLength)
+ {
+ if (IsClosed) throw new ObjectDisposedException(nameof(SafeHeapHandle));
+
+ ulong originalLength = 0;
+ if (handle == IntPtr.Zero)
+ {
+ handle = Marshal.AllocHGlobal((IntPtr)byteLength);
+ }
+ else
+ {
+ originalLength = ByteLength;
+
+ // This may or may not be the same handle, may realloc in place. If the
+ // handle changes Windows will deal with the old handle, trying to free it will
+ // cause an error.
+ handle = Marshal.ReAllocHGlobal(pv: handle, cb: (IntPtr)byteLength);
+ }
+
+ if (handle == IntPtr.Zero)
+ {
+ // Only real plausible answer
+ throw new OutOfMemoryException();
+ }
+
+ if (byteLength > originalLength)
+ {
+ // Add pressure
+ ulong addedBytes = byteLength - originalLength;
+ if (addedBytes > long.MaxValue)
+ {
+ GC.AddMemoryPressure(long.MaxValue);
+ GC.AddMemoryPressure((long)(addedBytes - long.MaxValue));
+ }
+ else
+ {
+ GC.AddMemoryPressure((long)addedBytes);
+ }
+ }
+ else
+ {
+ // Shrank or did nothing, release pressure if needed
+ RemoveMemoryPressure(originalLength - byteLength);
+ }
+
+ Initialize(byteLength);
+ }
+
+ private void RemoveMemoryPressure(ulong removedBytes)
+ {
+ if (removedBytes == 0) return;
+
+ if (removedBytes > long.MaxValue)
+ {
+ GC.RemoveMemoryPressure(long.MaxValue);
+ GC.RemoveMemoryPressure((long)(removedBytes - long.MaxValue));
+ }
+ else
+ {
+ GC.RemoveMemoryPressure((long)removedBytes);
+ }
+ }
+
+ protected override bool ReleaseHandle()
+ {
+ if (handle != IntPtr.Zero)
+ {
+ RemoveMemoryPressure(ByteLength);
+ Marshal.FreeHGlobal(handle);
+ }
+
+ handle = IntPtr.Zero;
+ return true;
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs
new file mode 100644
index 0000000000..725076ed66
--- /dev/null
+++ b/src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs
@@ -0,0 +1,97 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading;
+
+namespace System.Runtime.InteropServices
+{
+ /// <summary>
+ /// Allows limited thread safe reuse of heap buffers to limit memory pressure.
+ ///
+ /// This cache does not ensure that multiple copies of handles are not released back into the cache.
+ /// </summary>
+ internal sealed class SafeHeapHandleCache : IDisposable
+ {
+ private readonly ulong _minSize;
+ private readonly ulong _maxSize;
+
+ // internal for testing
+ internal readonly SafeHeapHandle[] _handleCache;
+
+ /// <param name="minSize">Smallest buffer size to allocate in bytes.</param>
+ /// <param name="maxSize">The largest buffer size to cache in bytes.</param>
+ /// <param name="maxHandles">The maximum number of handles to cache.</param>
+ public SafeHeapHandleCache(ulong minSize = 64, ulong maxSize = 1024 * 2, int maxHandles = 0)
+ {
+ _minSize = minSize;
+ _maxSize = maxSize;
+ _handleCache = new SafeHeapHandle[maxHandles > 0 ? maxHandles : Environment.ProcessorCount * 4];
+ }
+
+ /// <summary>
+ /// Get a HeapHandle
+ /// </summary>
+ public SafeHeapHandle Acquire(ulong minSize = 0)
+ {
+ if (minSize < _minSize) minSize = _minSize;
+
+ SafeHeapHandle handle = null;
+
+ for (int i = 0; i < _handleCache.Length; i++)
+ {
+ handle = Interlocked.Exchange(ref _handleCache[i], null);
+ if (handle != null) break;
+ }
+
+ if (handle != null)
+ {
+ // One possible future consideration is to attempt cycling through to
+ // find one that might already have sufficient capacity
+ if (handle.ByteLength < minSize)
+ handle.Resize(minSize);
+ }
+ else
+ {
+ handle = new SafeHeapHandle(minSize);
+ }
+
+ return handle;
+ }
+
+ /// <summary>
+ /// Give a HeapHandle back for potential reuse
+ /// </summary>
+ public void Release(SafeHeapHandle handle)
+ {
+ if (handle.ByteLength <= _maxSize)
+ {
+ for (int i = 0; i < _handleCache.Length; i++)
+ {
+ // Push the handles down, walking the last one off the end to keep
+ // the top of the "stack" fresh
+ handle = Interlocked.Exchange(ref _handleCache[i], handle);
+ if (handle == null) return;
+ }
+ }
+
+ handle.Dispose();
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (disposing && _handleCache != null)
+ {
+ foreach (SafeHeapHandle handle in _handleCache)
+ {
+ if (handle != null) handle.Dispose();
+ }
+ }
+ }
+ }
+}
diff --git a/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs
new file mode 100644
index 0000000000..8dce4f0350
--- /dev/null
+++ b/src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs
@@ -0,0 +1,355 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Runtime.InteropServices
+{
+ /// <summary>
+ /// Native buffer that deals in char size increments. Dispose to free memory. Allows buffers larger
+ /// than a maximum size string to enable working with very large string arrays. Always makes ordinal
+ /// comparisons.
+ ///
+ /// A more performant replacement for StringBuilder when performing native interop.
+ /// </summary>
+ /// <remarks>
+ /// Suggested use through P/Invoke: define DllImport arguments that take a character buffer as SafeHandle and pass StringBuffer.GetHandle().
+ /// </remarks>
+ internal class StringBuffer : NativeBuffer
+ {
+ private uint _length;
+
+ /// <summary>
+ /// Instantiate the buffer with capacity for at least the specified number of characters. Capacity
+ /// includes the trailing null character.
+ /// </summary>
+ public StringBuffer(uint initialCapacity = 0)
+ : base(initialCapacity * (ulong)sizeof(char))
+ {
+ }
+
+ /// <summary>
+ /// Get/set the character at the given index.
+ /// </summary>
+ /// <exception cref="ArgumentOutOfRangeException">Thrown if attempting to index outside of the buffer length.</exception>
+ public unsafe char this[uint index]
+ {
+ get
+ {
+ if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index));
+ return CharPointer[index];
+ }
+ set
+ {
+ if (index >= _length) throw new ArgumentOutOfRangeException(nameof(index));
+ CharPointer[index] = value;
+ }
+ }
+
+ /// <summary>
+ /// Character capacity of the buffer. Includes the count for the trailing null character.
+ /// </summary>
+ public uint CharCapacity
+ {
+ get
+ {
+ ulong byteCapacity = ByteCapacity;
+ ulong charCapacity = byteCapacity == 0 ? 0 : byteCapacity / sizeof(char);
+ return charCapacity > uint.MaxValue ? uint.MaxValue : (uint)charCapacity;
+ }
+ }
+
+ /// <summary>
+ /// Ensure capacity in characters is at least the given minimum.
+ /// </summary>
+ /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
+ public void EnsureCharCapacity(uint minCapacity)
+ {
+ EnsureByteCapacity(minCapacity * (ulong)sizeof(char));
+ }
+
+ /// <summary>
+ /// The logical length of the buffer in characters. (Does not include the final null.) Will automatically attempt to increase capacity.
+ /// This is where the usable data ends.
+ /// </summary>
+ /// <exception cref="OutOfMemoryException">Thrown if unable to allocate memory when setting.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">Thrown if the set size in bytes is uint.MaxValue (as space is implicitly reserved for the trailing null).</exception>
+ public unsafe uint Length
+ {
+ get { return _length; }
+ set
+ {
+ if (value == uint.MaxValue) throw new ArgumentOutOfRangeException(nameof(Length));
+
+ // Null terminate
+ EnsureCharCapacity(value + 1);
+ CharPointer[value] = '\0';
+
+ _length = value;
+ }
+ }
+
+ /// <summary>
+ /// For use when the native api null terminates but doesn't return a length.
+ /// If no null is found, the length will not be changed.
+ /// </summary>
+ public unsafe void SetLengthToFirstNull()
+ {
+ char* buffer = CharPointer;
+ uint capacity = CharCapacity;
+ for (uint i = 0; i < capacity; i++)
+ {
+ if (buffer[i] == '\0')
+ {
+ _length = i;
+ break;
+ }
+ }
+ }
+
+ internal unsafe char* CharPointer
+ {
+ get
+ {
+ return (char*)VoidPointer;
+ }
+ }
+
+ /// <summary>
+ /// True if the buffer contains the given character.
+ /// </summary>
+ [System.Security.SecurityCritical]
+ public unsafe bool Contains(char value)
+ {
+ char* start = CharPointer;
+ uint length = _length;
+
+ for (uint i = 0; i < length; i++)
+ {
+ if (*start++ == value) return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Returns true if the buffer starts with the given string.
+ /// </summary>
+ public bool StartsWith(string value)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ if (_length < (uint)value.Length) return false;
+ return SubstringEquals(value, startIndex: 0, count: value.Length);
+ }
+
+ /// <summary>
+ /// Returns true if the specified StringBuffer substring equals the given value.
+ /// </summary>
+ /// <param name="value">The value to compare against the specified substring.</param>
+ /// <param name="startIndex">Start index of the sub string.</param>
+ /// <param name="count">Length of the substring, or -1 to check all remaining.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
+ /// of the buffer's length.
+ /// </exception>
+ public unsafe bool SubstringEquals(string value, uint startIndex = 0, int count = -1)
+ {
+ if (value == null) return false;
+ if (count < -1) throw new ArgumentOutOfRangeException(nameof(count));
+ if (startIndex > _length) throw new ArgumentOutOfRangeException(nameof(startIndex));
+
+ uint realCount = count == -1 ? _length - startIndex : (uint)count;
+ if (checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count));
+
+ int length = value.Length;
+
+ // Check the substring length against the input length
+ if (realCount != (uint)length) return false;
+
+ fixed (char* valueStart = value)
+ {
+ char* bufferStart = CharPointer + startIndex;
+ for (int i = 0; i < length; i++)
+ {
+ // Note that indexing in this case generates faster code than trying to copy the pointer and increment it
+ if (*bufferStart++ != valueStart[i]) return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Append the given string.
+ /// </summary>
+ /// <param name="value">The string to append.</param>
+ /// <param name="startIndex">The index in the input string to start appending from.</param>
+ /// <param name="count">The count of characters to copy from the input string, or -1 for all remaining.</param>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
+ /// of <paramref name="value"/> characters.
+ /// </exception>
+ public void Append(string value, int startIndex = 0, int count = -1)
+ {
+ CopyFrom(
+ bufferIndex: _length,
+ source: value,
+ sourceIndex: startIndex,
+ count: count);
+ }
+
+ /// <summary>
+ /// Append the given buffer.
+ /// </summary>
+ /// <param name="value">The buffer to append.</param>
+ /// <param name="startIndex">The index in the input buffer to start appending from.</param>
+ /// <param name="count">The count of characters to copy from the buffer string.</param>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
+ /// of <paramref name="value"/> characters.
+ /// </exception>
+ public void Append(StringBuffer value, uint startIndex = 0)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ if (value.Length == 0) return;
+
+ value.CopyTo(
+ bufferIndex: startIndex,
+ destination: this,
+ destinationIndex: _length,
+ count: value.Length);
+ }
+
+ /// <summary>
+ /// Append the given buffer.
+ /// </summary>
+ /// <param name="value">The buffer to append.</param>
+ /// <param name="startIndex">The index in the input buffer to start appending from.</param>
+ /// <param name="count">The count of characters to copy from the buffer string.</param>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="value"/> is null.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range
+ /// of <paramref name="value"/> characters.
+ /// </exception>
+ public void Append(StringBuffer value, uint startIndex, uint count)
+ {
+ if (value == null) throw new ArgumentNullException(nameof(value));
+ if (count == 0) return;
+
+ value.CopyTo(
+ bufferIndex: startIndex,
+ destination: this,
+ destinationIndex: _length,
+ count: count);
+ }
+
+ /// <summary>
+ /// Copy contents to the specified buffer. Destination index must be within current destination length.
+ /// Will grow the destination buffer if needed.
+ /// </summary>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <paramref name="bufferIndex"/> or <paramref name="destinationIndex"/> or <paramref name="count"/> are outside the range
+ /// of <paramref name="value"/> characters.
+ /// </exception>
+ /// <exception cref="ArgumentNullException">Thrown if <paramref name="destination"/> is null.</exception>
+ public unsafe void CopyTo(uint bufferIndex, StringBuffer destination, uint destinationIndex, uint count)
+ {
+ if (destination == null) throw new ArgumentNullException(nameof(destination));
+ if (destinationIndex > destination._length) throw new ArgumentOutOfRangeException(nameof(destinationIndex));
+ if (bufferIndex >= _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex));
+ if (_length < checked(bufferIndex + count)) throw new ArgumentOutOfRangeException(nameof(count));
+
+ if (count == 0) return;
+ uint lastIndex = checked(destinationIndex + count);
+ if (destination._length < lastIndex) destination.Length = lastIndex;
+
+ Buffer.MemoryCopy(
+ source: CharPointer + bufferIndex,
+ destination: destination.CharPointer + destinationIndex,
+ destinationSizeInBytes: checked((long)(destination.ByteCapacity - (destinationIndex * sizeof(char)))),
+ sourceBytesToCopy: checked((long)count * sizeof(char)));
+ }
+
+ /// <summary>
+ /// Copy contents from the specified string into the buffer at the given index. Start index must be within the current length of
+ /// the buffer, will grow as necessary.
+ /// </summary>
+ public unsafe void CopyFrom(uint bufferIndex, string source, int sourceIndex = 0, int count = -1)
+ {
+ if (source == null) throw new ArgumentNullException(nameof(source));
+ if (bufferIndex > _length) throw new ArgumentOutOfRangeException(nameof(bufferIndex));
+ if (sourceIndex < 0 || sourceIndex > source.Length) throw new ArgumentOutOfRangeException(nameof(sourceIndex));
+ if (count == -1) count = source.Length - sourceIndex;
+ if (count < 0 || source.Length - count < sourceIndex) throw new ArgumentOutOfRangeException(nameof(count));
+
+ if (count == 0) return;
+ uint lastIndex = bufferIndex + (uint)count;
+ if (_length < lastIndex) Length = lastIndex;
+
+ fixed (char* content = source)
+ {
+ Buffer.MemoryCopy(
+ source: content + sourceIndex,
+ destination: CharPointer + bufferIndex,
+ destinationSizeInBytes: checked((long)(ByteCapacity - (bufferIndex * sizeof(char)))),
+ sourceBytesToCopy: (long)count * sizeof(char));
+ }
+ }
+
+ /// <summary>
+ /// Trim the specified values from the end of the buffer. If nothing is specified, nothing is trimmed.
+ /// </summary>
+ public unsafe void TrimEnd(char[] values)
+ {
+ if (values == null || values.Length == 0 || _length == 0) return;
+
+ char* end = CharPointer + _length - 1;
+
+ while (_length > 0 && Array.IndexOf(values, *end) >= 0)
+ {
+ Length = _length - 1;
+ end--;
+ }
+ }
+
+ /// <summary>
+ /// String representation of the entire buffer. If the buffer is larger than the maximum size string (int.MaxValue) this will throw.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">Thrown if the buffer is too big to fit into a string.</exception>
+ public unsafe override string ToString()
+ {
+ if (_length == 0) return string.Empty;
+ if (_length > int.MaxValue) throw new InvalidOperationException();
+ return new string(CharPointer, startIndex: 0, length: (int)_length);
+ }
+
+ /// <summary>
+ /// Get the given substring in the buffer.
+ /// </summary>
+ /// <param name="count">Count of characters to take, or remaining characters from <paramref name="startIndex"/> if -1.</param>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// Thrown if <paramref name="startIndex"/> or <paramref name="count"/> are outside the range of the buffer's length
+ /// or count is greater than the maximum string size (int.MaxValue).
+ /// </exception>
+ public unsafe string Substring(uint startIndex, int count = -1)
+ {
+ if (startIndex > (_length == 0 ? 0 : _length - 1)) throw new ArgumentOutOfRangeException(nameof(startIndex));
+ if (count < -1) throw new ArgumentOutOfRangeException(nameof(count));
+
+ uint realCount = count == -1 ? _length - startIndex : (uint)count;
+ if (realCount > int.MaxValue || checked(startIndex + realCount) > _length) throw new ArgumentOutOfRangeException(nameof(count));
+ if (realCount == 0) return string.Empty;
+
+ // The buffer could be bigger than will fit into a string, but the substring might fit. As the starting
+ // index might be bigger than int we need to index ourselves.
+ return new string(value: CharPointer + startIndex, startIndex: 0, length: (int)realCount);
+ }
+
+ public override void Free()
+ {
+ base.Free();
+ _length = 0;
+ }
+ }
+}
diff --git a/src/mscorlib/mscorlib.shared.sources.props b/src/mscorlib/mscorlib.shared.sources.props
index 82539946f4..2fdfa01cb5 100644
--- a/src/mscorlib/mscorlib.shared.sources.props
+++ b/src/mscorlib/mscorlib.shared.sources.props
@@ -106,7 +106,6 @@
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SEHException.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeBuffer.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHandle.cs" />
- <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandle.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\BStrWrapper.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\CurrencyWrapper.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ErrorWrapper.cs" />
@@ -118,8 +117,6 @@
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\InvalidComObjectException.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayRankMismatchException.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeArrayTypeMismatchException.cs" />
- <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeBuffer.cs" />
- <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\StringBuffer.cs" />
<InteropSources Condition="'$(FeatureCoreClr)'=='true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeCallableAttribute.cs" />
<InteropSources Condition="'$(FeatureCominterop)' != 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NonPortable.cs" />
<InteropSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\DispatchWrapper.cs" />
@@ -127,6 +124,17 @@
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ICustomFactory.cs" />
<InteropSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ObjectCreationDelegate.cs" />
</ItemGroup>
+ <ItemGroup Condition="'$(FeatureCoreFxInteropServices)' != 'true'">
+ <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\NativeBuffer.cs" />
+ <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\StringBuffer.cs" />
+ <InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandle.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="'$(FeatureCoreFxInteropServices)' == 'true'">
+ <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\NativeBuffer.cs" />
+ <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\StringBuffer.cs" />
+ <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandle.cs" />
+ <InteropSources Include="$(CoreFxSourcesRoot)\System\Runtime\InteropServices\SafeHeapHandleCache.cs" />
+ </ItemGroup>
<ItemGroup Condition="'$(FeatureClassicCominterop)' == 'true'">
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\IRegistrationServices.cs" />
<InteropSources Include="$(BclSourcesRoot)\System\Runtime\InteropServices\ITypeLibConverter.cs" />
@@ -760,10 +768,6 @@
<ThreadingSources Include="$(BclSourcesRoot)\System\Threading\Tasks\TaskToApm.cs" />
<ThreadingSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Threading\Tasks\IAsyncCausalityTracerStatics.cs" />
</ItemGroup>
- <ItemGroup Condition="'$(FeatureCoreFxFileStream)' != 'true'">
- <FileStreamSources Include="$(BclSourcesRoot)\System\IO\FileStream.cs" />
- <SafehandleSources Include="$(BclSourcesRoot)\Microsoft\Win32\SafeHandles\SafeFileHandle.cs" />
- </ItemGroup>
<ItemGroup Condition="'$(FeatureCoreFxFileStream)' == 'true'">
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.cs" />
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.NetStandard17.cs" />
@@ -779,6 +783,22 @@
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\FileStreamCompletionSource.Win32.cs" />
<FileStreamSources Include="$(CoreFxSourcesRoot)\System\IO\Win32Marshal.cs" />
</ItemGroup>
+ <ItemGroup Condition="'$(FeatureCoreFxPath)' == 'true'">
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.cs" />
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.cs" />
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.CaseSensitivity.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="'$(FeatureCoreFxPath)' == 'true' and '$(TargetsUnix)' == 'true'">
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Unix.cs" />
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Unix.cs" />
+ </ItemGroup>
+ <ItemGroup Condition="'$(FeatureCoreFxPath)' == 'true' and '$(TargetsUnix)' != 'true'">
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Win32.cs" />
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Windows.cs" />
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathHelper.Windows.cs" />
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Windows.cs" />
+ <IoSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Windows.StringBuffer.cs" />
+ </ItemGroup>
<ItemGroup Condition="'$(FeatureCoreFxOverlapped)' == 'true'" >
<ThreadingSources Include="$(CoreFxSourcesRoot)\System\Threading\DeferredDisposableLifetime.cs" />
<ThreadingSources Include="$(CoreFxSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.cs" />
@@ -811,10 +831,6 @@
<IoSources Include="$(BclSourcesRoot)\System\IO\FileAttributes.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\IOException.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\MemoryStream.cs" />
- <IoSources Include="$(BclSourcesRoot)\System\IO\Path.cs" />
- <IoSources Include="$(BclSourcesRoot)\System\IO\PathHelper.cs" />
- <IoSources Condition="'$(TargetsUnix)' != 'true'" Include="$(BclSourcesRoot)\System\IO\LongPathHelper.cs" />
- <IoSources Include="$(BclSourcesRoot)\System\IO\PathInternal.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\PathTooLongException.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\PinnedBufferMemoryStream.cs" />
<IoSources Include="$(BclSourcesRoot)\System\IO\ReadLinesIterator.cs" />
@@ -1176,6 +1192,8 @@
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\System\HResults.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\Interop.BOOL.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\Interop.Libraries.cs" />
+ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\BCrypt\Interop.BCryptGenRandom.cs" />
+ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\BCrypt\Interop.NTSTATUS.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.CancelIoEx.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.CloseHandle.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.CreateFile.cs" />
@@ -1186,6 +1204,10 @@
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.FormatMessage.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetFileInformationByHandleEx.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetFileType_SafeHandle.cs" />
+ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetFullPathNameW.cs" />
+ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetLongPathNameW.cs" />
+ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetTempFileNameW.cs" />
+ <WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.GetTempPathW.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.LockFile.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.ReadFile_SafeHandle_IntPtr.cs" />
<WindowsInteropSources Include="$(CoreFxSourcesRoot)\Interop\Windows\mincore\Interop.ReadFile_SafeHandle_NativeOverlapped.cs" />
@@ -1226,12 +1248,16 @@
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\Interop.IOErrors.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\Interop.Libraries.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Close.cs" />
+ <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.GetCwd.cs" />
+ <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.GetUnixName.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.FLock.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.FSync.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.FTruncate.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.LSeek.cs" />
+ <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.MksTemps.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Open.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.OpenFlags.cs" />
+ <UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.PathConf.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Permissions.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.PosixFAdvise.cs" />
<UnixInteropSources Include="$(CoreFxSourcesRoot)\Interop\Unix\System.Native\Interop.Read.cs" />
diff --git a/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs b/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
deleted file mode 100644
index ab06347ee4..0000000000
--- a/src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-/*============================================================
-**
-**
-**
-** A wrapper for file handles
-**
-**
-===========================================================*/
-
-using System;
-using System.Security;
-using System.Security.Permissions;
-using System.Runtime.InteropServices;
-using System.Runtime.CompilerServices;
-using System.Runtime.ConstrainedExecution;
-using System.Runtime.Versioning;
-using Microsoft.Win32;
-
-namespace Microsoft.Win32.SafeHandles {
-
- [System.Security.SecurityCritical] // auto-generated_required
- public sealed class SafeFileHandle: SafeHandleZeroOrMinusOneIsInvalid {
-
- private SafeFileHandle() : base(true)
- {
- }
-
- public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle) {
- SetHandle(preexistingHandle);
- }
-
- [System.Security.SecurityCritical]
- override protected bool ReleaseHandle()
- {
- return Win32Native.CloseHandle(handle);
- }
- }
-}
-
diff --git a/src/mscorlib/src/System/AppDomain.cs b/src/mscorlib/src/System/AppDomain.cs
index 475fdd76f4..03c061f038 100644
--- a/src/mscorlib/src/System/AppDomain.cs
+++ b/src/mscorlib/src/System/AppDomain.cs
@@ -3597,7 +3597,7 @@ namespace System {
AppDomainInitializerInfo initializerInfo = (AppDomainInitializerInfo)args[5];
string sandboxName = (string)args[6];
string[] propertyNames = (string[])args[7]; // can contain null elements
- string[] propertyValues = (string[])args[8]; // can contain null elements
+ string[] propertyValues = (string[])args[8]; // can contain null elements
// extract evidence
Evidence providedSecurityInfo = null;
Evidence creatorsSecurityInfo = null;
@@ -3617,8 +3617,8 @@ namespace System {
{
if(propertyValues[i]==null)
throw new ArgumentNullException("APPBASE");
-
- if (Path.IsRelative(propertyValues[i]))
+
+ if (PathInternal.IsPartiallyQualified(propertyValues[i]))
throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
newSetup.ApplicationBase = NormalizePath(propertyValues[i], fullCheck: true);
@@ -3700,7 +3700,7 @@ namespace System {
if( path.Length==0 ) // skip empty dirs
continue;
- if (Path.IsRelative(path))
+ if (PathInternal.IsPartiallyQualified(path))
throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
string appPath = NormalizePath(path, fullCheck: true);
@@ -3817,10 +3817,7 @@ namespace System {
maxPathLength: PathInternal.MaxShortPath,
expandShortPaths: true);
#else
- return Path.NormalizePath(
- path: path,
- fullCheck: fullCheck,
- expandShortPaths: true);
+ return Path.GetFullPath(path);
#endif
}
diff --git a/src/mscorlib/src/System/AppDomainSetup.cs b/src/mscorlib/src/System/AppDomainSetup.cs
index 43c85838a9..710df60d2b 100644
--- a/src/mscorlib/src/System/AppDomainSetup.cs
+++ b/src/mscorlib/src/System/AppDomainSetup.cs
@@ -896,7 +896,7 @@ namespace System {
else
fDelimiter = true;
- newPath.Append(Path.GetFullPathInternal(directories[i]));
+ newPath.Append(Path.GetFullPath(directories[i]));
}
}
diff --git a/src/mscorlib/src/System/CfgParser.cs b/src/mscorlib/src/System/CfgParser.cs
index 1996f8d444..aba416c820 100644
--- a/src/mscorlib/src/System/CfgParser.cs
+++ b/src/mscorlib/src/System/CfgParser.cs
@@ -264,7 +264,7 @@ namespace System
}
if (!skipSecurityStuff) {
- (new FileIOPermission( FileIOPermissionAccess.Read, System.IO.Path.GetFullPathInternal( fileName ) )).Demand();
+ (new FileIOPermission(FileIOPermissionAccess.Read, Path.GetFullPath(fileName))).Demand();
}
#pragma warning disable 618
(new SecurityPermission(SecurityPermissionFlag.UnmanagedCode)).Assert();
diff --git a/src/mscorlib/src/System/IO/Directory.cs b/src/mscorlib/src/System/IO/Directory.cs
index 03f9a1e6e5..b13226268d 100644
--- a/src/mscorlib/src/System/IO/Directory.cs
+++ b/src/mscorlib/src/System/IO/Directory.cs
@@ -45,9 +45,9 @@ namespace System.IO {
throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), nameof(path));
Contract.EndContractBlock();
- String fullPath = Path.GetFullPathInternal(path);
-
- String s = Path.GetDirectoryName(fullPath);
+ string fullPath = Path.GetFullPath(path);
+
+ string s = Path.GetDirectoryName(fullPath);
if (s==null)
return null;
return new DirectoryInfo(s);
@@ -82,7 +82,7 @@ namespace System.IO {
Contract.Requires(path != null);
Contract.Requires(path.Length != 0);
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
// You need read access to the directory to be returned back and write access to all the directories
// that you need to create. If we fail any security checks we will not create any directories at all.
@@ -142,12 +142,12 @@ namespace System.IO {
|| fullPath.EndsWith( Path.AltDirectorySeparatorChar ) )
demandPath = fullPath + ".";
else
- demandPath = fullPath + Path.DirectorySeparatorCharAsString + ".";
+ demandPath = fullPath + Path.DirectorySeparatorChar + ".";
}
else {
if (!(fullPath.EndsWith( Path.DirectorySeparatorChar )
|| fullPath.EndsWith( Path.AltDirectorySeparatorChar )) )
- demandPath = fullPath + Path.DirectorySeparatorCharAsString;
+ demandPath = fullPath + Path.DirectorySeparatorChar;
else
demandPath = fullPath;
}
@@ -170,13 +170,13 @@ namespace System.IO {
int length = fullPath.Length;
// We need to trim the trailing slash or the code will try to create 2 directories of the same name.
- if (length >= 2 && Path.IsDirectorySeparator(fullPath[length - 1]))
+ if (length >= 2 && PathInternal.IsDirectorySeparator(fullPath[length - 1]))
length--;
- int lengthRoot = Path.GetRootLength(fullPath);
+ int lengthRoot = PathInternal.GetRootLength(fullPath);
// For UNC paths that are only // or ///
- if (length == 2 && Path.IsDirectorySeparator(fullPath[1]))
+ if (length == 2 && PathInternal.IsDirectorySeparator(fullPath[1]))
throw new IOException(Environment.GetResourceString("IO.IO_CannotCreateDirectory", path));
// We can save a bunch of work if the directory we want to create already exists. This also
@@ -358,7 +358,7 @@ namespace System.IO {
// Get fully qualified file name ending in \* for security check
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
String demandPath = GetDemandDir(fullPath, true);
#if FEATURE_CORECLR
@@ -925,10 +925,10 @@ namespace System.IO {
if (path==null)
throw new ArgumentNullException(nameof(path));
Contract.EndContractBlock();
-
- String fullPath = Path.GetFullPathInternal(path);
- String root = fullPath.Substring(0, Path.GetRootLength(fullPath));
- String demandPath = GetDemandDir(root, true);
+
+ string fullPath = Path.GetFullPath(path);
+ string root = fullPath.Substring(0, PathInternal.GetRootLength(fullPath));
+ string demandPath = GetDemandDir(root, true);
#if FEATURE_CORECLR
FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, demandPath);
@@ -942,7 +942,7 @@ namespace System.IO {
internal static String InternalGetDirectoryRoot(String path) {
if (path == null) return null;
- return path.Substring(0, Path.GetRootLength(path));
+ return path.Substring(0, PathInternal.GetRootLength(path));
}
/*===============================CurrentDirectory===============================
@@ -1022,7 +1022,8 @@ namespace System.IO {
[System.Security.SecurityCritical]
private static string NewGetCurrentDirectory()
{
- using (StringBuffer buffer = new StringBuffer(PathInternal.MaxShortPath))
+ // Start with a buffer the size of MAX_PATH
+ using (StringBuffer buffer = new StringBuffer(260))
{
uint result = 0;
while ((result = Win32Native.GetCurrentDirectoryW(buffer.CharCapacity, buffer.GetHandle())) > buffer.CharCapacity)
@@ -1039,7 +1040,7 @@ namespace System.IO {
#if !PLATFORM_UNIX
if (buffer.Contains('~'))
- return LongPathHelper.GetLongPathName(buffer);
+ return Path.GetFullPath(buffer.ToString());
#endif
return buffer.ToString();
@@ -1067,7 +1068,7 @@ namespace System.IO {
new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand();
#pragma warning restore 618
- String fulldestDirName = Path.GetFullPathInternal(path);
+ String fulldestDirName = Path.GetFullPath(path);
if (!Win32Native.SetCurrentDirectory(fulldestDirName)) {
// If path doesn't exist, this sets last error to 2 (File
@@ -1103,13 +1104,13 @@ namespace System.IO {
throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), nameof(destDirName));
Contract.EndContractBlock();
- String fullsourceDirName = Path.GetFullPathInternal(sourceDirName);
+ String fullsourceDirName = Path.GetFullPath(sourceDirName);
String sourcePath = GetDemandDir(fullsourceDirName, false);
if (PathInternal.IsDirectoryTooLong(sourcePath))
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
- String fulldestDirName = Path.GetFullPathInternal(destDirName);
+ String fulldestDirName = Path.GetFullPath(destDirName);
String destPath = GetDemandDir(fulldestDirName, false);
if (PathInternal.IsDirectoryTooLong(destPath))
@@ -1153,21 +1154,21 @@ namespace System.IO {
[System.Security.SecuritySafeCritical]
public static void Delete(String path)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
Delete(fullPath, path, false, true);
}
[System.Security.SecuritySafeCritical]
public static void Delete(String path, bool recursive)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
Delete(fullPath, path, recursive, true);
}
[System.Security.SecurityCritical]
internal static void UnsafeDelete(String path, bool recursive)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
Delete(fullPath, path, recursive, false);
}
@@ -1235,12 +1236,12 @@ namespace System.IO {
Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
// Open a Find handle
- using (SafeFindHandle hnd = Win32Native.FindFirstFile(fullPath+Path.DirectorySeparatorCharAsString+"*", data)) {
+ using (SafeFindHandle hnd = Win32Native.FindFirstFile(fullPath + Path.DirectorySeparatorChar + "*", data)) {
if (hnd.IsInvalid) {
hr = Marshal.GetLastWin32Error();
__Error.WinIOError(hr, fullPath);
}
-
+
do {
bool isDir = (0!=(data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY));
if (isDir) {
@@ -1254,8 +1255,8 @@ namespace System.IO {
// itself.
bool shouldRecurse = (0 == (data.dwFileAttributes & (int) FileAttributes.ReparsePoint));
if (shouldRecurse) {
- String newFullPath = Path.InternalCombine(fullPath, data.cFileName);
- String newUserPath = Path.InternalCombine(userPath, data.cFileName);
+ String newFullPath = Path.Combine(fullPath, data.cFileName);
+ String newUserPath = Path.Combine(userPath, data.cFileName);
try {
DeleteHelper(newFullPath, newUserPath, recursive, false);
}
@@ -1270,7 +1271,7 @@ namespace System.IO {
// unmount it.
if (data.dwReserved0 == Win32Native.IO_REPARSE_TAG_MOUNT_POINT) {
// Use full path plus a trailing '\'
- String mountPoint = Path.InternalCombine(fullPath, data.cFileName + Path.DirectorySeparatorChar);
+ String mountPoint = Path.Combine(fullPath, data.cFileName + Path.DirectorySeparatorChar);
r = Win32Native.DeleteVolumeMountPoint(mountPoint);
if (!r) {
hr = Marshal.GetLastWin32Error();
@@ -1289,7 +1290,7 @@ namespace System.IO {
// RemoveDirectory on a symbolic link will
// remove the link itself.
- String reparsePoint = Path.InternalCombine(fullPath, data.cFileName);
+ String reparsePoint = Path.Combine(fullPath, data.cFileName);
r = Win32Native.RemoveDirectory(reparsePoint);
if (!r) {
hr = Marshal.GetLastWin32Error();
@@ -1307,7 +1308,7 @@ namespace System.IO {
}
}
else {
- String fileName = Path.InternalCombine(fullPath, data.cFileName);
+ String fileName = Path.Combine(fullPath, data.cFileName);
r = Win32Native.DeleteFile(fileName);
if (!r) {
hr = Marshal.GetLastWin32Error();
diff --git a/src/mscorlib/src/System/IO/DirectoryInfo.cs b/src/mscorlib/src/System/IO/DirectoryInfo.cs
index ec58ae9bff..d29ab5791c 100644
--- a/src/mscorlib/src/System/IO/DirectoryInfo.cs
+++ b/src/mscorlib/src/System/IO/DirectoryInfo.cs
@@ -84,7 +84,7 @@ namespace System.IO {
}
// Must fully qualify the path for the security check
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
demandDir = new String[] {Directory.GetDemandDir(fullPath, true)};
#if FEATURE_CORECLR
@@ -106,7 +106,7 @@ namespace System.IO {
#endif //FEATURE_CORESYSTEM
internal DirectoryInfo(String fullPath, bool junk)
{
- Contract.Assert(Path.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!");
+ Contract.Assert(PathInternal.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!");
// Fast path when we know a DirectoryInfo exists.
OriginalPath = Path.GetFileName(fullPath);
@@ -198,8 +198,8 @@ namespace System.IO {
{
Contract.Requires(path != null);
- String newDirs = Path.InternalCombine(FullPath, path);
- String fullPath = Path.GetFullPathInternal(newDirs);
+ String newDirs = Path.Combine(FullPath, path);
+ String fullPath = Path.GetFullPath(newDirs);
if (0!=String.Compare(FullPath,0,fullPath,0, FullPath.Length,StringComparison.OrdinalIgnoreCase)) {
String displayPath = __Error.GetDisplayablePath(DisplayPath, false);
@@ -519,7 +519,7 @@ namespace System.IO {
get
{
String demandPath;
- int rootLength = Path.GetRootLength(FullPath);
+ int rootLength = PathInternal.GetRootLength(FullPath);
String rootPath = FullPath.Substring(0, rootLength);
demandPath = Directory.GetDemandDir(rootPath, true);
@@ -547,7 +547,7 @@ namespace System.IO {
#else
new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, demandDir, false, false).Demand();
#endif
- String fullDestDirName = Path.GetFullPathInternal(destDirName);
+ String fullDestDirName = Path.GetFullPath(destDirName);
String demandPath;
if (!fullDestDirName.EndsWith(Path.DirectorySeparatorChar))
fullDestDirName = fullDestDirName + Path.DirectorySeparatorChar;
diff --git a/src/mscorlib/src/System/IO/DriveInfo.cs b/src/mscorlib/src/System/IO/DriveInfo.cs
index 62eac429af..efb322bf8a 100644
--- a/src/mscorlib/src/System/IO/DriveInfo.cs
+++ b/src/mscorlib/src/System/IO/DriveInfo.cs
@@ -63,7 +63,7 @@ namespace System.IO
_name = driveName + ":\\";
else {
// GetPathRoot does not check all invalid characters
- Path.CheckInvalidPathChars(driveName);
+ PathInternal.CheckInvalidPathChars(driveName);
_name = Path.GetPathRoot(driveName);
// Disallow null or empty drive letters and UNC paths
if (_name == null || _name.Length == 0 || _name.StartsWith("\\\\", StringComparison.Ordinal))
diff --git a/src/mscorlib/src/System/IO/File.cs b/src/mscorlib/src/System/IO/File.cs
index 76cd0b2280..ce7022096c 100644
--- a/src/mscorlib/src/System/IO/File.cs
+++ b/src/mscorlib/src/System/IO/File.cs
@@ -139,8 +139,8 @@ namespace System.IO {
Contract.Requires(sourceFileName.Length > 0);
Contract.Requires(destFileName.Length > 0);
- String fullSourceFileName = Path.GetFullPathInternal(sourceFileName);
- String fullDestFileName = Path.GetFullPathInternal(destFileName);
+ String fullSourceFileName = Path.GetFullPath(sourceFileName);
+ String fullDestFileName = Path.GetFullPath(destFileName);
#if FEATURE_CORECLR
if (checkHost) {
@@ -251,7 +251,7 @@ namespace System.IO {
[System.Security.SecurityCritical]
internal static void InternalDelete(String path, bool checkHost)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
#if FEATURE_CORECLR
if (checkHost)
@@ -282,7 +282,7 @@ namespace System.IO {
throw new ArgumentNullException(nameof(path));
Contract.EndContractBlock();
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
FileIOPermission.QuickDemand(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, fullPath, false, false);
bool r = Win32Native.DecryptFile(fullPath, 0);
@@ -306,7 +306,7 @@ namespace System.IO {
throw new ArgumentNullException(nameof(path));
Contract.EndContractBlock();
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
FileIOPermission.QuickDemand(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, fullPath, false, false);
bool r = Win32Native.EncryptFile(fullPath);
@@ -352,12 +352,12 @@ namespace System.IO {
if (path.Length == 0)
return false;
- path = Path.GetFullPathInternal(path);
+ path = Path.GetFullPath(path);
// After normalizing, check whether path ends in directory separator.
// Otherwise, FillAttributeInfo removes it and we may return a false positive.
- // GetFullPathInternal should never return null
- Contract.Assert(path != null, "File.Exists: GetFullPathInternal returned null");
- if (path.Length > 0 && Path.IsDirectorySeparator(path[path.Length - 1]))
+ // GetFullPath should never return null
+ Contract.Assert(path != null, "File.Exists: GetFullPath returned null");
+ if (path.Length > 0 && PathInternal.IsDirectorySeparator(path[path.Length - 1]))
{
return false;
}
@@ -441,7 +441,7 @@ namespace System.IO {
[System.Security.SecurityCritical]
private static DateTime InternalGetCreationTimeUtc(String path, bool checkHost)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
#if FEATURE_CORECLR
if (checkHost)
{
@@ -498,7 +498,7 @@ namespace System.IO {
[System.Security.SecurityCritical]
private static DateTime InternalGetLastAccessTimeUtc(String path, bool checkHost)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
#if FEATURE_CORECLR
if (checkHost)
{
@@ -555,7 +555,7 @@ namespace System.IO {
[System.Security.SecurityCritical]
private static DateTime InternalGetLastWriteTimeUtc(String path, bool checkHost)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
#if FEATURE_CORECLR
if (checkHost)
{
@@ -578,7 +578,7 @@ namespace System.IO {
[System.Security.SecuritySafeCritical]
public static FileAttributes GetAttributes(String path)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
#if FEATURE_CORECLR
FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, fullPath);
state.EnsureState();
@@ -601,7 +601,7 @@ namespace System.IO {
#endif
public static void SetAttributes(String path, FileAttributes fileAttributes)
{
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
#if !FEATURE_CORECLR
FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullPath, false, false);
#endif
@@ -633,7 +633,7 @@ namespace System.IO {
throw new ArgumentNullException(nameof(fileSecurity));
Contract.EndContractBlock();
- String fullPath = Path.GetFullPathInternal(path);
+ String fullPath = Path.GetFullPath(path);
// Appropriate security check should be done for us by FileSecurity.
fileSecurity.Persist(fullPath);
}
@@ -1052,8 +1052,8 @@ namespace System.IO {
throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), nameof(destFileName));
Contract.EndContractBlock();
- String fullSourceFileName = Path.GetFullPathInternal(sourceFileName);
- String fullDestFileName = Path.GetFullPathInternal(destFileName);
+ String fullSourceFileName = Path.GetFullPath(sourceFileName);
+ String fullDestFileName = Path.GetFullPath(destFileName);
#if FEATURE_CORECLR
if (checkHost) {
@@ -1107,11 +1107,11 @@ namespace System.IO {
// Write permission to all three files, read permission to source
// and dest.
- String fullSrcPath = Path.GetFullPathInternal(sourceFileName);
- String fullDestPath = Path.GetFullPathInternal(destinationFileName);
+ String fullSrcPath = Path.GetFullPath(sourceFileName);
+ String fullDestPath = Path.GetFullPath(destinationFileName);
String fullBackupPath = null;
if (destinationBackupFileName != null)
- fullBackupPath = Path.GetFullPathInternal(destinationBackupFileName);
+ fullBackupPath = Path.GetFullPath(destinationBackupFileName);
#if FEATURE_CORECLR
FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read | FileSecurityStateAccess.Write, sourceFileName, fullSrcPath);
@@ -1251,7 +1251,7 @@ namespace System.IO {
// we usually get ERROR_PATH_NOT_FOUND from the OS. We should
// probably be consistent w/ every other directory.
int hr = Marshal.GetLastWin32Error();
- String FullPath = Path.GetFullPathInternal(path);
+ String FullPath = Path.GetFullPath(path);
if (hr==__Error.ERROR_PATH_NOT_FOUND && FullPath.Equals(Directory.GetDirectoryRoot(FullPath)))
hr = __Error.ERROR_ACCESS_DENIED;
diff --git a/src/mscorlib/src/System/IO/FileInfo.cs b/src/mscorlib/src/System/IO/FileInfo.cs
index d9f57532cb..60a7b35d97 100644
--- a/src/mscorlib/src/System/IO/FileInfo.cs
+++ b/src/mscorlib/src/System/IO/FileInfo.cs
@@ -74,7 +74,7 @@ namespace System.IO {
{
OriginalPath = fileName;
// Must fully qualify the path for the security check
- String fullPath = Path.GetFullPathInternal(fileName);
+ String fullPath = Path.GetFullPath(fileName);
#if FEATURE_CORECLR
if (checkHost)
{
@@ -115,7 +115,7 @@ namespace System.IO {
#endif //FEATURE_CORESYSTEM
internal FileInfo(String fullPath, bool ignoreThis)
{
- Contract.Assert(Path.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!");
+ Contract.Assert(PathInternal.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!");
_name = Path.GetFileName(fullPath);
OriginalPath = _name;
FullPath = fullPath;
@@ -388,7 +388,7 @@ namespace System.IO {
throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), nameof(destFileName));
Contract.EndContractBlock();
- String fullDestFileName = Path.GetFullPathInternal(destFileName);
+ string fullDestFileName = Path.GetFullPath(destFileName);
#if FEATURE_CORECLR
FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Write | FileSecurityStateAccess.Read, DisplayPath, FullPath);
FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Write, destFileName, fullDestFileName);
diff --git a/src/mscorlib/src/System/IO/FileSecurityState.cs b/src/mscorlib/src/System/IO/FileSecurityState.cs
index a3fa1fb460..f5a3f63c84 100644
--- a/src/mscorlib/src/System/IO/FileSecurityState.cs
+++ b/src/mscorlib/src/System/IO/FileSecurityState.cs
@@ -56,7 +56,7 @@ namespace System.IO
else
{
VerifyPath(path);
- m_canonicalizedPath = System.IO.Path.GetFullPathInternal(path);
+ m_canonicalizedPath = IO.Path.GetFullPath(path);
}
}
@@ -115,7 +115,7 @@ namespace System.IO
throw new ArgumentOutOfRangeException(nameof(access), Environment.GetResourceString("Arg_EnumIllegalVal"));
}
- private static void VerifyPath(String path)
+ private static void VerifyPath(string path)
{
if (path != null)
{
@@ -126,7 +126,7 @@ namespace System.IO
throw new ArgumentException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
#endif
- System.IO.Path.CheckInvalidPathChars(path, checkAdditional: true);
+ PathInternal.CheckInvalidPathChars(path);
}
}
}
diff --git a/src/mscorlib/src/System/IO/FileStream.cs b/src/mscorlib/src/System/IO/FileStream.cs
deleted file mode 100644
index 2eb8744862..0000000000
--- a/src/mscorlib/src/System/IO/FileStream.cs
+++ /dev/null
@@ -1,2695 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-/*============================================================
-**
-**
-**
-**
-**
-** Purpose: Exposes a Stream around a file, with full
-** synchronous and asychronous support, and buffering.
-**
-**
-===========================================================*/
-using System;
-using Microsoft.Win32;
-using Microsoft.Win32.SafeHandles;
-using System.Security;
-#if FEATURE_MACL
-using System.Security.AccessControl;
-#endif
-using System.Security.Permissions;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Runtime.InteropServices;
-#if FEATURE_REMOTING
-using System.Runtime.Remoting.Messaging;
-#endif
-using System.Runtime.CompilerServices;
-using System.Globalization;
-using System.Runtime.Versioning;
-using System.Diagnostics.Contracts;
-using System.Diagnostics.Tracing;
-
-/*
- * FileStream supports different modes of accessing the disk - async mode
- * and sync mode. They are two completely different codepaths in the
- * sync & async methods (ie, Read/Write vs. BeginRead/BeginWrite). File
- * handles in NT can be opened in only sync or overlapped (async) mode,
- * and we have to deal with this pain. Stream has implementations of
- * the sync methods in terms of the async ones, so we'll
- * call through to our base class to get those methods when necessary.
- *
- * Also buffering is added into FileStream as well. Folded in the
- * code from BufferedStream, so all the comments about it being mostly
- * aggressive (and the possible perf improvement) apply to FileStream as
- * well. Also added some buffering to the async code paths.
- *
- * Class Invariants:
- * The class has one buffer, shared for reading & writing. It can only be
- * used for one or the other at any point in time - not both. The following
- * should be true:
- * 0 <= _readPos <= _readLen < _bufferSize
- * 0 <= _writePos < _bufferSize
- * _readPos == _readLen && _readPos > 0 implies the read buffer is valid,
- * but we're at the end of the buffer.
- * _readPos == _readLen == 0 means the read buffer contains garbage.
- * Either _writePos can be greater than 0, or _readLen & _readPos can be
- * greater than zero, but neither can be greater than zero at the same time.
- *
- */
-
-namespace System.IO {
-
- // This is an internal object implementing IAsyncResult with fields
- // for all of the relevant data necessary to complete the IO operation.
- // This is used by AsyncFSCallback and all of the async methods.
- // We should probably make this a nested type of FileStream. But
- // I don't know how to define a nested class in mscorlib.h
-
- unsafe internal sealed class FileStreamAsyncResult : IAsyncResult
- {
- // README:
- // If you modify the order of these fields, make sure to update
- // the native VM definition of this class as well!!!
- // User code callback
- private AsyncCallback _userCallback;
- private Object _userStateObject;
- private ManualResetEvent _waitHandle;
- [System.Security.SecurityCritical]
- private SafeFileHandle _handle; // For cancellation support.
-
- [SecurityCritical]
- private NativeOverlapped* _overlapped;
- internal NativeOverlapped* OverLapped { [SecurityCritical]get { return _overlapped; } }
- internal bool IsAsync { [SecuritySafeCritical]get { return _overlapped != null; } }
-
-
- internal int _EndXxxCalled; // Whether we've called EndXxx already.
- private int _numBytes; // number of bytes read OR written
- internal int NumBytes { get { return _numBytes; } }
-
- private int _errorCode;
- internal int ErrorCode { get { return _errorCode; } }
-
- private int _numBufferedBytes;
- internal int NumBufferedBytes { get { return _numBufferedBytes; } }
-
- internal int NumBytesRead { get { return _numBytes + _numBufferedBytes; } }
-
- private bool _isWrite; // Whether this is a read or a write
- internal bool IsWrite { get { return _isWrite; } }
-
- private bool _isComplete; // Value for IsCompleted property
- private bool _completedSynchronously; // Which thread called callback
-
- // The NativeOverlapped struct keeps a GCHandle to this IAsyncResult object.
- // So if the user doesn't call EndRead/EndWrite, a finalizer won't help because
- // it'll never get called.
-
- // Overlapped class will take care of the async IO operations in progress
- // when an appdomain unload occurs.
-
- [System.Security.SecurityCritical] // auto-generated
- private unsafe static IOCompletionCallback s_IOCallback;
-
- [SecuritySafeCritical]
- internal FileStreamAsyncResult(
- int numBufferedBytes,
- byte[] bytes,
- SafeFileHandle handle,
- AsyncCallback userCallback,
- Object userStateObject,
- bool isWrite)
- {
- _userCallback = userCallback;
- _userStateObject = userStateObject;
- _isWrite = isWrite;
- _numBufferedBytes = numBufferedBytes;
- _handle = handle;
-
- // For Synchronous IO, I could go with either a callback and using
- // the managed Monitor class, or I could create a handle and wait on it.
- ManualResetEvent waitHandle = new ManualResetEvent(false);
- _waitHandle = waitHandle;
-
- // Create a managed overlapped class
- // We will set the file offsets later
- Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, this);
-
- // Pack the Overlapped class, and store it in the async result
- if (userCallback != null)
- {
- var ioCallback = s_IOCallback; // cached static delegate; delay initialized due to it being SecurityCritical
- if (ioCallback == null) s_IOCallback = ioCallback = new IOCompletionCallback(AsyncFSCallback);
- _overlapped = overlapped.Pack(ioCallback, bytes);
- }
- else
- {
- _overlapped = overlapped.UnsafePack(null, bytes);
- }
-
- Contract.Assert(_overlapped != null, "Did Overlapped.Pack or Overlapped.UnsafePack just return a null?");
- }
-
- internal static FileStreamAsyncResult CreateBufferedReadResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite)
- {
- FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytes, userCallback, userStateObject, isWrite);
- asyncResult.CallUserCallback();
- return asyncResult;
- }
-
- // This creates a synchronous Async Result. We should consider making this a separate class and maybe merge it with
- // System.IO.Stream.SynchronousAsyncResult
- private FileStreamAsyncResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite)
- {
- _userCallback = userCallback;
- _userStateObject = userStateObject;
- _isWrite = isWrite;
- _numBufferedBytes = numBufferedBytes;
- }
-
- public Object AsyncState
- {
- get { return _userStateObject; }
- }
-
- public bool IsCompleted
- {
- get { return _isComplete; }
- }
-
- public WaitHandle AsyncWaitHandle
- {
- [System.Security.SecuritySafeCritical] // auto-generated
- get {
- // Consider uncommenting this someday soon - the EventHandle
- // in the Overlapped struct is really useless half of the
- // time today since the OS doesn't signal it. If users call
- // EndXxx after the OS call happened to complete, there's no
- // reason to create a synchronization primitive here. Fixing
- // this will save us some perf, assuming we can correctly
- // initialize the ManualResetEvent.
- if (_waitHandle == null) {
- ManualResetEvent mre = new ManualResetEvent(false);
- if (_overlapped != null && _overlapped->EventHandle != IntPtr.Zero) {
- mre.SafeWaitHandle = new SafeWaitHandle(_overlapped->EventHandle, true);
- }
-
- // make sure only one thread sets _waitHandle
- if (Interlocked.CompareExchange<ManualResetEvent>(ref _waitHandle, mre, null) == null) {
- if (_isComplete)
- _waitHandle.Set();
- }
- else {
- // There's a slight but acceptable race condition if we weren't
- // the thread that set _waitHandle and this code path
- // returns before the code in the if statement
- // executes (on the other thread). However, the
- // caller is waiting for the wait handle to be set,
- // which will still happen.
- mre.Close();
- }
- }
- return _waitHandle;
- }
- }
-
- // Returns true iff the user callback was called by the thread that
- // called BeginRead or BeginWrite. If we use an async delegate or
- // threadpool thread internally, this will be false. This is used
- // by code to determine whether a successive call to BeginRead needs
- // to be done on their main thread or in their callback to avoid a
- // stack overflow on many reads or writes.
- public bool CompletedSynchronously
- {
- get { return _completedSynchronously; }
- }
-
- private void CallUserCallbackWorker()
- {
- _isComplete = true;
-
- // ensure _isComplete is set before reading _waitHandle
- Thread.MemoryBarrier();
- if (_waitHandle != null)
- _waitHandle.Set();
-
- _userCallback(this);
- }
-
- internal void CallUserCallback()
- {
- // Convenience method for me, since I have to do this in a number
- // of places in the buffering code for fake IAsyncResults.
- // AsyncFSCallback intentionally does not use this method.
-
- if (_userCallback != null) {
- // Call user's callback on a threadpool thread.
- // Set completedSynchronously to false, since it's on another
- // thread, not the main thread.
- _completedSynchronously = false;
- ThreadPool.QueueUserWorkItem(state => ((FileStreamAsyncResult)state).CallUserCallbackWorker(), this);
- }
- else {
- _isComplete = true;
-
- // ensure _isComplete is set before reading _waitHandle
- Thread.MemoryBarrier();
- if (_waitHandle != null)
- _waitHandle.Set();
- }
- }
-
- [SecurityCritical]
- internal void ReleaseNativeResource()
- {
- // Free memory & GC handles.
- if (this._overlapped != null)
- Overlapped.Free(_overlapped);
- }
-
- internal void Wait()
- {
- if (_waitHandle != null)
- {
- // We must block to ensure that AsyncFSCallback has completed,
- // and we should close the WaitHandle in here. AsyncFSCallback
- // and the hand-ported imitation version in COMThreadPool.cpp
- // are the only places that set this event.
- try
- {
- _waitHandle.WaitOne();
- Contract.Assert(_isComplete == true, "FileStreamAsyncResult::Wait - AsyncFSCallback didn't set _isComplete to true!");
- }
- finally
- {
- _waitHandle.Close();
- }
- }
- }
-
- // When doing IO asynchronously (ie, _isAsync==true), this callback is
- // called by a free thread in the threadpool when the IO operation
- // completes.
- [System.Security.SecurityCritical] // auto-generated
- unsafe private static void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
- {
- BCLDebug.Log(String.Format("AsyncFSCallback called. errorCode: " + errorCode + " numBytes: " + numBytes));
-
- // Unpack overlapped
- Overlapped overlapped = Overlapped.Unpack(pOverlapped);
- // Free the overlapped struct in EndRead/EndWrite.
-
- // Extract async result from overlapped
- FileStreamAsyncResult asyncResult =
- (FileStreamAsyncResult)overlapped.AsyncResult;
- asyncResult._numBytes = (int)numBytes;
-
- if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
- FrameworkEventSource.Log.ThreadTransferReceive((long)(asyncResult.OverLapped), 2, string.Empty);
-
- // Handle reading from & writing to closed pipes. While I'm not sure
- // this is entirely necessary anymore, maybe it's possible for
- // an async read on a pipe to be issued and then the pipe is closed,
- // returning this error. This may very well be necessary.
- if (errorCode == FileStream.ERROR_BROKEN_PIPE || errorCode == FileStream.ERROR_NO_DATA)
- errorCode = 0;
-
- asyncResult._errorCode = (int)errorCode;
-
- // Call the user-provided callback. It can and often should
- // call EndRead or EndWrite. There's no reason to use an async
- // delegate here - we're already on a threadpool thread.
- // IAsyncResult's completedSynchronously property must return
- // false here, saying the user callback was called on another thread.
- asyncResult._completedSynchronously = false;
- asyncResult._isComplete = true;
-
- // ensure _isComplete is set before reading _waitHandle
- Thread.MemoryBarrier();
-
- // The OS does not signal this event. We must do it ourselves.
- ManualResetEvent wh = asyncResult._waitHandle;
- if (wh != null)
- {
- Contract.Assert(!wh.SafeWaitHandle.IsClosed, "ManualResetEvent already closed!");
- bool r = wh.Set();
- Contract.Assert(r, "ManualResetEvent::Set failed!");
- if (!r) __Error.WinIOError();
- }
-
- AsyncCallback userCallback = asyncResult._userCallback;
- if (userCallback != null)
- userCallback(asyncResult);
- }
-
- [SecuritySafeCritical]
- [HostProtection(ExternalThreading = true)]
- internal void Cancel()
- {
- Contract.Assert(_handle != null, "_handle should not be null.");
- Contract.Assert(_overlapped != null, "Cancel should only be called on true asynchronous FileStreamAsyncResult, i.e. _overlapped is not null");
-
- if (IsCompleted)
- return;
-
- if (_handle.IsInvalid)
- return;
-
- bool r = Win32Native.CancelIoEx(_handle, _overlapped);
- if (!r)
- {
- int errorCode = Marshal.GetLastWin32Error();
-
- // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel.
- // This probably means that the IO operation has completed.
- if (errorCode != Win32Native.ERROR_NOT_FOUND)
- __Error.WinIOError(errorCode, String.Empty);
- }
- }
- }
-
- [ComVisible(true)]
- public class FileStream : Stream
- {
- internal const int DefaultBufferSize = 4096;
-
- private byte[] _buffer; // Shared read/write buffer. Alloc on first use.
- private String _fileName; // Fully qualified file name.
- private bool _isAsync; // Whether we opened the handle for overlapped IO
- private bool _canRead;
- private bool _canWrite;
- private bool _canSeek;
- private bool _exposedHandle; // Could other code be using this handle?
- private bool _isPipe; // Whether to disable async buffering code.
- private int _readPos; // Read pointer within shared buffer.
- private int _readLen; // Number of bytes read in buffer from file.
- private int _writePos; // Write pointer within shared buffer.
- private int _bufferSize; // Length of internal buffer, if it's allocated.
- [System.Security.SecurityCritical] // auto-generated
- private SafeFileHandle _handle;
- private long _pos; // Cache current location in the file.
- private long _appendStart;// When appending, prevent overwriting file.
- private static AsyncCallback s_endReadTask;
- private static AsyncCallback s_endWriteTask;
- private static Action<object> s_cancelReadHandler;
- private static Action<object> s_cancelWriteHandler;
-
- //This exists only to support IsolatedStorageFileStream.
- //Any changes to FileStream must include the corresponding changes in IsolatedStorage.
- internal FileStream() {
- }
-#if FEATURE_CORECLR
- [System.Security.SecuritySafeCritical]
- public FileStream(String path, FileMode mode)
- : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) {
- }
-
- [System.Security.SecuritySafeCritical]
- public FileStream(String path, FileMode mode, FileAccess access)
- : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) {
- }
-
- [System.Security.SecuritySafeCritical]
- public FileStream(String path, FileMode mode, FileAccess access, FileShare share)
- : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) {
- }
-
- [System.Security.SecuritySafeCritical]
- public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
- : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false, false, true)
- {
- }
-
-#else // FEATURE_CORECLR
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(String path, FileMode mode)
- : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) {
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(String path, FileMode mode, FileAccess access)
- : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) {
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(String path, FileMode mode, FileAccess access, FileShare share)
- : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) {
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
- : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false)
- {
- }
-#endif // FEATURE_CORECLR
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
- : this(path, mode, access, share, bufferSize, options, Path.GetFileName(path), false)
- {
- }
-
- #if FEATURE_CORECLR
- [System.Security.SecurityCritical] // auto-generated
- #else
- [System.Security.SecuritySafeCritical]
- #endif
- public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync)
- : this(path, mode, access, share, bufferSize, (useAsync ? FileOptions.Asynchronous : FileOptions.None), Path.GetFileName(path), false)
- {
- }
-
-#if FEATURE_MACL
- // This constructor is done differently to avoid loading a few more
- // classes, and more importantly, to build correctly on Rotor.
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options, FileSecurity fileSecurity)
- {
- Object pinningHandle;
- Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share, fileSecurity, out pinningHandle);
- try {
- Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false);
- }
- finally {
- if (pinningHandle != null) {
- GCHandle pinHandle = (GCHandle) pinningHandle;
- pinHandle.Free();
- }
- }
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options)
- {
- Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
- Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false);
- }
-#endif
-
- [System.Security.SecurityCritical] // auto-generated
- internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy)
- {
- Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
- Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, false, false);
- }
-
- [System.Security.SecurityCritical]
- internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath)
- {
- Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
- Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, false);
- }
-
- [System.Security.SecurityCritical]
- internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost)
- {
- Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
- Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, checkHost);
- }
-
- // AccessControl namespace is not defined in Rotor
- [System.Security.SecuritySafeCritical]
- private void Init(String path, FileMode mode, FileAccess access, int rights, bool useRights, FileShare share, int bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path), Environment.GetResourceString("ArgumentNull_Path"));
- if (path.Length == 0)
- throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"));
- Contract.EndContractBlock();
-
-#if FEATURE_MACL
- FileSystemRights fileSystemRights = (FileSystemRights)rights;
-#endif
- // msgPath must be safe to hand back to untrusted code.
-
- _fileName = msgPath; // To handle odd cases of finalizing partially constructed objects.
- _exposedHandle = false;
-
- // don't include inheritable in our bounds check for share
- FileShare tempshare = share & ~FileShare.Inheritable;
- String badArg = null;
-
- if (mode < FileMode.CreateNew || mode > FileMode.Append)
- badArg = nameof(mode);
- else if (!useRights && (access < FileAccess.Read || access > FileAccess.ReadWrite))
- badArg = nameof(access);
-#if FEATURE_MACL
- else if (useRights && (fileSystemRights < FileSystemRights.ReadData || fileSystemRights > FileSystemRights.FullControl))
- badArg = "rights";
-#endif
- else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
- badArg = nameof(share);
-
- if (badArg != null)
- throw new ArgumentOutOfRangeException(badArg, Environment.GetResourceString("ArgumentOutOfRange_Enum"));
-
- // NOTE: any change to FileOptions enum needs to be matched here in the error validation
- if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0)
- throw new ArgumentOutOfRangeException(nameof(options), Environment.GetResourceString("ArgumentOutOfRange_Enum"));
-
- if (bufferSize <= 0)
- throw new ArgumentOutOfRangeException(nameof(bufferSize), Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
-
- // Write access validation
-#if FEATURE_MACL
- if ((!useRights && (access & FileAccess.Write) == 0)
- || (useRights && (fileSystemRights & FileSystemRights.Write) == 0))
-#else
- if (!useRights && (access & FileAccess.Write) == 0)
-#endif //FEATURE_MACL
- {
- if (mode==FileMode.Truncate || mode==FileMode.CreateNew || mode==FileMode.Create || mode==FileMode.Append) {
- // No write access
- if (!useRights)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&AccessCombo", mode, access));
-#if FEATURE_MACL
- else
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&RightsCombo", mode, fileSystemRights));
-#endif //FEATURE_MACL
- }
- }
-
-#if FEATURE_MACL
- // FileMode.Truncate only works with GENERIC_WRITE (FileAccess.Write), source:MSDN
- // For backcomp use FileAccess.Write when FileSystemRights.Write is specified
- if (useRights && (mode == FileMode.Truncate)) {
- if (fileSystemRights == FileSystemRights.Write) {
- useRights = false;
- access = FileAccess.Write;
- }
- else {
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileModeTruncate&RightsCombo", mode, fileSystemRights));
- }
- }
-#endif
-
- int fAccess;
- if (!useRights) {
- fAccess = access == FileAccess.Read? GENERIC_READ:
- access == FileAccess.Write? GENERIC_WRITE:
- GENERIC_READ | GENERIC_WRITE;
- }
- else {
- fAccess = rights;
- }
-
- // Get absolute path - Security needs this to prevent something
- // like trying to create a file in c:\tmp with the name
- // "..\WinNT\System32\ntoskrnl.exe". Store it for user convenience.
- int maxPath = useLongPath ? Path.MaxLongPath : Path.MaxPath;
- String filePath = Path.NormalizePath(path, true, maxPath);
-
- _fileName = filePath;
-
- // Prevent access to your disk drives as raw block devices.
- if (filePath.StartsWith("\\\\.\\", StringComparison.Ordinal))
- throw new ArgumentException(Environment.GetResourceString("Arg_DevicesNotSupported"));
-
- // In 4.0, we always construct a FileIOPermission object below.
- // If filePath contained a ':', we would throw a NotSupportedException in
- // System.Security.Util.StringExpressionSet.CanonicalizePath.
- // If filePath contained other illegal characters, we would throw an ArgumentException in
- // FileIOPermission.CheckIllegalCharacters.
- // In 4.5 we on longer construct the FileIOPermission object in full trust.
- // To preserve the 4.0 behavior we do an explicit check for ':' here and also call Path.CheckInvalidPathChars.
- // Note that we need to call CheckInvalidPathChars before checking for ':' because that is what FileIOPermission does.
-
- Path.CheckInvalidPathChars(filePath, true);
-
-#if !PLATFORM_UNIX
- if (filePath.IndexOf( ':', 2 ) != -1)
- throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) );
-#endif // !PLATFORM_UNIX
-
- bool read = false;
-
-#if FEATURE_MACL
- if ((!useRights && (access & FileAccess.Read) != 0) || (useRights && (fileSystemRights & FileSystemRights.ReadAndExecute) != 0))
-#else
- if (!useRights && (access & FileAccess.Read) != 0)
-#endif //FEATURE_MACL
- {
- if (mode == FileMode.Append)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidAppendMode"));
- else
- read = true;
- }
-
- // All demands in full trust domains are no-ops, so skip
-#if FEATURE_CAS_POLICY
- if (!CodeAccessSecurityEngine.QuickCheckForAllDemands())
-#endif // FEATURE_CAS_POLICY
- {
- // Build up security permissions required, as well as validate we
- // have a sensible set of parameters. IE, creating a brand new file
- // for reading doesn't make much sense.
- FileIOPermissionAccess secAccess = FileIOPermissionAccess.NoAccess;
-
- if (read)
- {
- Contract.Assert(mode != FileMode.Append);
- secAccess = secAccess | FileIOPermissionAccess.Read;
- }
-
- // I can't think of any combos of FileMode we should disallow if we
- // don't have read access. Writing would pretty much always be valid
- // in those cases.
-
- // For any FileSystemRights other than ReadAndExecute, demand Write permission
- // This is probably bit overkill for TakeOwnership etc but we don't have any
- // matching FileIOPermissionAccess to demand. It is better that we ask for Write permission.
-
-#if FEATURE_MACL
- // FileMode.OpenOrCreate & FileSystemRights.Synchronize can create 0-byte file; demand write
- if ((!useRights && (access & FileAccess.Write) != 0)
- || (useRights && (fileSystemRights & (FileSystemRights.Write | FileSystemRights.Delete
- | FileSystemRights.DeleteSubdirectoriesAndFiles
- | FileSystemRights.ChangePermissions
- | FileSystemRights.TakeOwnership)) != 0)
- || (useRights && ((fileSystemRights & FileSystemRights.Synchronize) != 0)
- && mode==FileMode.OpenOrCreate)
- )
-#else
- if (!useRights && (access & FileAccess.Write) != 0)
-#endif //FEATURE_MACL
- {
- if (mode==FileMode.Append)
- secAccess = secAccess | FileIOPermissionAccess.Append;
- else
- secAccess = secAccess | FileIOPermissionAccess.Write;
- }
-
-#if FEATURE_MACL
- bool specifiedAcl;
- unsafe {
- specifiedAcl = secAttrs != null && secAttrs.pSecurityDescriptor != null;
- }
-
- AccessControlActions control = specifiedAcl ? AccessControlActions.Change : AccessControlActions.None;
- new FileIOPermission(secAccess, control, new String[] { filePath }, false, false).Demand();
-#else
-#if FEATURE_CORECLR
- if (checkHost) {
- FileSecurityState state = new FileSecurityState(FileSecurityState.ToFileSecurityState(secAccess), path, filePath);
- state.EnsureState();
- }
-#else
- new FileIOPermission(secAccess, new String[] { filePath }, false, false).Demand();
-#endif // FEATURE_CORECLR
-#endif
- }
-
- // Our Inheritable bit was stolen from Windows, but should be set in
- // the security attributes class. Don't leave this bit set.
- share &= ~FileShare.Inheritable;
-
- bool seekToEnd = (mode==FileMode.Append);
- // Must use a valid Win32 constant here...
- if (mode == FileMode.Append)
- mode = FileMode.OpenOrCreate;
-
- // WRT async IO, do the right thing for whatever platform we're on.
- // This way, someone can easily write code that opens a file
- // asynchronously no matter what their platform is.
- if ((options & FileOptions.Asynchronous) != 0)
- _isAsync = true;
- else
- options &= ~FileOptions.Asynchronous;
-
- int flagsAndAttributes = (int) options;
-
-#if !PLATFORM_UNIX
- // For mitigating local elevation of privilege attack through named pipes
- // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the
- // named pipe server can't impersonate a high privileged client security context
- flagsAndAttributes |= (Win32Native.SECURITY_SQOS_PRESENT | Win32Native.SECURITY_ANONYMOUS);
-#endif
-
- // Don't pop up a dialog for reading from an emtpy floppy drive
- int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS);
- try {
- String tempPath = filePath;
- if (useLongPath)
- tempPath = Path.AddLongPathPrefix(tempPath);
- _handle = Win32Native.SafeCreateFile(tempPath, fAccess, share, secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
-
- if (_handle.IsInvalid) {
- // Return a meaningful exception, using the RELATIVE path to
- // the file to avoid returning extra information to the caller
- // unless they have path discovery permission, in which case
- // the full path is fine & useful.
-
- // NT5 oddity - when trying to open "C:\" as a FileStream,
- // we usually get ERROR_PATH_NOT_FOUND from the OS. We should
- // probably be consistent w/ every other directory.
- int errorCode = Marshal.GetLastWin32Error();
- if (errorCode==__Error.ERROR_PATH_NOT_FOUND && filePath.Equals(Directory.InternalGetDirectoryRoot(filePath)))
- errorCode = __Error.ERROR_ACCESS_DENIED;
-
- // We need to give an exception, and preferably it would include
- // the fully qualified path name. Do security check here. If
- // we fail, give back the msgPath, which should not reveal much.
- // While this logic is largely duplicated in
- // __Error.WinIOError, we need this for
- // IsolatedStorageFileStream.
- bool canGiveFullPath = false;
-
- if (!bFromProxy)
- {
- try {
-#if !FEATURE_CORECLR
- new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false ).Demand();
-#endif
- canGiveFullPath = true;
- }
- catch(SecurityException) {}
- }
-
- if (canGiveFullPath)
- __Error.WinIOError(errorCode, _fileName);
- else
- __Error.WinIOError(errorCode, msgPath);
- }
- }
- finally {
- Win32Native.SetErrorMode(oldMode);
- }
-
- // Disallow access to all non-file devices from the FileStream
- // constructors that take a String. Everyone else can call
- // CreateFile themselves then use the constructor that takes an
- // IntPtr. Disallows "con:", "com1:", "lpt1:", etc.
- int fileType = Win32Native.GetFileType(_handle);
- if (fileType != Win32Native.FILE_TYPE_DISK) {
- _handle.Close();
- throw new NotSupportedException(Environment.GetResourceString("NotSupported_FileStreamOnNonFiles"));
- }
-
- // This is necessary for async IO using IO Completion ports via our
- // managed Threadpool API's. This (theoretically) calls the OS's
- // BindIoCompletionCallback method, and passes in a stub for the
- // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
- // struct for this request and gets a delegate to a managed callback
- // from there, which it then calls on a threadpool thread. (We allocate
- // our native OVERLAPPED structs 2 pointers too large and store EE state
- // & GC handles there, one to an IAsyncResult, the other to a delegate.)
- if (_isAsync) {
- bool b = false;
- // BindHandle requires UnmanagedCode permission
-#pragma warning disable 618
- new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();
-#pragma warning restore 618
- try {
- b = ThreadPool.BindHandle(_handle);
- }
- finally {
- CodeAccessPermission.RevertAssert();
- if (!b) {
- // We should close the handle so that the handle is not open until SafeFileHandle GC
- Contract.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?");
- _handle.Close();
- }
- }
- if (!b)
- throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed"));
- }
-
- if (!useRights) {
- _canRead = (access & FileAccess.Read) != 0;
- _canWrite = (access & FileAccess.Write) != 0;
- }
-#if FEATURE_MACL
- else {
- _canRead = (fileSystemRights & FileSystemRights.ReadData) != 0;
- _canWrite = ((fileSystemRights & FileSystemRights.WriteData) != 0)
- || ((fileSystemRights & FileSystemRights.AppendData) != 0);
- }
-#endif //FEATURE_MACL
-
- _canSeek = true;
- _isPipe = false;
- _pos = 0;
- _bufferSize = bufferSize;
- _readPos = 0;
- _readLen = 0;
- _writePos = 0;
-
- // For Append mode...
- if (seekToEnd) {
- _appendStart = SeekCore(0, SeekOrigin.End);
- }
- else {
- _appendStart = -1;
- }
- }
-
- [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. http://go.microsoft.com/fwlink/?linkid=14202")]
- public FileStream(IntPtr handle, FileAccess access)
- : this(handle, access, true, DefaultBufferSize, false) {
- }
-
- [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")]
- public FileStream(IntPtr handle, FileAccess access, bool ownsHandle)
- : this(handle, access, ownsHandle, DefaultBufferSize, false) {
- }
-
- [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")]
- public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
- : this(handle, access, ownsHandle, bufferSize, false) {
- }
-
- // We explicitly do a Demand, not a LinkDemand here.
- [System.Security.SecuritySafeCritical] // auto-generated
- [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")]
-#pragma warning disable 618
- [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)]
-#pragma warning restore 618
- public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
- : this(new SafeFileHandle(handle, ownsHandle), access, bufferSize, isAsync) {
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(SafeFileHandle handle, FileAccess access)
- : this(handle, access, DefaultBufferSize, false) {
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
- : this(handle, access, bufferSize, false) {
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
-#pragma warning disable 618
- [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)]
-#pragma warning restore 618
- public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) {
- // To ensure we don't leak a handle, put it in a SafeFileHandle first
- if (handle.IsInvalid)
- throw new ArgumentException(Environment.GetResourceString("Arg_InvalidHandle"), nameof(handle));
- Contract.EndContractBlock();
-
- _handle = handle;
- _exposedHandle = true;
-
- // Now validate arguments.
- if (access < FileAccess.Read || access > FileAccess.ReadWrite)
- throw new ArgumentOutOfRangeException(nameof(access), Environment.GetResourceString("ArgumentOutOfRange_Enum"));
- if (bufferSize <= 0)
- throw new ArgumentOutOfRangeException(nameof(bufferSize), Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum"));
-
- int handleType = Win32Native.GetFileType(_handle);
- Contract.Assert(handleType == Win32Native.FILE_TYPE_DISK || handleType == Win32Native.FILE_TYPE_PIPE || handleType == Win32Native.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!");
- _isAsync = isAsync;
- _canRead = 0 != (access & FileAccess.Read);
- _canWrite = 0 != (access & FileAccess.Write);
- _canSeek = handleType == Win32Native.FILE_TYPE_DISK;
- _bufferSize = bufferSize;
- _readPos = 0;
- _readLen = 0;
- _writePos = 0;
- _fileName = null;
- _isPipe = handleType == Win32Native.FILE_TYPE_PIPE;
-
- // This is necessary for async IO using IO Completion ports via our
- // managed Threadpool API's. This calls the OS's
- // BindIoCompletionCallback method, and passes in a stub for the
- // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped
- // struct for this request and gets a delegate to a managed callback
- // from there, which it then calls on a threadpool thread. (We allocate
- // our native OVERLAPPED structs 2 pointers too large and store EE
- // state & a handle to a delegate there.)
-#if !FEATURE_CORECLR
- if (_isAsync) {
- bool b = false;
- try {
- b = ThreadPool.BindHandle(_handle);
- }
- catch (ApplicationException) {
- // If you passed in a synchronous handle and told us to use
- // it asynchronously, throw here.
- throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotAsync"));
- }
- if (!b) {
- throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed"));
- }
- }
- else {
-#endif // FEATURE_CORECLR
- if (handleType != Win32Native.FILE_TYPE_PIPE)
- VerifyHandleIsSync();
-#if !FEATURE_CORECLR
- }
-#endif // FEATURE_CORECLR
-
- if (_canSeek)
- SeekCore(0, SeekOrigin.Current);
- else
- _pos = 0;
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- private static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
- {
- Win32Native.SECURITY_ATTRIBUTES secAttrs = null;
- if ((share & FileShare.Inheritable) != 0) {
- secAttrs = new Win32Native.SECURITY_ATTRIBUTES();
- secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);
-
- secAttrs.bInheritHandle = 1;
- }
- return secAttrs;
- }
-
-#if FEATURE_MACL
- // If pinningHandle is not null, caller must free it AFTER the call to
- // CreateFile has returned.
- [System.Security.SecuritySafeCritical] // auto-generated
- private unsafe static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share, FileSecurity fileSecurity, out Object pinningHandle)
- {
- pinningHandle = null;
- Win32Native.SECURITY_ATTRIBUTES secAttrs = null;
- if ((share & FileShare.Inheritable) != 0 || fileSecurity != null) {
- secAttrs = new Win32Native.SECURITY_ATTRIBUTES();
- secAttrs.nLength = (int)Marshal.SizeOf(secAttrs);
-
- if ((share & FileShare.Inheritable) != 0) {
- secAttrs.bInheritHandle = 1;
- }
-
- // For ACL's, get the security descriptor from the FileSecurity.
- if (fileSecurity != null) {
- byte[] sd = fileSecurity.GetSecurityDescriptorBinaryForm();
- pinningHandle = GCHandle.Alloc(sd, GCHandleType.Pinned);
- fixed(byte* pSecDescriptor = sd)
- secAttrs.pSecurityDescriptor = pSecDescriptor;
- }
- }
- return secAttrs;
- }
-#endif
-
- // Verifies that this handle supports synchronous IO operations (unless you
- // didn't open it for either reading or writing).
- [System.Security.SecuritySafeCritical] // auto-generated
- private unsafe void VerifyHandleIsSync()
- {
- // Do NOT use this method on pipes. Reading or writing to a pipe may
- // cause an app to block incorrectly, introducing a deadlock (depending
- // on whether a write will wake up an already-blocked thread or this
- // FileStream's thread).
-
- // Do NOT change this to use a byte[] of length 0, or test test won't
- // work. Our ReadFile & WriteFile methods are special cased to return
- // for arrays of length 0, since we'd get an IndexOutOfRangeException
- // while using C#'s fixed syntax.
- byte[] bytes = new byte[1];
- int hr = 0;
- int r = 0;
-
- // If the handle is a pipe, ReadFile will block until there
- // has been a write on the other end. We'll just have to deal with it,
- // For the read end of a pipe, you can mess up and
- // accidentally read synchronously from an async pipe.
- if (CanRead) {
- r = ReadFileNative(_handle, bytes, 0, 0, null, out hr);
- }
- else if (CanWrite) {
- r = WriteFileNative(_handle, bytes, 0, 0, null, out hr);
- }
-
- if (hr==ERROR_INVALID_PARAMETER)
- throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync"));
- if (hr == Win32Native.ERROR_INVALID_HANDLE)
- __Error.WinIOError(hr, "<OS handle>");
- }
-
-
- public override bool CanRead {
- [Pure]
- get { return _canRead; }
- }
-
- public override bool CanWrite {
- [Pure]
- get { return _canWrite; }
- }
-
- public override bool CanSeek {
- [Pure]
- get { return _canSeek; }
- }
-
- public virtual bool IsAsync {
- get { return _isAsync; }
- }
-
- public override long Length {
- [System.Security.SecuritySafeCritical] // auto-generated
- get {
- if (_handle.IsClosed) __Error.FileNotOpen();
- if (!CanSeek) __Error.SeekNotSupported();
- int hi = 0, lo = 0;
-
- lo = Win32Native.GetFileSize(_handle, out hi);
-
- if (lo==-1) { // Check for either an error or a 4GB - 1 byte file.
- int hr = Marshal.GetLastWin32Error();
- if (hr != 0)
- __Error.WinIOError(hr, String.Empty);
- }
- long len = (((long)hi) << 32) | ((uint) lo);
- // If we're writing near the end of the file, we must include our
- // internal buffer in our Length calculation. Don't flush because
- // we use the length of the file in our async write method.
- if (_writePos > 0 && _pos + _writePos > len)
- len = _writePos + _pos;
- return len;
- }
- }
-
- public String Name {
- [System.Security.SecuritySafeCritical]
- get {
- if (_fileName == null)
- return Environment.GetResourceString("IO_UnknownFileName");
-#if FEATURE_CORECLR
- FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, _fileName);
- sourceState.EnsureState();
-#else
- new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false).Demand();
-#endif
- return _fileName;
- }
- }
-
- internal String NameInternal {
- get {
- if (_fileName == null)
- return "<UnknownFileName>";
- return _fileName;
- }
- }
-
- public override long Position {
- [System.Security.SecuritySafeCritical] // auto-generated
- get {
- if (_handle.IsClosed) __Error.FileNotOpen();
- if (!CanSeek) __Error.SeekNotSupported();
-
- Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both.");
-
- // Verify that internal position is in sync with the handle
- if (_exposedHandle)
- VerifyOSHandlePosition();
-
- // Compensate for buffer that we read from the handle (_readLen) Vs what the user
- // read so far from the internel buffer (_readPos). Of course add any unwrittern
- // buffered data
- return _pos + (_readPos - _readLen + _writePos);
- }
- set {
- if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- Contract.EndContractBlock();
- if (_writePos > 0) FlushWrite(false);
- _readPos = 0;
- _readLen = 0;
- Seek(value, SeekOrigin.Begin);
- }
- }
-
-#if FEATURE_MACL
- [System.Security.SecuritySafeCritical] // auto-generated
- public FileSecurity GetAccessControl()
- {
- if (_handle.IsClosed) __Error.FileNotOpen();
- return new FileSecurity(_handle, _fileName, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group);
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public void SetAccessControl(FileSecurity fileSecurity)
- {
- if (fileSecurity == null)
- throw new ArgumentNullException(nameof(fileSecurity));
- Contract.EndContractBlock();
-
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- fileSecurity.Persist(_handle, _fileName);
- }
-#endif
-
- [System.Security.SecuritySafeCritical] // auto-generated
- protected override void Dispose(bool disposing)
- {
- // Nothing will be done differently based on whether we are
- // disposing vs. finalizing. This is taking advantage of the
- // weak ordering between normal finalizable objects & critical
- // finalizable objects, which I included in the SafeHandle
- // design for FileStream, which would often "just work" when
- // finalized.
- try {
- if (_handle != null && !_handle.IsClosed) {
- // Flush data to disk iff we were writing. After
- // thinking about this, we also don't need to flush
- // our read position, regardless of whether the handle
- // was exposed to the user. They probably would NOT
- // want us to do this.
- if (_writePos > 0) {
- FlushWrite(!disposing);
- }
- }
- }
- finally {
- if (_handle != null && !_handle.IsClosed)
- _handle.Dispose();
-
- _canRead = false;
- _canWrite = false;
- _canSeek = false;
- // Don't set the buffer to null, to avoid a NullReferenceException
- // when users have a race condition in their code (ie, they call
- // Close when calling another method on Stream like Read).
- //_buffer = null;
- base.Dispose(disposing);
- }
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- ~FileStream()
- {
- if (_handle != null) {
- BCLDebug.Correctness(_handle.IsClosed, "You didn't close a FileStream & it got finalized. Name: \""+_fileName+"\"");
- Dispose(false);
- }
- }
-
- public override void Flush()
- {
- Flush(false);
- }
-
- [System.Security.SecuritySafeCritical]
- public virtual void Flush(Boolean flushToDisk)
- {
- // This code is duplicated in Dispose
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- FlushInternalBuffer();
-
- if (flushToDisk && CanWrite)
- {
- FlushOSBuffer();
- }
- }
-
- private void FlushInternalBuffer()
- {
- if (_writePos > 0)
- {
- FlushWrite(false);
- }
- else if (_readPos < _readLen && CanSeek)
- {
- FlushRead();
- }
- }
-
- [System.Security.SecuritySafeCritical]
- private void FlushOSBuffer()
- {
- if (!Win32Native.FlushFileBuffers(_handle))
- {
- __Error.WinIOError();
- }
- }
-
- // Reading is done by blocks from the file, but someone could read
- // 1 byte from the buffer then write. At that point, the OS's file
- // pointer is out of sync with the stream's position. All write
- // functions should call this function to preserve the position in the file.
- private void FlushRead() {
- Contract.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushRead!");
- if (_readPos - _readLen != 0) {
- Contract.Assert(CanSeek, "FileStream will lose buffered read data now.");
- SeekCore(_readPos - _readLen, SeekOrigin.Current);
- }
- _readPos = 0;
- _readLen = 0;
- }
-
- // Writes are buffered. Anytime the buffer fills up
- // (_writePos + delta > _bufferSize) or the buffer switches to reading
- // and there is left over data (_writePos > 0), this function must be called.
- private void FlushWrite(bool calledFromFinalizer) {
- Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWrite!");
-
- if (_isAsync) {
- IAsyncResult asyncResult = BeginWriteCore(_buffer, 0, _writePos, null, null);
- // With our Whidbey async IO & overlapped support for AD unloads,
- // we don't strictly need to block here to release resources
- // since that support takes care of the pinning & freeing the
- // overlapped struct. We need to do this when called from
- // Close so that the handle is closed when Close returns, but
- // we do't need to call EndWrite from the finalizer.
- // Additionally, if we do call EndWrite, we block forever
- // because AD unloads prevent us from running the managed
- // callback from the IO completion port. Blocking here when
- // called from the finalizer during AD unload is clearly wrong,
- // but we can't use any sort of test for whether the AD is
- // unloading because if we weren't unloading, an AD unload
- // could happen on a separate thread before we call EndWrite.
- if (!calledFromFinalizer)
- EndWrite(asyncResult);
- }
- else
- WriteCore(_buffer, 0, _writePos);
-
- _writePos = 0;
- }
-
-
- [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. http://go.microsoft.com/fwlink/?linkid=14202")]
- public virtual IntPtr Handle {
- [System.Security.SecurityCritical] // auto-generated_required
-#if !FEATURE_CORECLR
- [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
-#endif
- get {
- Flush();
- // Explicitly dump any buffered data, since the user could move our
- // position or write to the file.
- _readPos = 0;
- _readLen = 0;
- _writePos = 0;
- _exposedHandle = true;
-
- return _handle.DangerousGetHandle();
- }
- }
-
- public virtual SafeFileHandle SafeFileHandle {
- [System.Security.SecurityCritical] // auto-generated_required
-#if !FEATURE_CORECLR
- [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
-#endif
- get {
- Flush();
- // Explicitly dump any buffered data, since the user could move our
- // position or write to the file.
- _readPos = 0;
- _readLen = 0;
- _writePos = 0;
- _exposedHandle = true;
-
- return _handle;
- }
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public override void SetLength(long value)
- {
- if (value < 0)
- throw new ArgumentOutOfRangeException(nameof(value), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- Contract.EndContractBlock();
-
- if (_handle.IsClosed) __Error.FileNotOpen();
- if (!CanSeek) __Error.SeekNotSupported();
- if (!CanWrite) __Error.WriteNotSupported();
-
- // Handle buffering updates.
- if (_writePos > 0) {
- FlushWrite(false);
- }
- else if (_readPos < _readLen) {
- FlushRead();
- }
- _readPos = 0;
- _readLen = 0;
-
- if (_appendStart != -1 && value < _appendStart)
- throw new IOException(Environment.GetResourceString("IO.IO_SetLengthAppendTruncate"));
- SetLengthCore(value);
- }
-
- // We absolutely need this method broken out so that BeginWriteCore can call
- // a method without having to go through buffering code that might call
- // FlushWrite.
- [System.Security.SecuritySafeCritical] // auto-generated
- private void SetLengthCore(long value)
- {
- Contract.Assert(value >= 0, "value >= 0");
- long origPos = _pos;
-
- if (_exposedHandle)
- VerifyOSHandlePosition();
- if (_pos != value)
- SeekCore(value, SeekOrigin.Begin);
- if (!Win32Native.SetEndOfFile(_handle)) {
- int hr = Marshal.GetLastWin32Error();
- if (hr==__Error.ERROR_INVALID_PARAMETER)
- throw new ArgumentOutOfRangeException(nameof(value), Environment.GetResourceString("ArgumentOutOfRange_FileLengthTooBig"));
- __Error.WinIOError(hr, String.Empty);
- }
- // Return file pointer to where it was before setting length
- if (origPos != value) {
- if (origPos < value)
- SeekCore(origPos, SeekOrigin.Begin);
- else
- SeekCore(0, SeekOrigin.End);
- }
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public override int Read([In, Out] byte[] array, int offset, int count) {
- if (array==null)
- throw new ArgumentNullException(nameof(array), Environment.GetResourceString("ArgumentNull_Buffer"));
- if (offset < 0)
- throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (count < 0)
- throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (array.Length - offset < count)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
- Contract.EndContractBlock();
-
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both.");
-
- bool isBlocked = false;
- int n = _readLen - _readPos;
- // if the read buffer is empty, read into either user's array or our
- // buffer, depending on number of bytes user asked for and buffer size.
- if (n == 0) {
- if (!CanRead) __Error.ReadNotSupported();
- if (_writePos > 0) FlushWrite(false);
- if (!CanSeek || (count >= _bufferSize)) {
- n = ReadCore(array, offset, count);
- // Throw away read buffer.
- _readPos = 0;
- _readLen = 0;
- return n;
- }
- if (_buffer == null) _buffer = new byte[_bufferSize];
- n = ReadCore(_buffer, 0, _bufferSize);
- if (n == 0) return 0;
- isBlocked = n < _bufferSize;
- _readPos = 0;
- _readLen = n;
- }
- // Now copy min of count or numBytesAvailable (ie, near EOF) to array.
- if (n > count) n = count;
- Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n);
- _readPos += n;
-
- // We may have read less than the number of bytes the user asked
- // for, but that is part of the Stream contract. Reading again for
- // more data may cause us to block if we're using a device with
- // no clear end of file, such as a serial port or pipe. If we
- // blocked here & this code was used with redirected pipes for a
- // process's standard output, this can lead to deadlocks involving
- // two processes. But leave this here for files to avoid what would
- // probably be a breaking change. --
-
- // If we are reading from a device with no clear EOF like a
- // serial port or a pipe, this will cause us to block incorrectly.
- if (!_isPipe) {
- // If we hit the end of the buffer and didn't have enough bytes, we must
- // read some more from the underlying stream. However, if we got
- // fewer bytes from the underlying stream than we asked for (ie, we're
- // probably blocked), don't ask for more bytes.
- if (n < count && !isBlocked) {
- Contract.Assert(_readPos == _readLen, "Read buffer should be empty!");
- int moreBytesRead = ReadCore(array, offset + n, count - n);
- n += moreBytesRead;
- // We've just made our buffer inconsistent with our position
- // pointer. We must throw away the read buffer.
- _readPos = 0;
- _readLen = 0;
- }
- }
-
- return n;
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- private unsafe int ReadCore(byte[] buffer, int offset, int count) {
- Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed");
- Contract.Assert(CanRead, "CanRead");
-
- Contract.Assert(buffer != null, "buffer != null");
- Contract.Assert(_writePos == 0, "_writePos == 0");
- Contract.Assert(offset >= 0, "offset is negative");
- Contract.Assert(count >= 0, "count is negative");
-
- if (_isAsync) {
- IAsyncResult result = BeginReadCore(buffer, offset, count, null, null, 0);
- return EndRead(result);
- }
-
- // Make sure we are reading from the right spot
- if (_exposedHandle)
- VerifyOSHandlePosition();
-
- int hr = 0;
- int r = ReadFileNative(_handle, buffer, offset, count, null, out hr);
- if (r == -1) {
- // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe.
- if (hr == ERROR_BROKEN_PIPE) {
- r = 0;
- }
- else {
- if (hr == ERROR_INVALID_PARAMETER)
- throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync"));
-
- __Error.WinIOError(hr, String.Empty);
- }
- }
- Contract.Assert(r >= 0, "FileStream's ReadCore is likely broken.");
- _pos += r;
-
- return r;
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public override long Seek(long offset, SeekOrigin origin) {
- if (origin<SeekOrigin.Begin || origin>SeekOrigin.End)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin"));
- Contract.EndContractBlock();
- if (_handle.IsClosed) __Error.FileNotOpen();
- if (!CanSeek) __Error.SeekNotSupported();
-
- Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both.");
-
- // If we've got bytes in our buffer to write, write them out.
- // If we've read in and consumed some bytes, we'll have to adjust
- // our seek positions ONLY IF we're seeking relative to the current
- // position in the stream. This simulates doing a seek to the new
- // position, then a read for the number of bytes we have in our buffer.
- if (_writePos > 0) {
- FlushWrite(false);
- }
- else if (origin == SeekOrigin.Current) {
- // Don't call FlushRead here, which would have caused an infinite
- // loop. Simply adjust the seek origin. This isn't necessary
- // if we're seeking relative to the beginning or end of the stream.
- offset -= (_readLen - _readPos);
- }
-
- // Verify that internal position is in sync with the handle
- if (_exposedHandle)
- VerifyOSHandlePosition();
-
- long oldPos = _pos + (_readPos - _readLen);
- long pos = SeekCore(offset, origin);
-
- // Prevent users from overwriting data in a file that was opened in
- // append mode.
- if (_appendStart != -1 && pos < _appendStart) {
- SeekCore(oldPos, SeekOrigin.Begin);
- throw new IOException(Environment.GetResourceString("IO.IO_SeekAppendOverwrite"));
- }
-
- // We now must update the read buffer. We can in some cases simply
- // update _readPos within the buffer, copy around the buffer so our
- // Position property is still correct, and avoid having to do more
- // reads from the disk. Otherwise, discard the buffer's contents.
- if (_readLen > 0) {
- // We can optimize the following condition:
- // oldPos - _readPos <= pos < oldPos + _readLen - _readPos
- if (oldPos == pos) {
- if (_readPos > 0) {
- //Console.WriteLine("Seek: seeked for 0, adjusting buffer back by: "+_readPos+" _readLen: "+_readLen);
- Buffer.InternalBlockCopy(_buffer, _readPos, _buffer, 0, _readLen - _readPos);
- _readLen -= _readPos;
- _readPos = 0;
- }
- // If we still have buffered data, we must update the stream's
- // position so our Position property is correct.
- if (_readLen > 0)
- SeekCore(_readLen, SeekOrigin.Current);
- }
- else if (oldPos - _readPos < pos && pos < oldPos + _readLen - _readPos) {
- int diff = (int)(pos - oldPos);
- //Console.WriteLine("Seek: diff was "+diff+", readpos was "+_readPos+" adjusting buffer - shrinking by "+ (_readPos + diff));
- Buffer.InternalBlockCopy(_buffer, _readPos+diff, _buffer, 0, _readLen - (_readPos + diff));
- _readLen -= (_readPos + diff);
- _readPos = 0;
- if (_readLen > 0)
- SeekCore(_readLen, SeekOrigin.Current);
- }
- else {
- // Lose the read buffer.
- _readPos = 0;
- _readLen = 0;
- }
- Contract.Assert(_readLen >= 0 && _readPos <= _readLen, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen");
- Contract.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled.");
- }
- return pos;
- }
-
- // This doesn't do argument checking. Necessary for SetLength, which must
- // set the file pointer beyond the end of the file. This will update the
- // internal position
- [System.Security.SecuritySafeCritical] // auto-generated
- private long SeekCore(long offset, SeekOrigin origin) {
- Contract.Assert(!_handle.IsClosed && CanSeek, "!_handle.IsClosed && CanSeek");
- Contract.Assert(origin>=SeekOrigin.Begin && origin<=SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End");
- int hr = 0;
- long ret = 0;
-
- ret = Win32Native.SetFilePointer(_handle, offset, origin, out hr);
- if (ret == -1) {
- // #errorInvalidHandle
- // If ERROR_INVALID_HANDLE is returned, it doesn't suffice to set
- // the handle as invalid; the handle must also be closed.
- //
- // Marking the handle as invalid but not closing the handle
- // resulted in exceptions during finalization and locked column
- // values (due to invalid but unclosed handle) in SQL FileStream
- // scenarios.
- //
- // A more mainstream scenario involves accessing a file on a
- // network share. ERROR_INVALID_HANDLE may occur because the network
- // connection was dropped and the server closed the handle. However,
- // the client side handle is still open and even valid for certain
- // operations.
- //
- // Note that Dispose doesn't throw so we don't need to special case.
- // SetHandleAsInvalid only sets _closed field to true (without
- // actually closing handle) so we don't need to call that as well.
- if (hr == Win32Native.ERROR_INVALID_HANDLE)
- _handle.Dispose();
- __Error.WinIOError(hr, String.Empty);
- }
-
- _pos = ret;
- return ret;
- }
-
- // Checks the position of the OS's handle equals what we expect it to.
- // This will fail if someone else moved the FileStream's handle or if
- // we've hit a bug in FileStream's position updating code.
- private void VerifyOSHandlePosition()
- {
- if (!CanSeek)
- return;
-
- // SeekCore will override the current _pos, so save it now
- long oldPos = _pos;
- long curPos = SeekCore(0, SeekOrigin.Current);
-
- if (curPos != oldPos) {
- // For reads, this is non-fatal but we still could have returned corrupted
- // data in some cases. So discard the internal buffer. Potential MDA
- _readPos = 0;
- _readLen = 0;
- if(_writePos > 0) {
- // Discard the buffer and let the user know!
- _writePos = 0;
- throw new IOException(Environment.GetResourceString("IO.IO_FileStreamHandlePosition"));
- }
- }
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public override void Write(byte[] array, int offset, int count) {
- if (array==null)
- throw new ArgumentNullException(nameof(array), Environment.GetResourceString("ArgumentNull_Buffer"));
- if (offset < 0)
- throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (count < 0)
- throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (array.Length - offset < count)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
- Contract.EndContractBlock();
-
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- if (_writePos == 0)
- {
- // Ensure we can write to the stream, and ready buffer for writing.
- if (!CanWrite) __Error.WriteNotSupported();
- if (_readPos < _readLen) FlushRead();
- _readPos = 0;
- _readLen = 0;
- }
-
- // If our buffer has data in it, copy data from the user's array into
- // the buffer, and if we can fit it all there, return. Otherwise, write
- // the buffer to disk and copy any remaining data into our buffer.
- // The assumption here is memcpy is cheaper than disk (or net) IO.
- // (10 milliseconds to disk vs. ~20-30 microseconds for a 4K memcpy)
- // So the extra copying will reduce the total number of writes, in
- // non-pathological cases (ie, write 1 byte, then write for the buffer
- // size repeatedly)
- if (_writePos > 0) {
- int numBytes = _bufferSize - _writePos; // space left in buffer
- if (numBytes > 0) {
- if (numBytes > count)
- numBytes = count;
- Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes);
- _writePos += numBytes;
- if (count==numBytes) return;
- offset += numBytes;
- count -= numBytes;
- }
- // Reset our buffer. We essentially want to call FlushWrite
- // without calling Flush on the underlying Stream.
-
- if (_isAsync) {
- IAsyncResult result = BeginWriteCore(_buffer, 0, _writePos, null, null);
- EndWrite(result);
- }
- else
- {
- WriteCore(_buffer, 0, _writePos);
- }
-
- _writePos = 0;
- }
- // If the buffer would slow writes down, avoid buffer completely.
- if (count >= _bufferSize) {
- Contract.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted.");
- WriteCore(array, offset, count);
- return;
- }
- else if (count == 0)
- return; // Don't allocate a buffer then call memcpy for 0 bytes.
- if (_buffer==null) _buffer = new byte[_bufferSize];
- // Copy remaining bytes into buffer, to write at a later date.
- Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, count);
- _writePos = count;
- return;
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- private unsafe void WriteCore(byte[] buffer, int offset, int count) {
- Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed");
- Contract.Assert(CanWrite, "CanWrite");
-
- Contract.Assert(buffer != null, "buffer != null");
- Contract.Assert(_readPos == _readLen, "_readPos == _readLen");
- Contract.Assert(offset >= 0, "offset is negative");
- Contract.Assert(count >= 0, "count is negative");
-
- if (_isAsync) {
- IAsyncResult result = BeginWriteCore(buffer, offset, count, null, null);
- EndWrite(result);
- return;
- }
-
- // Make sure we are writing to the position that we think we are
- if (_exposedHandle)
- VerifyOSHandlePosition();
-
- int hr = 0;
- int r = WriteFileNative(_handle, buffer, offset, count, null, out hr);
- if (r == -1) {
- // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
- if (hr == ERROR_NO_DATA) {
- r = 0;
- }
- else {
- // ERROR_INVALID_PARAMETER may be returned for writes
- // where the position is too large (ie, writing at Int64.MaxValue
- // on Win9x) OR for synchronous writes to a handle opened
- // asynchronously.
- if (hr == ERROR_INVALID_PARAMETER)
- throw new IOException(Environment.GetResourceString("IO.IO_FileTooLongOrHandleNotSync"));
- __Error.WinIOError(hr, String.Empty);
- }
- }
- Contract.Assert(r >= 0, "FileStream's WriteCore is likely broken.");
- _pos += r;
- return;
- }
-
-
- [System.Security.SecuritySafeCritical] // auto-generated
- [HostProtection(ExternalThreading = true)]
- public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
- {
- if (array==null)
- throw new ArgumentNullException(nameof(array));
- if (offset < 0)
- throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (numBytes < 0)
- throw new ArgumentOutOfRangeException(nameof(numBytes), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (array.Length - offset < numBytes)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
- Contract.EndContractBlock();
-
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- if (!_isAsync)
- return base.BeginRead(array, offset, numBytes, userCallback, stateObject);
- else
- return BeginReadAsync(array, offset, numBytes, userCallback, stateObject);
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- [HostProtection(ExternalThreading = true)]
- private FileStreamAsyncResult BeginReadAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
- {
- Contract.Assert(_isAsync);
-
- if (!CanRead) __Error.ReadNotSupported();
-
- Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both.");
-
- if (_isPipe)
- {
- // When redirecting stdout & stderr with the Process class, it's easy to deadlock your
- // parent & child processes when doing writes 4K at a time. The
- // OS appears to use a 4K buffer internally. If you write to a
- // pipe that is full, you will block until someone read from
- // that pipe. If you try reading from an empty pipe and
- // FileStream's BeginRead blocks waiting for data to fill it's
- // internal buffer, you will be blocked. In a case where a child
- // process writes to stdout & stderr while a parent process tries
- // reading from both, you can easily get into a deadlock here.
- // To avoid this deadlock, don't buffer when doing async IO on
- // pipes. But don't completely ignore buffered data either.
- if (_readPos < _readLen)
- {
- int n = _readLen - _readPos;
- if (n > numBytes) n = numBytes;
- Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n);
- _readPos += n;
-
- // Return a synchronous FileStreamAsyncResult
- return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false);
- }
- else
- {
- Contract.Assert(_writePos == 0, "FileStream must not have buffered write data here! Pipes should be unidirectional.");
- return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0);
- }
- }
-
- Contract.Assert(!_isPipe, "Should not be a pipe.");
-
- // Handle buffering.
- if (_writePos > 0) FlushWrite(false);
- if (_readPos == _readLen)
- {
- // I can't see how to handle buffering of async requests when
- // filling the buffer asynchronously, without a lot of complexity.
- // The problems I see are issuing an async read, we do an async
- // read to fill the buffer, then someone issues another read
- // (either synchronously or asynchronously) before the first one
- // returns. This would involve some sort of complex buffer locking
- // that we probably don't want to get into, at least not in V1.
- // If we did a sync read to fill the buffer, we could avoid the
- // problem, and any async read less than 64K gets turned into a
- // synchronous read by NT anyways... --
-
- if (numBytes < _bufferSize)
- {
- if (_buffer == null) _buffer = new byte[_bufferSize];
- IAsyncResult bufferRead = BeginReadCore(_buffer, 0, _bufferSize, null, null, 0);
- _readLen = EndRead(bufferRead);
- int n = _readLen;
- if (n > numBytes) n = numBytes;
- Buffer.InternalBlockCopy(_buffer, 0, array, offset, n);
- _readPos = n;
-
- // Return a synchronous FileStreamAsyncResult
- return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false);
- }
- else
- {
- // Here we're making our position pointer inconsistent
- // with our read buffer. Throw away the read buffer's contents.
- _readPos = 0;
- _readLen = 0;
- return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0);
- }
- }
- else
- {
- int n = _readLen - _readPos;
- if (n > numBytes) n = numBytes;
- Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n);
- _readPos += n;
-
- if (n >= numBytes)
- {
- // Return a synchronous FileStreamAsyncResult
- return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false);
- }
- else
- {
- // For streams with no clear EOF like serial ports or pipes
- // we cannot read more data without causing an app to block
- // incorrectly. Pipes don't go down this path
- // though. This code needs to be fixed.
- // Throw away read buffer.
- _readPos = 0;
- _readLen = 0;
- return BeginReadCore(array, offset + n, numBytes - n, userCallback, stateObject, n);
- }
- // WARNING: all state on asyncResult objects must be set before
- // we call ReadFile in BeginReadCore, since the OS can run our
- // callback & the user's callback before ReadFile returns.
- }
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- unsafe private FileStreamAsyncResult BeginReadCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject, int numBufferedBytesRead)
- {
- Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed");
- Contract.Assert(CanRead, "CanRead");
- Contract.Assert(bytes != null, "bytes != null");
- Contract.Assert(_writePos == 0, "_writePos == 0");
- Contract.Assert(_isAsync, "BeginReadCore doesn't work on synchronous file streams!");
- Contract.Assert(offset >= 0, "offset is negative");
- Contract.Assert(numBytes >= 0, "numBytes is negative");
-
- // Create and store async stream class library specific data in the async result
-
- // Must pass in _numBufferedBytes here to ensure all the state on the IAsyncResult
- // object is set before we call ReadFile, which gives the OS an
- // opportunity to run our callback (including the user callback &
- // the call to EndRead) before ReadFile has returned.
- FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytesRead, bytes, _handle, userCallback, stateObject, false);
- NativeOverlapped* intOverlapped = asyncResult.OverLapped;
-
- // Calculate position in the file we should be at after the read is done
- if (CanSeek) {
- long len = Length;
-
- // Make sure we are reading from the position that we think we are
- if (_exposedHandle)
- VerifyOSHandlePosition();
-
- if (_pos + numBytes > len) {
- if (_pos <= len)
- numBytes = (int) (len - _pos);
- else
- numBytes = 0;
- }
-
- // Now set the position to read from in the NativeOverlapped struct
- // For pipes, we should leave the offset fields set to 0.
- intOverlapped->OffsetLow = unchecked((int)_pos);
- intOverlapped->OffsetHigh = (int)(_pos>>32);
-
- // When using overlapped IO, the OS is not supposed to
- // touch the file pointer location at all. We will adjust it
- // ourselves. This isn't threadsafe.
-
- // WriteFile should not update the file pointer when writing
- // in overlapped mode, according to MSDN. But it does update
- // the file pointer when writing to a UNC path!
- // So changed the code below to seek to an absolute
- // location, not a relative one. ReadFile seems consistent though.
- SeekCore(numBytes, SeekOrigin.Current);
- }
-
- if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
- FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false);
-
- // queue an async ReadFile operation and pass in a packed overlapped
- int hr = 0;
- int r = ReadFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr);
- // ReadFile, the OS version, will return 0 on failure. But
- // my ReadFileNative wrapper returns -1. My wrapper will return
- // the following:
- // On error, r==-1.
- // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING
- // on async requests that completed sequentially, r==0
- // You will NEVER RELIABLY be able to get the number of bytes
- // read back from this call when using overlapped structures! You must
- // not pass in a non-null lpNumBytesRead to ReadFile when using
- // overlapped structures! This is by design NT behavior.
- if (r==-1 && numBytes!=-1) {
-
- // For pipes, when they hit EOF, they will come here.
- if (hr == ERROR_BROKEN_PIPE) {
- // Not an error, but EOF. AsyncFSCallback will NOT be
- // called. Call the user callback here.
-
- // We clear the overlapped status bit for this special case.
- // Failure to do so looks like we are freeing a pending overlapped later.
- intOverlapped->InternalLow = IntPtr.Zero;
- asyncResult.CallUserCallback();
- // EndRead will free the Overlapped struct correctly.
- }
- else if (hr != ERROR_IO_PENDING) {
- if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere.
- SeekCore(0, SeekOrigin.Current);
-
- if (hr == ERROR_HANDLE_EOF)
- __Error.EndOfFile();
- else
- __Error.WinIOError(hr, String.Empty);
- }
- }
- else {
- // Due to a workaround for a race condition in NT's ReadFile &
- // WriteFile routines, we will always be returning 0 from ReadFileNative
- // when we do async IO instead of the number of bytes read,
- // irregardless of whether the operation completed
- // synchronously or asynchronously. We absolutely must not
- // set asyncResult._numBytes here, since will never have correct
- // results.
- //Console.WriteLine("ReadFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on a separate thread");
- }
-
- return asyncResult;
- }
-
- [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed.
- public unsafe override int EndRead(IAsyncResult asyncResult)
- {
- // There are 3 significantly different IAsyncResults we'll accept
- // here. One is from Stream::BeginRead. The other two are variations
- // on our FileStreamAsyncResult. One is from BeginReadCore,
- // while the other is from the BeginRead buffering wrapper.
- if (asyncResult==null)
- throw new ArgumentNullException(nameof(asyncResult));
- Contract.EndContractBlock();
-
- if (!_isAsync)
- return base.EndRead(asyncResult);
-
- FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult;
- if (afsar==null || afsar.IsWrite)
- __Error.WrongAsyncResult();
-
- // Ensure we don't have any race conditions by doing an interlocked
- // CompareExchange here. Avoids corrupting memory via freeing the
- // NativeOverlapped class or GCHandle twice. --
- if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0))
- __Error.EndReadCalledTwice();
-
- // Obtain the WaitHandle, but don't use public property in case we
- // delay initialize the manual reset event in the future.
- afsar.Wait();
-
- // Free memory & GC handles.
- afsar.ReleaseNativeResource();
-
- // Now check for any error during the read.
- if (afsar.ErrorCode != 0)
- __Error.WinIOError(afsar.ErrorCode, String.Empty);
-
- return afsar.NumBytesRead;
- }
-
- // Reads a byte from the file stream. Returns the byte cast to an int
- // or -1 if reading from the end of the stream.
- [System.Security.SecuritySafeCritical] // auto-generated
- public override int ReadByte() {
- if (_handle.IsClosed) __Error.FileNotOpen();
- if (_readLen==0 && !CanRead) __Error.ReadNotSupported();
- Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both.");
- if (_readPos == _readLen) {
- if (_writePos > 0) FlushWrite(false);
- Contract.Assert(_bufferSize > 0, "_bufferSize > 0");
- if (_buffer == null) _buffer = new byte[_bufferSize];
- _readLen = ReadCore(_buffer, 0, _bufferSize);
- _readPos = 0;
- }
- if (_readPos == _readLen)
- return -1;
-
- int result = _buffer[_readPos];
- _readPos++;
- return result;
- }
-
-
- [System.Security.SecuritySafeCritical] // auto-generated
- [HostProtection(ExternalThreading=true)]
- public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
- {
- if (array==null)
- throw new ArgumentNullException(nameof(array));
- if (offset < 0)
- throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (numBytes < 0)
- throw new ArgumentOutOfRangeException(nameof(numBytes), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (array.Length - offset < numBytes)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
- Contract.EndContractBlock();
-
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- if (!_isAsync)
- return base.BeginWrite(array, offset, numBytes, userCallback, stateObject);
- else
- return BeginWriteAsync(array, offset, numBytes, userCallback, stateObject);
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- [HostProtection(ExternalThreading = true)]
- private FileStreamAsyncResult BeginWriteAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
- {
- Contract.Assert(_isAsync);
-
- if (!CanWrite) __Error.WriteNotSupported();
-
- Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both.");
-
- if (_isPipe)
- {
- // When redirecting stdout & stderr with the Process class, it's easy to deadlock your
- // parent & child processes when doing writes 4K at a time. The
- // OS appears to use a 4K buffer internally. If you write to a
- // pipe that is full, you will block until someone read from
- // that pipe. If you try reading from an empty pipe and
- // FileStream's BeginRead blocks waiting for data to fill it's
- // internal buffer, you will be blocked. In a case where a child
- // process writes to stdout & stderr while a parent process tries
- // reading from both, you can easily get into a deadlock here.
- // To avoid this deadlock, don't buffer when doing async IO on
- // pipes.
- Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream must not have buffered data here! Pipes should be unidirectional.");
-
- if (_writePos > 0)
- FlushWrite(false);
-
- return BeginWriteCore(array, offset, numBytes, userCallback, stateObject);
- }
-
- // Handle buffering.
- if (_writePos == 0)
- {
- if (_readPos < _readLen) FlushRead();
- _readPos = 0;
- _readLen = 0;
- }
-
- int n = _bufferSize - _writePos;
- if (numBytes <= n)
- {
- if (_writePos == 0) _buffer = new byte[_bufferSize];
- Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes);
- _writePos += numBytes;
-
- // Return a synchronous FileStreamAsyncResult
- return FileStreamAsyncResult.CreateBufferedReadResult(numBytes, userCallback, stateObject, true);
- }
-
- if (_writePos > 0)
- FlushWrite(false);
-
- return BeginWriteCore(array, offset, numBytes, userCallback, stateObject);
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- unsafe private FileStreamAsyncResult BeginWriteCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject)
- {
- Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed");
- Contract.Assert(CanWrite, "CanWrite");
- Contract.Assert(bytes != null, "bytes != null");
- Contract.Assert(_readPos == _readLen, "_readPos == _readLen");
- Contract.Assert(_isAsync, "BeginWriteCore doesn't work on synchronous file streams!");
- Contract.Assert(offset >= 0, "offset is negative");
- Contract.Assert(numBytes >= 0, "numBytes is negative");
-
- // Create and store async stream class library specific data in the async result
- FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(0, bytes, _handle, userCallback, stateObject, true);
- NativeOverlapped* intOverlapped = asyncResult.OverLapped;
-
- if (CanSeek) {
- // Make sure we set the length of the file appropriately.
- long len = Length;
- //Console.WriteLine("BeginWrite - Calculating end pos. pos: "+pos+" len: "+len+" numBytes: "+numBytes);
-
- // Make sure we are writing to the position that we think we are
- if (_exposedHandle)
- VerifyOSHandlePosition();
-
- if (_pos + numBytes > len) {
- //Console.WriteLine("BeginWrite - Setting length to: "+(pos + numBytes));
- SetLengthCore(_pos + numBytes);
- }
-
- // Now set the position to read from in the NativeOverlapped struct
- // For pipes, we should leave the offset fields set to 0.
- intOverlapped->OffsetLow = (int)_pos;
- intOverlapped->OffsetHigh = (int)(_pos>>32);
-
- // When using overlapped IO, the OS is not supposed to
- // touch the file pointer location at all. We will adjust it
- // ourselves. This isn't threadsafe.
-
- SeekCore(numBytes, SeekOrigin.Current);
- }
-
- //Console.WriteLine("BeginWrite finishing. pos: "+pos+" numBytes: "+numBytes+" _pos: "+_pos+" Position: "+Position);
-
- if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer))
- FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false);
-
- int hr = 0;
- // queue an async WriteFile operation and pass in a packed overlapped
- int r = WriteFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr);
-
- // WriteFile, the OS version, will return 0 on failure. But
- // my WriteFileNative wrapper returns -1. My wrapper will return
- // the following:
- // On error, r==-1.
- // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING
- // On async requests that completed sequentially, r==0
- // You will NEVER RELIABLY be able to get the number of bytes
- // written back from this call when using overlapped IO! You must
- // not pass in a non-null lpNumBytesWritten to WriteFile when using
- // overlapped structures! This is ByDesign NT behavior.
- if (r==-1 && numBytes!=-1) {
- //Console.WriteLine("WriteFile returned 0; Write will complete asynchronously (if hr==3e5) hr: 0x{0:x}", hr);
-
- // For pipes, when they are closed on the other side, they will come here.
- if (hr == ERROR_NO_DATA) {
- // Not an error, but EOF. AsyncFSCallback will NOT be
- // called. Call the user callback here.
- asyncResult.CallUserCallback();
- // EndWrite will free the Overlapped struct correctly.
- }
- else if (hr != ERROR_IO_PENDING) {
- if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere.
- SeekCore(0, SeekOrigin.Current);
-
- if (hr == ERROR_HANDLE_EOF)
- __Error.EndOfFile();
- else
- __Error.WinIOError(hr, String.Empty);
- }
- }
- else {
- // Due to a workaround for a race condition in NT's ReadFile &
- // WriteFile routines, we will always be returning 0 from WriteFileNative
- // when we do async IO instead of the number of bytes written,
- // irregardless of whether the operation completed
- // synchronously or asynchronously. We absolutely must not
- // set asyncResult._numBytes here, since will never have correct
- // results.
- //Console.WriteLine("WriteFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on another thread.");
- }
-
- return asyncResult;
- }
-
- [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed.
- public unsafe override void EndWrite(IAsyncResult asyncResult)
- {
- if (asyncResult==null)
- throw new ArgumentNullException(nameof(asyncResult));
- Contract.EndContractBlock();
-
- if (!_isAsync) {
- base.EndWrite(asyncResult);
- return;
- }
-
- FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult;
- if (afsar==null || !afsar.IsWrite)
- __Error.WrongAsyncResult();
-
- // Ensure we can't have any race conditions by doing an interlocked
- // CompareExchange here. Avoids corrupting memory via freeing the
- // NativeOverlapped class or GCHandle twice. --
- if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0))
- __Error.EndWriteCalledTwice();
-
- // Obtain the WaitHandle, but don't use public property in case we
- // delay initialize the manual reset event in the future.
- afsar.Wait();
-
- // Free memory & GC handles.
- afsar.ReleaseNativeResource();
-
- // Now check for any error during the write.
- if (afsar.ErrorCode != 0)
- __Error.WinIOError(afsar.ErrorCode, String.Empty);
-
- // Number of bytes written is afsar._numBytes + afsar._numBufferedBytes.
- return;
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public override void WriteByte(byte value)
- {
- if (_handle.IsClosed) __Error.FileNotOpen();
- if (_writePos==0) {
- if (!CanWrite) __Error.WriteNotSupported();
- if (_readPos < _readLen) FlushRead();
- _readPos = 0;
- _readLen = 0;
- Contract.Assert(_bufferSize > 0, "_bufferSize > 0");
- if (_buffer==null) _buffer = new byte[_bufferSize];
- }
- if (_writePos == _bufferSize)
- FlushWrite(false);
-
- _buffer[_writePos] = value;
- _writePos++;
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public virtual void Lock(long position, long length) {
- if (position < 0 || length < 0)
- throw new ArgumentOutOfRangeException((position < 0 ? nameof(position) : nameof(length)), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- Contract.EndContractBlock();
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- int positionLow = unchecked((int)(position ));
- int positionHigh = unchecked((int)(position >> 32));
- int lengthLow = unchecked((int)(length ));
- int lengthHigh = unchecked((int)(length >> 32));
-
- if (!Win32Native.LockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh))
- __Error.WinIOError();
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- public virtual void Unlock(long position, long length) {
- if (position < 0 || length < 0)
- throw new ArgumentOutOfRangeException((position < 0 ? nameof(position) : nameof(length)), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- Contract.EndContractBlock();
- if (_handle.IsClosed) __Error.FileNotOpen();
-
- int positionLow = unchecked((int)(position ));
- int positionHigh = unchecked((int)(position >> 32));
- int lengthLow = unchecked((int)(length ));
- int lengthHigh = unchecked((int)(length >> 32));
-
- if (!Win32Native.UnlockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh))
- __Error.WinIOError();
- }
-
- // Windows API definitions, from winbase.h and others
-
- private const int FILE_ATTRIBUTE_NORMAL = 0x00000080;
- private const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000;
- private const int FILE_FLAG_OVERLAPPED = 0x40000000;
- internal const int GENERIC_READ = unchecked((int)0x80000000);
- private const int GENERIC_WRITE = 0x40000000;
-
- private const int FILE_BEGIN = 0;
- private const int FILE_CURRENT = 1;
- private const int FILE_END = 2;
-
- // Error codes (not HRESULTS), from winerror.h
- internal const int ERROR_BROKEN_PIPE = 109;
- internal const int ERROR_NO_DATA = 232;
- private const int ERROR_HANDLE_EOF = 38;
- private const int ERROR_INVALID_PARAMETER = 87;
- private const int ERROR_IO_PENDING = 997;
-
-
- // __ConsoleStream also uses this code.
- [System.Security.SecurityCritical] // auto-generated
- private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr)
- {
- Contract.Requires(handle != null, "handle != null");
- Contract.Requires(offset >= 0, "offset >= 0");
- Contract.Requires(count >= 0, "count >= 0");
- Contract.Requires(bytes != null, "bytes != null");
- // Don't corrupt memory when multiple threads are erroneously writing
- // to this stream simultaneously.
- if (bytes.Length - offset < count)
- throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition"));
- Contract.EndContractBlock();
-
- Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter mismatch in call to ReadFileNative.");
-
- // You can't use the fixed statement on an array of length 0.
- if (bytes.Length==0) {
- hr = 0;
- return 0;
- }
-
- int r = 0;
- int numBytesRead = 0;
-
- fixed(byte* p = bytes) {
- if (_isAsync)
- r = Win32Native.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped);
- else
- r = Win32Native.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero);
- }
-
- if (r==0) {
- hr = Marshal.GetLastWin32Error();
- // We should never silently drop an error here without some
- // extra work. We must make sure that BeginReadCore won't return an
- // IAsyncResult that will cause EndRead to block, since the OS won't
- // call AsyncFSCallback for us.
- if (hr == ERROR_BROKEN_PIPE || hr == Win32Native.ERROR_PIPE_NOT_CONNECTED) {
- // This handle was a pipe, and it's done. Not an error, but EOF.
- // However, the OS will not call AsyncFSCallback!
- // Let the caller handle this, since BeginReadCore & ReadCore
- // need to do different things.
- return -1;
- }
-
- // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)".
- if (hr == Win32Native.ERROR_INVALID_HANDLE)
- _handle.Dispose();
-
- return -1;
- }
- else
- hr = 0;
- return numBytesRead;
- }
-
- [System.Security.SecurityCritical] // auto-generated
- private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) {
- Contract.Requires(handle != null, "handle != null");
- Contract.Requires(offset >= 0, "offset >= 0");
- Contract.Requires(count >= 0, "count >= 0");
- Contract.Requires(bytes != null, "bytes != null");
- // Don't corrupt memory when multiple threads are erroneously writing
- // to this stream simultaneously. (the OS is reading from
- // the array we pass to WriteFile, but if we read beyond the end and
- // that memory isn't allocated, we could get an AV.)
- if (bytes.Length - offset < count)
- throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition"));
- Contract.EndContractBlock();
-
- Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter missmatch in call to WriteFileNative.");
-
- // You can't use the fixed statement on an array of length 0.
- if (bytes.Length==0) {
- hr = 0;
- return 0;
- }
-
- int numBytesWritten = 0;
- int r = 0;
-
- fixed(byte* p = bytes) {
- if (_isAsync)
- r = Win32Native.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped);
- else
- r = Win32Native.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero);
- }
-
- if (r==0) {
- hr = Marshal.GetLastWin32Error();
- // We should never silently drop an error here without some
- // extra work. We must make sure that BeginWriteCore won't return an
- // IAsyncResult that will cause EndWrite to block, since the OS won't
- // call AsyncFSCallback for us.
-
- if (hr==ERROR_NO_DATA) {
- // This handle was a pipe, and the pipe is being closed on the
- // other side. Let the caller handle this, since BeginWriteCore
- // & WriteCore need to do different things.
- return -1;
- }
-
- // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)".
- if (hr == Win32Native.ERROR_INVALID_HANDLE)
- _handle.Dispose();
-
- return -1;
- }
- else
- hr = 0;
- return numBytesWritten;
- }
-
-
- [HostProtection(ExternalThreading = true)]
- [ComVisible(false)]
- [SecuritySafeCritical]
- public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- if (buffer == null)
- throw new ArgumentNullException(nameof(buffer));
- if (offset < 0)
- throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (count < 0)
- throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (buffer.Length - offset < count)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
- Contract.EndContractBlock();
-
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Read() or BeginRead() which a subclass might have overriden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Read/BeginRead) when we are not sure.
- if (this.GetType() != typeof(FileStream))
- return base.ReadAsync(buffer, offset, count, cancellationToken);
-
- if (cancellationToken.IsCancellationRequested)
- return Task.FromCanceled<int>(cancellationToken);
-
- if (_handle.IsClosed)
- __Error.FileNotOpen();
-
- // If async IO is not supported on this platform or
- // if this FileStream was not opened with FileOptions.Asynchronous.
- if (!_isAsync)
- return base.ReadAsync(buffer, offset, count, cancellationToken);
-
- var readTask = new FileStreamReadWriteTask<int>(cancellationToken);
- var endReadTask = s_endReadTask;
- if (endReadTask == null) s_endReadTask = endReadTask = EndReadTask; // benign initialization race condition
- readTask._asyncResult = BeginReadAsync(buffer, offset, count, endReadTask, readTask);
-
- if (readTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled)
- {
- var cancelReadHandler = s_cancelReadHandler;
- if (cancelReadHandler == null) s_cancelReadHandler = cancelReadHandler = CancelTask<int>; // benign initialization race condition
- readTask._registration = cancellationToken.Register(cancelReadHandler, readTask);
-
- // In case the task is completed right before we register the cancellation callback.
- if (readTask._asyncResult.IsCompleted)
- readTask._registration.Dispose();
- }
-
- return readTask;
- }
-
- [HostProtection(ExternalThreading = true)]
- [ComVisible(false)]
- [SecuritySafeCritical]
- public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken)
- {
- if (buffer == null)
- throw new ArgumentNullException(nameof(buffer));
- if (offset < 0)
- throw new ArgumentOutOfRangeException(nameof(offset), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (count < 0)
- throw new ArgumentOutOfRangeException(nameof(count), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
- if (buffer.Length - offset < count)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen"));
- Contract.EndContractBlock();
-
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Write() or BeginWrite() which a subclass might have overriden.
- // To be safe we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Write/BeginWrite) when we are not sure.
- if (this.GetType() != typeof(FileStream))
- return base.WriteAsync(buffer, offset, count, cancellationToken);
-
- if (cancellationToken.IsCancellationRequested)
- return Task.FromCanceled(cancellationToken);
-
- if (_handle.IsClosed)
- __Error.FileNotOpen();
-
- // If async IO is not supported on this platform or
- // if this FileStream was not opened with FileOptions.Asynchronous.
- if (!_isAsync)
- return base.WriteAsync(buffer, offset, count, cancellationToken);
-
- var writeTask = new FileStreamReadWriteTask<VoidTaskResult>(cancellationToken);
- var endWriteTask = s_endWriteTask;
- if (endWriteTask == null) s_endWriteTask = endWriteTask = EndWriteTask; // benign initialization race condition
- writeTask._asyncResult = BeginWriteAsync(buffer, offset, count, endWriteTask, writeTask);
-
- if (writeTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled)
- {
- var cancelWriteHandler = s_cancelWriteHandler;
- if (cancelWriteHandler == null) s_cancelWriteHandler = cancelWriteHandler = CancelTask<VoidTaskResult>; // benign initialization race condition
- writeTask._registration = cancellationToken.Register(cancelWriteHandler, writeTask);
-
- // In case the task is completed right before we register the cancellation callback.
- if (writeTask._asyncResult.IsCompleted)
- writeTask._registration.Dispose();
- }
-
- return writeTask;
- }
-
- // The task instance returned from ReadAsync and WriteAsync.
- // Also stores all of the state necessary for those calls to avoid closures and extraneous delegate allocations.
- private sealed class FileStreamReadWriteTask<T> : Task<T>
- {
- internal CancellationToken _cancellationToken;
- internal CancellationTokenRegistration _registration;
- internal FileStreamAsyncResult _asyncResult; // initialized after Begin call completes
-
- internal FileStreamReadWriteTask(CancellationToken cancellationToken) : base()
- {
- _cancellationToken = cancellationToken;
- }
- }
-
- // Cancellation callback for both ReadAsync and WriteAsync.
- [SecuritySafeCritical]
- private static void CancelTask<T>(object state)
- {
- var task = state as FileStreamReadWriteTask<T>;
- Contract.Assert(task != null);
- FileStreamAsyncResult asyncResult = task._asyncResult;
-
- // This method is used as both the completion callback and the cancellation callback.
- // We should try to cancel the operation if this is running as the completion callback
- // or if cancellation is not applicable:
- // 1. asyncResult is not a FileStreamAsyncResult
- // 2. asyncResult.IsAsync is false: asyncResult is a "synchronous" FileStreamAsyncResult.
- // 3. The asyncResult is completed: this should never happen.
- Contract.Assert((!asyncResult.IsWrite && typeof(T) == typeof(int)) ||
- (asyncResult.IsWrite && typeof(T) == typeof(VoidTaskResult)));
- Contract.Assert(asyncResult != null);
- Contract.Assert(asyncResult.IsAsync);
-
- try
- {
- // Cancel the overlapped read and set the task to cancelled state.
- if (!asyncResult.IsCompleted)
- asyncResult.Cancel();
- }
- catch (Exception ex)
- {
- task.TrySetException(ex);
- }
- }
-
- // Completion callback for ReadAsync
- [SecuritySafeCritical]
- private static void EndReadTask(IAsyncResult iar)
- {
- FileStreamAsyncResult asyncResult = iar as FileStreamAsyncResult;
- Contract.Assert(asyncResult != null);
- Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?");
-
- var readTask = asyncResult.AsyncState as FileStreamReadWriteTask<int>;
- Contract.Assert(readTask != null);
-
- try
- {
- if (asyncResult.IsAsync)
- {
- asyncResult.ReleaseNativeResource();
-
- // release the resource held by CancellationTokenRegistration
- readTask._registration.Dispose();
- }
-
- if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED)
- {
- var cancellationToken = readTask._cancellationToken;
- Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?");
- readTask.TrySetCanceled(cancellationToken);
- }
- else
- readTask.TrySetResult(asyncResult.NumBytesRead);
- }
- catch (Exception ex)
- {
- readTask.TrySetException(ex);
- }
- }
-
- // Completion callback for WriteAsync
- [SecuritySafeCritical]
- private static void EndWriteTask(IAsyncResult iar)
- {
- var asyncResult = iar as FileStreamAsyncResult;
- Contract.Assert(asyncResult != null);
- Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?");
-
- var writeTask = iar.AsyncState as FileStreamReadWriteTask<VoidTaskResult>;
- Contract.Assert(writeTask != null);
-
- try
- {
- if (asyncResult.IsAsync)
- {
- asyncResult.ReleaseNativeResource();
-
- // release the resource held by CancellationTokenRegistration
- writeTask._registration.Dispose();
- }
-
- if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED)
- {
- var cancellationToken = writeTask._cancellationToken;
- Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?");
- writeTask.TrySetCanceled(cancellationToken);
- }
- else
- writeTask.TrySetResult(default(VoidTaskResult));
- }
- catch (Exception ex)
- {
- writeTask.TrySetException(ex);
- }
- }
-
- // Unlike Flush(), FlushAsync() always flushes to disk. This is intentional.
- // Legend is that we chose not to flush the OS file buffers in Flush() in fear of
- // perf problems with frequent, long running FlushFileBuffers() calls. But we don't
- // have that problem with FlushAsync() because we will call FlushFileBuffers() in the background.
- [HostProtection(ExternalThreading = true)]
- [ComVisible(false)]
- [System.Security.SecuritySafeCritical]
- public override Task FlushAsync(CancellationToken cancellationToken)
- {
- // If we have been inherited into a subclass, the following implementation could be incorrect
- // since it does not call through to Flush() which a subclass might have overriden. To be safe
- // we will only use this implementation in cases where we know it is safe to do so,
- // and delegate to our base class (which will call into Flush) when we are not sure.
- if (this.GetType() != typeof(FileStream))
- return base.FlushAsync(cancellationToken);
-
- if (cancellationToken.IsCancellationRequested)
- return Task.FromCanceled(cancellationToken);
-
- if (_handle.IsClosed)
- __Error.FileNotOpen();
-
- // The always synchronous data transfer between the OS and the internal buffer is intentional
- // because this is needed to allow concurrent async IO requests. Concurrent data transfer
- // between the OS and the internal buffer will result in race conditions. Since FlushWrite and
- // FlushRead modify internal state of the stream and transfer data between the OS and the
- // internal buffer, they cannot be truly async. We will, however, flush the OS file buffers
- // asynchronously because it doesn't modify any internal state of the stream and is potentially
- // a long running process.
- try
- {
- FlushInternalBuffer();
- }
- catch (Exception e)
- {
- return Task.FromException(e);
- }
-
- if (CanWrite)
- return Task.Factory.StartNew(
- state => ((FileStream)state).FlushOSBuffer(),
- this,
- cancellationToken,
- TaskCreationOptions.DenyChildAttach,
- TaskScheduler.Default);
- else
- return Task.CompletedTask;
- }
-
- }
-}
diff --git a/src/mscorlib/src/System/IO/FileSystemEnumerable.cs b/src/mscorlib/src/System/IO/FileSystemEnumerable.cs
index c2e603c06a..21e355d9fa 100644
--- a/src/mscorlib/src/System/IO/FileSystemEnumerable.cs
+++ b/src/mscorlib/src/System/IO/FileSystemEnumerable.cs
@@ -211,7 +211,7 @@ namespace System.IO
_resultHandler = resultHandler;
this.searchOption = searchOption;
- fullPath = Path.GetFullPathInternal(path);
+ fullPath = Path.GetFullPath(path);
String fullSearchString = GetFullSearchString(fullPath, normalizedSearchPattern);
normalizedSearchPath = Path.GetDirectoryName(fullSearchString);
@@ -260,7 +260,7 @@ namespace System.IO
Contract.Assert(searchCriteria != null && searchData != null, "searchCriteria and searchData should be initialized");
// Execute searchCriteria against the current directory
- String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
+ String searchPath = Path.Combine(searchData.fullPath, searchCriteria);
Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
@@ -416,7 +416,7 @@ namespace System.IO
AddSearchableDirsToStack(searchData);
// Execute searchCriteria against the current directory
- String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria);
+ String searchPath = Path.Combine(searchData.fullPath, searchCriteria);
// Open a Find handle
_hnd = Win32Native.FindFirstFile(searchPath, data);
@@ -509,8 +509,8 @@ namespace System.IO
[System.Security.SecurityCritical]
private SearchResult CreateSearchResult(Directory.SearchData localSearchData, Win32Native.WIN32_FIND_DATA findData)
{
- String userPathFinal = Path.InternalCombine(localSearchData.userPath, findData.cFileName);
- String fullPathFinal = Path.InternalCombine(localSearchData.fullPath, findData.cFileName);
+ String userPathFinal = Path.Combine(localSearchData.userPath, findData.cFileName);
+ String fullPathFinal = Path.Combine(localSearchData.fullPath, findData.cFileName);
return new SearchResult(fullPathFinal, userPathFinal, findData);
}
@@ -526,7 +526,7 @@ namespace System.IO
{
Contract.Requires(localSearchData != null);
- String searchPath = Path.InternalCombine(localSearchData.fullPath, "*");
+ String searchPath = Path.Combine(localSearchData.fullPath, "*");
SafeFindHandle hnd = null;
Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA();
try
@@ -553,8 +553,8 @@ namespace System.IO
{
if (FileSystemEnumerableHelpers.IsDir(data))
{
- String tempFullPath = Path.InternalCombine(localSearchData.fullPath, data.cFileName);
- String tempUserPath = Path.InternalCombine(localSearchData.userPath, data.cFileName);
+ String tempFullPath = Path.Combine(localSearchData.fullPath, data.cFileName);
+ String tempUserPath = Path.Combine(localSearchData.userPath, data.cFileName);
SearchOption option = localSearchData.searchOption;
@@ -598,8 +598,8 @@ namespace System.IO
{
Contract.Requires(searchPattern != null);
- // Win32 normalization trims only U+0020.
- String tempSearchPattern = searchPattern.TrimEnd(Path.TrimEndChars);
+ // Win32 normalization trims only U+0020.
+ String tempSearchPattern = searchPattern.TrimEnd(PathInternal.s_trimEndChars);
// Make this corner case more useful, like dir
if (tempSearchPattern.Equals("."))
@@ -607,7 +607,7 @@ namespace System.IO
tempSearchPattern = "*";
}
- Path.CheckSearchPattern(tempSearchPattern);
+ PathInternal.CheckSearchPattern(tempSearchPattern);
return tempSearchPattern;
}
@@ -619,7 +619,7 @@ namespace System.IO
String searchCriteria = null;
char lastChar = fullPathMod[fullPathMod.Length - 1];
- if (Path.IsDirectorySeparator(lastChar))
+ if (PathInternal.IsDirectorySeparator(lastChar))
{
// Can happen if the path is C:\temp, in which case GetDirectoryName would return C:\
searchCriteria = fullSearchString.Substring(fullPathMod.Length);
@@ -637,11 +637,11 @@ namespace System.IO
Contract.Requires(fullPath != null);
Contract.Requires(searchPattern != null);
- String tempStr = Path.InternalCombine(fullPath, searchPattern);
+ String tempStr = Path.Combine(fullPath, searchPattern);
// If path ends in a trailing slash (\), append a * or we'll get a "Cannot find the file specified" exception
char lastChar = tempStr[tempStr.Length - 1];
- if (Path.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar)
+ if (PathInternal.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar)
{
tempStr = tempStr + '*';
}
diff --git a/src/mscorlib/src/System/IO/FileSystemInfo.cs b/src/mscorlib/src/System/IO/FileSystemInfo.cs
index eaf559ba74..fe39b2d32d 100644
--- a/src/mscorlib/src/System/IO/FileSystemInfo.cs
+++ b/src/mscorlib/src/System/IO/FileSystemInfo.cs
@@ -63,10 +63,10 @@ namespace System.IO {
if (info == null)
throw new ArgumentNullException(nameof(info));
Contract.EndContractBlock();
-
+
// Must use V1 field names here, since V1 didn't implement
// ISerializable.
- FullPath = Path.GetFullPathInternal(info.GetString("FullPath"));
+ FullPath = Path.GetFullPath(info.GetString("FullPath"));
OriginalPath = info.GetString("OriginalPath");
// Lazily initialize the file attributes.
diff --git a/src/mscorlib/src/System/IO/Path.cs b/src/mscorlib/src/System/IO/Path.cs
deleted file mode 100644
index 6670ad738b..0000000000
--- a/src/mscorlib/src/System/IO/Path.cs
+++ /dev/null
@@ -1,1433 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-/*============================================================
-**
-**
-**
-**
-**
-** Purpose: A collection of path manipulation methods.
-**
-**
-===========================================================*/
-
-using System;
-using System.Security.Permissions;
-using Win32Native = Microsoft.Win32.Win32Native;
-using System.Text;
-using System.Runtime.InteropServices;
-using System.Security;
-#if FEATURE_LEGACYSURFACE
-using System.Security.Cryptography;
-#endif
-using System.Runtime.CompilerServices;
-using System.Globalization;
-using System.Runtime.Versioning;
-using System.Diagnostics.Contracts;
-
-namespace System.IO {
- // Provides methods for processing directory strings in an ideally
- // cross-platform manner. Most of the methods don't do a complete
- // full parsing (such as examining a UNC hostname), but they will
- // handle most string operations.
- [ComVisible(true)]
- public static class Path
- {
- // Platform specific directory separator character. This is backslash
- // ('\') on Windows and slash ('/') on Unix.
- //
-#if !PLATFORM_UNIX
- public static readonly char DirectorySeparatorChar = '\\';
- internal const string DirectorySeparatorCharAsString = "\\";
-#else
- public static readonly char DirectorySeparatorChar = '/';
- internal const string DirectorySeparatorCharAsString = "/";
-#endif // !PLATFORM_UNIX
-
- // Platform specific alternate directory separator character.
- // There is only one directory separator char on Unix,
- // so the same definition is used for both Unix and Windows.
- public static readonly char AltDirectorySeparatorChar = '/';
-
- // Platform specific volume separator character. This is colon (':')
- // on Windows and MacOS, and slash ('/') on Unix. This is mostly
- // useful for parsing paths like "c:\windows" or "MacVolume:System Folder".
- //
-#if !PLATFORM_UNIX
- public static readonly char VolumeSeparatorChar = ':';
-#else
- public static readonly char VolumeSeparatorChar = '/';
-#endif // !PLATFORM_UNIX
-
- // Platform specific invalid list of characters in a path.
- // See the "Naming a File" MSDN conceptual docs for more details on
- // what is valid in a file name (which is slightly different from what
- // is legal in a path name).
- // Note: This list is duplicated in CheckInvalidPathChars
- [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")]
-#if !PLATFORM_UNIX
- public static readonly char[] InvalidPathChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31 };
-#else
- public static readonly char[] InvalidPathChars = { '\0' };
-#endif // !PLATFORM_UNIX
-
- // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space.
- // String.WhitespaceChars will trim aggressively than what the underlying FS does (for ex, NTFS, FAT).
- internal static readonly char[] TrimEndChars =
- {
- (char)0x09, // Horizontal tab
- (char)0x0A, // Line feed
- (char)0x0B, // Vertical tab
- (char)0x0C, // Form feed
- (char)0x0D, // Carriage return
- (char)0x20, // Space
- (char)0x85, // Next line
- (char)0xA0 // Non breaking space
- };
-
-#if !PLATFORM_UNIX
- private static readonly char[] RealInvalidPathChars = PathInternal.InvalidPathChars;
-
- private static readonly char[] InvalidFileNameChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, ':', '*', '?', '\\', '/' };
-#else
- private static readonly char[] RealInvalidPathChars = { '\0' };
-
- private static readonly char[] InvalidFileNameChars = { '\0', '/' };
-#endif // !PLATFORM_UNIX
-
-#if !PLATFORM_UNIX
- public static readonly char PathSeparator = ';';
-#else
- public static readonly char PathSeparator = ':';
-#endif // !PLATFORM_UNIX
-
-
- // The max total path is 260, and the max individual component length is 255.
- // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars.
- internal static readonly int MaxPath = PathInternal.MaxShortPath;
-
- internal static readonly int MaxPathComponentLength = PathInternal.MaxComponentLength;
-
- // Windows API definitions
- internal const int MAX_PATH = 260; // From WinDef.h
- internal const int MAX_DIRECTORY_PATH = 248; // cannot create directories greater than 248 characters
-
- // Changes the extension of a file path. The path parameter
- // specifies a file path, and the extension parameter
- // specifies a file extension (with a leading period, such as
- // ".exe" or ".cs").
- //
- // The function returns a file path with the same root, directory, and base
- // name parts as path, but with the file extension changed to
- // the specified extension. If path is null, the function
- // returns null. If path does not contain a file extension,
- // the new file extension is appended to the path. If extension
- // is null, any exsiting extension is removed from path.
- //
- public static String ChangeExtension(String path, String extension) {
- if (path != null) {
- CheckInvalidPathChars(path);
-
- String s = path;
- for (int i = path.Length; --i >= 0;) {
- char ch = path[i];
- if (ch == '.') {
- s = path.Substring(0, i);
- break;
- }
- if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break;
- }
- if (extension != null && path.Length != 0) {
- if (extension.Length == 0 || extension[0] != '.') {
- s = s + ".";
- }
- s = s + extension;
- }
- return s;
- }
- return null;
- }
-
- // Returns the directory path of a file path. This method effectively
- // removes the last element of the given file path, i.e. it returns a
- // string consisting of all characters up to but not including the last
- // backslash ("\") in the file path. The returned value is null if the file
- // path is null or if the file path denotes a root (such as "\", "C:", or
- // "\\server\share").
- public static String GetDirectoryName(String path)
- {
- return GetDirectoryNameInternal(path);
- }
-
- [System.Security.SecuritySafeCritical]
- private static string GetDirectoryNameInternal(string path)
- {
- if (path != null)
- {
- CheckInvalidPathChars(path);
-
- // Expanding short paths is dangerous in this case as the results will change with the current directory.
- //
- // Suppose you have a path called "PICTUR~1\Foo". Now suppose you have two folders on disk "C:\Mine\Pictures Of Me"
- // and "C:\Yours\Pictures of You". If the current directory is neither you'll get back "PICTUR~1". If it is "C:\Mine"
- // get back "Pictures Of Me". "C:\Yours" would give back "Pictures of You".
- //
- // Because of this and as it isn't documented that short paths are expanded we will not expand short names unless
- // we're in legacy mode.
- string normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths:
-#if FEATURE_PATHCOMPAT
- AppContextSwitches.UseLegacyPathHandling
-#else
- false
-#endif
- );
-
- // If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths
- // as this would leak information about paths to which the user would not have access to.
- if (path.Length > 0
-#if FEATURE_CAS_POLICY
- // Only do the extra logic if we're not in full trust
- && !CodeAccessSecurityEngine.QuickCheckForAllDemands()
-#endif
- )
- {
- try
- {
- // If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it.
- string tempPath = RemoveLongPathPrefix(path);
-
- // FileIOPermission cannot handle paths that contain ? or *
- // So we only pass to FileIOPermission the text up to them.
- int pos = 0;
- while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*'))
- pos++;
-
- // GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw
- // SecurityException if we don't.
- // While we don't use the result of this call we are using it as a consistent way of
- // doing the security checks.
- if (pos > 0)
- GetFullPath(tempPath.Substring(0, pos));
- }
- catch (SecurityException)
- {
- // If the user did not have permissions to the path, make sure that we don't leak expanded short paths
- // Only re-normalize if the original path had a ~ in it.
- if (path.IndexOf("~", StringComparison.Ordinal) != -1)
- {
- normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: false);
- }
- }
- catch (PathTooLongException) { }
- catch (NotSupportedException) { } // Security can throw this on "c:\foo:"
- catch (IOException) { }
- catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http:
- }
-
- path = normalizedPath;
-
- int root = GetRootLength(path);
- int i = path.Length;
- if (i > root)
- {
- i = path.Length;
- if (i == root) return null;
- while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar);
- return path.Substring(0, i);
- }
- }
- return null;
- }
-
- // Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers
- // are specified for the first part of the DirectoryInfo name.
- //
- internal static int GetRootLength(string path)
- {
- CheckInvalidPathChars(path);
-
-#if !PLATFORM_UNIX && FEATURE_PATHCOMPAT
- if (AppContextSwitches.UseLegacyPathHandling)
- {
- int i = 0;
- int length = path.Length;
-
- if (length >= 1 && (IsDirectorySeparator(path[0])))
- {
- // handles UNC names and directories off current drive's root.
- i = 1;
- if (length >= 2 && (IsDirectorySeparator(path[1])))
- {
- i = 2;
- int n = 2;
- while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0)) i++;
- }
- }
- else if (length >= 2 && path[1] == VolumeSeparatorChar)
- {
- // handles A:\foo.
- i = 2;
- if (length >= 3 && (IsDirectorySeparator(path[2]))) i++;
- }
- return i;
- }
- else
-#endif // !PLATFORM_UNIX && FEATURE_PATHCOMPAT
- {
- return PathInternal.GetRootLength(path);
- }
- }
-
- internal static bool IsDirectorySeparator(char c) {
- return (c==DirectorySeparatorChar || c == AltDirectorySeparatorChar);
- }
-
- public static char[] GetInvalidPathChars()
- {
- return (char[]) RealInvalidPathChars.Clone();
- }
-
- public static char[] GetInvalidFileNameChars()
- {
- return (char[]) InvalidFileNameChars.Clone();
- }
-
- // Returns the extension of the given path. The returned value includes the
- // period (".") character of the extension except when you have a terminal period when you get String.Empty, such as ".exe" or
- // ".cpp". The returned value is null if the given path is
- // null or if the given path does not include an extension.
- //
- [Pure]
- public static String GetExtension(String path) {
- if (path==null)
- return null;
-
- CheckInvalidPathChars(path);
- int length = path.Length;
- for (int i = length; --i >= 0;) {
- char ch = path[i];
- if (ch == '.')
- {
- if (i != length - 1)
- return path.Substring(i, length - i);
- else
- return String.Empty;
- }
- if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
- break;
- }
- return String.Empty;
- }
-
- // Expands the given path to a fully qualified path. The resulting string
- // consists of a drive letter, a colon, and a root relative path. This
- // function does not verify that the resulting path
- // refers to an existing file or directory on the associated volume.
- [Pure]
- [System.Security.SecuritySafeCritical]
- public static String GetFullPath(String path) {
- String fullPath = GetFullPathInternal(path);
-#if FEATURE_CORECLR
- FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, fullPath);
- state.EnsureState();
-#else
- FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false);
-#endif
- return fullPath;
- }
-
- [System.Security.SecurityCritical]
- internal static String UnsafeGetFullPath(String path)
- {
- String fullPath = GetFullPathInternal(path);
-#if !FEATURE_CORECLR
- FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false);
-#endif
- return fullPath;
- }
-
- // This method is package access to let us quickly get a string name
- // while avoiding a security check. This also serves a slightly
- // different purpose - when we open a file, we need to resolve the
- // path into a fully qualified, non-relative path name. This
- // method does that, finding the current drive &; directory. But
- // as long as we don't return this info to the user, we're good. However,
- // the public GetFullPath does need to do a security check.
- internal static string GetFullPathInternal(string path)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- Contract.EndContractBlock();
-
- string newPath = NormalizePath(path, fullCheck: true);
- return newPath;
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- internal unsafe static string NormalizePath(string path, bool fullCheck)
- {
- return NormalizePath(path, fullCheck,
-#if FEATURE_PATHCOMPAT
- AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath :
-#endif
- PathInternal.MaxLongPath);
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- internal unsafe static string NormalizePath(string path, bool fullCheck, bool expandShortPaths)
- {
- return NormalizePath(path, fullCheck,
-#if FEATURE_PATHCOMPAT
- AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath :
-#endif
- PathInternal.MaxLongPath,
- expandShortPaths);
- }
-
- [System.Security.SecuritySafeCritical] // auto-generated
- internal static string NormalizePath(string path, bool fullCheck, int maxPathLength)
- {
- return NormalizePath(path, fullCheck, maxPathLength, expandShortPaths: true);
- }
-
- [System.Security.SecuritySafeCritical]
- internal static string NormalizePath(string path, bool fullCheck, int maxPathLength, bool expandShortPaths)
- {
-#if FEATURE_PATHCOMPAT
- if (AppContextSwitches.UseLegacyPathHandling)
- {
- return LegacyNormalizePath(path, fullCheck, maxPathLength, expandShortPaths);
- }
- else
-#endif // FEATURE_APPCOMPAT
- {
- if (PathInternal.IsExtended(path))
- {
- // We can't really know what is valid for all cases of extended paths.
- //
- // - object names can include other characters as well (':', '/', etc.)
- // - even file objects have different rules (pipe names can contain most characters)
- //
- // As such we will do no further analysis of extended paths to avoid blocking known and unknown
- // scenarios as well as minimizing compat breaks should we block now and need to unblock later.
- return path;
- }
-
- string normalizedPath = null;
-
- if (fullCheck == false)
- {
- // Disabled fullCheck is only called by GetDirectoryName and GetPathRoot.
- // Avoid adding addtional callers and try going direct to lighter weight NormalizeDirectorySeparators.
- normalizedPath = NewNormalizePathLimitedChecks(path, maxPathLength, expandShortPaths);
- }
- else
- {
- normalizedPath = NewNormalizePath(path, maxPathLength, expandShortPaths: true);
- }
-
- if (string.IsNullOrWhiteSpace(normalizedPath))
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
- return normalizedPath;
- }
- }
-
- [System.Security.SecuritySafeCritical]
- private static string NewNormalizePathLimitedChecks(string path, int maxPathLength, bool expandShortPaths)
- {
- string normalized = PathInternal.NormalizeDirectorySeparators(path);
-
- if (PathInternal.IsPathTooLong(normalized) || PathInternal.AreSegmentsTooLong(normalized))
- throw new PathTooLongException();
-
-#if !PLATFORM_UNIX
- if (!PathInternal.IsDevice(normalized) && PathInternal.HasInvalidVolumeSeparator(path))
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- if (expandShortPaths && normalized.IndexOf('~') != -1)
- {
- try
- {
- return LongPathHelper.GetLongPathName(normalized);
- }
- catch
- {
- // Don't care if we can't get the long path- might not exist, etc.
- }
- }
-#endif
-
- return normalized;
- }
-
- /// <summary>
- /// Normalize the path and check for bad characters or other invalid syntax.
- /// </summary>
- [System.Security.SecuritySafeCritical]
- private static string NewNormalizePath(string path, int maxPathLength, bool expandShortPaths)
- {
- Contract.Requires(path != null, "path can't be null");
-
- // Embedded null characters are the only invalid character case we want to check up front.
- // This is because the nulls will signal the end of the string to Win32 and therefore have
- // unpredictable results. Other invalid characters we give a chance to be normalized out.
- if (path.IndexOf('\0') != -1)
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
-
-#if !PLATFORM_UNIX
- // Note that colon and wildcard checks happen in FileIOPermissions
-
- // Technically this doesn't matter but we used to throw for this case
- if (string.IsNullOrWhiteSpace(path))
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- // We don't want to check invalid characters for device format- see comments for extended above
- return LongPathHelper.Normalize(path, (uint)maxPathLength, checkInvalidCharacters: !PathInternal.IsDevice(path), expandShortPaths: expandShortPaths);
-#else
- if (path.Length == 0)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- // Expand with current directory if necessary
- if (!IsPathRooted(path))
- path = Combine(Directory.GetCurrentDirectory(), path);
-
- // We would ideally use realpath to do this, but it resolves symlinks, requires that the file actually exist,
- // and turns it into a full path, which we only want if fullCheck is true.
- string collapsedString = PathInternal.RemoveRelativeSegments(path);
-
- if (collapsedString.Length > maxPathLength)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
- return collapsedString.Length == 0 ? "/" : collapsedString;
-#endif // PLATFORM_UNIX
- }
-
-#if FEATURE_PATHCOMPAT
- [System.Security.SecurityCritical] // auto-generated
- internal unsafe static String LegacyNormalizePath(String path, bool fullCheck, int maxPathLength, bool expandShortPaths) {
-
- Contract.Requires(path != null, "path can't be null");
- // If we're doing a full path check, trim whitespace and look for
- // illegal path characters.
- if (fullCheck) {
- // Trim whitespace off the end of the string.
- // Win32 normalization trims only U+0020.
- path = path.TrimEnd(TrimEndChars);
-
- // Look for illegal path characters.
- if (PathInternal.AnyPathHasIllegalCharacters(path))
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
- }
-
- int index = 0;
- // We prefer to allocate on the stack for workingset/perf gain. If the
- // starting path is less than MaxPath then we can stackalloc; otherwise we'll
- // use a StringBuilder (PathHelper does this under the hood). The latter may
- // happen in 2 cases:
- // 1. Starting path is greater than MaxPath but it normalizes down to MaxPath.
- // This is relevant for paths containing escape sequences. In this case, we
- // attempt to normalize down to MaxPath, but the caller pays a perf penalty
- // since StringBuilder is used.
- // 2. IsolatedStorage, which supports paths longer than MaxPath (value given
- // by maxPathLength.
- PathHelper newBuffer;
- if (path.Length + 1 <= MaxPath) {
- char* m_arrayPtr = stackalloc char[MaxPath];
- newBuffer = new PathHelper(m_arrayPtr, MaxPath);
- } else {
- newBuffer = new PathHelper(path.Length + Path.MaxPath, maxPathLength);
- }
-
- uint numSpaces = 0;
- uint numDots = 0;
- bool fixupDirectorySeparator = false;
- // Number of significant chars other than potentially suppressible
- // dots and spaces since the last directory or volume separator char
- uint numSigChars = 0;
- int lastSigChar = -1; // Index of last significant character.
- // Whether this segment of the path (not the complete path) started
- // with a volume separator char. Reject "c:...".
- bool startedWithVolumeSeparator = false;
- bool firstSegment = true;
- int lastDirectorySeparatorPos = 0;
-
-#if !PLATFORM_UNIX
- bool mightBeShortFileName = false;
-
- // LEGACY: This code is here for backwards compatibility reasons. It
- // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being
- // turned into \foo.cs\bar.cs.
- if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) {
- newBuffer.Append('\\');
- index++;
- lastSigChar = 0;
- }
-#endif
-
- // Normalize the string, stripping out redundant dots, spaces, and
- // slashes.
- while (index < path.Length) {
- char currentChar = path[index];
-
- // We handle both directory separators and dots specially. For
- // directory separators, we consume consecutive appearances.
- // For dots, we consume all dots beyond the second in
- // succession. All other characters are added as is. In
- // addition we consume all spaces after the last other char
- // in a directory name up until the directory separator.
-
- if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar) {
- // If we have a path like "123.../foo", remove the trailing dots.
- // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't.
- // Also remove trailing spaces from both files & directory names.
- // This was agreed on with the OS team to fix undeletable directory
- // names ending in spaces.
-
- // If we saw a '\' as the previous last significant character and
- // are simply going to write out dots, suppress them.
- // If we only contain dots and slashes though, only allow
- // a string like [dot]+ [space]*. Ignore everything else.
- // Legal: "\.. \", "\...\", "\. \"
- // Illegal: "\.. .\", "\. .\", "\ .\"
- if (numSigChars == 0) {
- // Dot and space handling
- if (numDots > 0) {
- // Look for ".[space]*" or "..[space]*"
- int start = lastSigChar + 1;
- if (path[start] != '.')
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- // Only allow "[dot]+[space]*", and normalize the
- // legal ones to "." or ".."
- if (numDots >= 2) {
- // Reject "C:..."
- if (startedWithVolumeSeparator && numDots > 2)
-
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- if (path[start + 1] == '.') {
- // Search for a space in the middle of the
- // dots and throw
- for(int i=start + 2; i < start + numDots; i++) {
- if (path[i] != '.')
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
- }
-
- numDots = 2;
- }
- else {
- if (numDots > 1)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
- numDots = 1;
- }
- }
-
- if (numDots == 2) {
- newBuffer.Append('.');
- }
-
- newBuffer.Append('.');
- fixupDirectorySeparator = false;
-
- // Continue in this case, potentially writing out '\'.
- }
-
- if (numSpaces > 0 && firstSegment) {
- // Handle strings like " \\server\share".
- if (index + 1 < path.Length &&
- (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar))
- {
- newBuffer.Append(DirectorySeparatorChar);
- }
- }
- }
- numDots = 0;
- numSpaces = 0; // Suppress trailing spaces
-
- if (!fixupDirectorySeparator) {
- fixupDirectorySeparator = true;
- newBuffer.Append(DirectorySeparatorChar);
- }
- numSigChars = 0;
- lastSigChar = index;
- startedWithVolumeSeparator = false;
- firstSegment = false;
-
-#if !PLATFORM_UNIX
- // For short file names, we must try to expand each of them as
- // soon as possible. We need to allow people to specify a file
- // name that doesn't exist using a path with short file names
- // in it, such as this for a temp file we're trying to create:
- // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp
- // We could try doing this afterwards piece by piece, but it's
- // probably a lot simpler to do it here.
- if (mightBeShortFileName) {
- newBuffer.TryExpandShortFileName();
- mightBeShortFileName = false;
- }
-#endif
- int thisPos = newBuffer.Length - 1;
- if (thisPos - lastDirectorySeparatorPos > MaxPathComponentLength)
- {
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
- }
- lastDirectorySeparatorPos = thisPos;
- } // if (Found directory separator)
- else if (currentChar == '.') {
- // Reduce only multiple .'s only after slash to 2 dots. For
- // instance a...b is a valid file name.
- numDots++;
- // Don't flush out non-terminal spaces here, because they may in
- // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo"
- // which is the conclusion of removing trailing dots & spaces,
- // as well as folding multiple '\' characters.
- }
- else if (currentChar == ' ') {
- numSpaces++;
- }
- else { // Normal character logic
-#if !PLATFORM_UNIX
- if (currentChar == '~' && expandShortPaths)
- mightBeShortFileName = true;
-#endif
-
- fixupDirectorySeparator = false;
-
-#if !PLATFORM_UNIX
- // To reject strings like "C:...\foo" and "C :\foo"
- if (firstSegment && currentChar == VolumeSeparatorChar) {
- // Only accept "C:", not "c :" or ":"
- // Get a drive letter or ' ' if index is 0.
- char driveLetter = (index > 0) ? path[index-1] : ' ';
- bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' '));
- if (!validPath)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- startedWithVolumeSeparator = true;
- // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA"
- if (numSigChars > 1) { // Common case, simply do nothing
- int spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset.
- while((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ')
- spaceCount++;
- if (numSigChars - spaceCount == 1) {
- //Safe to update stack ptr directly
- newBuffer.Length = 0;
- newBuffer.Append(driveLetter); // Overwrite spaces, we need a special case to not break " foo" as a relative path.
- }
- }
- numSigChars = 0;
- }
- else
-#endif // !PLATFORM_UNIX
- {
- numSigChars += 1 + numDots + numSpaces;
- }
-
- // Copy any spaces & dots since the last significant character
- // to here. Note we only counted the number of dots & spaces,
- // and don't know what order they're in. Hence the copy.
- if (numDots > 0 || numSpaces > 0) {
- int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index;
- if (numCharsToCopy > 0) {
- for (int i=0; i<numCharsToCopy; i++) {
- newBuffer.Append(path[lastSigChar + 1 + i]);
- }
- }
- numDots = 0;
- numSpaces = 0;
- }
-
- newBuffer.Append(currentChar);
- lastSigChar = index;
- }
-
- index++;
- } // end while
-
- if (newBuffer.Length - 1 - lastDirectorySeparatorPos > MaxPathComponentLength)
- {
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
- }
-
- // Drop any trailing dots and spaces from file & directory names, EXCEPT
- // we MUST make sure that "C:\foo\.." is correctly handled.
- // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\"
- if (numSigChars == 0) {
- if (numDots > 0) {
- // Look for ".[space]*" or "..[space]*"
- int start = lastSigChar + 1;
- if (path[start] != '.')
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- // Only allow "[dot]+[space]*", and normalize the
- // legal ones to "." or ".."
- if (numDots >= 2) {
- // Reject "C:..."
- if (startedWithVolumeSeparator && numDots > 2)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- if (path[start + 1] == '.') {
- // Search for a space in the middle of the
- // dots and throw
- for(int i=start + 2; i < start + numDots; i++) {
- if (path[i] != '.')
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
- }
-
- numDots = 2;
- }
- else {
- if (numDots > 1)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
- numDots = 1;
- }
- }
-
- if (numDots == 2) {
- newBuffer.Append('.');
- }
-
- newBuffer.Append('.');
- }
- } // if (numSigChars == 0)
-
- // If we ended up eating all the characters, bail out.
- if (newBuffer.Length == 0)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal"));
-
- // Disallow URL's here. Some of our other Win32 API calls will reject
- // them later, so we might be better off rejecting them here.
- // Note we've probably turned them into "file:\D:\foo.tmp" by now.
- // But for compatibility, ensure that callers that aren't doing a
- // full check aren't rejected here.
- if (fullCheck) {
- if ( newBuffer.OrdinalStartsWith("http:", false) ||
- newBuffer.OrdinalStartsWith("file:", false))
- {
- throw new ArgumentException(Environment.GetResourceString("Argument_PathUriFormatNotSupported"));
- }
- }
-
-#if !PLATFORM_UNIX
- // If the last part of the path (file or directory name) had a tilde,
- // expand that too.
- if (mightBeShortFileName) {
- newBuffer.TryExpandShortFileName();
- }
-#endif
-
- // Call the Win32 API to do the final canonicalization step.
- int result = 1;
-
- if (fullCheck) {
- // NOTE: Win32 GetFullPathName requires the input buffer to be big enough to fit the initial
- // path which is a concat of CWD and the relative path, this can be of an arbitrary
- // size and could be > MAX_PATH (which becomes an artificial limit at this point),
- // even though the final normalized path after fixing up the relative path syntax
- // might be well within the MAX_PATH restriction. For ex,
- // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a
- // buffer well with in the MAX_PATH as the normalized path is just "c:\foo.txt"
- // This buffer requirement seems wrong, it could be a bug or a perf optimization
- // like returning required buffer length quickly or avoid stratch buffer etc.
- // Ideally we would get the required buffer length first by calling GetFullPathName
- // once without the buffer and use that in the later call but this doesn't always work
- // due to Win32 GetFullPathName bug. For instance, in Win2k, when the path we are trying to
- // fully qualify is a single letter name (such as "a", "1", ",") GetFullPathName
- // fails to return the right buffer size (i.e, resulting in insufficient buffer).
- // To workaround this bug we will start with MAX_PATH buffer and grow it once if the
- // return value is > MAX_PATH.
-
- result = newBuffer.GetFullPathName();
-
-#if !PLATFORM_UNIX
- // If we called GetFullPathName with something like "foo" and our
- // command window was in short file name mode (ie, by running edlin or
- // DOS versions of grep, etc), we might have gotten back a short file
- // name. So, check to see if we need to expand it.
- mightBeShortFileName = false;
- for(int i=0; i < newBuffer.Length && !mightBeShortFileName; i++) {
- if (newBuffer[i] == '~' && expandShortPaths)
- mightBeShortFileName = true;
- }
-
- if (mightBeShortFileName) {
- bool r = newBuffer.TryExpandShortFileName();
- // Consider how the path "Doesn'tExist" would expand. If
- // we add in the current directory, it too will need to be
- // fully expanded, which doesn't happen if we use a file
- // name that doesn't exist.
- if (!r) {
- int lastSlash = -1;
-
- for (int i = newBuffer.Length - 1; i >= 0; i--) {
- if (newBuffer[i] == DirectorySeparatorChar) {
- lastSlash = i;
- break;
- }
- }
-
- if (lastSlash >= 0) {
-
- // This bounds check is for safe memcpy but we should never get this far
- if (newBuffer.Length >= maxPathLength)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
- int lenSavedName = newBuffer.Length - lastSlash - 1;
- Contract.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'");
-
- newBuffer.Fixup(lenSavedName, lastSlash);
- }
- }
- }
-#endif // PLATFORM_UNIX
- }
-
- if (result != 0) {
- /* Throw an ArgumentException for paths like \\, \\server, \\server\
- This check can only be properly done after normalizing, so
- \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\
- (an internal kernel path) because it provides aliases for drives. */
- if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\') {
- int startIndex = 2;
- while (startIndex < result) {
- if (newBuffer[startIndex] == '\\') {
- startIndex++;
- break;
- }
- else {
- startIndex++;
- }
- }
- if (startIndex == result)
- throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC"));
-
- // Check for \\?\Globalroot, an internal mechanism to the kernel
- // that provides aliases for drives and other undocumented stuff.
- // The kernel team won't even describe the full set of what
- // is available here - we don't want managed apps mucking
- // with this for security reasons.
- if ( newBuffer.OrdinalStartsWith("\\\\?\\globalroot", true))
- throw new ArgumentException(Environment.GetResourceString("Arg_PathGlobalRoot"));
- }
- }
-
- // Check our result and form the managed string as necessary.
- if (newBuffer.Length >= maxPathLength)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
- if (result == 0) {
- int errorCode = Marshal.GetLastWin32Error();
- if (errorCode == 0)
- errorCode = Win32Native.ERROR_BAD_PATHNAME;
- __Error.WinIOError(errorCode, path);
- return null; // Unreachable - silence a compiler error.
- }
-
- return newBuffer.ToStringOrExisting(path);
- }
-#endif // FEATURE_PATHCOMPAT
-
- internal const int MaxLongPath = PathInternal.MaxLongPath;
-
- private const string LongPathPrefix = PathInternal.ExtendedPathPrefix;
- private const string UNCPathPrefix = PathInternal.UncPathPrefix;
- private const string UNCLongPathPrefixToInsert = PathInternal.UncExtendedPrefixToInsert;
- private const string UNCLongPathPrefix = PathInternal.UncExtendedPathPrefix;
-
- internal static bool HasLongPathPrefix(string path)
- {
-#if FEATURE_PATHCOMPAT
- if (AppContextSwitches.UseLegacyPathHandling)
- return path.StartsWith(LongPathPrefix, StringComparison.Ordinal);
- else
-#endif
- return PathInternal.IsExtended(path);
- }
-
- internal static string AddLongPathPrefix(string path)
- {
-#if FEATURE_PATHCOMPAT
- if (AppContextSwitches.UseLegacyPathHandling)
- {
- if (path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
- return path;
-
- if (path.StartsWith(UNCPathPrefix, StringComparison.Ordinal))
- return path.Insert(2, UNCLongPathPrefixToInsert); // Given \\server\share in longpath becomes \\?\UNC\server\share => UNCLongPathPrefix + path.SubString(2); => The actual command simply reduces the operation cost.
-
- return LongPathPrefix + path;
- }
- else
-#endif
- {
- return PathInternal.EnsureExtendedPrefix(path);
- }
- }
-
- internal static string RemoveLongPathPrefix(string path)
- {
-#if FEATURE_PATHCOMPAT
- if (AppContextSwitches.UseLegacyPathHandling)
- {
- if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal))
- return path;
-
- if (path.StartsWith(UNCLongPathPrefix, StringComparison.OrdinalIgnoreCase))
- return path.Remove(2, 6); // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost.
-
- return path.Substring(4);
- }
- else
-#endif
- {
- return PathInternal.RemoveExtendedPrefix(path);
- }
- }
-
- internal static StringBuilder RemoveLongPathPrefix(StringBuilder pathSB)
- {
-#if FEATURE_PATHCOMPAT
- if (AppContextSwitches.UseLegacyPathHandling)
- {
- if (!PathInternal.StartsWithOrdinal(pathSB, LongPathPrefix))
- return pathSB;
-
- // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost.
- if (PathInternal.StartsWithOrdinal(pathSB, UNCLongPathPrefix, ignoreCase: true))
- return pathSB.Remove(2, 6);
-
- return pathSB.Remove(0, 4);
- }
- else
-#endif
- {
- return PathInternal.RemoveExtendedPrefix(pathSB);
- }
- }
-
-
- // Returns the name and extension parts of the given path. The resulting
- // string contains the characters of path that follow the last
- // backslash ("\"), slash ("/"), or colon (":") character in
- // path. The resulting string is the entire path if path
- // contains no backslash after removing trailing slashes, slash, or colon characters. The resulting
- // string is null if path is null.
- //
- [Pure]
- public static String GetFileName(String path) {
- if (path != null) {
- CheckInvalidPathChars(path);
-
- int length = path.Length;
- for (int i = length; --i >= 0;) {
- char ch = path[i];
- if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar)
- return path.Substring(i + 1, length - i - 1);
-
- }
- }
- return path;
- }
-
- [Pure]
- public static String GetFileNameWithoutExtension(String path) {
- path = GetFileName(path);
- if (path != null)
- {
- int i;
- if ((i=path.LastIndexOf('.')) == -1)
- return path; // No path extension found
- else
- return path.Substring(0,i);
- }
- return null;
- }
-
-
-
- // Returns the root portion of the given path. The resulting string
- // consists of those rightmost characters of the path that constitute the
- // root of the path. Possible patterns for the resulting string are: An
- // empty string (a relative path on the current drive), "\" (an absolute
- // path on the current drive), "X:" (a relative path on a given drive,
- // where X is the drive letter), "X:\" (an absolute path on a given drive),
- // and "\\server\share" (a UNC path for a given server and share name).
- // The resulting string is null if path is null.
- //
- [Pure]
- public static String GetPathRoot(String path) {
- if (path == null) return null;
-
- // Expanding short paths has no impact on the path root- there is no such thing as an
- // 8.3 volume or server/share name.
- path = NormalizePath(path, fullCheck: false, expandShortPaths: false);
- return path.Substring(0, GetRootLength(path));
- }
-
- [System.Security.SecuritySafeCritical]
- public static String GetTempPath()
- {
-#if !FEATURE_CORECLR
- new EnvironmentPermission(PermissionState.Unrestricted).Demand();
-#endif
- StringBuilder sb = new StringBuilder(PathInternal.MaxShortPath);
- uint r = Win32Native.GetTempPath(PathInternal.MaxShortPath, sb);
- String path = sb.ToString();
- if (r==0) __Error.WinIOError();
- path = GetFullPathInternal(path);
-#if FEATURE_CORECLR
- FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path);
- state.EnsureState();
-#endif
- return path;
- }
-
- internal static bool IsRelative(string path)
- {
- Contract.Assert(path != null, "path can't be null");
- return PathInternal.IsPartiallyQualified(path);
- }
-
- // Returns a cryptographically strong random 8.3 string that can be
- // used as either a folder name or a file name.
-#if FEATURE_CORECLR
- [System.Security.SecuritySafeCritical]
-#endif
- public static String GetRandomFileName()
- {
- // 5 bytes == 40 bits == 40/5 == 8 chars in our encoding
- // This gives us exactly 8 chars. We want to avoid the 8.3 short name issue
- byte[] key = new byte[10];
-
-#if FEATURE_CORECLR
- Win32Native.Random(true, key, key.Length);
-#else
- // RNGCryptoServiceProvider is disposable in post-Orcas desktop mscorlibs, but not in CoreCLR's
- // mscorlib, so we need to do a manual using block for it.
- RNGCryptoServiceProvider rng = null;
- try
- {
- rng = new RNGCryptoServiceProvider();
-
- rng.GetBytes(key);
- }
- finally
- {
- if (rng != null)
- {
- rng.Dispose();
- }
- }
-#endif
-
- // rndCharArray is expected to be 16 chars
- char[] rndCharArray = Path.ToBase32StringSuitableForDirName(key).ToCharArray();
- rndCharArray[8] = '.';
- return new String(rndCharArray, 0, 12);
- }
-
- // Returns a unique temporary file name, and creates a 0-byte file by that
- // name on disk.
- [System.Security.SecuritySafeCritical]
- public static String GetTempFileName()
- {
- return InternalGetTempFileName(true);
- }
-
- [System.Security.SecurityCritical]
- internal static String UnsafeGetTempFileName()
- {
- return InternalGetTempFileName(false);
- }
-
- [System.Security.SecurityCritical]
- private static String InternalGetTempFileName(bool checkHost)
- {
- String path = GetTempPath();
-
- // Since this can write to the temp directory and theoretically
- // cause a denial of service attack, demand FileIOPermission to
- // that directory.
-
-#if FEATURE_CORECLR
- if (checkHost)
- {
- FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path);
- state.EnsureState();
- }
-#else
- FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, path);
-#endif
- StringBuilder sb = new StringBuilder(MaxPath);
- uint r = Win32Native.GetTempFileName(path, "tmp", 0, sb);
- if (r==0) __Error.WinIOError();
- return sb.ToString();
- }
-
- // Tests if a path includes a file extension. The result is
- // true if the characters that follow the last directory
- // separator ('\\' or '/') or volume separator (':') in the path include
- // a period (".") other than a terminal period. The result is false otherwise.
- //
- [Pure]
- public static bool HasExtension(String path) {
- if (path != null) {
- CheckInvalidPathChars(path);
-
- for (int i = path.Length; --i >= 0;) {
- char ch = path[i];
- if (ch == '.') {
- if ( i != path.Length - 1)
- return true;
- else
- return false;
- }
- if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break;
- }
- }
- return false;
- }
-
- // Tests if the given path contains a root. A path is considered rooted
- // if it starts with a backslash ("\") or a drive letter and a colon (":").
- //
- [Pure]
- public static bool IsPathRooted(String path) {
- if (path != null) {
- CheckInvalidPathChars(path);
-
- int length = path.Length;
-#if !PLATFORM_UNIX
- if ((length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) || (length >= 2 && path[1] == VolumeSeparatorChar))
- return true;
-#else
- if (length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar))
- return true;
-#endif
- }
- return false;
- }
-
- public static String Combine(String path1, String path2) {
- if (path1==null || path2==null)
- throw new ArgumentNullException((path1==null) ? nameof(path1) : nameof(path2));
- Contract.EndContractBlock();
- CheckInvalidPathChars(path1);
- CheckInvalidPathChars(path2);
-
- return CombineNoChecks(path1, path2);
- }
-
- public static String Combine(String path1, String path2, String path3) {
- if (path1 == null || path2 == null || path3 == null)
- throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : nameof(path3));
- Contract.EndContractBlock();
- CheckInvalidPathChars(path1);
- CheckInvalidPathChars(path2);
- CheckInvalidPathChars(path3);
-
- return CombineNoChecks(CombineNoChecks(path1, path2), path3);
- }
-
- public static String Combine(String path1, String path2, String path3, String path4) {
- if (path1 == null || path2 == null || path3 == null || path4 == null)
- throw new ArgumentNullException((path1 == null) ? nameof(path1) : (path2 == null) ? nameof(path2) : (path3 == null) ? nameof(path3) : nameof(path4));
- Contract.EndContractBlock();
- CheckInvalidPathChars(path1);
- CheckInvalidPathChars(path2);
- CheckInvalidPathChars(path3);
- CheckInvalidPathChars(path4);
-
- return CombineNoChecks(CombineNoChecks(CombineNoChecks(path1, path2), path3), path4);
- }
-
- public static String Combine(params String[] paths) {
- if (paths == null) {
- throw new ArgumentNullException(nameof(paths));
- }
- Contract.EndContractBlock();
-
- int finalSize = 0;
- int firstComponent = 0;
-
- // We have two passes, the first calcuates how large a buffer to allocate and does some precondition
- // checks on the paths passed in. The second actually does the combination.
-
- for (int i = 0; i < paths.Length; i++) {
- if (paths[i] == null) {
- throw new ArgumentNullException(nameof(paths));
- }
-
- if (paths[i].Length == 0) {
- continue;
- }
-
- CheckInvalidPathChars(paths[i]);
-
- if (Path.IsPathRooted(paths[i])) {
- firstComponent = i;
- finalSize = paths[i].Length;
- } else {
- finalSize += paths[i].Length;
- }
-
- char ch = paths[i][paths[i].Length - 1];
- if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
- finalSize++;
- }
-
- StringBuilder finalPath = StringBuilderCache.Acquire(finalSize);
-
- for (int i = firstComponent; i < paths.Length; i++) {
- if (paths[i].Length == 0) {
- continue;
- }
-
- if (finalPath.Length == 0) {
- finalPath.Append(paths[i]);
- } else {
- char ch = finalPath[finalPath.Length - 1];
- if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) {
- finalPath.Append(DirectorySeparatorChar);
- }
-
- finalPath.Append(paths[i]);
- }
- }
-
- return StringBuilderCache.GetStringAndRelease(finalPath);
- }
-
- private static String CombineNoChecks(String path1, String path2) {
- if (path2.Length == 0)
- return path1;
-
- if (path1.Length == 0)
- return path2;
-
- if (IsPathRooted(path2))
- return path2;
-
- char ch = path1[path1.Length - 1];
- if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
- return path1 + DirectorySeparatorCharAsString + path2;
- return path1 + path2;
- }
-
- private static readonly Char[] s_Base32Char = {
- 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
- 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
- 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
- 'y', 'z', '0', '1', '2', '3', '4', '5'};
-
- internal static String ToBase32StringSuitableForDirName(byte[] buff)
- {
- // This routine is optimised to be used with buffs of length 20
- Contract.Assert(((buff.Length % 5) == 0), "Unexpected hash length");
-
- StringBuilder sb = StringBuilderCache.Acquire();
- byte b0, b1, b2, b3, b4;
- int l, i;
-
- l = buff.Length;
- i = 0;
-
- // Create l chars using the last 5 bits of each byte.
- // Consume 3 MSB bits 5 bytes at a time.
-
- do
- {
- b0 = (i < l) ? buff[i++] : (byte)0;
- b1 = (i < l) ? buff[i++] : (byte)0;
- b2 = (i < l) ? buff[i++] : (byte)0;
- b3 = (i < l) ? buff[i++] : (byte)0;
- b4 = (i < l) ? buff[i++] : (byte)0;
-
- // Consume the 5 Least significant bits of each byte
- sb.Append(s_Base32Char[b0 & 0x1F]);
- sb.Append(s_Base32Char[b1 & 0x1F]);
- sb.Append(s_Base32Char[b2 & 0x1F]);
- sb.Append(s_Base32Char[b3 & 0x1F]);
- sb.Append(s_Base32Char[b4 & 0x1F]);
-
- // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
- sb.Append(s_Base32Char[(
- ((b0 & 0xE0) >> 5) |
- ((b3 & 0x60) >> 2))]);
-
- sb.Append(s_Base32Char[(
- ((b1 & 0xE0) >> 5) |
- ((b4 & 0x60) >> 2))]);
-
- // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
-
- b2 >>= 5;
-
- Contract.Assert(((b2 & 0xF8) == 0), "Unexpected set bits");
-
- if ((b3 & 0x80) != 0)
- b2 |= 0x08;
- if ((b4 & 0x80) != 0)
- b2 |= 0x10;
-
- sb.Append(s_Base32Char[b2]);
-
- } while (i < l);
-
- return StringBuilderCache.GetStringAndRelease(sb);
- }
-
- // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow
- // the user being able to use it to move up directories. Here are some examples eg
- // Valid: a..b abc..d
- // Invalid: ..ab ab.. .. abc..d\abc..
- //
- internal static void CheckSearchPattern(String searchPattern)
- {
- int index;
- while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) {
-
- if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".."
- throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
-
- if ((searchPattern[index+2] == DirectorySeparatorChar)
- || (searchPattern[index+2] == AltDirectorySeparatorChar))
- throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern"));
-
- searchPattern = searchPattern.Substring(index + 2);
- }
-
- }
-
- internal static void CheckInvalidPathChars(String path, bool checkAdditional = false)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
-
- if (PathInternal.HasIllegalCharacters(path, checkAdditional))
- throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
- }
-
- internal static String InternalCombine(String path1, String path2) {
- if (path1==null || path2==null)
- throw new ArgumentNullException((path1==null) ? nameof(path1) : nameof(path2));
- Contract.EndContractBlock();
- CheckInvalidPathChars(path1);
- CheckInvalidPathChars(path2);
-
- if (path2.Length == 0)
- throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), nameof(path2));
- if (IsPathRooted(path2))
- throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), nameof(path2));
- int i = path1.Length;
- if (i == 0) return path2;
- char ch = path1[i - 1];
- if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar)
- return path1 + DirectorySeparatorCharAsString + path2;
- return path1 + path2;
- }
-
- }
-}
diff --git a/src/mscorlib/src/System/IO/PathHelper.cs b/src/mscorlib/src/System/IO/PathHelper.cs
deleted file mode 100644
index 8e39b3c537..0000000000
--- a/src/mscorlib/src/System/IO/PathHelper.cs
+++ /dev/null
@@ -1,448 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-#if FEATURE_PATHCOMPAT
-using System;
-using System.Collections;
-using System.Text;
-using Microsoft.Win32;
-using System.Runtime.InteropServices;
-using System.Runtime.CompilerServices;
-using System.Globalization;
-using System.Runtime.Versioning;
-using System.Security;
-using System.Security.Permissions;
-using System.Diagnostics.Contracts;
-
-namespace System.IO {
-
- // ABOUT:
- // Helps with path normalization; support allocating on the stack or heap
- //
- // PathHelper can't stackalloc the array for obvious reasons; you must pass
- // in an array of chars allocated on the stack.
- //
- // USAGE:
- // Suppose you need to represent a char array of length len. Then this is the
- // suggested way to instantiate PathHelper:
- // ***************************************************************************
- // PathHelper pathHelper;
- // if (charArrayLength less than stack alloc threshold == Path.MaxPath)
- // char* arrayPtr = stackalloc char[Path.MaxPath];
- // pathHelper = new PathHelper(arrayPtr);
- // else
- // pathHelper = new PathHelper(capacity, maxPath);
- // ***************************************************************************
- //
- // note in the StringBuilder ctor:
- // - maxPath may be greater than Path.MaxPath (for isolated storage)
- // - capacity may be greater than maxPath. This is even used for non-isolated
- // storage scenarios where we want to temporarily allow strings greater
- // than Path.MaxPath if they can be normalized down to Path.MaxPath. This
- // can happen if the path contains escape characters "..".
- //
- unsafe internal struct PathHelper { // should not be serialized
-
- // maximum size, max be greater than max path if contains escape sequence
- private int m_capacity;
- // current length (next character position)
- private int m_length;
- // max path, may be less than capacity
- private int m_maxPath;
-
- // ptr to stack alloc'd array of chars
- [SecurityCritical]
- private char* m_arrayPtr;
-
- // StringBuilder
- private StringBuilder m_sb;
-
- // whether to operate on stack alloc'd or heap alloc'd array
- private bool useStackAlloc;
-
- // Whether to skip calls to Win32Native.GetLongPathName becasue we tried before and failed:
- private bool doNotTryExpandShortFileName;
-
- // Instantiates a PathHelper with a stack alloc'd array of chars
- [System.Security.SecurityCritical]
- internal PathHelper(char* charArrayPtr, int length) {
- Contract.Requires(charArrayPtr != null);
- // force callers to be aware of this
- Contract.Requires(length == Path.MaxPath);
- this.m_length = 0;
- this.m_sb = null;
-
- this.m_arrayPtr = charArrayPtr;
- this.m_capacity = length;
- this.m_maxPath = Path.MaxPath;
- useStackAlloc = true;
- doNotTryExpandShortFileName = false;
- }
-
- // Instantiates a PathHelper with a heap alloc'd array of ints. Will create a StringBuilder
- [System.Security.SecurityCritical]
- internal PathHelper(int capacity, int maxPath)
- {
- this.m_length = 0;
- this.m_arrayPtr = null;
- this.useStackAlloc = false;
-
- this.m_sb = new StringBuilder(capacity);
- this.m_capacity = capacity;
- this.m_maxPath = maxPath;
- doNotTryExpandShortFileName = false;
- }
-
- internal int Length {
- get {
- if (useStackAlloc) {
- return m_length;
- }
- else {
- return m_sb.Length;
- }
- }
- set {
- if (useStackAlloc) {
- m_length = value;
- }
- else {
- m_sb.Length = value;
- }
- }
- }
-
- internal int Capacity {
- get {
- return m_capacity;
- }
- }
-
- internal char this[int index] {
- [System.Security.SecurityCritical]
- get {
- Contract.Requires(index >= 0 && index < Length);
- if (useStackAlloc) {
- return m_arrayPtr[index];
- }
- else {
- return m_sb[index];
- }
- }
- [System.Security.SecurityCritical]
- set {
- Contract.Requires(index >= 0 && index < Length);
- if (useStackAlloc) {
- m_arrayPtr[index] = value;
- }
- else {
- m_sb[index] = value;
- }
- }
- }
-
- [System.Security.SecurityCritical]
- internal unsafe void Append(char value) {
- if (Length + 1 >= m_capacity)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
- if (useStackAlloc) {
- m_arrayPtr[Length] = value;
- m_length++;
- }
- else {
- m_sb.Append(value);
- }
- }
-
- [System.Security.SecurityCritical]
- internal unsafe int GetFullPathName() {
- if (useStackAlloc) {
- char* finalBuffer = stackalloc char[Path.MaxPath + 1];
- int result = Win32Native.GetFullPathName(m_arrayPtr, Path.MaxPath + 1, finalBuffer, IntPtr.Zero);
-
- // If success, the return buffer length does not account for the terminating null character.
- // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
- // If failure, the return buffer length is zero
- if (result > Path.MaxPath) {
- char* tempBuffer = stackalloc char[result];
- finalBuffer = tempBuffer;
- result = Win32Native.GetFullPathName(m_arrayPtr, result, finalBuffer, IntPtr.Zero);
- }
-
- // Full path is genuinely long
- if (result >= Path.MaxPath)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
- Contract.Assert(result < Path.MaxPath, "did we accidently remove a PathTooLongException check?");
- if (result == 0 && m_arrayPtr[0] != '\0') {
- __Error.WinIOError();
- }
-
- else if (result < Path.MaxPath) {
- // Null terminate explicitly (may be only needed for some cases such as empty strings)
- // GetFullPathName return length doesn't account for null terminating char...
- finalBuffer[result] = '\0'; // Safe to write directly as result is < Path.MaxPath
- }
-
- // We have expanded the paths and GetLongPathName may or may not behave differently from before.
- // We need to call it again to see:
- doNotTryExpandShortFileName = false;
-
- String.wstrcpy(m_arrayPtr, finalBuffer, result);
- // Doesn't account for null terminating char. Think of this as the last
- // valid index into the buffer but not the length of the buffer
- Length = result;
- return result;
- }
- else {
- StringBuilder finalBuffer = new StringBuilder(m_capacity + 1);
- int result = Win32Native.GetFullPathName(m_sb.ToString(), m_capacity + 1, finalBuffer, IntPtr.Zero);
-
- // If success, the return buffer length does not account for the terminating null character.
- // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
- // If failure, the return buffer length is zero
- if (result > m_maxPath) {
- finalBuffer.Length = result;
- result = Win32Native.GetFullPathName(m_sb.ToString(), result, finalBuffer, IntPtr.Zero);
- }
-
- // Fullpath is genuinely long
- if (result >= m_maxPath)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
- Contract.Assert(result < m_maxPath, "did we accidentally remove a PathTooLongException check?");
- if (result == 0 && m_sb[0] != '\0') {
- if (Length >= m_maxPath) {
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
- }
- __Error.WinIOError();
- }
-
- // We have expanded the paths and GetLongPathName may or may not behave differently from before.
- // We need to call it again to see:
- doNotTryExpandShortFileName = false;
-
- m_sb = finalBuffer;
- return result;
- }
- }
-
- [System.Security.SecurityCritical]
- internal unsafe bool TryExpandShortFileName() {
-
- if (doNotTryExpandShortFileName)
- return false;
-
- if (useStackAlloc) {
- NullTerminate();
- char* buffer = UnsafeGetArrayPtr();
- char* shortFileNameBuffer = stackalloc char[Path.MaxPath + 1];
-
- int r = Win32Native.GetLongPathName(buffer, shortFileNameBuffer, Path.MaxPath);
-
- // If success, the return buffer length does not account for the terminating null character.
- // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
- // If failure, the return buffer length is zero
- if (r >= Path.MaxPath)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
- if (r == 0) {
- // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a
- // path like \\.\PHYSICALDEVICE0 - some device driver doesn't
- // support GetLongPathName on that string. This behavior is
- // by design, according to the Core File Services team.
- // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs
- // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\
-
- // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist:
- int lastErr = Marshal.GetLastWin32Error();
- if (lastErr == Win32Native.ERROR_FILE_NOT_FOUND || lastErr == Win32Native.ERROR_PATH_NOT_FOUND)
- doNotTryExpandShortFileName = true;
-
- return false;
- }
-
- // Safe to copy as we have already done Path.MaxPath bound checking
- String.wstrcpy(buffer, shortFileNameBuffer, r);
- Length = r;
- // We should explicitly null terminate as in some cases the long version of the path
- // might actually be shorter than what we started with because of Win32's normalization
- // Safe to write directly as bufferLength is guaranteed to be < Path.MaxPath
- NullTerminate();
- return true;
- }
- else {
- StringBuilder sb = GetStringBuilder();
-
- String origName = sb.ToString();
- String tempName = origName;
- bool addedPrefix = false;
- if (tempName.Length > Path.MaxPath) {
- tempName = Path.AddLongPathPrefix(tempName);
- addedPrefix = true;
- }
- sb.Capacity = m_capacity;
- sb.Length = 0;
- int r = Win32Native.GetLongPathName(tempName, sb, m_capacity);
-
- if (r == 0) {
- // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a
- // path like \\.\PHYSICALDEVICE0 - some device driver doesn't
- // support GetLongPathName on that string. This behavior is
- // by design, according to the Core File Services team.
- // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs
- // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\
-
- // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist:
- int lastErr = Marshal.GetLastWin32Error();
- if (Win32Native.ERROR_FILE_NOT_FOUND == lastErr || Win32Native.ERROR_PATH_NOT_FOUND == lastErr)
- doNotTryExpandShortFileName = true;
-
- sb.Length = 0;
- sb.Append(origName);
- return false;
- }
-
- if (addedPrefix)
- r -= 4;
-
- // If success, the return buffer length does not account for the terminating null character.
- // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character.
- // If failure, the return buffer length is zero
- if (r >= m_maxPath)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
-
-
- sb = Path.RemoveLongPathPrefix(sb);
- Length = sb.Length;
- return true;
-
- }
- }
-
- [System.Security.SecurityCritical]
- internal unsafe void Fixup(int lenSavedName, int lastSlash) {
- if (useStackAlloc) {
- char* savedName = stackalloc char[lenSavedName];
- String.wstrcpy(savedName, m_arrayPtr + lastSlash + 1, lenSavedName);
- Length = lastSlash;
- NullTerminate();
- doNotTryExpandShortFileName = false;
- bool r = TryExpandShortFileName();
- // Clean up changes made to the newBuffer.
- Append(Path.DirectorySeparatorChar);
- if (Length + lenSavedName >= Path.MaxPath)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
- String.wstrcpy(m_arrayPtr + Length, savedName, lenSavedName);
- Length = Length + lenSavedName;
-
- }
- else {
- String savedName = m_sb.ToString(lastSlash + 1, lenSavedName);
- Length = lastSlash;
- doNotTryExpandShortFileName = false;
- bool r = TryExpandShortFileName();
- // Clean up changes made to the newBuffer.
- Append(Path.DirectorySeparatorChar);
- if (Length + lenSavedName >= m_maxPath)
- throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
- m_sb.Append(savedName);
- }
- }
-
- [System.Security.SecurityCritical]
- internal unsafe bool OrdinalStartsWith(String compareTo, bool ignoreCase) {
- if (Length < compareTo.Length)
- return false;
-
- if (useStackAlloc) {
- NullTerminate();
- if (ignoreCase) {
- String s = new String(m_arrayPtr, 0, compareTo.Length);
- return compareTo.Equals(s, StringComparison.OrdinalIgnoreCase);
- }
- else {
- for (int i = 0; i < compareTo.Length; i++) {
- if (m_arrayPtr[i] != compareTo[i]) {
- return false;
- }
- }
- return true;
- }
- }
- else {
- if (ignoreCase) {
- return m_sb.ToString().StartsWith(compareTo, StringComparison.OrdinalIgnoreCase);
- }
- else {
- return m_sb.ToString().StartsWith(compareTo, StringComparison.Ordinal);
- }
- }
- }
-
- [System.Security.SecurityCritical]
- private unsafe bool OrdinalEqualsStackAlloc(String compareTo)
- {
- Contract.Requires(useStackAlloc, "Currently no efficient implementation for StringBuilder.OrdinalEquals(String)");
-
- if (Length != compareTo.Length) {
- return false;
- }
-
- for (int i = 0; i < compareTo.Length; i++) {
- if (m_arrayPtr[i] != compareTo[i]) {
- return false;
- }
- }
-
- return true;
- }
-
- [System.Security.SecuritySafeCritical]
- public override String ToString() {
- if (useStackAlloc) {
- return new String(m_arrayPtr, 0, Length);
- }
- else {
- return m_sb.ToString();
- }
- }
-
- [System.Security.SecuritySafeCritical]
- internal String ToStringOrExisting(String existingString)
- {
- if (useStackAlloc) {
- return OrdinalEqualsStackAlloc(existingString) ?
- existingString :
- new String(m_arrayPtr, 0, Length);
- }
- else {
- string newString = m_sb.ToString(); // currently no good StringBuilder.OrdinalEquals(string)
- return String.Equals(newString, existingString, StringComparison.Ordinal) ?
- existingString :
- newString;
- }
- }
-
- [System.Security.SecurityCritical]
- private unsafe char* UnsafeGetArrayPtr() {
- Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder");
- return m_arrayPtr;
- }
-
- private StringBuilder GetStringBuilder() {
- Contract.Requires(!useStackAlloc, "This should never be called for PathHelpers that wrap a stackalloc'd buffer");
- return m_sb;
- }
-
- [System.Security.SecurityCritical]
- private unsafe void NullTerminate() {
- Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder");
- m_arrayPtr[m_length] = '\0';
- }
-
- }
-}
-#endif // FEATURE_PATHCOMPAT \ No newline at end of file
diff --git a/src/mscorlib/src/System/IO/PathInternal.cs b/src/mscorlib/src/System/IO/PathInternal.cs
deleted file mode 100644
index 9545905327..0000000000
--- a/src/mscorlib/src/System/IO/PathInternal.cs
+++ /dev/null
@@ -1,822 +0,0 @@
-// Copyright (c) Microsoft. All rights reserved.
-// Licensed under the MIT license. See LICENSE file in the project root for full license information.
-
-using Microsoft.Win32;
-using System;
-using System.Diagnostics.Contracts;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Security;
-using System.Text;
-
-namespace System.IO
-{
- /// <summary>Contains internal path helpers that are shared between many projects.</summary>
- internal static class PathInternal
- {
- internal const string ExtendedPathPrefix = @"\\?\";
- internal const string UncPathPrefix = @"\\";
- internal const string UncExtendedPrefixToInsert = @"?\UNC\";
- internal const string UncExtendedPathPrefix = @"\\?\UNC\";
- internal const string DevicePathPrefix = @"\\.\";
- // \\?\, \\.\, \??\
- internal const int DevicePrefixLength = 4;
- // \\
- internal const int UncPrefixLength = 2;
- // \\?\UNC\, \\.\UNC\
- internal const int UncExtendedPrefixLength = 8;
-#if !PLATFORM_UNIX
- internal const int MaxShortPath = 260;
- internal const int MaxShortDirectoryPath = 248;
-#else
- internal const int MaxShortPath = 1024;
- internal const int MaxShortDirectoryPath = MaxShortPath;
-#endif
-
- // Windows is limited in long paths by the max size of its internal representation of a unicode string.
- // UNICODE_STRING has a max length of USHORT in _bytes_ without a trailing null.
- // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx
- internal const int MaxLongPath = short.MaxValue;
- internal static readonly int MaxComponentLength = 255;
-
-#if !PLATFORM_UNIX
- internal static readonly char[] InvalidPathChars =
- {
- '\"', '<', '>', '|', '\0',
- (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
- (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
- (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
- (char)31
- };
-#else
- internal static readonly char[] InvalidPathChars = { '\0' };
-#endif
-
-
- /// <summary>
- /// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams.
- /// </summary>
- /// <returns>True if the path has an invalid volume separator.</returns>
- internal static bool HasInvalidVolumeSeparator(string path)
- {
- // Toss out paths with colons that aren't a valid drive specifier.
- // Cannot start with a colon and can only be of the form "C:" or "\\?\C:".
- // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
-
- // We don't care about skipping starting space for extended paths. Assume no knowledge of extended paths if we're forcing old path behavior.
- bool isExtended =
-#if FEATURE_PATHCOMPAT
- !AppContextSwitches.UseLegacyPathHandling &&
-#endif
- IsExtended(path);
- int startIndex = isExtended ? ExtendedPathPrefix.Length : PathStartSkip(path);
-
- // If we start with a colon
- if ((path.Length > startIndex && path[startIndex] == Path.VolumeSeparatorChar)
- // Or have an invalid drive letter and colon
- || (path.Length >= startIndex + 2 && path[startIndex + 1] == Path.VolumeSeparatorChar && !IsValidDriveChar(path[startIndex]))
- // Or have any colons beyond the drive colon
- || (path.Length > startIndex + 2 && path.IndexOf(Path.VolumeSeparatorChar, startIndex + 2) != -1))
- {
- return true;
- }
-
- return false;
- }
-
- /// <summary>
- /// Returns true if the given StringBuilder starts with the given value.
- /// </summary>
- /// <param name="value">The string to compare against the start of the StringBuilder.</param>
- internal static bool StartsWithOrdinal(StringBuilder builder, string value, bool ignoreCase = false)
- {
- if (value == null || builder.Length < value.Length)
- return false;
-
- if (ignoreCase)
- {
- for (int i = 0; i < value.Length; i++)
- if (char.ToUpperInvariant(builder[i]) != char.ToUpperInvariant(value[i])) return false;
- }
- else
- {
- for (int i = 0; i < value.Length; i++)
- if (builder[i] != value[i]) return false;
- }
-
- return true;
- }
-
- /// <summary>
- /// Returns true if the given character is a valid drive letter
- /// </summary>
- internal static bool IsValidDriveChar(char value)
- {
- return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z'));
- }
-
- /// <summary>
- /// Returns true if the path is too long
- /// </summary>
- internal static bool IsPathTooLong(string fullPath)
- {
- // We'll never know precisely what will fail as paths get changed internally in Windows and
- // may grow beyond / shrink below exceed MaxLongPath.
-#if FEATURE_PATHCOMPAT
- if (AppContextSwitches.BlockLongPaths)
- {
- // We allow paths of any length if extended (and not in compat mode)
- if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath))
- return fullPath.Length >= MaxShortPath;
- }
-#endif
-
- return fullPath.Length >= MaxLongPath;
- }
-
- /// <summary>
- /// Return true if any path segments are too long
- /// </summary>
- internal static bool AreSegmentsTooLong(string fullPath)
- {
- int length = fullPath.Length;
- int lastSeparator = 0;
-
- for (int i = 0; i < length; i++)
- {
- if (IsDirectorySeparator(fullPath[i]))
- {
- if (i - lastSeparator > MaxComponentLength)
- return true;
- lastSeparator = i;
- }
- }
-
- if (length - 1 - lastSeparator > MaxComponentLength)
- return true;
-
- return false;
- }
-
- /// <summary>
- /// Returns true if the directory is too long
- /// </summary>
- internal static bool IsDirectoryTooLong(string fullPath)
- {
-#if FEATURE_PATHCOMPAT
- if (AppContextSwitches.BlockLongPaths)
- {
- // We allow paths of any length if extended (and not in compat mode)
- if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath))
- return (fullPath.Length >= MaxShortDirectoryPath);
- }
-#endif
-
- return IsPathTooLong(fullPath);
- }
-
- /// <summary>
- /// Adds the extended path prefix (\\?\) if not relative or already a device path.
- /// </summary>
- internal static string EnsureExtendedPrefix(string path)
- {
- // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which
- // means adding to relative paths will prevent them from getting the appropriate current directory inserted.
-
- // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it
- // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly
- // in the future we wouldn't want normalization to come back and break existing code.
-
- // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this
- // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a
- // normalized base path.)
- if (IsPartiallyQualified(path) || IsDevice(path))
- return path;
-
- // Given \\server\share in longpath becomes \\?\UNC\server\share
- if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase))
- return path.Insert(2, UncExtendedPrefixToInsert);
-
- return ExtendedPathPrefix + path;
- }
-
- /// <summary>
- /// 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)
- /// </summary>
- internal static string EnsureExtendedPrefixOverMaxPath(string path)
- {
- if (path != null && path.Length >= MaxShortPath)
- {
- return EnsureExtendedPrefix(path);
- }
- else
- {
- return path;
- }
- }
-
- /// <summary>
- /// Removes the extended path prefix (\\?\) if present.
- /// </summary>
- internal static string RemoveExtendedPrefix(string path)
- {
- if (!IsExtended(path))
- return path;
-
- // Given \\?\UNC\server\share we return \\server\share
- if (IsExtendedUnc(path))
- return path.Remove(2, 6);
-
- return path.Substring(DevicePrefixLength);
- }
-
- /// <summary>
- /// Removes the extended path prefix (\\?\) if present.
- /// </summary>
- internal static StringBuilder RemoveExtendedPrefix(StringBuilder path)
- {
- if (!IsExtended(path))
- return path;
-
- // Given \\?\UNC\server\share we return \\server\share
- if (IsExtendedUnc(path))
- return path.Remove(2, 6);
-
- return path.Remove(0, DevicePrefixLength);
- }
-
- /// <summary>
- /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
- /// </summary>
- internal static bool IsDevice(string path)
- {
- // If the path begins with any two separators it will be recognized and normalized and prepped with
- // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
- return IsExtended(path)
- ||
- (
- path.Length >= DevicePrefixLength
- && IsDirectorySeparator(path[0])
- && IsDirectorySeparator(path[1])
- && (path[2] == '.' || path[2] == '?')
- && IsDirectorySeparator(path[3])
- );
- }
-
- /// <summary>
- /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
- /// </summary>
- internal static bool IsDevice(StringBuffer path)
- {
- // If the path begins with any two separators it will be recognized and normalized and prepped with
- // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
- return IsExtended(path)
- ||
- (
- path.Length >= DevicePrefixLength
- && IsDirectorySeparator(path[0])
- && IsDirectorySeparator(path[1])
- && (path[2] == '.' || path[2] == '?')
- && IsDirectorySeparator(path[3])
- );
- }
-
- /// <summary>
- /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
- /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
- /// and path length checks.
- /// </summary>
- internal static bool IsExtended(string path)
- {
- // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
- // Skipping of normalization will *only* occur if back slashes ('\') are used.
- return path.Length >= DevicePrefixLength
- && path[0] == '\\'
- && (path[1] == '\\' || path[1] == '?')
- && path[2] == '?'
- && path[3] == '\\';
- }
-
- /// <summary>
- /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
- /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
- /// and path length checks.
- /// </summary>
- internal static bool IsExtended(StringBuilder path)
- {
- // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
- // Skipping of normalization will *only* occur if back slashes ('\') are used.
- return path.Length >= DevicePrefixLength
- && path[0] == '\\'
- && (path[1] == '\\' || path[1] == '?')
- && path[2] == '?'
- && path[3] == '\\';
- }
-
- /// <summary>
- /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the
- /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization
- /// and path length checks.
- /// </summary>
- internal static bool IsExtended(StringBuffer path)
- {
- // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
- // Skipping of normalization will *only* occur if back slashes ('\') are used.
- return path.Length >= DevicePrefixLength
- && path[0] == '\\'
- && (path[1] == '\\' || path[1] == '?')
- && path[2] == '?'
- && path[3] == '\\';
- }
-
- /// <summary>
- /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\)
- /// </summary>
- internal static bool IsExtendedUnc(string path)
- {
- return path.Length >= UncExtendedPathPrefix.Length
- && IsExtended(path)
- && char.ToUpper(path[4]) == 'U'
- && char.ToUpper(path[5]) == 'N'
- && char.ToUpper(path[6]) == 'C'
- && path[7] == '\\';
- }
-
- /// <summary>
- /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\)
- /// </summary>
- internal static bool IsExtendedUnc(StringBuilder path)
- {
- return path.Length >= UncExtendedPathPrefix.Length
- && IsExtended(path)
- && char.ToUpper(path[4]) == 'U'
- && char.ToUpper(path[5]) == 'N'
- && char.ToUpper(path[6]) == 'C'
- && path[7] == '\\';
- }
-
- /// <summary>
- /// Returns a value indicating if the given path contains invalid characters (", &lt;, &gt;, |
- /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31).
- /// Does not check for wild card characters ? and *.
- ///
- /// Will not check if the path is a device path and not in Legacy mode as many of these
- /// characters are valid for devices (pipes for example).
- /// </summary>
- internal static bool HasIllegalCharacters(string path, bool checkAdditional = false)
- {
- if (
-#if FEATURE_PATHCOMPAT
- !AppContextSwitches.UseLegacyPathHandling &&
-#endif
- IsDevice(path))
- {
- return false;
- }
-
- return AnyPathHasIllegalCharacters(path, checkAdditional: checkAdditional);
- }
-
- /// <summary>
- /// Version of HasIllegalCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care
- /// about proper device path handling.
- /// </summary>
- internal static bool AnyPathHasIllegalCharacters(string path, bool checkAdditional = false)
- {
- return path.IndexOfAny(InvalidPathChars) >= 0
-#if !PLATFORM_UNIX
- || (checkAdditional && AnyPathHasWildCardCharacters(path))
-#endif
- ;
- }
-
- /// <summary>
- /// Check for ? and *.
- /// </summary>
- internal static bool HasWildCardCharacters(string path)
- {
- // Question mark is part of some device paths
- int startIndex =
-#if FEATURE_PATHCOMPAT
- AppContextSwitches.UseLegacyPathHandling ? 0 :
-#endif
- IsDevice(path) ? ExtendedPathPrefix.Length : 0;
- return AnyPathHasWildCardCharacters(path, startIndex: startIndex);
- }
-
- /// <summary>
- /// Version of HasWildCardCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care
- /// about proper device path handling.
- /// </summary>
- internal static bool AnyPathHasWildCardCharacters(string path, int startIndex = 0)
- {
- char currentChar;
- for (int i = startIndex; i < path.Length; i++)
- {
- currentChar = path[i];
- if (currentChar == '*' || currentChar == '?') return true;
- }
- return false;
- }
-
- /// <summary>
- /// Gets the length of the root of the path (drive, share, etc.).
- /// </summary>
- [System.Security.SecuritySafeCritical]
- internal unsafe static int GetRootLength(string path)
- {
- fixed (char* value = path)
- {
- return (int)GetRootLength(value, (ulong)path.Length);
- }
- }
-
- /// <summary>
- /// Gets the length of the root of the path (drive, share, etc.).
- /// </summary>
- [System.Security.SecuritySafeCritical]
- internal unsafe static uint GetRootLength(StringBuffer path)
- {
- if (path.Length == 0) return 0;
- return GetRootLength(path.CharPointer, path.Length);
- }
-
- [System.Security.SecurityCritical]
- private unsafe static uint GetRootLength(char* path, ulong pathLength)
- {
- uint i = 0;
-
-#if PLATFORM_UNIX
- if (pathLength >= 1 && (IsDirectorySeparator(path[0])))
- i = 1;
-#else
- uint volumeSeparatorLength = 2; // Length to the colon "C:"
- uint uncRootLength = 2; // Length to the start of the server name "\\"
-
- bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix);
- bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix);
- if (extendedSyntax)
- {
- // Shift the position we look for the root from to account for the extended prefix
- if (extendedUncSyntax)
- {
- // "\\" -> "\\?\UNC\"
- uncRootLength = (uint)UncExtendedPathPrefix.Length;
- }
- else
- {
- // "C:" -> "\\?\C:"
- volumeSeparatorLength += (uint)ExtendedPathPrefix.Length;
- }
- }
-
- if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0]))
- {
- // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo")
-
- i = 1; // Drive rooted (\foo) is one character
- if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1])))
- {
- // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most
- // (e.g. to \\?\UNC\Server\Share or \\Server\Share\)
- i = uncRootLength;
- int n = 2; // Maximum separators to skip
- while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++;
- }
- }
- else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar)
- {
- // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:)
- // If the colon is followed by a directory separator, move past it
- i = volumeSeparatorLength;
- if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++;
- }
-#endif // !PLATFORM_UNIX
- return i;
- }
-
- [System.Security.SecurityCritical]
- private unsafe static bool StartsWithOrdinal(char* source, ulong sourceLength, string value)
- {
- if (sourceLength < (ulong)value.Length) return false;
- for (int i = 0; i < value.Length; i++)
- {
- if (value[i] != source[i]) return false;
- }
- return true;
- }
-
- /// <summary>
- /// Returns true if the path specified is relative to the current drive or working directory.
- /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
- /// validation of the path (URIs will be returned as relative as a result).
- /// </summary>
- /// <remarks>
- /// Handles paths that use the alternate directory separator. It is a frequent mistake to
- /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
- /// "C:a" is drive relative- meaning that it will be resolved against the current directory
- /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
- /// will not be used to modify the path).
- /// </remarks>
- internal static bool IsPartiallyQualified(string path)
- {
-#if PLATFORM_UNIX
- return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar);
-#else
- if (path.Length < 2)
- {
- // It isn't fixed, it must be relative. There is no way to specify a fixed
- // path with one character (or less).
- return true;
- }
-
- if (IsDirectorySeparator(path[0]))
- {
- // There is no valid way to specify a relative path with two initial slashes or
- // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
- return !(path[1] == '?' || IsDirectorySeparator(path[1]));
- }
-
- // The only way to specify a fixed path that doesn't begin with two slashes
- // is the drive, colon, slash format- i.e. C:\
- return !((path.Length >= 3)
- && (path[1] == Path.VolumeSeparatorChar)
- && IsDirectorySeparator(path[2])
- // To match old behavior we'll check the drive character for validity as the path is technically
- // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
- && IsValidDriveChar(path[0]));
-#endif // !PLATFORM_UNIX
- }
-
- /// <summary>
- /// Returns true if the path specified is relative to the current drive or working directory.
- /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
- /// validation of the path (URIs will be returned as relative as a result).
- /// </summary>
- /// <remarks>
- /// Handles paths that use the alternate directory separator. It is a frequent mistake to
- /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
- /// "C:a" is drive relative- meaning that it will be resolved against the current directory
- /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
- /// will not be used to modify the path).
- /// </remarks>
- internal static bool IsPartiallyQualified(StringBuffer path)
- {
-#if PLATFORM_UNIX
- return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar);
-#else
- if (path.Length < 2)
- {
- // It isn't fixed, it must be relative. There is no way to specify a fixed
- // path with one character (or less).
- return true;
- }
-
- if (IsDirectorySeparator(path[0]))
- {
- // There is no valid way to specify a relative path with two initial slashes or
- // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
- return !(path[1] == '?' || IsDirectorySeparator(path[1]));
- }
-
- // The only way to specify a fixed path that doesn't begin with two slashes
- // is the drive, colon, slash format- i.e. C:\
- return !((path.Length >= 3)
- && (path[1] == Path.VolumeSeparatorChar)
- && IsDirectorySeparator(path[2])
- // To match old behavior we'll check the drive character for validity as the path is technically
- // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream.
- && IsValidDriveChar(path[0]));
-#endif // !PLATFORM_UNIX
- }
-
- /// <summary>
- /// On Windows, returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator.
- /// (examples are " C:", " \")
- /// This is a legacy behavior of Path.GetFullPath().
- /// </summary>
- /// <remarks>
- /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip.
- /// </remarks>
- internal static int PathStartSkip(string path)
- {
-#if !PLATFORM_UNIX
- int startIndex = 0;
- while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
-
- if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex]))
- || (startIndex + 1 < path.Length && path[startIndex + 1] == Path.VolumeSeparatorChar && IsValidDriveChar(path[startIndex])))
- {
- // Go ahead and skip spaces as we're either " C:" or " \"
- return startIndex;
- }
-#endif
-
- return 0;
- }
-
- /// <summary>
- /// True if the given character is a directory separator.
- /// </summary>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static bool IsDirectorySeparator(char c)
- {
- return c == Path.DirectorySeparatorChar
-#if !PLATFORM_UNIX
- || c == Path.AltDirectorySeparatorChar
-#endif
- ;
- }
-
- /// <summary>
- /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present.
- /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip).
- ///
- /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false.
- /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as
- /// such can't be used here (and is overkill for our uses).
- ///
- /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments.
- /// </summary>
- /// <remarks>
- /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do
- /// not need trimming of trailing whitespace here.
- ///
- /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization.
- ///
- /// For legacy desktop behavior with ExpandShortPaths:
- /// - It has no impact on GetPathRoot() so doesn't need consideration.
- /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share).
- ///
- /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was
- /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you
- /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by
- /// this undocumented behavior.
- /// </remarks>
- internal static string NormalizeDirectorySeparators(string path)
- {
- if (string.IsNullOrEmpty(path)) return path;
-
- char current;
- int start = PathStartSkip(path);
-
- if (start == 0)
- {
- // Make a pass to see if we need to normalize so we can potentially skip allocating
- bool normalized = true;
-
- for (int i = 0; i < path.Length; i++)
- {
- current = path[i];
- if (IsDirectorySeparator(current)
- && (current != Path.DirectorySeparatorChar
-#if !PLATFORM_UNIX
- // Check for sequential separators past the first position (we need to keep initial two for UNC/extended)
- || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))
-#endif
- ))
- {
- normalized = false;
- break;
- }
- }
-
- if (normalized) return path;
- }
-
- StringBuilder builder = StringBuilderCache.Acquire(path.Length);
-
-#if !PLATFORM_UNIX
- // On Windows we always keep the first separator, even if the next is a separator (we need to keep initial two for UNC/extended)
- if (IsDirectorySeparator(path[start]))
- {
- start++;
- builder.Append(Path.DirectorySeparatorChar);
- }
-#endif
-
- for (int i = start; i < path.Length; i++)
- {
- current = path[i];
-
- // If we have a separator
- if (IsDirectorySeparator(current))
- {
- // If the next is a separator, skip adding this
- if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1]))
- {
- continue;
- }
-
- // Ensure it is the primary separator
- current = Path.DirectorySeparatorChar;
- }
-
- builder.Append(current);
- }
-
- return StringBuilderCache.GetStringAndRelease(builder);
- }
-
-#if PLATFORM_UNIX
- // We rely on Windows to remove relative segments on Windows. This would need to be updated to
- // handle the proper rooting on Windows if we for some reason need it.
-
- /// <summary>
- /// Try to remove relative segments from the given path (without combining with a root).
- /// </summary>
- /// <param name="skip">Skip the specified number of characters before evaluating.</param>
- internal static string RemoveRelativeSegments(string path, int skip = 0)
- {
- bool flippedSeparator = false;
-
- // Remove "//", "/./", and "/../" from the path by copying each character to the output,
- // except the ones we're removing, such that the builder contains the normalized path
- // at the end.
- var sb = StringBuilderCache.Acquire(path.Length);
- if (skip > 0)
- {
- sb.Append(path, 0, skip);
- }
-
- int componentCharCount = 0;
- for (int i = skip; i < path.Length; i++)
- {
- char c = path[i];
-
- if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length)
- {
- componentCharCount = 0;
-
- // Skip this character if it's a directory separator and if the next character is, too,
- // e.g. "parent//child" => "parent/child"
- if (PathInternal.IsDirectorySeparator(path[i + 1]))
- {
- continue;
- }
-
- // Skip this character and the next if it's referring to the current directory,
- // e.g. "parent/./child" =? "parent/child"
- if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) &&
- path[i + 1] == '.')
- {
- i++;
- continue;
- }
-
- // Skip this character and the next two if it's referring to the parent directory,
- // e.g. "parent/child/../grandchild" => "parent/grandchild"
- if (i + 2 < path.Length &&
- (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) &&
- path[i + 1] == '.' && path[i + 2] == '.')
- {
- // Unwind back to the last slash (and if there isn't one, clear out everything).
- int s;
- for (s = sb.Length - 1; s >= 0; s--)
- {
- if (PathInternal.IsDirectorySeparator(sb[s]))
- {
- sb.Length = s;
- break;
- }
- }
- if (s < 0)
- {
- sb.Length = 0;
- }
-
- i += 2;
- continue;
- }
- }
-
- if (++componentCharCount > PathInternal.MaxComponentLength)
- {
- throw new PathTooLongException();
- }
-
- // Normalize the directory separator if needed
- if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar)
- {
- c = Path.DirectorySeparatorChar;
- flippedSeparator = true;
- }
-
- sb.Append(c);
- }
-
- if (flippedSeparator || sb.Length != path.Length)
- {
- return StringBuilderCache.GetStringAndRelease(sb);
- }
- else
- {
- // We haven't changed the source path, return the original
- StringBuilderCache.Release(sb);
- return path;
- }
- }
-#endif // PLATFORM_UNIX
- }
-} \ No newline at end of file
diff --git a/src/mscorlib/src/System/IO/__Error.cs b/src/mscorlib/src/System/IO/__Error.cs
index a31d9657e8..c67bc964f0 100644
--- a/src/mscorlib/src/System/IO/__Error.cs
+++ b/src/mscorlib/src/System/IO/__Error.cs
@@ -85,7 +85,7 @@ namespace System.IO {
bool isFullyQualified = false;
if (path.Length < 2)
return path;
- if (Path.IsDirectorySeparator(path[0]) && Path.IsDirectorySeparator(path[1]))
+ if (PathInternal.IsDirectorySeparator(path[0]) && PathInternal.IsDirectorySeparator(path[1]))
isFullyQualified = true;
else if (path[1] == Path.VolumeSeparatorChar) {
isFullyQualified = true;
@@ -116,7 +116,7 @@ namespace System.IO {
}
if (!safeToReturn) {
- if (Path.IsDirectorySeparator(path[path.Length - 1]))
+ if (PathInternal.IsDirectorySeparator(path[path.Length - 1]))
path = Environment.GetResourceString("IO.IO_NoPermissionToDirectoryName");
else
path = Path.GetFileName(path);
diff --git a/src/mscorlib/src/System/Reflection/Assembly.cs b/src/mscorlib/src/System/Reflection/Assembly.cs
index 04f7faef97..dfa74c341c 100644
--- a/src/mscorlib/src/System/Reflection/Assembly.cs
+++ b/src/mscorlib/src/System/Reflection/Assembly.cs
@@ -114,7 +114,7 @@ namespace System.Reflection
{
if(assemblyFile == null)
throw new ArgumentNullException(nameof(assemblyFile));
- string fullPath = Path.GetFullPathInternal(assemblyFile);
+ string fullPath = Path.GetFullPath(assemblyFile);
return AssemblyLoadContext.Default.LoadFromAssemblyPath(fullPath);
}
#else
@@ -584,15 +584,15 @@ namespace System.Reflection
if(path == null)
throw new ArgumentNullException(nameof(path));
- if (Path.IsRelative(path))
+ if (PathInternal.IsPartiallyQualified(path))
{
throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(path));
}
- string normalizedPath = Path.GetFullPathInternal(path);
+ string normalizedPath = Path.GetFullPath(path);
lock(s_loadfile)
- {
+ {
if(s_loadfile.TryGetValue(normalizedPath, out result))
return result;
AssemblyLoadContext alc = new IndividualAssemblyLoadContext();
@@ -2396,10 +2396,10 @@ namespace System.Reflection
else if ((len > 2) && (codebase[0] == '\\') && (codebase[1] == '\\'))
return "file://" + codebase;
else
- return "file:///" + Path.GetFullPathInternal( codebase );
+ return "file:///" + Path.GetFullPath(codebase);
#else
else
- return "file://" + Path.GetFullPathInternal( codebase );
+ return "file://" + Path.GetFullPath(codebase);
#endif // !PLATFORM_UNIX
}
diff --git a/src/mscorlib/src/System/Reflection/AssemblyName.cs b/src/mscorlib/src/System/Reflection/AssemblyName.cs
index c45a4619ea..abcbd8939d 100644
--- a/src/mscorlib/src/System/Reflection/AssemblyName.cs
+++ b/src/mscorlib/src/System/Reflection/AssemblyName.cs
@@ -199,7 +199,7 @@ namespace System.Reflection {
// Assembly.GetNameInternal() will not demand path discovery
// permission, so do that first.
- String fullPath = Path.GetFullPathInternal(assemblyFile);
+ string fullPath = Path.GetFullPath(assemblyFile);
new FileIOPermission( FileIOPermissionAccess.PathDiscovery, fullPath ).Demand();
return nGetFileInformation(fullPath);
}
diff --git a/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs b/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs
index d2a95b0626..c474b703f4 100644
--- a/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs
+++ b/src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs
@@ -589,7 +589,7 @@ namespace System.Reflection.Emit
}
#endregion
-
+
#region Module Overrides
// m_internalModuleBuilder is null iff this is a "internal" ModuleBuilder
@@ -962,7 +962,7 @@ namespace System.Reflection.Emit
if (ContainingAssemblyBuilder.m_assemblyData.m_strDir != null)
{
fullyQualifiedName = Path.Combine(ContainingAssemblyBuilder.m_assemblyData.m_strDir, fullyQualifiedName);
- fullyQualifiedName = Path.UnsafeGetFullPath(fullyQualifiedName);
+ fullyQualifiedName = Path.GetFullPath(fullyQualifiedName);
}
if (ContainingAssemblyBuilder.m_assemblyData.m_strDir != null && fullyQualifiedName != null)
diff --git a/src/mscorlib/src/System/Reflection/Module.cs b/src/mscorlib/src/System/Reflection/Module.cs
index 6d492d8e74..e19a569cbd 100644
--- a/src/mscorlib/src/System/Reflection/Module.cs
+++ b/src/mscorlib/src/System/Reflection/Module.cs
@@ -1082,7 +1082,7 @@ namespace System.Reflection
if (fullyQualifiedName != null) {
bool checkPermission = true;
try {
- Path.GetFullPathInternal(fullyQualifiedName);
+ Path.GetFullPath(fullyQualifiedName);
}
catch(ArgumentException) {
checkPermission = false;
diff --git a/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs b/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs
index fd896a6b42..37ac3096ab 100644
--- a/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs
+++ b/src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs
@@ -104,7 +104,7 @@ namespace System.Runtime.Loader
throw new ArgumentNullException(nameof(assemblyPath));
}
- if (Path.IsRelative(assemblyPath))
+ if (PathInternal.IsPartiallyQualified(assemblyPath))
{
throw new ArgumentException( Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(assemblyPath));
}
@@ -121,12 +121,12 @@ namespace System.Runtime.Loader
throw new ArgumentNullException(nameof(nativeImagePath));
}
- if (Path.IsRelative(nativeImagePath))
+ if (PathInternal.IsPartiallyQualified(nativeImagePath))
{
throw new ArgumentException( Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(nativeImagePath));
}
- if (assemblyPath != null && Path.IsRelative(assemblyPath))
+ if (assemblyPath != null && PathInternal.IsPartiallyQualified(assemblyPath))
{
throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(assemblyPath));
}
@@ -308,7 +308,7 @@ namespace System.Runtime.Loader
{
throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"), nameof(unmanagedDllPath));
}
- if (Path.IsRelative(unmanagedDllPath))
+ if (PathInternal.IsPartiallyQualified(unmanagedDllPath))
{
throw new ArgumentException(Environment.GetResourceString("Argument_AbsolutePathRequired"), nameof(unmanagedDllPath));
}
@@ -391,8 +391,8 @@ namespace System.Runtime.Loader
{
throw new ArgumentNullException(nameof(assemblyPath));
}
-
- String fullPath = Path.GetFullPathInternal(assemblyPath);
+
+ string fullPath = Path.GetFullPath(assemblyPath);
return nGetFileInformation(fullPath);
}
diff --git a/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs b/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs
index b4d4141f82..3ae193b96f 100644
--- a/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs
+++ b/src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs
@@ -558,7 +558,7 @@ namespace System.Security.Permissions {
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars"));
if (!onlyCheckExtras)
- Path.CheckInvalidPathChars(str[i]);
+ PathInternal.CheckInvalidPathChars(str[i]);
}
#else
// There are no "extras" on Unix
@@ -567,7 +567,7 @@ namespace System.Security.Permissions {
for (int i = 0; i < str.Length; ++i)
{
- Path.CheckInvalidPathChars(str[i]);
+ PathInternal.CheckInvalidPathChars(str[i]);
}
#endif
}
@@ -1090,15 +1090,18 @@ namespace System.Security.Permissions {
// These checks are done via CheckIllegalCharacters() and StringExpressionSet in AddPathList() above.
//
// We have to check the beginning as some paths may be passed in as path + @"\.", which will be normalized away.
+#if !FEATURE_CORECLR
BCLDebug.Assert(
fullPath.StartsWith(Path.NormalizePath(fullPath, fullCheck: false), StringComparison.OrdinalIgnoreCase),
string.Format("path isn't normalized: {0}", fullPath));
+#endif
+#if !PLATFORM_UNIX
// Checking for colon / invalid characters on device paths blocks legitimate access to objects such as named pipes.
if (
-#if FEATURE_PATHCOMPAT
+ #if FEATURE_PATHCOMPAT
AppContextSwitches.UseLegacyPathHandling ||
-#endif
+ #endif
!PathInternal.IsDevice(fullPath))
{
// GetFullPath already checks normal invalid path characters. We need to just check additional (wildcard) characters here.
@@ -1113,6 +1116,7 @@ namespace System.Security.Permissions {
throw new NotSupportedException(Environment.GetResourceString("Argument_PathFormatNotSupported"));
}
}
+#endif // !PLATFORM_UNIX
}
}
diff --git a/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs b/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs
index 03d6998c6d..c12ca2ff6f 100644
--- a/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs
+++ b/src/mscorlib/src/System/Security/Util/StringExpressionSet.cs
@@ -165,7 +165,7 @@ namespace System.Security.Util {
{
if (m_throwOnRelative)
{
- if (Path.IsRelative(temp))
+ if (PathInternal.IsPartiallyQualified(temp))
{
throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
}
@@ -325,7 +325,7 @@ namespace System.Security.Util {
{
if (m_throwOnRelative)
{
- if (Path.IsRelative(temp))
+ if (PathInternal.IsPartiallyQualified(temp))
{
throw new ArgumentException( Environment.GetResourceString( "Argument_AbsolutePathRequired" ) );
}
@@ -742,7 +742,7 @@ namespace System.Security.Util {
{
if (needFullPath)
{
- string newPath = Path.GetFullPathInternal(path);
+ string newPath = Path.GetFullPath(path);
if (path.EndsWith(m_directorySeparator + ".", StringComparison.Ordinal))
{
if (newPath.EndsWith(m_directorySeparator))
diff --git a/src/mscorlib/src/System/Security/Util/URLString.cs b/src/mscorlib/src/System/Security/Util/URLString.cs
index 997cb2cf12..bc05104e51 100644
--- a/src/mscorlib/src/System/Security/Util/URLString.cs
+++ b/src/mscorlib/src/System/Security/Util/URLString.cs
@@ -484,10 +484,14 @@ namespace System.Security.Util {
private static void CheckPathTooLong(StringBuilder path)
{
if (path.Length >= (
-#if FEATURE_PATHCOMPAT
+#if PLATFORM_UNIX
+ Interop.Sys.MaxPath))
+#else
+ #if FEATURE_PATHCOMPAT
AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath :
-#endif
+ #endif
PathInternal.MaxLongPath))
+#endif
{
throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong"));
}
@@ -513,7 +517,7 @@ namespace System.Security.Util {
// file:/home/johndoe/here
// file:../johndoe/here
// file:~/johndoe/here
- String temp = url;
+ String temp = url;
int nbSlashes = 0;
while(nbSlashes<temp.Length && '/'==temp[nbSlashes])
nbSlashes++;
@@ -533,7 +537,7 @@ namespace System.Security.Util {
{
String temp = url;
-#if !PLATFORM_UNIX
+#if !PLATFORM_UNIX
int index = temp.IndexOf( '/');
if (index != -1 &&
@@ -651,7 +655,7 @@ namespace System.Security.Util {
}
else
{
-#if !PLATFORM_UNIX
+#if !PLATFORM_UNIX
String site = temp.Substring( 0, index );
m_localSite = null;
m_siteString = new SiteString( site );
diff --git a/src/mscorlib/src/System/Threading/Mutex.cs b/src/mscorlib/src/System/Threading/Mutex.cs
index 434ab37b6d..98789d9301 100644
--- a/src/mscorlib/src/System/Threading/Mutex.cs
+++ b/src/mscorlib/src/System/Threading/Mutex.cs
@@ -182,7 +182,7 @@ namespace System.Threading
#if PLATFORM_UNIX
case Win32Native.ERROR_FILENAME_EXCED_RANGE:
// On Unix, length validation is done by CoreCLR's PAL after converting to utf-8
- throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPathComponentLength), "name");
+ throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", PathInternal.MaxComponentLength), "name");
#endif
case Win32Native.ERROR_INVALID_HANDLE:
@@ -357,7 +357,7 @@ namespace System.Threading
if (name != null && errorCode == Win32Native.ERROR_FILENAME_EXCED_RANGE)
{
// On Unix, length validation is done by CoreCLR's PAL after converting to utf-8
- throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", Path.MaxPathComponentLength), nameof(name));
+ throw new ArgumentException(Environment.GetResourceString("Argument_WaitHandleNameTooLong", PathInternal.MaxComponentLength), nameof(name));
}
#endif
diff --git a/src/mscorlib/src/mscorlib.txt b/src/mscorlib/src/mscorlib.txt
index 7574417107..e3904efb49 100644
--- a/src/mscorlib/src/mscorlib.txt
+++ b/src/mscorlib/src/mscorlib.txt
@@ -1277,6 +1277,7 @@ InvalidOperation_ClaimCannotBeRemoved = The Claim '{0}' was not able to be remov
InvalidOperationException_ActorGraphCircular = Actor cannot be set so that circular directed graph will exist chaining the subjects together.
InvalidOperation_AsyncIOInProgress = The stream is currently in use by a previous operation on the stream.
InvalidOperation_APIInvalidForCurrentContext = The API '{0}' cannot be used on the current platform. See http://go.microsoft.com/fwlink/?LinkId=248273 for more information.
+InvalidOperation_Cryptography = Unable to use cryptographic functionality.
; InvalidProgramException
InvalidProgram_Default = Common Language Runtime detected an invalid program.