Use CoreFX Path code (#8132)
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>
Thu, 17 Nov 2016 21:51:43 +0000 (13:51 -0800)
committerGitHub <noreply@github.com>
Thu, 17 Nov 2016 21:51:43 +0000 (13:51 -0800)
* Use CoreFX Path code

This is to address consistency and perf issues by moving the Path
code down to CoreCLR. It is more of an issue with Windows as
normalization requires complicated logic and costly platform calls.

* Delete unused files and avoid Unix crypto lib

* Address feedback, fix bug

Remove Unix crypto files and ifdef out usages completely.
Fix issue with static initialization of PathInternal.

* Use TriState to initialize case sensitivity

54 files changed:
clr.coreclr.props
src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetCwd.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Unix/System.Native/Interop.GetUnixName.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Unix/System.Native/Interop.MksTemps.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Unix/System.Native/Interop.PathConf.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.BCryptGenRandom.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Windows/BCrypt/Interop.NTSTATUS.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetFullPathNameW.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetLongPathNameW.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempFileNameW.cs [new file with mode: 0644]
src/mscorlib/corefx/Interop/Windows/mincore/Interop.GetTempPathW.cs [new file with mode: 0644]
src/mscorlib/corefx/SR.cs
src/mscorlib/corefx/System/IO/Path.Unix.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/Path.Win32.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/Path.Windows.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/Path.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/PathHelper.Windows.cs [moved from src/mscorlib/src/System/IO/LongPathHelper.cs with 66% similarity]
src/mscorlib/corefx/System/IO/PathInternal.CaseSensitivity.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/PathInternal.Unix.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/PathInternal.Windows.cs [new file with mode: 0644]
src/mscorlib/corefx/System/IO/PathInternal.cs [new file with mode: 0644]
src/mscorlib/corefx/System/Runtime/InteropServices/NativeBuffer.cs [new file with mode: 0644]
src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandle.cs [new file with mode: 0644]
src/mscorlib/corefx/System/Runtime/InteropServices/SafeHeapHandleCache.cs [new file with mode: 0644]
src/mscorlib/corefx/System/Runtime/InteropServices/StringBuffer.cs [new file with mode: 0644]
src/mscorlib/mscorlib.shared.sources.props
src/mscorlib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs [deleted file]
src/mscorlib/src/System/AppDomain.cs
src/mscorlib/src/System/AppDomainSetup.cs
src/mscorlib/src/System/CfgParser.cs
src/mscorlib/src/System/IO/Directory.cs
src/mscorlib/src/System/IO/DirectoryInfo.cs
src/mscorlib/src/System/IO/DriveInfo.cs
src/mscorlib/src/System/IO/File.cs
src/mscorlib/src/System/IO/FileInfo.cs
src/mscorlib/src/System/IO/FileSecurityState.cs
src/mscorlib/src/System/IO/FileStream.cs [deleted file]
src/mscorlib/src/System/IO/FileSystemEnumerable.cs
src/mscorlib/src/System/IO/FileSystemInfo.cs
src/mscorlib/src/System/IO/Path.cs [deleted file]
src/mscorlib/src/System/IO/PathHelper.cs [deleted file]
src/mscorlib/src/System/IO/PathInternal.cs [deleted file]
src/mscorlib/src/System/IO/__Error.cs
src/mscorlib/src/System/Reflection/Assembly.cs
src/mscorlib/src/System/Reflection/AssemblyName.cs
src/mscorlib/src/System/Reflection/Emit/ModuleBuilder.cs
src/mscorlib/src/System/Reflection/Module.cs
src/mscorlib/src/System/Runtime/Loader/AssemblyLoadContext.cs
src/mscorlib/src/System/Security/Permissions/FileIOPermission.cs
src/mscorlib/src/System/Security/Util/StringExpressionSet.cs
src/mscorlib/src/System/Security/Util/URLString.cs
src/mscorlib/src/System/Threading/Mutex.cs
src/mscorlib/src/mscorlib.txt

index 8ff0f0b..516f4ea 100644 (file)
@@ -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 (file)
index 0000000..724e342
--- /dev/null
@@ -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 (file)
index 0000000..33664c4
--- /dev/null
@@ -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 (file)
index 0000000..b8694d9
--- /dev/null
@@ -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 (file)
index 0000000..4a1fcf6
--- /dev/null
@@ -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 (file)
index 0000000..d2ce413
--- /dev/null
@@ -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 (file)
index 0000000..49d674f
--- /dev/null
@@ -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 (file)
index 0000000..a34cc33
--- /dev/null
@@ -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 (file)
index 0000000..d50db66
--- /dev/null
@@ -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 (file)
index 0000000..f06d11b
--- /dev/null
@@ -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 (file)
index 0000000..0ccc27c
--- /dev/null
@@ -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);
+    }
+}
index d234c9d..d820613 100644 (file)
@@ -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 (file)
index 0000000..2dd1907
--- /dev/null
@@ -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 (file)
index 0000000..8a9e62e
--- /dev/null
@@ -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 (file)
index 0000000..b597efc
--- /dev/null
@@ -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 (file)
index 0000000..3b1ba6b
--- /dev/null
@@ -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;
+            }
+        }
+    }
+}
@@ -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 (file)
index 0000000..bea2df9
--- /dev/null
@@ -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 (file)
index 0000000..6c39f99
--- /dev/null
@@ -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 (file)
index 0000000..fec2218
--- /dev/null
@@ -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 (file)
index 0000000..bd7f1ea
--- /dev/null
@@ -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 (file)
index 0000000..ee67680
--- /dev/null
@@ -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 (file)
index 0000000..875009a
--- /dev/null
@@ -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 (file)
index 0000000..92b3d98
--- /dev/null
@@ -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 (file)
index 0000000..725076e
--- /dev/null
@@ -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 (file)
index 0000000..8dce4f0
--- /dev/null
@@ -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;
+        }
+    }
+}
index 8253994..2fdfa01 100644 (file)
     <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" />
     <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" />
     <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" />
     <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" />
     <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" />
     <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" />
     <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" />
     <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" />
     <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 (file)
index ab06347..0000000
+++ /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);
-        }
-    }
-}
-
index 475fdd7..03c061f 100644 (file)
@@ -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
         }
 
index 43c8583..710df60 100644 (file)
@@ -896,7 +896,7 @@ namespace System {
                             else
                                 fDelimiter = true;
                             
-                            newPath.Append(Path.GetFullPathInternal(directories[i]));
+                            newPath.Append(Path.GetFullPath(directories[i]));
                         }
                     }
                     
index 1996f8d..aba416c 100644 (file)
@@ -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();
index 03f9a1e..b132262 100644 (file)
@@ -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();
index ec58ae9..d29ab57 100644 (file)
@@ -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;
index 62eac42..efb322b 100644 (file)
@@ -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))
index 76cd0b2..ce70220 100644 (file)
@@ -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;
 
index d9f5753..60a7b35 100644 (file)
@@ -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);
index a3fa1fb..f5a3f63 100644 (file)
@@ -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 (file)
index 2eb8744..0000000
+++ /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;
-        }
-
-    }
-}
index c2e603c..21e355d 100644 (file)
@@ -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 + '*';
             }
index eaf559b..fe39b2d 100644 (file)
@@ -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 (file)
index 6670ad7..0000000
+++ /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 (file)
index 8e39b3c..0000000
+++ /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 (file)
index 9545905..0000000
+++ /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
index a31d965..c67bc96 100644 (file)
@@ -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);
index 04f7fae..dfa74c3 100644 (file)
@@ -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
         }
 
index c45a461..abcbd89 100644 (file)
@@ -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);
         }
index d2a95b0..c474b70 100644 (file)
@@ -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) 
index 6d492d8..e19a569 100644 (file)
@@ -1082,7 +1082,7 @@ namespace System.Reflection
                 if (fullyQualifiedName != null) {
                     bool checkPermission = true;
                     try {
-                        Path.GetFullPathInternal(fullyQualifiedName);
+                        Path.GetFullPath(fullyQualifiedName);
                     }
                     catch(ArgumentException) {
                         checkPermission = false;
index fd896a6..37ac309 100644 (file)
@@ -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);
         }
 
index b4d4141..3ae193b 100644 (file)
@@ -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
         }
     }
 
index 03d6998..c12ca2f 100644 (file)
@@ -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))
index 997cb2c..bc05104 100644 (file)
@@ -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 );
index 434ab37..98789d9 100644 (file)
@@ -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
 
index 7574417..e3904ef 100644 (file)
@@ -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.