Move S.Buffers and S.IO sources shared with corert into shared folder (#9778)
authorAlex Perovich <alperovi@microsoft.com>
Sat, 25 Feb 2017 06:46:59 +0000 (00:46 -0600)
committerJan Kotas <jkotas@microsoft.com>
Sat, 25 Feb 2017 06:46:59 +0000 (22:46 -0800)
51 files changed:
src/mscorlib/System.Private.CoreLib.csproj
src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs [deleted file]
src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs [deleted file]
src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs [deleted file]
src/mscorlib/corefx/System/Buffers/ArrayPool.cs [deleted file]
src/mscorlib/corefx/System/Buffers/ArrayPoolEventSource.cs [deleted file]
src/mscorlib/corefx/System/Buffers/ConfigurableArrayPool.cs [deleted file]
src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs [deleted file]
src/mscorlib/corefx/System/Buffers/Utilities.cs [deleted file]
src/mscorlib/corefx/System/IO/Error.cs [deleted file]
src/mscorlib/corefx/System/IO/FileStream.Linux.cs [deleted file]
src/mscorlib/corefx/System/IO/FileStream.OSX.cs [deleted file]
src/mscorlib/corefx/System/IO/FileStream.Unix.cs [deleted file]
src/mscorlib/corefx/System/IO/FileStream.Win32.cs [deleted file]
src/mscorlib/corefx/System/IO/FileStream.cs [deleted file]
src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs [deleted file]
src/mscorlib/corefx/System/IO/Path.Unix.cs [deleted file]
src/mscorlib/corefx/System/IO/Path.Win32.cs [deleted file]
src/mscorlib/corefx/System/IO/Path.Windows.cs [deleted file]
src/mscorlib/corefx/System/IO/Path.cs [deleted file]
src/mscorlib/corefx/System/IO/PathHelper.Windows.cs [deleted file]
src/mscorlib/corefx/System/IO/PathInternal.Unix.cs [deleted file]
src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs [deleted file]
src/mscorlib/corefx/System/IO/PathInternal.Windows.cs [deleted file]
src/mscorlib/corefx/System/IO/PathInternal.cs [deleted file]
src/mscorlib/corefx/System/IO/Win32Marshal.cs [deleted file]
src/mscorlib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs [new file with mode: 0644]
src/mscorlib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs [new file with mode: 0644]
src/mscorlib/shared/System.Private.CoreLib.Shared.sources [new file with mode: 0644]
src/mscorlib/shared/System/Buffers/ArrayPool.cs [new file with mode: 0644]
src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs [new file with mode: 0644]
src/mscorlib/shared/System/Buffers/ConfigurableArrayPool.cs [new file with mode: 0644]
src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs [new file with mode: 0644]
src/mscorlib/shared/System/Buffers/Utilities.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/Error.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/FileStream.Linux.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/FileStream.OSX.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/FileStream.Unix.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/FileStream.Win32.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/FileStream.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/FileStreamCompletionSource.Win32.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/Path.Unix.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/Path.Win32.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/Path.Windows.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/Path.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/PathHelper.Windows.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/PathInternal.Unix.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/PathInternal.Windows.StringBuffer.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/PathInternal.Windows.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/PathInternal.cs [new file with mode: 0644]
src/mscorlib/shared/System/IO/Win32Marshal.cs [new file with mode: 0644]

index d2b443ffbd91bea1b0d5dd2aa8f25868577dcc50..f5d9817f630235ab7435965e608714e1f5322bc4 100644 (file)
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <!-- Always use latest Roslyn compiler -->
   <Import Project="..\..\Tools\net45\roslyn\build\Microsoft.Net.Compilers.props" Condition="'$(OS)'=='Windows_NT'" />
     <MscorlibSources Include="$(BclSourcesRoot)\System\Threading\Tasks\TaskToApm.cs" />
     <MscorlibSources Condition="'$(FeatureCominterop)' == 'true'" Include="$(BclSourcesRoot)\System\Threading\Tasks\IAsyncCausalityTracerStatics.cs" />
   </ItemGroup>
-  <ItemGroup>
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\Error.cs" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.Unix.cs" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetsUnix)' == 'true' and '$(TargetsOSX)' == 'true'">
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.OSX.cs" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetsUnix)' == 'true' and '$(TargetsOSX)' != 'true'">
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.Linux.cs" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetsUnix)' != 'true'">
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\Microsoft\Win32\SafeHandles\SafeFileHandle.Windows.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\FileStream.Win32.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\FileStreamCompletionSource.Win32.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\Win32Marshal.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\Path.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.cs" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetsUnix)' == 'true'">
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Unix.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Unix.cs" />
-  </ItemGroup>
-  <ItemGroup Condition="'$(TargetsUnix)' != 'true'">
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Win32.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\Path.Windows.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\PathHelper.Windows.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Windows.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\IO\PathInternal.Windows.StringBuffer.cs" />
-  </ItemGroup>
   <ItemGroup>
     <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Threading\DeferredDisposableLifetime.cs" />
     <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Threading\ClrThreadPoolBoundHandle.cs" />
     <MscorlibSources Include="$(BclSourcesRoot)\System\Numerics\Hashing\HashHelpers.cs" />
   </ItemGroup>
   <ItemGroup>
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Buffers\ArrayPool.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Buffers\ArrayPoolEventSource.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Buffers\ConfigurableArrayPool.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Buffers\TlsOverPerCoreLockedStacksArrayPool.cs" />
-    <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Buffers\Utilities.cs" />
     <MscorlibSources Include="$(CoreFxSourcesRoot)\System\Security\CryptographicException.cs" />
   </ItemGroup>
   <ItemGroup Condition="'$(TargetsUnix)' != 'true'">
     <!-- TODO list of types to be cleaned up from CoreLib -->
     <MscorlibSources Include="$(BclSourcesRoot)\CleanupToDoList.cs" />
   </ItemGroup>
+
+  <Import Project="$(MSBuildThisFileDirectory)\shared\System.Private.CoreLib.Shared.sources"/>
+
   <ItemGroup>
     <!-- We want the sources to show up nicely in VS-->
     <Compile Include="@(MscorlibSources)">
   </PropertyGroup>
   <Import Project="GenerateSplitStringResources.targets" />
   <Import Project="GenerateCompilerResponseFile.targets" />
-</Project>
\ No newline at end of file
+</Project>
diff --git a/src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
deleted file mode 100644 (file)
index d13b536..0000000
+++ /dev/null
@@ -1,119 +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.
-
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Win32.SafeHandles
-{
-    public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
-    {
-        /// <summary>A handle value of -1.</summary>
-        private static readonly IntPtr s_invalidHandle = new IntPtr(-1);
-
-        private SafeFileHandle() : this(ownsHandle: true)
-        {
-        }
-
-        private SafeFileHandle(bool ownsHandle)
-            : base(ownsHandle)
-        {
-            SetHandle(s_invalidHandle);
-        }
-
-        public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : this(ownsHandle)
-        {
-            SetHandle(preexistingHandle);
-        }
-
-        internal bool? IsAsync { get; set; }
-
-        /// <summary>Opens the specified file with the requested flags and mode.</summary>
-        /// <param name="path">The path to the file.</param>
-        /// <param name="flags">The flags with which to open the file.</param>
-        /// <param name="mode">The mode for opening the file.</param>
-        /// <returns>A SafeFileHandle for the opened file.</returns>
-        internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int mode)
-        {
-            Debug.Assert(path != null);
-
-            // If we fail to open the file due to a path not existing, we need to know whether to blame
-            // the file itself or its directory.  If we're creating the file, then we blame the directory,
-            // otherwise we blame the file.
-            bool enoentDueToDirectory = (flags & Interop.Sys.OpenFlags.O_CREAT) != 0;
-
-            // Open the file. 
-            SafeFileHandle handle = Interop.CheckIo(
-                Interop.Sys.Open(path, flags, mode),
-                path, 
-                isDirectory: enoentDueToDirectory,
-                errorRewriter: e => (e.Error == Interop.Error.EISDIR) ? Interop.Error.EACCES.Info() : e);
-
-            // Make sure it's not a directory; we do this after opening it once we have a file descriptor 
-            // to avoid race conditions.
-            Interop.Sys.FileStatus status;
-            if (Interop.Sys.FStat(handle, out status) != 0)
-            {
-                handle.Dispose();
-                throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), path);
-            }
-            if ((status.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR)
-            {
-                handle.Dispose();
-                throw Interop.GetExceptionForIoErrno(Interop.Error.EACCES.Info(), path, isDirectory: true);
-            }
-
-            return handle;
-        }
-
-        /// <summary>Opens a SafeFileHandle for a file descriptor created by a provided delegate.</summary>
-        /// <param name="fdFunc">
-        /// The function that creates the file descriptor. Returns the file descriptor on success, or an invalid
-        /// file descriptor on error with Marshal.GetLastWin32Error() set to the error code.
-        /// </param>
-        /// <returns>The created SafeFileHandle.</returns>
-        internal static SafeFileHandle Open(Func<SafeFileHandle> fdFunc)
-        {
-            SafeFileHandle handle = Interop.CheckIo(fdFunc());
-
-            Debug.Assert(!handle.IsInvalid, "File descriptor is invalid");
-            return handle;
-        }
-
-        protected override bool ReleaseHandle()
-        {
-            // When the SafeFileHandle was opened, we likely issued an flock on the created descriptor in order to add 
-            // an advisory lock.  This lock should be removed via closing the file descriptor, but close can be
-            // interrupted, and we don't retry closes.  As such, we could end up leaving the file locked,
-            // which could prevent subsequent usage of the file until this process dies.  To avoid that, we proactively
-            // try to release the lock before we close the handle. (If it's not locked, there's no behavioral
-            // problem trying to unlock it.)
-            Interop.Sys.FLock(handle, Interop.Sys.LockOperations.LOCK_UN); // ignore any errors
-
-            // Close the descriptor. Although close is documented to potentially fail with EINTR, we never want
-            // to retry, as the descriptor could actually have been closed, been subsequently reassigned, and
-            // be in use elsewhere in the process.  Instead, we simply check whether the call was successful.
-            int result = Interop.Sys.Close(handle);
-#if DEBUG
-            if (result != 0)
-            {
-                Debug.Fail(string.Format(
-                    "Close failed with result {0} and error {1}", 
-                    result, Interop.Sys.GetLastErrorInfo()));
-            }
-#endif
-            return result == 0;
-        }
-
-        public override bool IsInvalid
-        {
-            get
-            {
-                long h = (long)handle;
-                return h < 0 || h > int.MaxValue;
-            }
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
deleted file mode 100644 (file)
index 4eabe8f..0000000
+++ /dev/null
@@ -1,50 +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.
-
-using System;
-using System.Security;
-using System.Runtime.InteropServices;
-using System.Threading;
-using Microsoft.Win32;
-
-namespace Microsoft.Win32.SafeHandles
-{
-    public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
-    {
-        private bool? _isAsync;
-
-        private SafeFileHandle() : base(true)
-        {
-            _isAsync = null;
-        }
-
-        public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle)
-        {
-            SetHandle(preexistingHandle);
-
-            _isAsync = null;
-        }
-
-        internal bool? IsAsync
-        {
-            get
-            {
-                return _isAsync;
-            }
-
-            set
-            {
-                _isAsync = value;
-            }
-        }
-
-        internal ThreadPoolBoundHandle ThreadPoolBinding { get; set; }
-
-        override protected bool ReleaseHandle()
-        {
-            return Interop.Kernel32.CloseHandle(handle);
-        }
-    }
-}
-
diff --git a/src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs b/src/mscorlib/corefx/Microsoft/Win32/SafeHandles/SafeThreadPoolIOHandle.cs
deleted file mode 100644 (file)
index 3dbc2bb..0000000
+++ /dev/null
@@ -1,28 +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.
-
-using System;
-using System.Runtime.InteropServices;
-
-namespace Microsoft.Win32.SafeHandles
-{
-    internal class SafeThreadPoolIOHandle : SafeHandle
-    {
-        private SafeThreadPoolIOHandle() 
-            : base(IntPtr.Zero, true)
-        {
-        }
-
-        public override bool IsInvalid
-        {
-            get { return handle == IntPtr.Zero; }
-        }
-
-        protected override bool ReleaseHandle()
-        {
-            Interop.mincore.CloseThreadpoolIo(handle);
-            return true;
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/Buffers/ArrayPool.cs b/src/mscorlib/corefx/System/Buffers/ArrayPool.cs
deleted file mode 100644 (file)
index 77a07f7..0000000
+++ /dev/null
@@ -1,100 +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.
-
-namespace System.Buffers
-{
-    /// <summary>
-    /// Provides a resource pool that enables reusing instances of type <see cref="T:T[]"/>. 
-    /// </summary>
-    /// <remarks>
-    /// <para>
-    /// Renting and returning buffers with an <see cref="ArrayPool{T}"/> can increase performance
-    /// in situations where arrays are created and destroyed frequently, resulting in significant
-    /// memory pressure on the garbage collector.
-    /// </para>
-    /// <para>
-    /// This class is thread-safe.  All members may be used by multiple threads concurrently.
-    /// </para>
-    /// </remarks>
-    public abstract class ArrayPool<T>
-    {
-        /// <summary>
-        /// Retrieves a shared <see cref="ArrayPool{T}"/> instance.
-        /// </summary>
-        /// <remarks>
-        /// The shared pool provides a default implementation of <see cref="ArrayPool{T}"/>
-        /// that's intended for general applicability.  It maintains arrays of multiple sizes, and 
-        /// may hand back a larger array than was actually requested, but will never hand back a smaller 
-        /// array than was requested. Renting a buffer from it with <see cref="Rent"/> will result in an 
-        /// existing buffer being taken from the pool if an appropriate buffer is available or in a new 
-        /// buffer being allocated if one is not available.
-        /// byte[] and char[] are the most commonly pooled array types. For these we use a special pool type
-        /// optimized for very fast access speeds, at the expense of more memory consumption.
-        /// The shared pool instance is created lazily on first access.
-        /// </remarks>
-        public static ArrayPool<T> Shared { get; } =
-            typeof(T) == typeof(byte) || typeof(T) == typeof(char) ? new TlsOverPerCoreLockedStacksArrayPool<T>() :
-            Create();
-
-        /// <summary>
-        /// Creates a new <see cref="ArrayPool{T}"/> instance using default configuration options.
-        /// </summary>
-        /// <returns>A new <see cref="ArrayPool{T}"/> instance.</returns>
-        public static ArrayPool<T> Create() => new ConfigurableArrayPool<T>();
-
-        /// <summary>
-        /// Creates a new <see cref="ArrayPool{T}"/> instance using custom configuration options.
-        /// </summary>
-        /// <param name="maxArrayLength">The maximum length of array instances that may be stored in the pool.</param>
-        /// <param name="maxArraysPerBucket">
-        /// The maximum number of array instances that may be stored in each bucket in the pool.  The pool
-        /// groups arrays of similar lengths into buckets for faster access.
-        /// </param>
-        /// <returns>A new <see cref="ArrayPool{T}"/> instance with the specified configuration options.</returns>
-        /// <remarks>
-        /// The created pool will group arrays into buckets, with no more than <paramref name="maxArraysPerBucket"/>
-        /// in each bucket and with those arrays not exceeding <paramref name="maxArrayLength"/> in length.
-        /// </remarks>
-        public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket) =>
-            new ConfigurableArrayPool<T>(maxArrayLength, maxArraysPerBucket);
-
-        /// <summary>
-        /// Retrieves a buffer that is at least the requested length.
-        /// </summary>
-        /// <param name="minimumLength">The minimum length of the array needed.</param>
-        /// <returns>
-        /// An <see cref="T:T[]"/> that is at least <paramref name="minimumLength"/> in length.
-        /// </returns>
-        /// <remarks>
-        /// This buffer is loaned to the caller and should be returned to the same pool via 
-        /// <see cref="Return"/> so that it may be reused in subsequent usage of <see cref="Rent"/>.  
-        /// It is not a fatal error to not return a rented buffer, but failure to do so may lead to 
-        /// decreased application performance, as the pool may need to create a new buffer to replace
-        /// the one lost.
-        /// </remarks>
-        public abstract T[] Rent(int minimumLength);
-
-        /// <summary>
-        /// Returns to the pool an array that was previously obtained via <see cref="Rent"/> on the same 
-        /// <see cref="ArrayPool{T}"/> instance.
-        /// </summary>
-        /// <param name="array">
-        /// The buffer previously obtained from <see cref="Rent"/> to return to the pool.
-        /// </param>
-        /// <param name="clearArray">
-        /// If <c>true</c> and if the pool will store the buffer to enable subsequent reuse, <see cref="Return"/>
-        /// will clear <paramref name="array"/> of its contents so that a subsequent consumer via <see cref="Rent"/> 
-        /// will not see the previous consumer's content.  If <c>false</c> or if the pool will release the buffer,
-        /// the array's contents are left unchanged.
-        /// </param>
-        /// <remarks>
-        /// Once a buffer has been returned to the pool, the caller gives up all ownership of the buffer 
-        /// and must not use it. The reference returned from a given call to <see cref="Rent"/> must only be
-        /// returned via <see cref="Return"/> once.  The default <see cref="ArrayPool{T}"/>
-        /// may hold onto the returned buffer in order to rent it again, or it may release the returned buffer
-        /// if it's determined that the pool already has enough buffers stored.
-        /// </remarks>
-        public abstract void Return(T[] array, bool clearArray = false);
-    }
-}
diff --git a/src/mscorlib/corefx/System/Buffers/ArrayPoolEventSource.cs b/src/mscorlib/corefx/System/Buffers/ArrayPoolEventSource.cs
deleted file mode 100644 (file)
index 9482744..0000000
+++ /dev/null
@@ -1,78 +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.
-
-using System.Diagnostics.Tracing;
-
-namespace System.Buffers
-{
-    [EventSource(Name = "System.Buffers.ArrayPoolEventSource")]
-    internal sealed class ArrayPoolEventSource : EventSource
-    {
-        internal readonly static ArrayPoolEventSource Log = new ArrayPoolEventSource();
-
-        /// <summary>The reason for a BufferAllocated event.</summary>
-        internal enum BufferAllocatedReason : int
-        {
-            /// <summary>The pool is allocating a buffer to be pooled in a bucket.</summary>
-            Pooled,
-            /// <summary>The requested buffer size was too large to be pooled.</summary>
-            OverMaximumSize,
-            /// <summary>The pool has already allocated for pooling as many buffers of a particular size as it's allowed.</summary>
-            PoolExhausted
-        }
-
-        /// <summary>
-        /// Event for when a buffer is rented.  This is invoked once for every successful call to Rent,
-        /// regardless of whether a buffer is allocated or a buffer is taken from the pool.  In a
-        /// perfect situation where all rented buffers are returned, we expect to see the number
-        /// of BufferRented events exactly match the number of BuferReturned events, with the number
-        /// of BufferAllocated events being less than or equal to those numbers (ideally significantly
-        /// less than).
-        /// </summary>
-        [Event(1, Level = EventLevel.Verbose)]
-        internal unsafe void BufferRented(int bufferId, int bufferSize, int poolId, int bucketId)
-        {
-            EventData* payload = stackalloc EventData[4];
-            payload[0].Size = sizeof(int);
-            payload[0].DataPointer = ((IntPtr)(&bufferId));
-            payload[1].Size = sizeof(int);
-            payload[1].DataPointer = ((IntPtr)(&bufferSize));
-            payload[2].Size = sizeof(int);
-            payload[2].DataPointer = ((IntPtr)(&poolId));
-            payload[3].Size = sizeof(int);
-            payload[3].DataPointer = ((IntPtr)(&bucketId));
-            WriteEventCore(1, 4, payload);
-        }
-
-        /// <summary>
-        /// Event for when a buffer is allocated by the pool.  In an ideal situation, the number
-        /// of BufferAllocated events is significantly smaller than the number of BufferRented and
-        /// BufferReturned events.
-        /// </summary>
-        [Event(2, Level = EventLevel.Informational)]
-        internal unsafe void BufferAllocated(int bufferId, int bufferSize, int poolId, int bucketId, BufferAllocatedReason reason)
-        {
-            EventData* payload = stackalloc EventData[5];
-            payload[0].Size = sizeof(int);
-            payload[0].DataPointer = ((IntPtr)(&bufferId));
-            payload[1].Size = sizeof(int);
-            payload[1].DataPointer = ((IntPtr)(&bufferSize));
-            payload[2].Size = sizeof(int);
-            payload[2].DataPointer = ((IntPtr)(&poolId));
-            payload[3].Size = sizeof(int);
-            payload[3].DataPointer = ((IntPtr)(&bucketId));
-            payload[4].Size = sizeof(BufferAllocatedReason);
-            payload[4].DataPointer = ((IntPtr)(&reason));
-            WriteEventCore(2, 5, payload);
-        }
-
-        /// <summary>
-        /// Event raised when a buffer is returned to the pool.  This event is raised regardless of whether
-        /// the returned buffer is stored or dropped.  In an ideal situation, the number of BufferReturned
-        /// events exactly matches the number of BufferRented events.
-        /// </summary>
-        [Event(3, Level = EventLevel.Verbose)]
-        internal void BufferReturned(int bufferId, int bufferSize, int poolId) => WriteEvent(3, bufferId, bufferSize, poolId);
-    }
-}
diff --git a/src/mscorlib/corefx/System/Buffers/ConfigurableArrayPool.cs b/src/mscorlib/corefx/System/Buffers/ConfigurableArrayPool.cs
deleted file mode 100644 (file)
index f7b6034..0000000
+++ /dev/null
@@ -1,265 +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.
-
-using System.Diagnostics;
-using System.Threading;
-
-namespace System.Buffers
-{
-    internal sealed partial class ConfigurableArrayPool<T> : ArrayPool<T>
-    {
-        /// <summary>The default maximum length of each array in the pool (2^20).</summary>
-        private const int DefaultMaxArrayLength = 1024 * 1024;
-        /// <summary>The default maximum number of arrays per bucket that are available for rent.</summary>
-        private const int DefaultMaxNumberOfArraysPerBucket = 50;
-
-        private readonly Bucket[] _buckets;
-
-        internal ConfigurableArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket)
-        {
-        }
-
-        internal ConfigurableArrayPool(int maxArrayLength, int maxArraysPerBucket)
-        {
-            if (maxArrayLength <= 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(maxArrayLength));
-            }
-            if (maxArraysPerBucket <= 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(maxArraysPerBucket));
-            }
-
-            // Our bucketing algorithm has a min length of 2^4 and a max length of 2^30.
-            // Constrain the actual max used to those values.
-            const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
-            if (maxArrayLength > MaximumArrayLength)
-            {
-                maxArrayLength = MaximumArrayLength;
-            }
-            else if (maxArrayLength < MinimumArrayLength)
-            {
-                maxArrayLength = MinimumArrayLength;
-            }
-
-            // Create the buckets.
-            int poolId = Id;
-            int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
-            var buckets = new Bucket[maxBuckets + 1];
-            for (int i = 0; i < buckets.Length; i++)
-            {
-                buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
-            }
-            _buckets = buckets;
-        }
-
-        /// <summary>Gets an ID for the pool to use with events.</summary>
-        private int Id => GetHashCode();
-
-        public override T[] Rent(int minimumLength)
-        {
-            // Arrays can't be smaller than zero.  We allow requesting zero-length arrays (even though
-            // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
-            // to be usable in general instead of using `new`, even for computed lengths.
-            if (minimumLength < 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(minimumLength));
-            }
-            else if (minimumLength == 0)
-            {
-                // No need for events with the empty array.  Our pool is effectively infinite
-                // and we'll never allocate for rents and never store for returns.
-                return Array.Empty<T>();
-            }
-
-            var log = ArrayPoolEventSource.Log;
-            T[] buffer = null;
-
-            int index = Utilities.SelectBucketIndex(minimumLength);
-            if (index < _buckets.Length)
-            {
-                // Search for an array starting at the 'index' bucket. If the bucket is empty, bump up to the
-                // next higher bucket and try that one, but only try at most a few buckets.
-                const int MaxBucketsToTry = 2;
-                int i = index;
-                do
-                {
-                    // Attempt to rent from the bucket.  If we get a buffer from it, return it.
-                    buffer = _buckets[i].Rent();
-                    if (buffer != null)
-                    {
-                        if (log.IsEnabled())
-                        {
-                            log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
-                        }
-                        return buffer;
-                    }
-                }
-                while (++i < _buckets.Length && i != index + MaxBucketsToTry);
-
-                // The pool was exhausted for this buffer size.  Allocate a new buffer with a size corresponding
-                // to the appropriate bucket.
-                buffer = new T[_buckets[index]._bufferLength];
-            }
-            else
-            {
-                // The request was for a size too large for the pool.  Allocate an array of exactly the requested length.
-                // When it's returned to the pool, we'll simply throw it away.
-                buffer = new T[minimumLength];
-            }
-
-            if (log.IsEnabled())
-            {
-                int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer
-                log.BufferRented(bufferId, buffer.Length, Id, bucketId);
-                log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ?
-                    ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
-            }
-
-            return buffer;
-        }
-
-        public override void Return(T[] array, bool clearArray = false)
-        {
-            if (array == null)
-            {
-                throw new ArgumentNullException(nameof(array));
-            }
-            else if (array.Length == 0)
-            {
-                // Ignore empty arrays.  When a zero-length array is rented, we return a singleton
-                // rather than actually taking a buffer out of the lowest bucket.
-                return;
-            }
-
-            // Determine with what bucket this array length is associated
-            int bucket = Utilities.SelectBucketIndex(array.Length);
-
-            // If we can tell that the buffer was allocated, drop it. Otherwise, check if we have space in the pool
-            if (bucket < _buckets.Length)
-            {
-                // Clear the array if the user requests
-                if (clearArray)
-                {
-                    Array.Clear(array, 0, array.Length);
-                }
-
-                // Return the buffer to its bucket.  In the future, we might consider having Return return false
-                // instead of dropping a bucket, in which case we could try to return to a lower-sized bucket,
-                // just as how in Rent we allow renting from a higher-sized bucket.
-                _buckets[bucket].Return(array);
-            }
-
-            // Log that the buffer was returned
-            var log = ArrayPoolEventSource.Log;
-            if (log.IsEnabled())
-            {
-                log.BufferReturned(array.GetHashCode(), array.Length, Id);
-            }
-        }
-
-        /// <summary>Provides a thread-safe bucket containing buffers that can be Rent'd and Return'd.</summary>
-        private sealed class Bucket
-        {
-            internal readonly int _bufferLength;
-            private readonly T[][] _buffers;
-            private readonly int _poolId;
-
-            private SpinLock _lock; // do not make this readonly; it's a mutable struct
-            private int _index;
-
-            /// <summary>
-            /// Creates the pool with numberOfBuffers arrays where each buffer is of bufferLength length.
-            /// </summary>
-            internal Bucket(int bufferLength, int numberOfBuffers, int poolId)
-            {
-                _lock = new SpinLock(Debugger.IsAttached); // only enable thread tracking if debugger is attached; it adds non-trivial overheads to Enter/Exit
-                _buffers = new T[numberOfBuffers][];
-                _bufferLength = bufferLength;
-                _poolId = poolId;
-            }
-
-            /// <summary>Gets an ID for the bucket to use with events.</summary>
-            internal int Id => GetHashCode();
-
-            /// <summary>Takes an array from the bucket.  If the bucket is empty, returns null.</summary>
-            internal T[] Rent()
-            {
-                T[][] buffers = _buffers;
-                T[] buffer = null;
-
-                // While holding the lock, grab whatever is at the next available index and
-                // update the index.  We do as little work as possible while holding the spin
-                // lock to minimize contention with other threads.  The try/finally is
-                // necessary to properly handle thread aborts on platforms which have them.
-                bool lockTaken = false, allocateBuffer = false;
-                try
-                {
-                    _lock.Enter(ref lockTaken);
-
-                    if (_index < buffers.Length)
-                    {
-                        buffer = buffers[_index];
-                        buffers[_index++] = null;
-                        allocateBuffer = buffer == null;
-                    }
-                }
-                finally
-                {
-                    if (lockTaken) _lock.Exit(false);
-                }
-
-                // While we were holding the lock, we grabbed whatever was at the next available index, if
-                // there was one.  If we tried and if we got back null, that means we hadn't yet allocated
-                // for that slot, in which case we should do so now.
-                if (allocateBuffer)
-                {
-                    buffer = new T[_bufferLength];
-
-                    var log = ArrayPoolEventSource.Log;
-                    if (log.IsEnabled())
-                    {
-                        log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id,
-                            ArrayPoolEventSource.BufferAllocatedReason.Pooled);
-                    }
-                }
-
-                return buffer;
-            }
-
-            /// <summary>
-            /// Attempts to return the buffer to the bucket.  If successful, the buffer will be stored
-            /// in the bucket and true will be returned; otherwise, the buffer won't be stored, and false
-            /// will be returned.
-            /// </summary>
-            internal void Return(T[] array)
-            {
-                // Check to see if the buffer is the correct size for this bucket
-                if (array.Length != _bufferLength)
-                {
-                    throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array));
-                }
-
-                // While holding the spin lock, if there's room available in the bucket,
-                // put the buffer into the next available slot.  Otherwise, we just drop it.
-                // The try/finally is necessary to properly handle thread aborts on platforms
-                // which have them.
-                bool lockTaken = false;
-                try
-                {
-                    _lock.Enter(ref lockTaken);
-
-                    if (_index != 0)
-                    {
-                        _buffers[--_index] = array;
-                    }
-                }
-                finally
-                {
-                    if (lockTaken) _lock.Exit(false);
-                }
-            }
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/mscorlib/corefx/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
deleted file mode 100644 (file)
index 64c5ceb..0000000
+++ /dev/null
@@ -1,292 +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.
-
-using Microsoft.Win32;
-using System.Runtime.CompilerServices;
-using System.Threading;
-
-namespace System.Buffers
-{
-    /// <summary>
-    /// Provides an ArrayPool implementation meant to be used as the singleton returned from ArrayPool.Shared.
-    /// </summary>
-    /// <remarks>
-    /// The implementation uses a tiered caching scheme, with a small per-thread cache for each array size, followed
-    /// by a cache per array size shared by all threads, split into per-core stacks meant to be used by threads
-    /// running on that core.  Locks are used to protect each per-core stack, because a thread can migrate after
-    /// checking its processor number, because multiple threads could interleave on the same core, and because
-    /// a thread is allowed to check other core's buckets if its core's bucket is empty/full.
-    /// </remarks>
-    internal sealed partial class TlsOverPerCoreLockedStacksArrayPool<T> : ArrayPool<T>
-    {
-        // TODO: #7747: "Investigate optimizing ArrayPool heuristics"
-        // - Explore caching in TLS more than one array per size per thread, and moving stale buffers to the global queue.
-        // - Explore dumping stale buffers from the global queue, similar to PinnableBufferCache (maybe merging them).
-        // - Explore changing the size of each per-core bucket, potentially dynamically or based on other factors like array size.
-        // - Explore changing number of buckets and what sizes of arrays are cached.
-        // - Investigate whether false sharing is causing any issues, in particular on LockedStack's count and the contents of its array.
-        // ...
-
-        /// <summary>The number of buckets (array sizes) in the pool, one for each array length, starting from length 16.</summary>
-        private const int NumBuckets = 17; // Utilities.SelectBucketIndex(2*1024*1024)
-        /// <summary>Maximum number of per-core stacks to use per array size.</summary>
-        private const int MaxPerCorePerArraySizeStacks = 64; // selected to avoid needing to worry about processor groups
-        /// <summary>The maximum number of buffers to store in a bucket's global queue.</summary>
-        private const int MaxBuffersPerArraySizePerCore = 8;
-
-        /// <summary>The length of arrays stored in the corresponding indices in <see cref="_buckets"/> and <see cref="t_tlsBuckets"/>.</summary>
-        private readonly int[] _bucketArraySizes;
-        /// <summary>
-        /// An array of per-core array stacks. The slots are lazily initialized to avoid creating
-        /// lots of overhead for unused array sizes.
-        /// </summary>
-        private readonly PerCoreLockedStacks[] _buckets = new PerCoreLockedStacks[NumBuckets];
-        /// <summary>A per-thread array of arrays, to cache one array per array size per thread.</summary>
-        [ThreadStatic]
-        private static T[][] t_tlsBuckets;
-
-        /// <summary>Initialize the pool.</summary>
-        public TlsOverPerCoreLockedStacksArrayPool()
-        {
-            var sizes = new int[NumBuckets];
-            for (int i = 0; i < sizes.Length; i++)
-            {
-                sizes[i] = Utilities.GetMaxSizeForBucket(i);
-            }
-            _bucketArraySizes = sizes;
-        }
-
-        /// <summary>Allocate a new PerCoreLockedStacks and try to store it into the <see cref="_buckets"/> array.</summary>
-        private PerCoreLockedStacks CreatePerCoreLockedStacks(int bucketIndex)
-        {
-            var inst = new PerCoreLockedStacks();
-            return Interlocked.CompareExchange(ref _buckets[bucketIndex], inst, null) ?? inst;
-        }
-
-        /// <summary>Gets an ID for the pool to use with events.</summary>
-        private int Id => GetHashCode();
-
-        public override T[] Rent(int minimumLength)
-        {
-            // Arrays can't be smaller than zero.  We allow requesting zero-length arrays (even though
-            // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
-            // to be usable in general instead of using `new`, even for computed lengths.
-            if (minimumLength < 0)
-            {
-                throw new ArgumentOutOfRangeException(nameof(minimumLength));
-            }
-            else if (minimumLength == 0)
-            {
-                // No need to log the empty array.  Our pool is effectively infinite
-                // and we'll never allocate for rents and never store for returns.
-                return Array.Empty<T>();
-            }
-
-            ArrayPoolEventSource log = ArrayPoolEventSource.Log;
-            T[] buffer;
-
-            // Get the bucket number for the array length
-            int bucketIndex = Utilities.SelectBucketIndex(minimumLength);
-
-            // If the array could come from a bucket...
-            if (bucketIndex < _buckets.Length)
-            {
-                // First try to get it from TLS if possible.
-                T[][] tlsBuckets = t_tlsBuckets;
-                if (tlsBuckets != null)
-                {
-                    buffer = tlsBuckets[bucketIndex];
-                    if (buffer != null)
-                    {
-                        tlsBuckets[bucketIndex] = null;
-                        if (log.IsEnabled())
-                        {
-                            log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex);
-                        }
-                        return buffer;
-                    }
-                }
-
-                // We couldn't get a buffer from TLS, so try the global stack.
-                PerCoreLockedStacks b = _buckets[bucketIndex];
-                if (b != null)
-                {
-                    buffer = b.TryPop();
-                    if (buffer != null)
-                    {
-                        if (log.IsEnabled())
-                        {
-                            log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex);
-                        }
-                        return buffer;
-                    }
-                }
-
-                // No buffer available.  Allocate a new buffer with a size corresponding to the appropriate bucket.
-                buffer = new T[_bucketArraySizes[bucketIndex]];
-            }
-            else
-            {
-                // The request was for a size too large for the pool.  Allocate an array of exactly the requested length.
-                // When it's returned to the pool, we'll simply throw it away.
-                buffer = new T[minimumLength];
-            }
-
-            if (log.IsEnabled())
-            {
-                int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer
-                log.BufferRented(bufferId, buffer.Length, Id, bucketId);
-                log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, bucketIndex >= _buckets.Length ?
-                    ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize :
-                    ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
-            }
-
-            return buffer;
-        }
-
-        public override void Return(T[] array, bool clearArray = false)
-        {
-            if (array == null)
-            {
-                throw new ArgumentNullException(nameof(array));
-            }
-
-            // Determine with what bucket this array length is associated
-            int bucketIndex = Utilities.SelectBucketIndex(array.Length);
-
-            // If we can tell that the buffer was allocated (or empty), drop it. Otherwise, check if we have space in the pool.
-            if (bucketIndex < _buckets.Length)
-            {
-                // Clear the array if the user requests.
-                if (clearArray)
-                {
-                    Array.Clear(array, 0, array.Length);
-                }
-
-                // Check to see if the buffer is the correct size for this bucket
-                if (array.Length != _bucketArraySizes[bucketIndex])
-                {
-                    throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array));
-                }
-
-                // Write through the TLS bucket.  If there weren't any buckets, create them
-                // and store this array into it.  If there were, store this into it, and
-                // if there was a previous one there, push that to the global stack.  This
-                // helps to keep LIFO access such that the most recently pushed stack will
-                // be in TLS and the first to be popped next.
-                T[][] tlsBuckets = t_tlsBuckets;
-                if (tlsBuckets == null)
-                {
-                    t_tlsBuckets = tlsBuckets = new T[NumBuckets][];
-                    tlsBuckets[bucketIndex] = array;
-                }
-                else
-                {
-                    T[] prev = tlsBuckets[bucketIndex];
-                    tlsBuckets[bucketIndex] = array;
-                    if (prev != null)
-                    {
-                        PerCoreLockedStacks bucket = _buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex);
-                        bucket.TryPush(prev);
-                    }
-                }
-            }
-
-            // Log that the buffer was returned
-            ArrayPoolEventSource log = ArrayPoolEventSource.Log;
-            if (log.IsEnabled())
-            {
-                log.BufferReturned(array.GetHashCode(), array.Length, Id);
-            }
-        }
-
-        /// <summary>
-        /// Stores a set of stacks of arrays, with one stack per core.
-        /// </summary>
-        private sealed class PerCoreLockedStacks
-        {
-            /// <summary>The stacks.</summary>
-            private readonly LockedStack[] _perCoreStacks;
-
-            /// <summary>Initializes the stacks.</summary>
-            public PerCoreLockedStacks()
-            {
-                // Create the stacks.  We create as many as there are processors, limited by our max.
-                var stacks = new LockedStack[Math.Min(Environment.ProcessorCount, MaxPerCorePerArraySizeStacks)];
-                for (int i = 0; i < stacks.Length; i++)
-                {
-                    stacks[i] = new LockedStack();
-                }
-                _perCoreStacks = stacks;
-            }
-
-            /// <summary>Try to push the array into the stacks. If each is full when it's tested, the array will be dropped.</summary>
-            [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            public void TryPush(T[] array)
-            {
-                // Try to push on to the associated stack first.  If that fails,
-                // round-robin through the other stacks.
-                LockedStack[] stacks = _perCoreStacks;
-                int index = Environment.CurrentExecutionId % stacks.Length;
-                for (int i = 0; i < stacks.Length; i++)
-                {
-                    if (stacks[index].TryPush(array)) return;
-                    if (++index == stacks.Length) index = 0;
-                }
-            }
-
-            /// <summary>Try to get an array from the stacks.  If each is empty when it's tested, null will be returned.</summary>
-            [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            public T[] TryPop()
-            {
-                // Try to pop from the associated stack first.  If that fails,
-                // round-robin through the other stacks.
-                T[] arr;
-                LockedStack[] stacks = _perCoreStacks;
-                int index = Environment.CurrentExecutionId % stacks.Length;
-                for (int i = 0; i < stacks.Length; i++)
-                {
-                    if ((arr = stacks[index].TryPop()) != null) return arr;
-                    if (++index == stacks.Length) index = 0;
-                }
-                return null;
-            }
-        }
-
-        /// <summary>Provides a simple stack of arrays, protected by a lock.</summary>
-        private sealed class LockedStack
-        {
-            private readonly T[][] _arrays = new T[MaxBuffersPerArraySizePerCore][];
-            private int _count;
-
-            [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            public bool TryPush(T[] array)
-            {
-                bool enqueued = false;
-                Monitor.Enter(this);
-                if (_count < MaxBuffersPerArraySizePerCore)
-                {
-                    _arrays[_count++] = array;
-                    enqueued = true;
-                }
-                Monitor.Exit(this);
-                return enqueued;
-            }
-
-            [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            public T[] TryPop()
-            {
-                T[] arr = null;
-                Monitor.Enter(this);
-                if (_count > 0)
-                {
-                    arr = _arrays[--_count];
-                    _arrays[_count] = null;
-                }
-                Monitor.Exit(this);
-                return arr;
-            }
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/Buffers/Utilities.cs b/src/mscorlib/corefx/System/Buffers/Utilities.cs
deleted file mode 100644 (file)
index 4f115fe..0000000
+++ /dev/null
@@ -1,35 +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.
-
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-
-namespace System.Buffers
-{
-    internal static class Utilities
-    {
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static int SelectBucketIndex(int bufferSize)
-        {
-            uint bitsRemaining = ((uint)bufferSize - 1) >> 4;
-
-            int poolIndex = 0;
-            if (bitsRemaining > 0xFFFF) { bitsRemaining >>= 16; poolIndex = 16; }
-            if (bitsRemaining > 0xFF) { bitsRemaining >>= 8; poolIndex += 8; }
-            if (bitsRemaining > 0xF) { bitsRemaining >>= 4; poolIndex += 4; }
-            if (bitsRemaining > 0x3) { bitsRemaining >>= 2; poolIndex += 2; }
-            if (bitsRemaining > 0x1) { bitsRemaining >>= 1; poolIndex += 1; }
-
-            return poolIndex + (int)bitsRemaining;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static int GetMaxSizeForBucket(int binIndex)
-        {
-            int maxSize = 16 << binIndex;
-            Debug.Assert(maxSize >= 0);
-            return maxSize;
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/Error.cs b/src/mscorlib/corefx/System/IO/Error.cs
deleted file mode 100644 (file)
index c8434ff..0000000
+++ /dev/null
@@ -1,44 +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.
-
-using System;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Globalization;
-using System.Diagnostics.Contracts;
-
-namespace System.IO
-{
-    /// <summary>
-    ///     Provides centralized methods for creating exceptions for System.IO.FileSystem.
-    /// </summary>
-    [Pure]
-    internal static class Error
-    {
-        internal static Exception GetEndOfFile()
-        {
-            return new EndOfStreamException(SR.IO_EOF_ReadBeyondEOF);
-        }
-
-        internal static Exception GetFileNotOpen()
-        {
-            return new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed);
-        }
-
-        internal static Exception GetReadNotSupported()
-        {
-            return new NotSupportedException(SR.NotSupported_UnreadableStream);
-        }
-
-        internal static Exception GetSeekNotSupported()
-        {
-            return new NotSupportedException(SR.NotSupported_UnseekableStream);
-        }
-
-        internal static Exception GetWriteNotSupported()
-        {
-            return new NotSupportedException(SR.NotSupported_UnwritableStream);
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/FileStream.Linux.cs b/src/mscorlib/corefx/System/IO/FileStream.Linux.cs
deleted file mode 100644 (file)
index 873c4eb..0000000
+++ /dev/null
@@ -1,30 +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.
-
-using Microsoft.Win32.SafeHandles;
-using System.Diagnostics;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.IO
-{
-    public partial class FileStream : Stream
-    {
-        /// <summary>Prevents other processes from reading from or writing to the FileStream.</summary>
-        /// <param name="position">The beginning of the range to lock.</param>
-        /// <param name="length">The range to be locked.</param>
-        private void LockInternal(long position, long length)
-        {
-            CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_WRLCK));
-        }
-
-        /// <summary>Allows access by other processes to all or part of a file that was previously locked.</summary>
-        /// <param name="position">The beginning of the range to unlock.</param>
-        /// <param name="length">The range to be unlocked.</param>
-        private void UnlockInternal(long position, long length)
-        {
-            CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_UNLCK));
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/FileStream.OSX.cs b/src/mscorlib/corefx/System/IO/FileStream.OSX.cs
deleted file mode 100644 (file)
index a1167bf..0000000
+++ /dev/null
@@ -1,19 +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.
-
-namespace System.IO
-{
-    public partial class FileStream : Stream
-    {
-        private void LockInternal(long position, long length)
-        {
-            throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_OSXFileLocking"));
-        }
-
-        private void UnlockInternal(long position, long length)
-        {
-            throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_OSXFileLocking"));
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/FileStream.Unix.cs b/src/mscorlib/corefx/System/IO/FileStream.Unix.cs
deleted file mode 100644 (file)
index 87bcc6c..0000000
+++ /dev/null
@@ -1,918 +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.
-
-using Microsoft.Win32.SafeHandles;
-using System.Diagnostics;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace System.IO
-{
-    /// <summary>Provides an implementation of a file stream for Unix files.</summary>
-    public partial class FileStream : Stream
-    {
-        /// <summary>File mode.</summary>
-        private FileMode _mode;
-
-        /// <summary>Advanced options requested when opening the file.</summary>
-        private FileOptions _options;
-
-        /// <summary>If the file was opened with FileMode.Append, the length of the file when opened; otherwise, -1.</summary>
-        private long _appendStart = -1;
-
-        /// <summary>
-        /// Extra state used by the file stream when _useAsyncIO is true.  This includes
-        /// the semaphore used to serialize all operation, the buffer/offset/count provided by the
-        /// caller for ReadAsync/WriteAsync operations, and the last successful task returned
-        /// synchronously from ReadAsync which can be reused if the count matches the next request.
-        /// Only initialized when <see cref="_useAsyncIO"/> is true.
-        /// </summary>
-        private AsyncState _asyncState;
-
-        /// <summary>Lazily-initialized value for whether the file supports seeking.</summary>
-        private bool? _canSeek;
-
-        private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
-        {
-            // FileStream performs most of the general argument validation.  We can assume here that the arguments
-            // are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.)
-            // Store the arguments
-            _mode = mode;
-            _options = options;
-
-            if (_useAsyncIO)
-                _asyncState = new AsyncState();
-
-            // Translate the arguments into arguments for an open call.
-            Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, _access, options); // FileShare currently ignored
-
-            // If the file gets created a new, we'll select the permissions for it.  Most Unix utilities by default use 666 (read and 
-            // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
-            // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
-            // actual permissions will typically be less than what we select here.
-            const Interop.Sys.Permissions OpenPermissions =
-                Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
-                Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
-                Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
-
-            // Open the file and store the safe handle.
-            return SafeFileHandle.Open(_path, openFlags, (int)OpenPermissions);
-        }
-
-        /// <summary>Initializes a stream for reading or writing a Unix file.</summary>
-        /// <param name="mode">How the file should be opened.</param>
-        /// <param name="share">What other access to the file should be allowed.  This is currently ignored.</param>
-        private void Init(FileMode mode, FileShare share)
-        {
-            _fileHandle.IsAsync = _useAsyncIO;
-
-            // Lock the file if requested via FileShare.  This is only advisory locking. FileShare.None implies an exclusive 
-            // lock on the file and all other modes use a shared lock.  While this is not as granular as Windows, not mandatory, 
-            // and not atomic with file opening, it's better than nothing.
-            Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
-            if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
-            {
-                // The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
-                // else and we would block trying to access it.  Other errors, such as ENOTSUP (locking isn't supported) or
-                // EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
-                // given again that this is only advisory / best-effort.
-                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
-                if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
-                {
-                    throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
-                }
-            }
-
-            // These provide hints around how the file will be accessed.  Specifying both RandomAccess
-            // and Sequential together doesn't make sense as they are two competing options on the same spectrum,
-            // so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
-            Interop.Sys.FileAdvice fadv =
-                (_options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
-                (_options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
-                0;
-            if (fadv != 0)
-            {
-                CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv),
-                    ignoreNotSupported: true); // just a hint.
-            }
-
-            // Jump to the end of the file if opened as Append.
-            if (_mode == FileMode.Append)
-            {
-                _appendStart = SeekCore(0, SeekOrigin.End);
-            }
-        }
-
-        /// <summary>Initializes a stream from an already open file handle (file descriptor).</summary>
-        /// <param name="handle">The handle to the file.</param>
-        /// <param name="bufferSize">The size of the buffer to use when buffering.</param>
-        /// <param name="useAsyncIO">Whether access to the stream is performed asynchronously.</param>
-        private void InitFromHandle(SafeFileHandle handle)
-        {
-            if (_useAsyncIO)
-                _asyncState = new AsyncState();
-
-            if (CanSeekCore) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor
-                SeekCore(0, SeekOrigin.Current);
-        }
-
-        /// <summary>Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.</summary>
-        /// <param name="mode">The FileMode provided to the stream's constructor.</param>
-        /// <param name="access">The FileAccess provided to the stream's constructor</param>
-        /// <param name="options">The FileOptions provided to the stream's constructor</param>
-        /// <returns>The flags value to be passed to the open system call.</returns>
-        private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileOptions options)
-        {
-            // Translate FileMode.  Most of the values map cleanly to one or more options for open.
-            Interop.Sys.OpenFlags flags = default(Interop.Sys.OpenFlags);
-            switch (mode)
-            {
-                default:
-                case FileMode.Open: // Open maps to the default behavior for open(...).  No flags needed.
-                    break;
-
-                case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
-                case FileMode.OpenOrCreate:
-                    flags |= Interop.Sys.OpenFlags.O_CREAT;
-                    break;
-
-                case FileMode.Create:
-                    flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_TRUNC);
-                    break;
-
-                case FileMode.CreateNew:
-                    flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
-                    break;
-
-                case FileMode.Truncate:
-                    flags |= Interop.Sys.OpenFlags.O_TRUNC;
-                    break;
-            }
-
-            // Translate FileAccess.  All possible values map cleanly to corresponding values for open.
-            switch (access)
-            {
-                case FileAccess.Read:
-                    flags |= Interop.Sys.OpenFlags.O_RDONLY;
-                    break;
-
-                case FileAccess.ReadWrite:
-                    flags |= Interop.Sys.OpenFlags.O_RDWR;
-                    break;
-
-                case FileAccess.Write:
-                    flags |= Interop.Sys.OpenFlags.O_WRONLY;
-                    break;
-            }
-
-            // Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
-            // - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
-            // - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
-            // - Encrypted: No equivalent on Unix and is ignored
-            // - RandomAccess: Implemented after open if posix_fadvise is available
-            // - SequentialScan: Implemented after open if posix_fadvise is available
-            // - WriteThrough: Handled here
-            if ((options & FileOptions.WriteThrough) != 0)
-            {
-                flags |= Interop.Sys.OpenFlags.O_SYNC;
-            }
-
-            return flags;
-        }
-
-        /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
-        public override bool CanSeek => CanSeekCore;
-
-        /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
-        /// <remarks>Separated out of CanSeek to enable making non-virtual call to this logic.</remarks>
-        private bool CanSeekCore
-        {
-            get
-            {
-                if (_fileHandle.IsClosed)
-                {
-                    return false;
-                }
-
-                if (!_canSeek.HasValue)
-                {
-                    // Lazily-initialize whether we're able to seek, tested by seeking to our current location.
-                    _canSeek = Interop.Sys.LSeek(_fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;
-                }
-                return _canSeek.Value;
-            }
-        }
-
-        private long GetLengthInternal()
-        {
-            // Get the length of the file as reported by the OS
-            Interop.Sys.FileStatus status;
-            CheckFileCall(Interop.Sys.FStat(_fileHandle, out status));
-            long length = status.Size;
-
-            // But we may have buffered some data to be written that puts our length
-            // beyond what the OS is aware of.  Update accordingly.
-            if (_writePos > 0 && _filePosition + _writePos > length)
-            {
-                length = _writePos + _filePosition;
-            }
-
-            return length;
-        }
-
-        /// <summary>Releases the unmanaged resources used by the stream.</summary>
-        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
-        protected override void Dispose(bool disposing)
-        {
-            try
-            {
-                if (_fileHandle != null && !_fileHandle.IsClosed)
-                {
-                    // Flush any remaining data in the file
-                    FlushWriteBuffer();
-
-                    // If DeleteOnClose was requested when constructed, delete the file now.
-                    // (Unix doesn't directly support DeleteOnClose, so we mimic it here.)
-                    if (_path != null && (_options & FileOptions.DeleteOnClose) != 0)
-                    {
-                        // Since we still have the file open, this will end up deleting
-                        // it (assuming we're the only link to it) once it's closed, but the
-                        // name will be removed immediately.
-                        Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist
-                    }
-                }
-            }
-            finally
-            {
-                if (_fileHandle != null && !_fileHandle.IsClosed)
-                {
-                    _fileHandle.Dispose();
-                }
-                base.Dispose(disposing);
-            }
-        }
-
-        /// <summary>Flushes the OS buffer.  This does not flush the internal read/write buffer.</summary>
-        private void FlushOSBuffer()
-        {
-            if (Interop.Sys.FSync(_fileHandle) < 0)
-            {
-                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
-                switch (errorInfo.Error)
-                {
-                    case Interop.Error.EROFS:
-                    case Interop.Error.EINVAL:
-                    case Interop.Error.ENOTSUP:
-                        // Ignore failures due to the FileStream being bound to a special file that
-                        // doesn't support synchronization.  In such cases there's nothing to flush.
-                        break;
-                    default:
-                        throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
-                }
-            }
-        }
-
-        /// <summary>Writes any data in the write buffer to the underlying stream and resets the buffer.</summary>
-        private void FlushWriteBuffer()
-        {
-            AssertBufferInvariants();
-            if (_writePos > 0)
-            {
-                WriteNative(GetBuffer(), 0, _writePos);
-                _writePos = 0;
-            }
-        }
-
-        /// <summary>Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device.</summary>
-        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
-        /// <returns>A task that represents the asynchronous flush operation.</returns>
-        private Task FlushAsyncInternal(CancellationToken cancellationToken)
-        {
-            if (cancellationToken.IsCancellationRequested)
-            {
-                return Task.FromCanceled(cancellationToken);
-            }
-            if (_fileHandle.IsClosed)
-            {
-                throw Error.GetFileNotOpen();
-            }
-
-            // As with Win32FileStream, flush the buffers synchronously to avoid race conditions.
-            try
-            {
-                FlushInternalBuffer();
-            }
-            catch (Exception e)
-            {
-                return Task.FromException(e);
-            }
-
-            // We then separately flush to disk asynchronously.  This is only 
-            // necessary if we support writing; otherwise, we're done.
-            if (CanWrite)
-            {
-                return Task.Factory.StartNew(
-                    state => ((FileStream)state).FlushOSBuffer(),
-                    this,
-                    cancellationToken,
-                    TaskCreationOptions.DenyChildAttach,
-                    TaskScheduler.Default);
-            }
-            else
-            {
-                return Task.CompletedTask;
-            }
-        }
-
-        /// <summary>Sets the length of this stream to the given value.</summary>
-        /// <param name="value">The new length of the stream.</param>
-        private void SetLengthInternal(long value)
-        {
-            FlushInternalBuffer();
-
-            if (_appendStart != -1 && value < _appendStart)
-            {
-                throw new IOException(SR.IO_SetLengthAppendTruncate);
-            }
-
-            long origPos = _filePosition;
-
-            VerifyOSHandlePosition();
-
-            if (_filePosition != value)
-            {
-                SeekCore(value, SeekOrigin.Begin);
-            }
-
-            CheckFileCall(Interop.Sys.FTruncate(_fileHandle, value));
-
-            // 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);
-                }
-            }
-        }
-
-        /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
-        /// <param name="array">
-        /// When this method returns, contains the specified byte array with the values between offset and 
-        /// (offset + count - 1) replaced by the bytes read from the current source.
-        /// </param>
-        /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
-        /// <param name="count">The maximum number of bytes to read. </param>
-        /// <returns>
-        /// The total number of bytes read into the buffer. This might be less than the number of bytes requested 
-        /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
-        /// </returns>
-        public override int Read(byte[] array, int offset, int count)
-        {
-            ValidateReadWriteArgs(array, offset, count);
-
-            if (_useAsyncIO)
-            {
-                _asyncState.Wait();
-                try { return ReadCore(array, offset, count); }
-                finally { _asyncState.Release(); }
-            }
-            else
-            {
-                return ReadCore(array, offset, count);
-            }
-        }
-
-        /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
-        /// <param name="array">
-        /// When this method returns, contains the specified byte array with the values between offset and 
-        /// (offset + count - 1) replaced by the bytes read from the current source.
-        /// </param>
-        /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
-        /// <param name="count">The maximum number of bytes to read. </param>
-        /// <returns>
-        /// The total number of bytes read into the buffer. This might be less than the number of bytes requested 
-        /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
-        /// </returns>
-        private int ReadCore(byte[] array, int offset, int count)
-        {
-            PrepareForReading();
-
-            // Are there any bytes available in the read buffer? If yes,
-            // we can just return from the buffer.  If the buffer is empty
-            // or has no more available data in it, we can either refill it
-            // (and then read from the buffer into the user's buffer) or
-            // we can just go directly into the user's buffer, if they asked
-            // for more data than we'd otherwise buffer.
-            int numBytesAvailable = _readLength - _readPos;
-            bool readFromOS = false;
-            if (numBytesAvailable == 0)
-            {
-                // If we're not able to seek, then we're not able to rewind the stream (i.e. flushing
-                // a read buffer), in which case we don't want to use a read buffer.  Similarly, if
-                // the user has asked for more data than we can buffer, we also want to skip the buffer.
-                if (!CanSeek || (count >= _bufferLength))
-                {
-                    // Read directly into the user's buffer
-                    _readPos = _readLength = 0;
-                    return ReadNative(array, offset, count);
-                }
-                else
-                {
-                    // Read into our buffer.
-                    _readLength = numBytesAvailable = ReadNative(GetBuffer(), 0, _bufferLength);
-                    _readPos = 0;
-                    if (numBytesAvailable == 0)
-                    {
-                        return 0;
-                    }
-
-                    // Note that we did an OS read as part of this Read, so that later
-                    // we don't try to do one again if what's in the buffer doesn't
-                    // meet the user's request.
-                    readFromOS = true;
-                }
-            }
-
-            // Now that we know there's data in the buffer, read from it into the user's buffer.
-            Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here");
-            int bytesRead = Math.Min(numBytesAvailable, count);
-            Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, bytesRead);
-            _readPos += bytesRead;
-
-            // We may not have had enough data in the buffer to completely satisfy the user's request.
-            // While Read doesn't require that we return as much data as the user requested (any amount
-            // up to the requested count is fine), FileStream on Windows tries to do so by doing a 
-            // subsequent read from the file if we tried to satisfy the request with what was in the 
-            // buffer but the buffer contained less than the requested count. To be consistent with that 
-            // behavior, we do the same thing here on Unix.  Note that we may still get less the requested 
-            // amount, as the OS may give us back fewer than we request, either due to reaching the end of 
-            // file, or due to its own whims.
-            if (!readFromOS && bytesRead < count)
-            {
-                Debug.Assert(_readPos == _readLength, "bytesToRead should only be < count if numBytesAvailable < count");
-                _readPos = _readLength = 0; // no data left in the read buffer
-                bytesRead += ReadNative(array, offset + bytesRead, count - bytesRead);
-            }
-
-            return bytesRead;
-        }
-
-        /// <summary>Unbuffered, reads a block of bytes from the stream and writes the data in a given buffer.</summary>
-        /// <param name="array">
-        /// When this method returns, contains the specified byte array with the values between offset and 
-        /// (offset + count - 1) replaced by the bytes read from the current source.
-        /// </param>
-        /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
-        /// <param name="count">The maximum number of bytes to read. </param>
-        /// <returns>
-        /// The total number of bytes read into the buffer. This might be less than the number of bytes requested 
-        /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
-        /// </returns>
-        private unsafe int ReadNative(byte[] array, int offset, int count)
-        {
-            FlushWriteBuffer(); // we're about to read; dump the write buffer
-
-            VerifyOSHandlePosition();
-
-            int bytesRead;
-            fixed (byte* bufPtr = array)
-            {
-                bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr + offset, count));
-                Debug.Assert(bytesRead <= count);
-            }
-            _filePosition += bytesRead;
-            return bytesRead;
-        }
-
-        /// <summary>
-        /// Asynchronously reads a sequence of bytes from the current stream and advances
-        /// the position within the stream by the number of bytes read.
-        /// </summary>
-        /// <param name="buffer">The buffer to write the data into.</param>
-        /// <param name="offset">The byte offset in buffer at which to begin writing data from the stream.</param>
-        /// <param name="count">The maximum number of bytes to read.</param>
-        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
-        /// <returns>A task that represents the asynchronous read operation.</returns>
-        private Task<int> ReadAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
-        {
-            if (_useAsyncIO)
-            {
-                if (!CanRead) // match Windows behavior; this gets thrown synchronously
-                {
-                    throw Error.GetReadNotSupported();
-                }
-
-                // Serialize operations using the semaphore.
-                Task waitTask = _asyncState.WaitAsync();
-
-                // If we got ownership immediately, and if there's enough data in our buffer
-                // to satisfy the full request of the caller, hand back the buffered data.
-                // While it would be a legal implementation of the Read contract, we don't
-                // hand back here less than the amount requested so as to match the behavior
-                // in ReadCore that will make a native call to try to fulfill the remainder
-                // of the request.
-                if (waitTask.Status == TaskStatus.RanToCompletion)
-                {
-                    int numBytesAvailable = _readLength - _readPos;
-                    if (numBytesAvailable >= count)
-                    {
-                        try
-                        {
-                            PrepareForReading();
-
-                            Buffer.BlockCopy(GetBuffer(), _readPos, buffer, offset, count);
-                            _readPos += count;
-
-                            return _asyncState._lastSuccessfulReadTask != null && _asyncState._lastSuccessfulReadTask.Result == count ?
-                                _asyncState._lastSuccessfulReadTask :
-                                (_asyncState._lastSuccessfulReadTask = Task.FromResult(count));
-                        }
-                        catch (Exception exc)
-                        {
-                            return Task.FromException<int>(exc);
-                        }
-                        finally
-                        {
-                            _asyncState.Release();
-                        }
-                    }
-                }
-
-                // Otherwise, issue the whole request asynchronously.
-                _asyncState.Update(buffer, offset, count);
-                return waitTask.ContinueWith((t, s) =>
-                {
-                    // The options available on Unix for writing asynchronously to an arbitrary file 
-                    // handle typically amount to just using another thread to do the synchronous write, 
-                    // which is exactly  what this implementation does. This does mean there are subtle
-                    // differences in certain FileStream behaviors between Windows and Unix when multiple 
-                    // asynchronous operations are issued against the stream to execute concurrently; on 
-                    // Unix the operations will be serialized due to the usage of a semaphore, but the 
-                    // position /length information won't be updated until after the write has completed, 
-                    // whereas on Windows it may happen before the write has completed.
-
-                    Debug.Assert(t.Status == TaskStatus.RanToCompletion);
-                    var thisRef = (FileStream)s;
-                    try
-                    {
-                        byte[] b = thisRef._asyncState._buffer;
-                        thisRef._asyncState._buffer = null; // remove reference to user's buffer
-                        return thisRef.ReadCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
-                    }
-                    finally { thisRef._asyncState.Release(); }
-                }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
-            }
-            else
-            {
-                return base.ReadAsync(buffer, offset, count, cancellationToken);
-            }
-        }
-
-        /// <summary>
-        /// Reads a byte from the stream and advances the position within the stream
-        /// by one byte, or returns -1 if at the end of the stream.
-        /// </summary>
-        /// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
-        public override int ReadByte()
-        {
-            if (_useAsyncIO)
-            {
-                _asyncState.Wait();
-                try { return ReadByteCore(); }
-                finally { _asyncState.Release(); }
-            }
-            else
-            {
-                return ReadByteCore();
-            }
-        }
-
-        /// <summary>Writes a block of bytes to the file stream.</summary>
-        /// <param name="array">The buffer containing data to write to the stream.</param>
-        /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
-        /// <param name="count">The maximum number of bytes to write.</param>
-        public override void Write(byte[] array, int offset, int count)
-        {
-            ValidateReadWriteArgs(array, offset, count);
-
-            if (_useAsyncIO)
-            {
-                _asyncState.Wait();
-                try { WriteCore(array, offset, count); }
-                finally { _asyncState.Release(); }
-            }
-            else
-            {
-                WriteCore(array, offset, count);
-            }
-        }
-
-        /// <summary>Writes a block of bytes to the file stream.</summary>
-        /// <param name="array">The buffer containing data to write to the stream.</param>
-        /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
-        /// <param name="count">The maximum number of bytes to write.</param>
-        private void WriteCore(byte[] array, int offset, int count)
-        {
-            PrepareForWriting();
-
-            // If no data is being written, nothing more to do.
-            if (count == 0)
-            {
-                return;
-            }
-
-            // If there's already data in our write buffer, then we need to go through
-            // our buffer to ensure data isn't corrupted.
-            if (_writePos > 0)
-            {
-                // If there's space remaining in the buffer, then copy as much as
-                // we can from the user's buffer into ours.
-                int spaceRemaining = _bufferLength - _writePos;
-                if (spaceRemaining > 0)
-                {
-                    int bytesToCopy = Math.Min(spaceRemaining, count);
-                    Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, bytesToCopy);
-                    _writePos += bytesToCopy;
-
-                    // If we've successfully copied all of the user's data, we're done.
-                    if (count == bytesToCopy)
-                    {
-                        return;
-                    }
-
-                    // Otherwise, keep track of how much more data needs to be handled.
-                    offset += bytesToCopy;
-                    count -= bytesToCopy;
-                }
-
-                // At this point, the buffer is full, so flush it out.
-                FlushWriteBuffer();
-            }
-
-            // Our buffer is now empty.  If using the buffer would slow things down (because
-            // the user's looking to write more data than we can store in the buffer),
-            // skip the buffer.  Otherwise, put the remaining data into the buffer.
-            Debug.Assert(_writePos == 0);
-            if (count >= _bufferLength)
-            {
-                WriteNative(array, offset, count);
-            }
-            else
-            {
-                Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count);
-                _writePos = count;
-            }
-        }
-
-        /// <summary>Unbuffered, writes a block of bytes to the file stream.</summary>
-        /// <param name="array">The buffer containing data to write to the stream.</param>
-        /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
-        /// <param name="count">The maximum number of bytes to write.</param>
-        private unsafe void WriteNative(byte[] array, int offset, int count)
-        {
-            VerifyOSHandlePosition();
-
-            fixed (byte* bufPtr = array)
-            {
-                while (count > 0)
-                {
-                    int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count));
-                    Debug.Assert(bytesWritten <= count);
-
-                    _filePosition += bytesWritten;
-                    count -= bytesWritten;
-                    offset += bytesWritten;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Asynchronously writes a sequence of bytes to the current stream, advances
-        /// the current position within this stream by the number of bytes written, and
-        /// monitors cancellation requests.
-        /// </summary>
-        /// <param name="buffer">The buffer to write data from.</param>
-        /// <param name="offset">The zero-based byte offset in buffer from which to begin copying bytes to the stream.</param>
-        /// <param name="count">The maximum number of bytes to write.</param>
-        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
-        /// <returns>A task that represents the asynchronous write operation.</returns>
-        private Task WriteAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
-        {
-            if (cancellationToken.IsCancellationRequested)
-                return Task.FromCanceled(cancellationToken);
-
-            if (_fileHandle.IsClosed)
-                throw Error.GetFileNotOpen();
-
-            if (_useAsyncIO)
-            {
-                if (!CanWrite) // match Windows behavior; this gets thrown synchronously
-                {
-                    throw Error.GetWriteNotSupported();
-                }
-
-                // Serialize operations using the semaphore.
-                Task waitTask = _asyncState.WaitAsync();
-
-                // If we got ownership immediately, and if there's enough space in our buffer
-                // to buffer the entire write request, then do so and we're done.
-                if (waitTask.Status == TaskStatus.RanToCompletion)
-                {
-                    int spaceRemaining = _bufferLength - _writePos;
-                    if (spaceRemaining >= count)
-                    {
-                        try
-                        {
-                            PrepareForWriting();
-
-                            Buffer.BlockCopy(buffer, offset, GetBuffer(), _writePos, count);
-                            _writePos += count;
-
-                            return Task.CompletedTask;
-                        }
-                        catch (Exception exc)
-                        {
-                            return Task.FromException(exc);
-                        }
-                        finally
-                        {
-                            _asyncState.Release();
-                        }
-                    }
-                }
-
-                // Otherwise, issue the whole request asynchronously.
-                _asyncState.Update(buffer, offset, count);
-                return waitTask.ContinueWith((t, s) =>
-                {
-                    // The options available on Unix for writing asynchronously to an arbitrary file 
-                    // handle typically amount to just using another thread to do the synchronous write, 
-                    // which is exactly  what this implementation does. This does mean there are subtle
-                    // differences in certain FileStream behaviors between Windows and Unix when multiple 
-                    // asynchronous operations are issued against the stream to execute concurrently; on 
-                    // Unix the operations will be serialized due to the usage of a semaphore, but the 
-                    // position /length information won't be updated until after the write has completed, 
-                    // whereas on Windows it may happen before the write has completed.
-
-                    Debug.Assert(t.Status == TaskStatus.RanToCompletion);
-                    var thisRef = (FileStream)s;
-                    try
-                    {
-                        byte[] b = thisRef._asyncState._buffer;
-                        thisRef._asyncState._buffer = null; // remove reference to user's buffer
-                        thisRef.WriteCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
-                    }
-                    finally { thisRef._asyncState.Release(); }
-                }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
-            }
-            else
-            {
-                return base.WriteAsync(buffer, offset, count, cancellationToken);
-            }
-        }
-
-        /// <summary>
-        /// Writes a byte to the current position in the stream and advances the position
-        /// within the stream by one byte.
-        /// </summary>
-        /// <param name="value">The byte to write to the stream.</param>
-        public override void WriteByte(byte value) // avoids an array allocation in the base implementation
-        {
-            if (_useAsyncIO)
-            {
-                _asyncState.Wait();
-                try { WriteByteCore(value); }
-                finally { _asyncState.Release(); }
-            }
-            else
-            {
-                WriteByteCore(value);
-            }
-        }
-
-        /// <summary>Sets the current position of this stream to the given value.</summary>
-        /// <param name="offset">The point relative to origin from which to begin seeking. </param>
-        /// <param name="origin">
-        /// Specifies the beginning, the end, or the current position as a reference 
-        /// point for offset, using a value of type SeekOrigin.
-        /// </param>
-        /// <returns>The new position in the stream.</returns>
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
-            {
-                throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
-            }
-            if (_fileHandle.IsClosed)
-            {
-                throw Error.GetFileNotOpen();
-            }
-            if (!CanSeek)
-            {
-                throw Error.GetSeekNotSupported();
-            }
-
-            VerifyOSHandlePosition();
-
-            // Flush our write/read buffer.  FlushWrite will output any write buffer we have and reset _bufferWritePos.
-            // We don't call FlushRead, as that will do an unnecessary seek to rewind the read buffer, and since we're 
-            // about to seek and update our position, we can simply update the offset as necessary and reset our read 
-            // position and length to 0. (In the future, for some simple cases we could potentially add an optimization 
-            // here to just move data around in the buffer for short jumps, to avoid re-reading the data from disk.)
-            FlushWriteBuffer();
-            if (origin == SeekOrigin.Current)
-            {
-                offset -= (_readLength - _readPos);
-            }
-            _readPos = _readLength = 0;
-
-            // Keep track of where we were, in case we're in append mode and need to verify
-            long oldPos = 0;
-            if (_appendStart >= 0)
-            {
-                oldPos = SeekCore(0, SeekOrigin.Current);
-            }
-
-            // Jump to the new location
-            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(SR.IO_SeekAppendOverwrite);
-            }
-
-            // Return the new position
-            return pos;
-        }
-
-        /// <summary>Sets the current position of this stream to the given value.</summary>
-        /// <param name="offset">The point relative to origin from which to begin seeking. </param>
-        /// <param name="origin">
-        /// Specifies the beginning, the end, or the current position as a reference 
-        /// point for offset, using a value of type SeekOrigin.
-        /// </param>
-        /// <returns>The new position in the stream.</returns>
-        private long SeekCore(long offset, SeekOrigin origin)
-        {
-            Debug.Assert(!_fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeek)); // verify that we can seek, but only if CanSeek won't be a virtual call (which could happen in the ctor)
-            Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End);
-
-            long pos = CheckFileCall(Interop.Sys.LSeek(_fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values
-            _filePosition = pos;
-            return pos;
-        }
-
-        private long CheckFileCall(long result, bool ignoreNotSupported = false)
-        {
-            if (result < 0)
-            {
-                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
-                if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
-                {
-                    throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
-                }
-            }
-
-            return result;
-        }
-
-        private int CheckFileCall(int result, bool ignoreNotSupported = false)
-        {
-            CheckFileCall((long)result, ignoreNotSupported);
-
-            return result;
-        }
-
-        /// <summary>State used when the stream is in async mode.</summary>
-        private sealed class AsyncState : SemaphoreSlim
-        {
-            /// <summary>The caller's buffer currently being used by the active async operation.</summary>
-            internal byte[] _buffer;
-            /// <summary>The caller's offset currently being used by the active async operation.</summary>
-            internal int _offset;
-            /// <summary>The caller's count currently being used by the active async operation.</summary>
-            internal int _count;
-            /// <summary>The last task successfully, synchronously returned task from ReadAsync.</summary>
-            internal Task<int> _lastSuccessfulReadTask;
-
-            /// <summary>Initialize the AsyncState.</summary>
-            internal AsyncState() : base(initialCount: 1, maxCount: 1) { }
-
-            /// <summary>Sets the active buffer, offset, and count.</summary>
-            internal void Update(byte[] buffer, int offset, int count)
-            {
-                _buffer = buffer;
-                _offset = offset;
-                _count = count;
-            }
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/FileStream.Win32.cs b/src/mscorlib/corefx/System/IO/FileStream.Win32.cs
deleted file mode 100644 (file)
index 82e7473..0000000
+++ /dev/null
@@ -1,1770 +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.
-
-using System.Buffers;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Win32.SafeHandles;
-using System.Runtime.CompilerServices;
-
-/*
- * Win32FileStream supports different modes of accessing the disk - async mode
- * and sync mode.  They are two completely different codepaths in the
- * sync & async methods (i.e. Read/Write vs. ReadAsync/WriteAsync).  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 Win32FileStream as well. Folded in the
- * code from BufferedStream, so all the comments about it being mostly
- * aggressive (and the possible perf improvement) apply to Win32FileStream 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
-{
-    public partial class FileStream : Stream
-    {
-        private bool _canSeek;
-        private bool _isPipe;      // Whether to disable async buffering code.
-        private long _appendStart; // When appending, prevent overwriting file.
-
-        private static unsafe IOCompletionCallback s_ioCallback = FileStreamCompletionSource.IOCallback;
-
-        private Task<int> _lastSynchronouslyCompletedTask = null;   // cached task for read ops that complete synchronously
-        private Task _activeBufferOperation = null;                 // tracks in-progress async ops using the buffer
-        private PreAllocatedOverlapped _preallocatedOverlapped;     // optimization for async ops to avoid per-op allocations
-        private FileStreamCompletionSource _currentOverlappedOwner; // async op currently using the preallocated overlapped
-
-        private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
-        {
-            Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
-
-            int fAccess =
-                ((_access & FileAccess.Read) == FileAccess.Read ? GENERIC_READ : 0) |
-                ((_access & FileAccess.Write) == FileAccess.Write ? GENERIC_WRITE : 0);
-
-            // 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;
-
-            // Must use a valid Win32 constant here...
-            if (mode == FileMode.Append)
-                mode = FileMode.OpenOrCreate;
-
-            int flagsAndAttributes = (int)options;
-
-            // 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 |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS);
-
-            // Don't pop up a dialog for reading from an empty floppy drive
-            uint oldMode = Interop.Kernel32.SetErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS);
-            try
-            {
-                SafeFileHandle fileHandle = Interop.Kernel32.SafeCreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
-                fileHandle.IsAsync = _useAsyncIO;
-
-                if (fileHandle.IsInvalid)
-                {
-                    // Return a meaningful exception with the full path.
-
-                    // NT5 oddity - when trying to open "C:\" as a Win32FileStream,
-                    // 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 == Interop.Errors.ERROR_PATH_NOT_FOUND && _path.Equals(Directory.InternalGetDirectoryRoot(_path)))
-                        errorCode = Interop.Errors.ERROR_ACCESS_DENIED;
-
-                    throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
-                }
-
-                return fileHandle;
-            }
-            finally
-            {
-                Interop.Kernel32.SetErrorMode(oldMode);
-            }
-        }
-
-        private void Init(FileMode mode, FileShare share)
-        {
-            // Disallow access to all non-file devices from the Win32FileStream
-            // 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 = Interop.Kernel32.GetFileType(_fileHandle);
-            if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK)
-            {
-                _fileHandle.Dispose();
-                throw new NotSupportedException(SR.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 (_useAsyncIO)
-            {
-                try
-                {
-                    _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle);
-                }
-                catch (ArgumentException ex)
-                {
-                    throw new IOException(SR.IO_BindHandleFailed, ex);
-                }
-                finally
-                {
-                    if (_fileHandle.ThreadPoolBinding == null)
-                    {
-                        // We should close the handle so that the handle is not open until SafeFileHandle GC
-                        Debug.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?");
-                        _fileHandle.Dispose();
-                    }
-                }
-            }
-
-            _canSeek = true;
-
-            // For Append mode...
-            if (mode == FileMode.Append)
-            {
-                _appendStart = SeekCore(0, SeekOrigin.End);
-            }
-            else
-            {
-                _appendStart = -1;
-            }
-        }
-
-        private void InitFromHandle(SafeFileHandle handle)
-        {
-            int handleType = Interop.Kernel32.GetFileType(_fileHandle);
-            Debug.Assert(handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!");
-
-            _canSeek = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
-            _isPipe = handleType == Interop.Kernel32.FileTypes.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, however, we've already bound this file handle to our completion port,
-            // don't try to bind it again because it will fail.  A handle can only be
-            // bound to a single completion port at a time.
-            if (_useAsyncIO && !GetSuppressBindHandle(handle))
-            {
-                try
-                {
-                    _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle);
-                }
-                catch (Exception ex)
-                {
-                    // If you passed in a synchronous handle and told us to use
-                    // it asynchronously, throw here.
-                    throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle), ex);
-                }
-            }
-            else if (!_useAsyncIO)
-            {
-                if (handleType != Interop.Kernel32.FileTypes.FILE_TYPE_PIPE)
-                    VerifyHandleIsSync();
-            }
-
-            if (_canSeek)
-                SeekCore(0, SeekOrigin.Current);
-            else
-                _filePosition = 0;
-        }
-
-        private static bool GetSuppressBindHandle(SafeFileHandle handle)
-        {
-            return handle.IsAsync.HasValue ? handle.IsAsync.Value : false;
-        }
-
-        private unsafe static Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
-        {
-            Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default(Interop.Kernel32.SECURITY_ATTRIBUTES);
-            if ((share & FileShare.Inheritable) != 0)
-            {
-                secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES();
-                secAttrs.nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES);
-
-                secAttrs.bInheritHandle = Interop.BOOL.TRUE;
-            }
-            return secAttrs;
-        }
-
-        // Verifies that this handle supports synchronous IO operations (unless you
-        // didn't open it for either reading or writing).
-        private unsafe void VerifyHandleIsSync()
-        {
-            Debug.Assert(!_useAsyncIO);
-
-            // 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
-            // Win32FileStream's thread).
-            Debug.Assert(Interop.Kernel32.GetFileType(_fileHandle) != Interop.Kernel32.FileTypes.FILE_TYPE_PIPE);
-
-            byte* bytes = stackalloc byte[1];
-            int numBytesReadWritten;
-            int r = -1;
-
-            // 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 ((_access & FileAccess.Read) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor
-            {
-                r = Interop.Kernel32.ReadFile(_fileHandle, bytes, 0, out numBytesReadWritten, IntPtr.Zero);
-            }
-            else if ((_access & FileAccess.Write) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor
-            {
-                r = Interop.Kernel32.WriteFile(_fileHandle, bytes, 0, out numBytesReadWritten, IntPtr.Zero);
-            }
-
-            if (r == 0)
-            {
-                int errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(throwIfInvalidHandle: true);
-                if (errorCode == ERROR_INVALID_PARAMETER)
-                    throw new ArgumentException(SR.Arg_HandleNotSync, "handle");
-            }
-        }
-
-        private bool HasActiveBufferOperation
-        {
-            get { return _activeBufferOperation != null && !_activeBufferOperation.IsCompleted; }
-        }
-
-        public override bool CanSeek
-        {
-            get { return _canSeek; }
-        }
-
-        private long GetLengthInternal()
-        {
-            Interop.Kernel32.FILE_STANDARD_INFO info = new Interop.Kernel32.FILE_STANDARD_INFO();
-
-            if (!Interop.Kernel32.GetFileInformationByHandleEx(_fileHandle, Interop.Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, out info, (uint)Marshal.SizeOf<Interop.Kernel32.FILE_STANDARD_INFO>()))
-                throw Win32Marshal.GetExceptionForLastWin32Error();
-            long len = info.EndOfFile;
-            // 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 && _filePosition + _writePos > len)
-                len = _writePos + _filePosition;
-            return len;
-        }
-
-        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 Win32FileStream, which would often "just work" when 
-            // finalized.
-            try
-            {
-                if (_fileHandle != null && !_fileHandle.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)
-                    {
-                        FlushWriteBuffer(!disposing);
-                    }
-                }
-            }
-            finally
-            {
-                if (_fileHandle != null && !_fileHandle.IsClosed)
-                {
-                    if (_fileHandle.ThreadPoolBinding != null)
-                        _fileHandle.ThreadPoolBinding.Dispose();
-
-                    _fileHandle.Dispose();
-                }
-
-                if (_preallocatedOverlapped != null)
-                    _preallocatedOverlapped.Dispose();
-
-                _canSeek = false;
-
-                // Don't set the buffer to null, to avoid a NullReferenceException
-                // when users have a race condition in their code (i.e. they call
-                // Close when calling another method on Stream like Read).
-                //_buffer = null;
-                base.Dispose(disposing);
-            }
-        }
-
-        private void FlushOSBuffer()
-        {
-            if (!Interop.Kernel32.FlushFileBuffers(_fileHandle))
-            {
-                throw Win32Marshal.GetExceptionForLastWin32Error();
-            }
-        }
-
-        // Returns a task that flushes the internal write buffer
-        private Task FlushWriteAsync(CancellationToken cancellationToken)
-        {
-            Debug.Assert(_useAsyncIO);
-            Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!");
-
-            // If the buffer is already flushed, don't spin up the OS write
-            if (_writePos == 0) return Task.CompletedTask;
-
-            Task flushTask = WriteInternalCoreAsync(GetBuffer(), 0, _writePos, cancellationToken);
-            _writePos = 0;
-
-            // Update the active buffer operation
-            _activeBufferOperation = HasActiveBufferOperation ?
-                Task.WhenAll(_activeBufferOperation, flushTask) :
-                flushTask;
-
-            return flushTask;
-        }
-
-        // 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 FlushWriteBuffer(bool calledFromFinalizer = false)
-        {
-            if (_writePos == 0) return;
-            Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWrite!");
-
-            if (_useAsyncIO)
-            {
-                Task writeTask = FlushWriteAsync(CancellationToken.None);
-                // 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 don'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)
-                {
-                    writeTask.GetAwaiter().GetResult();
-                }
-            }
-            else
-            {
-                WriteCore(GetBuffer(), 0, _writePos);
-            }
-
-            _writePos = 0;
-        }
-
-        private void SetLengthInternal(long value)
-        {
-            // Handle buffering updates.
-            if (_writePos > 0)
-            {
-                FlushWriteBuffer();
-            }
-            else if (_readPos < _readLength)
-            {
-                FlushReadBuffer();
-            }
-            _readPos = 0;
-            _readLength = 0;
-
-            if (_appendStart != -1 && value < _appendStart)
-                throw new IOException(SR.IO_SetLengthAppendTruncate);
-            SetLengthCore(value);
-        }
-
-        // We absolutely need this method broken out so that WriteInternalCoreAsync can call
-        // a method without having to go through buffering code that might call FlushWrite.
-        private void SetLengthCore(long value)
-        {
-            Debug.Assert(value >= 0, "value >= 0");
-            long origPos = _filePosition;
-
-            VerifyOSHandlePosition();
-            if (_filePosition != value)
-                SeekCore(value, SeekOrigin.Begin);
-            if (!Interop.Kernel32.SetEndOfFile(_fileHandle))
-            {
-                int errorCode = Marshal.GetLastWin32Error();
-                if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER)
-                    throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_FileLengthTooBig);
-                throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-            }
-            // 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);
-            }
-        }
-
-        // Instance method to help code external to this MarshalByRefObject avoid
-        // accessing its fields by ref.  This avoids a compiler warning.
-        private FileStreamCompletionSource CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource newSource, FileStreamCompletionSource existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource);
-
-        public override int Read(byte[] array, int offset, int count)
-        {
-            ValidateReadWriteArgs(array, offset, count);
-            return ReadCore(array, offset, count);
-        }
-
-        private int ReadCore(byte[] array, int offset, int count)
-        {
-            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength),
-                "We're either reading or writing, but not both.");
-
-            bool isBlocked = false;
-            int n = _readLength - _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) throw Error.GetReadNotSupported();
-                if (_writePos > 0) FlushWriteBuffer();
-                if (!CanSeek || (count >= _bufferLength))
-                {
-                    n = ReadNative(array, offset, count);
-                    // Throw away read buffer.
-                    _readPos = 0;
-                    _readLength = 0;
-                    return n;
-                }
-                n = ReadNative(GetBuffer(), 0, _bufferLength);
-                if (n == 0) return 0;
-                isBlocked = n < _bufferLength;
-                _readPos = 0;
-                _readLength = n;
-            }
-            // Now copy min of count or numBytesAvailable (i.e. near EOF) to array.
-            if (n > count) n = count;
-            Buffer.BlockCopy(GetBuffer(), _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 (i.e. we're 
-                // probably blocked), don't ask for more bytes.
-                if (n < count && !isBlocked)
-                {
-                    Debug.Assert(_readPos == _readLength, "Read buffer should be empty!");
-                    int moreBytesRead = ReadNative(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;
-                    _readLength = 0;
-                }
-            }
-
-            return n;
-        }
-
-        [Conditional("DEBUG")]
-        private void AssertCanRead(byte[] buffer, int offset, int count)
-        {
-            Debug.Assert(!_fileHandle.IsClosed, "!_fileHandle.IsClosed");
-            Debug.Assert(CanRead, "CanRead");
-            Debug.Assert(buffer != null, "buffer != null");
-            Debug.Assert(_writePos == 0, "_writePos == 0");
-            Debug.Assert(offset >= 0, "offset is negative");
-            Debug.Assert(count >= 0, "count is negative");
-        }
-
-        private unsafe int ReadNative(byte[] buffer, int offset, int count)
-        {
-            AssertCanRead(buffer, offset, count);
-
-            if (_useAsyncIO)
-                return ReadNativeAsync(buffer, offset, count, 0, CancellationToken.None).GetAwaiter().GetResult();
-
-            // Make sure we are reading from the right spot
-            VerifyOSHandlePosition();
-
-            int errorCode = 0;
-            int r = ReadFileNative(_fileHandle, buffer, offset, count, null, out errorCode);
-
-            if (r == -1)
-            {
-                // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe.
-                if (errorCode == ERROR_BROKEN_PIPE)
-                {
-                    r = 0;
-                }
-                else
-                {
-                    if (errorCode == ERROR_INVALID_PARAMETER)
-                        throw new ArgumentException(SR.Arg_HandleNotSync, "_fileHandle");
-
-                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-                }
-            }
-            Debug.Assert(r >= 0, "FileStream's ReadNative is likely broken.");
-            _filePosition += r;
-
-            return r;
-        }
-
-        public override long Seek(long offset, SeekOrigin origin)
-        {
-            if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
-                throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
-            if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
-            if (!CanSeek) throw Error.GetSeekNotSupported();
-
-            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "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)
-            {
-                FlushWriteBuffer();
-            }
-            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 -= (_readLength - _readPos);
-            }
-            _readPos = _readLength = 0;
-
-            // Verify that internal position is in sync with the handle
-            VerifyOSHandlePosition();
-
-            long oldPos = _filePosition + (_readPos - _readLength);
-            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(SR.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 (_readLength > 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.BlockCopy(GetBuffer(), _readPos, GetBuffer(), 0, _readLength - _readPos);
-                        _readLength -= _readPos;
-                        _readPos = 0;
-                    }
-                    // If we still have buffered data, we must update the stream's 
-                    // position so our Position property is correct.
-                    if (_readLength > 0)
-                        SeekCore(_readLength, SeekOrigin.Current);
-                }
-                else if (oldPos - _readPos < pos && pos < oldPos + _readLength - _readPos)
-                {
-                    int diff = (int)(pos - oldPos);
-                    //Console.WriteLine("Seek: diff was "+diff+", readpos was "+_readPos+"  adjusting buffer - shrinking by "+ (_readPos + diff));
-                    Buffer.BlockCopy(GetBuffer(), _readPos + diff, GetBuffer(), 0, _readLength - (_readPos + diff));
-                    _readLength -= (_readPos + diff);
-                    _readPos = 0;
-                    if (_readLength > 0)
-                        SeekCore(_readLength, SeekOrigin.Current);
-                }
-                else
-                {
-                    // Lose the read buffer.
-                    _readPos = 0;
-                    _readLength = 0;
-                }
-                Debug.Assert(_readLength >= 0 && _readPos <= _readLength, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen");
-                Debug.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
-        // This is called during construction so it should avoid any virtual
-        // calls
-        private long SeekCore(long offset, SeekOrigin origin)
-        {
-            Debug.Assert(!_fileHandle.IsClosed && _canSeek, "!_handle.IsClosed && _parent.CanSeek");
-            Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End");
-            long ret = 0;
-
-            if (!Interop.Kernel32.SetFilePointerEx(_fileHandle, offset, out ret, (uint)origin))
-            {
-                int errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
-                throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-            }
-
-            _filePosition = ret;
-            return ret;
-        }
-
-        partial void OnBufferAllocated()
-        {
-            Debug.Assert(_buffer != null);
-            Debug.Assert(_preallocatedOverlapped == null);
-
-            if (_useAsyncIO)
-                _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer);
-        }
-
-        public override void Write(byte[] array, int offset, int count)
-        {
-            ValidateReadWriteArgs(array, offset, count);
-
-            if (_writePos == 0)
-            {
-                // Ensure we can write to the stream, and ready buffer for writing.
-                if (!CanWrite) throw Error.GetWriteNotSupported();
-                if (_readPos < _readLength) FlushReadBuffer();
-                _readPos = 0;
-                _readLength = 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 (i.e. write 1 byte, then write for the buffer 
-            // size repeatedly)
-            if (_writePos > 0)
-            {
-                int numBytes = _bufferLength - _writePos;   // space left in buffer
-                if (numBytes > 0)
-                {
-                    if (numBytes > count)
-                        numBytes = count;
-                    Buffer.BlockCopy(array, offset, GetBuffer(), _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 (_useAsyncIO)
-                {
-                    WriteInternalCoreAsync(GetBuffer(), 0, _writePos, CancellationToken.None).GetAwaiter().GetResult();
-                }
-                else
-                {
-                    WriteCore(GetBuffer(), 0, _writePos);
-                }
-                _writePos = 0;
-            }
-            // If the buffer would slow writes down, avoid buffer completely.
-            if (count >= _bufferLength)
-            {
-                Debug.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.
-            }
-
-            // Copy remaining bytes into buffer, to write at a later date.
-            Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count);
-            _writePos = count;
-            return;
-        }
-
-        private unsafe void WriteCore(byte[] buffer, int offset, int count)
-        {
-            Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
-            Debug.Assert(CanWrite, "_parent.CanWrite");
-
-            Debug.Assert(buffer != null, "buffer != null");
-            Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
-            Debug.Assert(offset >= 0, "offset is negative");
-            Debug.Assert(count >= 0, "count is negative");
-            if (_useAsyncIO)
-            {
-                WriteInternalCoreAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
-                return;
-            }
-
-            // Make sure we are writing to the position that we think we are
-            VerifyOSHandlePosition();
-
-            int errorCode = 0;
-            int r = WriteFileNative(_fileHandle, buffer, offset, count, null, out errorCode);
-
-            if (r == -1)
-            {
-                // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
-                if (errorCode == ERROR_NO_DATA)
-                {
-                    r = 0;
-                }
-                else
-                {
-                    // ERROR_INVALID_PARAMETER may be returned for writes
-                    // where the position is too large (i.e. writing at Int64.MaxValue 
-                    // on Win9x) OR for synchronous writes to a handle opened 
-                    // asynchronously.
-                    if (errorCode == ERROR_INVALID_PARAMETER)
-                        throw new IOException(SR.IO_FileTooLongOrHandleNotSync);
-                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-                }
-            }
-            Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken.");
-            _filePosition += r;
-            return;
-        }
-
-        private Task<int> ReadAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken)
-        {
-            // If async IO is not supported on this platform or 
-            // if this Win32FileStream was not opened with FileOptions.Asynchronous.
-            if (!_useAsyncIO)
-            {
-                return base.ReadAsync(array, offset, numBytes, cancellationToken);
-            }
-
-            if (!CanRead) throw Error.GetReadNotSupported();
-
-            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
-
-            if (_isPipe)
-            {
-                // Pipes are tricky, at least when you have 2 different pipes
-                // that you want to use simultaneously.  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 
-                // Win32FileStream's ReadAsync 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 < _readLength)
-                {
-                    int n = _readLength - _readPos;
-                    if (n > numBytes) n = numBytes;
-                    Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n);
-                    _readPos += n;
-
-                    // Return a completed task
-                    return TaskFromResultOrCache(n);
-                }
-                else
-                {
-                    Debug.Assert(_writePos == 0, "Win32FileStream must not have buffered write data here!  Pipes should be unidirectional.");
-                    return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken);
-                }
-            }
-
-            Debug.Assert(!_isPipe, "Should not be a pipe.");
-
-            // Handle buffering.
-            if (_writePos > 0) FlushWriteBuffer();
-            if (_readPos == _readLength)
-            {
-                // 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 < _bufferLength)
-                {
-                    Task<int> readTask = ReadNativeAsync(GetBuffer(), 0, _bufferLength, 0, cancellationToken);
-                    _readLength = readTask.GetAwaiter().GetResult();
-                    int n = _readLength;
-                    if (n > numBytes) n = numBytes;
-                    Buffer.BlockCopy(GetBuffer(), 0, array, offset, n);
-                    _readPos = n;
-
-                    // Return a completed task (recycling the one above if possible)
-                    return (_readLength == n ? readTask : TaskFromResultOrCache(n));
-                }
-                else
-                {
-                    // Here we're making our position pointer inconsistent
-                    // with our read buffer.  Throw away the read buffer's contents.
-                    _readPos = 0;
-                    _readLength = 0;
-                    return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken);
-                }
-            }
-            else
-            {
-                int n = _readLength - _readPos;
-                if (n > numBytes) n = numBytes;
-                Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n);
-                _readPos += n;
-
-                if (n >= numBytes)
-                {
-                    // Return a completed task
-                    return TaskFromResultOrCache(n);
-                }
-                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;
-                    _readLength = 0;
-                    return ReadNativeAsync(array, offset + n, numBytes - n, n, cancellationToken);
-                }
-            }
-        }
-
-        unsafe private Task<int> ReadNativeAsync(byte[] bytes, int offset, int numBytes, int numBufferedBytesRead, CancellationToken cancellationToken)
-        {
-            AssertCanRead(bytes, offset, numBytes);
-            Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!");
-
-            // Create and store async stream class library specific data in the async result
-
-            FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, numBufferedBytesRead, bytes, cancellationToken);
-            NativeOverlapped* intOverlapped = completionSource.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
-                VerifyOSHandlePosition();
-
-                if (_filePosition + numBytes > len)
-                {
-                    if (_filePosition <= len)
-                        numBytes = (int)(len - _filePosition);
-                    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)_filePosition);
-                intOverlapped->OffsetHigh = (int)(_filePosition >> 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);
-            }
-
-            // queue an async ReadFile operation and pass in a packed overlapped
-            int errorCode = 0;
-            int r = ReadFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
-            // 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/ errorCode==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 (errorCode == 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;
-                    completionSource.SetCompletedSynchronously(0);
-                }
-                else if (errorCode != ERROR_IO_PENDING)
-                {
-                    if (!_fileHandle.IsClosed && CanSeek)  // Update Position - It could be anywhere.
-                    {
-                        SeekCore(0, SeekOrigin.Current);
-                    }
-
-                    completionSource.ReleaseNativeResource();
-
-                    if (errorCode == ERROR_HANDLE_EOF)
-                    {
-                        throw Error.GetEndOfFile();
-                    }
-                    else
-                    {
-                        throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-                    }
-                }
-                else
-                {
-                    // Only once the IO is pending do we register for cancellation
-                    completionSource.RegisterForCancellation();
-                }
-            }
-            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 completionSource.Task;
-        }
-
-        // Reads a byte from the file stream.  Returns the byte cast to an int
-        // or -1 if reading from the end of the stream.
-        public override int ReadByte()
-        {
-            return ReadByteCore();
-        }
-
-        private Task WriteAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken)
-        {
-            // If async IO is not supported on this platform or 
-            // if this Win32FileStream was not opened with FileOptions.Asynchronous.
-            if (!_useAsyncIO)
-            {
-                return base.WriteAsync(array, offset, numBytes, cancellationToken);
-            }
-
-            if (!CanWrite) throw Error.GetWriteNotSupported();
-
-            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
-            Debug.Assert(!_isPipe || (_readPos == 0 && _readLength == 0), "Win32FileStream must not have buffered data here!  Pipes should be unidirectional.");
-
-            bool writeDataStoredInBuffer = false;
-            if (!_isPipe) // avoid async buffering with pipes, as doing so can lead to deadlocks (see comments in ReadInternalAsyncCore)
-            {
-                // Ensure the buffer is clear for writing
-                if (_writePos == 0)
-                {
-                    if (_readPos < _readLength)
-                    {
-                        FlushReadBuffer();
-                    }
-                    _readPos = 0;
-                    _readLength = 0;
-                }
-
-                // Determine how much space remains in the buffer
-                int remainingBuffer = _bufferLength - _writePos;
-                Debug.Assert(remainingBuffer >= 0);
-
-                // Simple/common case:
-                // - The write is smaller than our buffer, such that it's worth considering buffering it.
-                // - There's no active flush operation, such that we don't have to worry about the existing buffer being in use.
-                // - And the data we're trying to write fits in the buffer, meaning it wasn't already filled by previous writes.
-                // In that case, just store it in the buffer.
-                if (numBytes < _bufferLength && !HasActiveBufferOperation && numBytes <= remainingBuffer)
-                {
-                    Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, numBytes);
-                    _writePos += numBytes;
-                    writeDataStoredInBuffer = true;
-
-                    // There is one special-but-common case, common because devs often use
-                    // byte[] sizes that are powers of 2 and thus fit nicely into our buffer, which is
-                    // also a power of 2. If after our write the buffer still has remaining space,
-                    // then we're done and can return a completed task now.  But if we filled the buffer
-                    // completely, we want to do the asynchronous flush/write as part of this operation 
-                    // rather than waiting until the next write that fills the buffer.
-                    if (numBytes != remainingBuffer)
-                        return Task.CompletedTask;
-
-                    Debug.Assert(_writePos == _bufferLength);
-                }
-            }
-
-            // At this point, at least one of the following is true:
-            // 1. There was an active flush operation (it could have completed by now, though).
-            // 2. The data doesn't fit in the remaining buffer (or it's a pipe and we chose not to try).
-            // 3. We wrote all of the data to the buffer, filling it.
-            //
-            // If there's an active operation, we can't touch the current buffer because it's in use.
-            // That gives us a choice: we can either allocate a new buffer, or we can skip the buffer
-            // entirely (even if the data would otherwise fit in it).  For now, for simplicity, we do
-            // the latter; it could also have performance wins due to OS-level optimizations, and we could
-            // potentially add support for PreAllocatedOverlapped due to having a single buffer. (We can
-            // switch to allocating a new buffer, potentially experimenting with buffer pooling, should
-            // performance data suggest it's appropriate.)
-            //
-            // If the data doesn't fit in the remaining buffer, it could be because it's so large
-            // it's greater than the entire buffer size, in which case we'd always skip the buffer,
-            // or it could be because there's more data than just the space remaining.  For the latter
-            // case, we need to issue an asynchronous write to flush that data, which then turns this into
-            // the first case above with an active operation.
-            //
-            // If we already stored the data, then we have nothing additional to write beyond what
-            // we need to flush.
-            //
-            // In any of these cases, we have the same outcome:
-            // - If there's data in the buffer, flush it by writing it out asynchronously.
-            // - Then, if there's any data to be written, issue a write for it concurrently.
-            // We return a Task that represents one or both.
-
-            // Flush the buffer asynchronously if there's anything to flush
-            Task flushTask = null;
-            if (_writePos > 0)
-            {
-                flushTask = FlushWriteAsync(cancellationToken);
-
-                // If we already copied all of the data into the buffer,
-                // simply return the flush task here.  Same goes for if the task has 
-                // already completed and was unsuccessful.
-                if (writeDataStoredInBuffer ||
-                    flushTask.IsFaulted ||
-                    flushTask.IsCanceled)
-                {
-                    return flushTask;
-                }
-            }
-
-            Debug.Assert(!writeDataStoredInBuffer);
-            Debug.Assert(_writePos == 0);
-
-            // Finally, issue the write asynchronously, and return a Task that logically
-            // represents the write operation, including any flushing done.
-            Task writeTask = WriteInternalCoreAsync(array, offset, numBytes, cancellationToken);
-            return
-                (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask :
-                (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask :
-                Task.WhenAll(flushTask, writeTask);
-        }
-
-        private unsafe Task WriteInternalCoreAsync(byte[] bytes, int offset, int numBytes, CancellationToken cancellationToken)
-        {
-            Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
-            Debug.Assert(CanWrite, "_parent.CanWrite");
-            Debug.Assert(bytes != null, "bytes != null");
-            Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
-            Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!");
-            Debug.Assert(offset >= 0, "offset is negative");
-            Debug.Assert(numBytes >= 0, "numBytes is negative");
-
-            // Create and store async stream class library specific data in the async result
-            FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, 0, bytes, cancellationToken);
-            NativeOverlapped* intOverlapped = completionSource.Overlapped;
-
-            if (CanSeek)
-            {
-                // Make sure we set the length of the file appropriately.
-                long len = Length;
-                //Console.WriteLine("WriteInternalCoreAsync - Calculating end pos.  pos: "+pos+"  len: "+len+"  numBytes: "+numBytes);
-
-                // Make sure we are writing to the position that we think we are
-                VerifyOSHandlePosition();
-
-                if (_filePosition + numBytes > len)
-                {
-                    //Console.WriteLine("WriteInternalCoreAsync - Setting length to: "+(pos + numBytes));
-                    SetLengthCore(_filePosition + 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)_filePosition;
-                intOverlapped->OffsetHigh = (int)(_filePosition >> 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("WriteInternalCoreAsync finishing.  pos: "+pos+"  numBytes: "+numBytes+"  _pos: "+_pos+"  Position: "+Position);
-
-            int errorCode = 0;
-            // queue an async WriteFile operation and pass in a packed overlapped
-            int r = WriteFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
-
-            // 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/ errorCode==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 errorCode==3e5)  errorCode: 0x{0:x}", errorCode);
-
-                // For pipes, when they are closed on the other side, they will come here.
-                if (errorCode == ERROR_NO_DATA)
-                {
-                    // Not an error, but EOF. AsyncFSCallback will NOT be called.
-                    // Completing TCS and return cached task allowing the GC to collect TCS.
-                    completionSource.SetCompletedSynchronously(0);
-                    return Task.CompletedTask;
-                }
-                else if (errorCode != ERROR_IO_PENDING)
-                {
-                    if (!_fileHandle.IsClosed && CanSeek)  // Update Position - It could be anywhere.
-                    {
-                        SeekCore(0, SeekOrigin.Current);
-                    }
-
-                    completionSource.ReleaseNativeResource();
-
-                    if (errorCode == ERROR_HANDLE_EOF)
-                    {
-                        throw Error.GetEndOfFile();
-                    }
-                    else
-                    {
-                        throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-                    }
-                }
-                else // ERROR_IO_PENDING
-                {
-                    // Only once the IO is pending do we register for cancellation
-                    completionSource.RegisterForCancellation();
-                }
-            }
-            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 completionSource.Task;
-        }
-
-        public override void WriteByte(byte value)
-        {
-            WriteByteCore(value);
-        }
-
-        // 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. 
-        private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode)
-        {
-            Debug.Assert(handle != null, "handle != null");
-            Debug.Assert(offset >= 0, "offset >= 0");
-            Debug.Assert(count >= 0, "count >= 0");
-            Debug.Assert(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(SR.IndexOutOfRange_IORaceCondition);
-
-            Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to ReadFileNative.");
-
-            // You can't use the fixed statement on an array of length 0.
-            if (bytes.Length == 0)
-            {
-                errorCode = 0;
-                return 0;
-            }
-
-            int r = 0;
-            int numBytesRead = 0;
-
-            fixed (byte* p = &bytes[0])
-            {
-                if (_useAsyncIO)
-                    r = Interop.Kernel32.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped);
-                else
-                    r = Interop.Kernel32.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero);
-            }
-
-            if (r == 0)
-            {
-                errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
-                return -1;
-            }
-            else
-            {
-                errorCode = 0;
-                return numBytesRead;
-            }
-        }
-
-        private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode)
-        {
-            Debug.Assert(handle != null, "handle != null");
-            Debug.Assert(offset >= 0, "offset >= 0");
-            Debug.Assert(count >= 0, "count >= 0");
-            Debug.Assert(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(SR.IndexOutOfRange_IORaceCondition);
-
-            Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to WriteFileNative.");
-
-            // You can't use the fixed statement on an array of length 0.
-            if (bytes.Length == 0)
-            {
-                errorCode = 0;
-                return 0;
-            }
-
-            int numBytesWritten = 0;
-            int r = 0;
-
-            fixed (byte* p = &bytes[0])
-            {
-                if (_useAsyncIO)
-                    r = Interop.Kernel32.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped);
-                else
-                    r = Interop.Kernel32.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero);
-            }
-
-            if (r == 0)
-            {
-                errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
-                return -1;
-            }
-            else
-            {
-                errorCode = 0;
-                return numBytesWritten;
-            }
-        }
-
-        private int GetLastWin32ErrorAndDisposeHandleIfInvalid(bool throwIfInvalidHandle = false)
-        {
-            int errorCode = Marshal.GetLastWin32Error();
-
-            // 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 Win32FileStream
-            // 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 _parent.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 (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
-            {
-                _fileHandle.Dispose();
-
-                if (throwIfInvalidHandle)
-                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-            }
-
-            return errorCode;
-        }
-
-        public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
-        {
-            // If we're in sync mode, just use the shared CopyToAsync implementation that does
-            // typical read/write looping.  We also need to take this path if this is a derived
-            // instance from FileStream, as a derived type could have overridden ReadAsync, in which
-            // case our custom CopyToAsync implementation isn't necessarily correct.
-            if (!_useAsyncIO || GetType() != typeof(FileStream))
-            {
-                return base.CopyToAsync(destination, bufferSize, cancellationToken);
-            }
-
-            StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize);
-
-            // Bail early for cancellation if cancellation has been requested
-            if (cancellationToken.IsCancellationRequested)
-            {
-                return Task.FromCanceled<int>(cancellationToken);
-            }
-
-            // Fail if the file was closed
-            if (_fileHandle.IsClosed)
-            {
-                throw Error.GetFileNotOpen();
-            }
-
-            // Do the async copy, with differing implementations based on whether the FileStream was opened as async or sync
-            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
-            return AsyncModeCopyToAsync(destination, bufferSize, cancellationToken);
-        }
-
-        private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
-        {
-            Debug.Assert(_useAsyncIO, "This implementation is for async mode only");
-            Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
-            Debug.Assert(CanRead, "_parent.CanRead");
-
-            // Make sure any pending writes have been flushed before we do a read.
-            if (_writePos > 0)
-            {
-                await FlushWriteAsync(cancellationToken).ConfigureAwait(false);
-            }
-
-            // Typically CopyToAsync would be invoked as the only "read" on the stream, but it's possible some reading is
-            // done and then the CopyToAsync is issued.  For that case, see if we have any data available in the buffer.
-            if (GetBuffer() != null)
-            {
-                int bufferedBytes = _readLength - _readPos;
-                if (bufferedBytes > 0)
-                {
-                    await destination.WriteAsync(GetBuffer(), _readPos, bufferedBytes, cancellationToken).ConfigureAwait(false);
-                    _readPos = _readLength = 0;
-                }
-            }
-
-            // For efficiency, we avoid creating a new task and associated state for each asynchronous read.
-            // Instead, we create a single reusable awaitable object that will be triggered when an await completes
-            // and reset before going again.
-            var readAwaitable = new AsyncCopyToAwaitable(this);
-
-            // Make sure we are reading from the position that we think we are.
-            // Only set the position in the awaitable if we can seek (e.g. not for pipes).
-            bool canSeek = CanSeek;
-            if (canSeek)
-            {
-                VerifyOSHandlePosition();
-                readAwaitable._position = _filePosition;
-            }
-
-            // Get the buffer to use for the copy operation, as the base CopyToAsync does. We don't try to use
-            // _buffer here, even if it's not null, as concurrent operations are allowed, and another operation may
-            // actually be using the buffer already. Plus, it'll be rare for _buffer to be non-null, as typically
-            // CopyToAsync is used as the only operation performed on the stream, and the buffer is lazily initialized.
-            // Further, typically the CopyToAsync buffer size will be larger than that used by the FileStream, such that
-            // we'd likely be unable to use it anyway.  Instead, we rent the buffer from a pool.
-            byte[] copyBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
-            bufferSize = 0; // repurpose bufferSize to be the high water mark for the buffer, to avoid an extra field in the state machine
-
-            // Allocate an Overlapped we can use repeatedly for all operations
-            var awaitableOverlapped = new PreAllocatedOverlapped(AsyncCopyToAwaitable.s_callback, readAwaitable, copyBuffer);
-            var cancellationReg = default(CancellationTokenRegistration);
-            try
-            {
-                // Register for cancellation.  We do this once for the whole copy operation, and just try to cancel
-                // whatever read operation may currently be in progress, if there is one.  It's possible the cancellation
-                // request could come in between operations, in which case we flag that with explicit calls to ThrowIfCancellationRequested
-                // in the read/write copy loop.
-                if (cancellationToken.CanBeCanceled)
-                {
-                    cancellationReg = cancellationToken.Register(s =>
-                    {
-                        var innerAwaitable = (AsyncCopyToAwaitable)s;
-                        unsafe
-                        {
-                            lock (innerAwaitable.CancellationLock) // synchronize with cleanup of the overlapped
-                            {
-                                if (innerAwaitable._nativeOverlapped != null)
-                                {
-                                    // Try to cancel the I/O.  We ignore the return value, as cancellation is opportunistic and we
-                                    // don't want to fail the operation because we couldn't cancel it.
-                                    Interop.Kernel32.CancelIoEx(innerAwaitable._fileStream._fileHandle, innerAwaitable._nativeOverlapped);
-                                }
-                            }
-                        }
-                    }, readAwaitable);
-                }
-
-                // Repeatedly read from this FileStream and write the results to the destination stream.
-                while (true)
-                {
-                    cancellationToken.ThrowIfCancellationRequested();
-                    readAwaitable.ResetForNextOperation();
-
-                    try
-                    {
-                        bool synchronousSuccess;
-                        int errorCode;
-                        unsafe
-                        {
-                            // Allocate a native overlapped for our reusable overlapped, and set position to read based on the next
-                            // desired address stored in the awaitable.  (This position may be 0, if either we're at the beginning or
-                            // if the stream isn't seekable.)
-                            readAwaitable._nativeOverlapped = _fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(awaitableOverlapped);
-                            if (canSeek)
-                            {
-                                readAwaitable._nativeOverlapped->OffsetLow = unchecked((int)readAwaitable._position);
-                                readAwaitable._nativeOverlapped->OffsetHigh = (int)(readAwaitable._position >> 32);
-                            }
-
-                            // Kick off the read.
-                            synchronousSuccess = ReadFileNative(_fileHandle, copyBuffer, 0, copyBuffer.Length, readAwaitable._nativeOverlapped, out errorCode) >= 0;
-                        }
-
-                        // If the operation did not synchronously succeed, it either failed or initiated the asynchronous operation.
-                        if (!synchronousSuccess)
-                        {
-                            switch (errorCode)
-                            {
-                                case ERROR_IO_PENDING:
-                                    // Async operation in progress.
-                                    break;
-                                case ERROR_BROKEN_PIPE:
-                                case ERROR_HANDLE_EOF:
-                                    // We're at or past the end of the file, and the overlapped callback
-                                    // won't be raised in these cases. Mark it as completed so that the await
-                                    // below will see it as such.
-                                    readAwaitable.MarkCompleted();
-                                    break;
-                                default:
-                                    // Everything else is an error (and there won't be a callback).
-                                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-                            }
-                        }
-
-                        // Wait for the async operation (which may or may not have already completed), then throw if it failed.
-                        await readAwaitable;
-                        switch (readAwaitable._errorCode)
-                        {
-                            case 0: // success
-                                Debug.Assert(readAwaitable._numBytes >= 0, $"Expected non-negative numBytes, got {readAwaitable._numBytes}");
-                                break;
-                            case ERROR_BROKEN_PIPE: // logically success with 0 bytes read (write end of pipe closed)
-                            case ERROR_HANDLE_EOF:  // logically success with 0 bytes read (read at end of file)
-                                Debug.Assert(readAwaitable._numBytes == 0, $"Expected 0 bytes read, got {readAwaitable._numBytes}");
-                                break;
-                            case Interop.Errors.ERROR_OPERATION_ABORTED: // canceled
-                                throw new OperationCanceledException(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true));
-                            default: // error
-                                throw Win32Marshal.GetExceptionForWin32Error((int)readAwaitable._errorCode);
-                        }
-
-                        // Successful operation.  If we got zero bytes, we're done: exit the read/write loop.
-                        int numBytesRead = (int)readAwaitable._numBytes;
-                        if (numBytesRead == 0)
-                        {
-                            break;
-                        }
-
-                        // Otherwise, update the read position for next time accordingly.
-                        if (canSeek)
-                        {
-                            readAwaitable._position += numBytesRead;
-                        }
-
-                        // (and keep track of the maximum number of bytes in the buffer we used, to avoid excessive and unnecessary
-                        // clearing of the buffer before we return it to the pool)
-                        if (numBytesRead > bufferSize)
-                        {
-                            bufferSize = numBytesRead;
-                        }
-                    }
-                    finally
-                    {
-                        // Free the resources for this read operation
-                        unsafe
-                        {
-                            NativeOverlapped* overlapped;
-                            lock (readAwaitable.CancellationLock) // just an Exchange, but we need this to be synchronized with cancellation, so using the same lock
-                            {
-                                overlapped = readAwaitable._nativeOverlapped;
-                                readAwaitable._nativeOverlapped = null;
-                            }
-                            if (overlapped != null)
-                            {
-                                _fileHandle.ThreadPoolBinding.FreeNativeOverlapped(overlapped);
-                            }
-                        }
-                    }
-
-                    // Write out the read data.
-                    await destination.WriteAsync(copyBuffer, 0, (int)readAwaitable._numBytes, cancellationToken).ConfigureAwait(false);
-                }
-            }
-            finally
-            {
-                // Cleanup from the whole copy operation
-                cancellationReg.Dispose();
-                awaitableOverlapped.Dispose();
-
-                Array.Clear(copyBuffer, 0, bufferSize);
-                ArrayPool<byte>.Shared.Return(copyBuffer, clearArray: false);
-
-                // Make sure the stream's current position reflects where we ended up
-                if (!_fileHandle.IsClosed && CanSeek)
-                {
-                    SeekCore(0, SeekOrigin.End);
-                }
-            }
-        }
-
-        /// <summary>Used by CopyToAsync to enable awaiting the result of an overlapped I/O operation with minimal overhead.</summary>
-        private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion
-        {
-            /// <summary>Sentinel object used to indicate that the I/O operation has completed before being awaited.</summary>
-            private readonly static Action s_sentinel = () => { };
-            /// <summary>Cached delegate to IOCallback.</summary>
-            internal static readonly IOCompletionCallback s_callback = IOCallback;
-
-            /// <summary>The FileStream that owns this instance.</summary>
-            internal readonly FileStream _fileStream;
-
-            /// <summary>Tracked position representing the next location from which to read.</summary>
-            internal long _position;
-            /// <summary>The current native overlapped pointer.  This changes for each operation.</summary>
-            internal NativeOverlapped* _nativeOverlapped;
-            /// <summary>
-            /// null if the operation is still in progress,
-            /// s_sentinel if the I/O operation completed before the await,
-            /// s_callback if it completed after the await yielded.
-            /// </summary>
-            internal Action _continuation;
-            /// <summary>Last error code from completed operation.</summary>
-            internal uint _errorCode;
-            /// <summary>Last number of read bytes from completed operation.</summary>
-            internal uint _numBytes;
-
-            /// <summary>Lock object used to protect cancellation-related access to _nativeOverlapped.</summary>
-            internal object CancellationLock => this;
-
-            /// <summary>Initialize the awaitable.</summary>
-            internal unsafe AsyncCopyToAwaitable(FileStream fileStream)
-            {
-                _fileStream = fileStream;
-            }
-
-            /// <summary>Reset state to prepare for the next read operation.</summary>
-            internal void ResetForNextOperation()
-            {
-                Debug.Assert(_position >= 0, $"Expected non-negative position, got {_position}");
-                _continuation = null;
-                _errorCode = 0;
-                _numBytes = 0;
-            }
-
-            /// <summary>Overlapped callback: store the results, then invoke the continuation delegate.</summary>
-            internal unsafe static void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP)
-            {
-                var awaitable = (AsyncCopyToAwaitable)ThreadPoolBoundHandle.GetNativeOverlappedState(pOVERLAP);
-
-                Debug.Assert(awaitable._continuation != s_sentinel, "Sentinel must not have already been set as the continuation");
-                awaitable._errorCode = errorCode;
-                awaitable._numBytes = numBytes;
-
-                (awaitable._continuation ?? Interlocked.CompareExchange(ref awaitable._continuation, s_sentinel, null))?.Invoke();
-            }
-
-            /// <summary>
-            /// Called when it's known that the I/O callback for an operation will not be invoked but we'll
-            /// still be awaiting the awaitable.
-            /// </summary>
-            internal void MarkCompleted()
-            {
-                Debug.Assert(_continuation == null, "Expected null continuation");
-                _continuation = s_sentinel;
-            }
-
-            public AsyncCopyToAwaitable GetAwaiter() => this;
-            public bool IsCompleted => _continuation == s_sentinel;
-            public void GetResult() { }
-            public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation);
-            public void UnsafeOnCompleted(Action continuation)
-            {
-                if (_continuation == s_sentinel ||
-                    Interlocked.CompareExchange(ref _continuation, continuation, null) != null)
-                {
-                    Debug.Assert(_continuation == s_sentinel, $"Expected continuation set to s_sentinel, got ${_continuation}");
-                    Task.Run(continuation);
-                }
-            }
-        }
-
-        // 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.
-        private Task FlushAsyncInternal(CancellationToken cancellationToken)
-        {
-            if (cancellationToken.IsCancellationRequested)
-                return Task.FromCanceled(cancellationToken);
-
-            if (_fileHandle.IsClosed)
-                throw Error.GetFileNotOpen();
-
-            // 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;
-            }
-        }
-
-        private Task<int> TaskFromResultOrCache(int result)
-        {
-            Task<int> completedTask = _lastSynchronouslyCompletedTask;
-            Debug.Assert(completedTask == null || completedTask.Status == TaskStatus.RanToCompletion, "Cached task should have completed successfully");
-
-            if ((completedTask == null) || (completedTask.Result != result))
-            {
-                completedTask = Task.FromResult(result);
-                _lastSynchronouslyCompletedTask = completedTask;
-            }
-
-            return completedTask;
-        }
-
-        private void LockInternal(long position, long length)
-        {
-            int positionLow = unchecked((int)(position));
-            int positionHigh = unchecked((int)(position >> 32));
-            int lengthLow = unchecked((int)(length));
-            int lengthHigh = unchecked((int)(length >> 32));
-
-            if (!Interop.Kernel32.LockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
-            {
-                throw Win32Marshal.GetExceptionForLastWin32Error();
-            }
-        }
-
-        private void UnlockInternal(long position, long length)
-        {
-            int positionLow = unchecked((int)(position));
-            int positionHigh = unchecked((int)(position >> 32));
-            int lengthLow = unchecked((int)(length));
-            int lengthHigh = unchecked((int)(length >> 32));
-
-            if (!Interop.Kernel32.UnlockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
-            {
-                throw Win32Marshal.GetExceptionForLastWin32Error();
-            }
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/FileStream.cs b/src/mscorlib/corefx/System/IO/FileStream.cs
deleted file mode 100644 (file)
index 7db8518..0000000
+++ /dev/null
@@ -1,684 +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.
-
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Win32.SafeHandles;
-using System.Diagnostics;
-
-namespace System.IO
-{
-    public partial class FileStream : Stream
-    {
-        private const FileShare DefaultShare = FileShare.Read;
-        private const bool DefaultIsAsync = false;
-        internal const int DefaultBufferSize = 4096;
-
-        private byte[] _buffer;
-        private int _bufferLength;
-        private readonly SafeFileHandle _fileHandle;
-
-        /// <summary>Whether the file is opened for reading, writing, or both.</summary>
-        private readonly FileAccess _access;
-
-        /// <summary>The path to the opened file.</summary>
-        private readonly string _path;
-
-        /// <summary>The next available byte to be read from the _buffer.</summary>
-        private int _readPos;
-
-        /// <summary>The number of valid bytes in _buffer.</summary>
-        private int _readLength;
-
-        /// <summary>The next location in which a write should occur to the buffer.</summary>
-        private int _writePos;
-
-        /// <summary>
-        /// Whether asynchronous read/write/flush operations should be performed using async I/O.
-        /// On Windows FileOptions.Asynchronous controls how the file handle is configured, 
-        /// and then as a result how operations are issued against that file handle.  On Unix, 
-        /// there isn't any distinction around how file descriptors are created for async vs 
-        /// sync, but we still differentiate how the operations are issued in order to provide
-        /// similar behavioral semantics and performance characteristics as on Windows.  On
-        /// Windows, if non-async, async read/write requests just delegate to the base stream,
-        /// and no attempt is made to synchronize between sync and async operations on the stream;
-        /// if async, then async read/write requests are implemented specially, and sync read/write
-        /// requests are coordinated with async ones by implementing the sync ones over the async
-        /// ones.  On Unix, we do something similar.  If non-async, async read/write requests just
-        /// delegate to the base stream, and no attempt is made to synchronize.  If async, we use
-        /// a semaphore to coordinate both sync and async operations.
-        /// </summary>
-        private readonly bool _useAsyncIO;
-
-        /// <summary>
-        /// Currently cached position in the stream.  This should always mirror the underlying file's actual position,
-        /// and should only ever be out of sync if another stream with access to this same file manipulates it, at which
-        /// point we attempt to error out.
-        /// </summary>
-        private long _filePosition;
-
-        /// <summary>Whether the file stream's handle has been exposed.</summary>
-        private bool _exposedHandle;
-
-        [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)
-        {
-        }
-
-        [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")]
-        public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
-            : this(new SafeFileHandle(handle, ownsHandle), access, bufferSize, isAsync)
-        {
-        }
-
-        public FileStream(SafeFileHandle handle, FileAccess access)
-            : this(handle, access, DefaultBufferSize)
-        {
-        }
-
-        public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
-            : this(handle, access, bufferSize, GetDefaultIsAsync(handle))
-        {
-        }
-
-        public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
-        {
-            if (handle.IsInvalid)
-                throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle));
-
-            if (access < FileAccess.Read || access > FileAccess.ReadWrite)
-                throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
-            if (bufferSize <= 0)
-                throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
-
-            if (handle.IsClosed)
-                throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
-            if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.Value)
-                throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle));
-
-            _access = access;
-            _useAsyncIO = isAsync;
-            _exposedHandle = true;
-            _bufferLength = bufferSize;
-            _fileHandle = handle;
-
-            InitFromHandle(handle);
-        }
-
-        public FileStream(string path, FileMode mode) :
-            this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), DefaultShare, DefaultBufferSize, DefaultIsAsync)
-        { }
-
-        public FileStream(string path, FileMode mode, FileAccess access) :
-            this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync)
-        { }
-
-        public FileStream(string path, FileMode mode, FileAccess access, FileShare share) :
-            this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync)
-        { }
-
-        public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) :
-            this(path, mode, access, share, bufferSize, DefaultIsAsync)
-        { }
-
-        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)
-        { }
-
-        public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
-        {
-            if (path == null)
-                throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path);
-            if (path.Length == 0)
-                throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
-
-            // 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 (access < FileAccess.Read || access > FileAccess.ReadWrite)
-                badArg = nameof(access);
-            else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
-                badArg = nameof(share);
-
-            if (badArg != null)
-                throw new ArgumentOutOfRangeException(badArg, SR.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), SR.ArgumentOutOfRange_Enum);
-
-            if (bufferSize <= 0)
-                throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
-
-            // Write access validation
-            if ((access & FileAccess.Write) == 0)
-            {
-                if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
-                {
-                    // No write access, mode and access disagree but flag access since mode comes first
-                    throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access));
-                }
-            }
-
-            if ((access & FileAccess.Read) != 0 && mode == FileMode.Append)
-                throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
-
-            string fullPath = Path.GetFullPath(path);
-
-            _path = fullPath;
-            _access = access;
-            _bufferLength = bufferSize;
-
-            if ((options & FileOptions.Asynchronous) != 0)
-                _useAsyncIO = true;
-
-            _fileHandle = OpenHandle(mode, share, options);
-
-            try
-            {
-                Init(mode, share);
-            }
-            catch
-            {
-                // If anything goes wrong while setting up the stream, make sure we deterministically dispose
-                // of the opened handle.
-                _fileHandle.Dispose();
-                _fileHandle = null;
-                throw;
-            }
-        }
-
-        private static bool GetDefaultIsAsync(SafeFileHandle handle)
-        {
-            // This will eventually get more complicated as we can actually check the underlying handle type on Windows
-            return handle.IsAsync.HasValue ? handle.IsAsync.Value : false;
-        }
-
-        [Obsolete("This property has been deprecated.  Please use FileStream's SafeFileHandle property instead.  http://go.microsoft.com/fwlink/?linkid=14202")]
-        public virtual IntPtr Handle { get { return SafeFileHandle.DangerousGetHandle(); } }
-
-        public virtual void Lock(long position, long length)
-        {
-            if (position < 0 || length < 0)
-            {
-                throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
-            }
-
-            if (_fileHandle.IsClosed)
-            {
-                throw Error.GetFileNotOpen();
-            }
-
-            LockInternal(position, length);
-        }
-
-        public virtual void Unlock(long position, long length)
-        {
-            if (position < 0 || length < 0)
-            {
-                throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
-            }
-
-            if (_fileHandle.IsClosed)
-            {
-                throw Error.GetFileNotOpen();
-            }
-
-            UnlockInternal(position, length);
-        }
-
-        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 overridden.  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 (GetType() != typeof(FileStream))
-                return base.FlushAsync(cancellationToken);
-
-            return FlushAsyncInternal(cancellationToken);
-        }
-
-        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
-        {
-            if (buffer == null)
-                throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
-            if (offset < 0)
-                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (count < 0)
-                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (buffer.Length - offset < count)
-                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
-
-            // If we have been inherited into a subclass, the following implementation could be incorrect
-            // since it does not call through to Read() or ReadAsync() which a subclass might have overridden.  
-            // 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/ReadAsync) when we are not sure.
-            if (GetType() != typeof(FileStream))
-                return base.ReadAsync(buffer, offset, count, cancellationToken);
-
-            if (cancellationToken.IsCancellationRequested)
-                return Task.FromCanceled<int>(cancellationToken);
-
-            if (IsClosed)
-                throw Error.GetFileNotOpen();
-
-            return ReadAsyncInternal(buffer, offset, count, cancellationToken);
-        }
-
-        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
-        {
-            if (buffer == null)
-                throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
-            if (offset < 0)
-                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (count < 0)
-                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (buffer.Length - offset < count)
-                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
-
-            // If we have been inherited into a subclass, the following implementation could be incorrect
-            // since it does not call through to Write() or WriteAsync() which a subclass might have overridden.  
-            // 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/WriteAsync) when we are not sure.
-            if (GetType() != typeof(FileStream))
-                return base.WriteAsync(buffer, offset, count, cancellationToken);
-
-            if (cancellationToken.IsCancellationRequested)
-                return Task.FromCanceled(cancellationToken);
-
-            if (IsClosed)
-                throw Error.GetFileNotOpen();
-
-            return WriteAsyncInternal(buffer, offset, count, cancellationToken);
-        }
-
-        /// <summary>
-        /// Clears buffers for this stream and causes any buffered data to be written to the file.
-        /// </summary>
-        public override void Flush()
-        {
-            // Make sure that we call through the public virtual API
-            Flush(flushToDisk: false);
-        }
-
-        /// <summary>
-        /// Clears buffers for this stream, and if <param name="flushToDisk"/> is true, 
-        /// causes any buffered data to be written to the file.
-        /// </summary>
-        public virtual void Flush(bool flushToDisk)
-        {
-            if (IsClosed) throw Error.GetFileNotOpen();
-
-            FlushInternalBuffer();
-
-            if (flushToDisk && CanWrite)
-            {
-                FlushOSBuffer();
-            }
-        }
-
-        /// <summary>Gets a value indicating whether the current stream supports reading.</summary>
-        public override bool CanRead
-        {
-            get { return !_fileHandle.IsClosed && (_access & FileAccess.Read) != 0; }
-        }
-
-        /// <summary>Gets a value indicating whether the current stream supports writing.</summary>
-        public override bool CanWrite
-        {
-            get { return !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0; }
-        }
-
-        /// <summary>Validates arguments to Read and Write and throws resulting exceptions.</summary>
-        /// <param name="array">The buffer to read from or write to.</param>
-        /// <param name="offset">The zero-based offset into the array.</param>
-        /// <param name="count">The maximum number of bytes to read or write.</param>
-        private void ValidateReadWriteArgs(byte[] array, int offset, int count)
-        {
-            if (array == null)
-                throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
-            if (offset < 0)
-                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (count < 0)
-                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (array.Length - offset < count)
-                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
-            if (_fileHandle.IsClosed)
-                throw Error.GetFileNotOpen();
-        }
-
-        /// <summary>Sets the length of this stream to the given value.</summary>
-        /// <param name="value">The new length of the stream.</param>
-        public override void SetLength(long value)
-        {
-            if (value < 0)
-                throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (_fileHandle.IsClosed)
-                throw Error.GetFileNotOpen();
-            if (!CanSeek)
-                throw Error.GetSeekNotSupported();
-            if (!CanWrite)
-                throw Error.GetWriteNotSupported();
-
-            SetLengthInternal(value);
-        }
-
-        public virtual SafeFileHandle SafeFileHandle
-        {
-            get
-            {
-                Flush();
-                _exposedHandle = true;
-                return _fileHandle;
-            }
-        }
-
-        /// <summary>Gets the path that was passed to the constructor.</summary>
-        public virtual string Name { get { return _path ?? SR.IO_UnknownFileName; } }
-
-        /// <summary>Gets a value indicating whether the stream was opened for I/O to be performed synchronously or asynchronously.</summary>
-        public virtual bool IsAsync
-        {
-            get { return _useAsyncIO; }
-        }
-
-        /// <summary>Gets the length of the stream in bytes.</summary>
-        public override long Length
-        {
-            get
-            {
-                if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
-                if (!CanSeek) throw Error.GetSeekNotSupported();
-                return GetLengthInternal();
-            }
-        }
-
-        /// <summary>
-        /// Verify that the actual position of the OS's handle equals what we expect it to.
-        /// This will fail if someone else moved the UnixFileStream's handle or if
-        /// our position updating code is incorrect.
-        /// </summary>
-        private void VerifyOSHandlePosition()
-        {
-            bool verifyPosition = _exposedHandle; // in release, only verify if we've given out the handle such that someone else could be manipulating it
-#if DEBUG
-            verifyPosition = true; // in debug, always make sure our position matches what the OS says it should be
-#endif
-            if (verifyPosition && CanSeek)
-            {
-                long oldPos = _filePosition; // SeekCore will override the current _position, so save it now
-                long curPos = SeekCore(0, SeekOrigin.Current);
-                if (oldPos != curPos)
-                {
-                    // For reads, this is non-fatal but we still could have returned corrupted 
-                    // data in some cases, so discard the internal buffer. For writes, 
-                    // this is a problem; discard the buffer and error out.
-                    _readPos = _readLength = 0;
-                    if (_writePos > 0)
-                    {
-                        _writePos = 0;
-                        throw new IOException(SR.IO_FileStreamHandlePosition);
-                    }
-                }
-            }
-        }
-
-        /// <summary>Verifies that state relating to the read/write buffer is consistent.</summary>
-        [Conditional("DEBUG")]
-        private void AssertBufferInvariants()
-        {
-            // Read buffer values must be in range: 0 <= _bufferReadPos <= _bufferReadLength <= _bufferLength
-            Debug.Assert(0 <= _readPos && _readPos <= _readLength && _readLength <= _bufferLength);
-
-            // Write buffer values must be in range: 0 <= _bufferWritePos <= _bufferLength
-            Debug.Assert(0 <= _writePos && _writePos <= _bufferLength);
-
-            // Read buffering and write buffering can't both be active
-            Debug.Assert((_readPos == 0 && _readLength == 0) || _writePos == 0);
-        }
-
-        /// <summary>Validates that we're ready to read from the stream.</summary>
-        private void PrepareForReading()
-        {
-            if (_fileHandle.IsClosed)
-                throw Error.GetFileNotOpen();
-            if (_readLength == 0 && !CanRead)
-                throw Error.GetReadNotSupported();
-
-            AssertBufferInvariants();
-        }
-
-        /// <summary>Gets or sets the position within the current stream</summary>
-        public override long Position
-        {
-            get
-            {
-                if (_fileHandle.IsClosed)
-                    throw Error.GetFileNotOpen();
-
-                if (!CanSeek)
-                    throw Error.GetSeekNotSupported();
-
-                AssertBufferInvariants();
-                VerifyOSHandlePosition();
-
-                // We may have read data into our buffer from the handle, such that the handle position
-                // is artificially further along than the consumer's view of the stream's position.
-                // Thus, when reading, our position is really starting from the handle position negatively
-                // offset by the number of bytes in the buffer and positively offset by the number of
-                // bytes into that buffer we've read.  When writing, both the read length and position
-                // must be zero, and our position is just the handle position offset positive by how many
-                // bytes we've written into the buffer.
-                return (_filePosition - _readLength) + _readPos + _writePos;
-            }
-            set
-            {
-                if (value < 0)
-                    throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
-
-                Seek(value, SeekOrigin.Begin);
-            }
-        }
-
-        internal virtual bool IsClosed => _fileHandle.IsClosed;
-
-        /// <summary>
-        /// Gets the array used for buffering reading and writing.  
-        /// If the array hasn't been allocated, this will lazily allocate it.
-        /// </summary>
-        /// <returns>The buffer.</returns>
-        private byte[] GetBuffer()
-        {
-            Debug.Assert(_buffer == null || _buffer.Length == _bufferLength);
-            if (_buffer == null)
-            {
-                _buffer = new byte[_bufferLength];
-                OnBufferAllocated();
-            }
-
-            return _buffer;
-        }
-
-        partial void OnBufferAllocated();
-
-        /// <summary>
-        /// Flushes the internal read/write buffer for this stream.  If write data has been buffered,
-        /// that data is written out to the underlying file.  Or if data has been buffered for 
-        /// reading from the stream, the data is dumped and our position in the underlying file 
-        /// is rewound as necessary.  This does not flush the OS buffer.
-        /// </summary>
-        private void FlushInternalBuffer()
-        {
-            AssertBufferInvariants();
-            if (_writePos > 0)
-            {
-                FlushWriteBuffer();
-            }
-            else if (_readPos < _readLength && CanSeek)
-            {
-                FlushReadBuffer();
-            }
-        }
-
-        /// <summary>Dumps any read data in the buffer and rewinds our position in the stream, accordingly, as necessary.</summary>
-        private void FlushReadBuffer()
-        {
-            // 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.
-
-            AssertBufferInvariants();
-            Debug.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushReadBuffer!");
-
-            int rewind = _readPos - _readLength;
-            if (rewind != 0)
-            {
-                Debug.Assert(CanSeek, "FileStream will lose buffered read data now.");
-                SeekCore(rewind, SeekOrigin.Current);
-            }
-            _readPos = _readLength = 0;
-        }
-
-        private int ReadByteCore()
-        {
-            PrepareForReading();
-
-            byte[] buffer = GetBuffer();
-            if (_readPos == _readLength)
-            {
-                FlushWriteBuffer();
-                Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
-
-                _readLength = ReadNative(buffer, 0, _bufferLength);
-                _readPos = 0;
-                if (_readLength == 0)
-                {
-                    return -1;
-                }
-            }
-
-            return buffer[_readPos++];
-        }
-
-        private void WriteByteCore(byte value)
-        {
-            PrepareForWriting();
-
-            // Flush the write buffer if it's full
-            if (_writePos == _bufferLength)
-                FlushWriteBuffer();
-
-            // We now have space in the buffer. Store the byte.
-            GetBuffer()[_writePos++] = value;
-        }
-
-        /// <summary>
-        /// Validates that we're ready to write to the stream,
-        /// including flushing a read buffer if necessary.
-        /// </summary>
-        private void PrepareForWriting()
-        {
-            if (_fileHandle.IsClosed)
-                throw Error.GetFileNotOpen();
-
-            // Make sure we're good to write.  We only need to do this if there's nothing already
-            // in our write buffer, since if there is something in the buffer, we've already done 
-            // this checking and flushing.
-            if (_writePos == 0)
-            {
-                if (!CanWrite) throw Error.GetWriteNotSupported();
-                FlushReadBuffer();
-                Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
-            }
-        }
-
-        ~FileStream()
-        {
-            // Preserved for compatibility since FileStream has defined a 
-            // finalizer in past releases and derived classes may depend
-            // on Dispose(false) call.
-            Dispose(false);
-        }
-
-        public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
-        {
-            if (array == null)
-                throw new ArgumentNullException(nameof(array));
-            if (offset < 0)
-                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (numBytes < 0)
-                throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (array.Length - offset < numBytes)
-                throw new ArgumentException(SR.Argument_InvalidOffLen);
-
-            if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
-            if (!CanRead) throw new NotSupportedException(SR.NotSupported_UnreadableStream);
-
-            if (!IsAsync)
-                return base.BeginRead(array, offset, numBytes, callback, state);
-            else
-                return TaskToApm.Begin(ReadAsyncInternal(array, offset, numBytes, CancellationToken.None), callback, state);
-        }
-
-        public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
-        {
-            if (array == null)
-                throw new ArgumentNullException(nameof(array));
-            if (offset < 0)
-                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (numBytes < 0)
-                throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
-            if (array.Length - offset < numBytes)
-                throw new ArgumentException(SR.Argument_InvalidOffLen);
-
-            if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
-            if (!CanWrite) throw new NotSupportedException(SR.NotSupported_UnwritableStream);
-
-            if (!IsAsync)
-                return base.BeginWrite(array, offset, numBytes, callback, state);
-            else
-                return TaskToApm.Begin(WriteAsyncInternal(array, offset, numBytes, CancellationToken.None), callback, state);
-        }
-
-        public override int EndRead(IAsyncResult asyncResult)
-        {
-            if (asyncResult == null)
-                throw new ArgumentNullException(nameof(asyncResult));
-
-            if (!IsAsync)
-                return base.EndRead(asyncResult);
-            else
-                return TaskToApm.End<int>(asyncResult);
-        }
-
-        public override void EndWrite(IAsyncResult asyncResult)
-        {
-            if (asyncResult == null)
-                throw new ArgumentNullException(nameof(asyncResult));
-
-            if (!IsAsync)
-                base.EndWrite(asyncResult);
-            else
-                TaskToApm.End(asyncResult);
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs b/src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs
deleted file mode 100644 (file)
index 7dca133..0000000
+++ /dev/null
@@ -1,222 +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.
-
-using System.Security;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Runtime.InteropServices;
-using System.Diagnostics;
-
-namespace System.IO
-{
-    public partial class FileStream : Stream
-    {
-        // This is an internal object extending TaskCompletionSource with fields
-        // for all of the relevant data necessary to complete the IO operation.
-        // This is used by IOCallback and all of the async methods.
-        unsafe private sealed class FileStreamCompletionSource : TaskCompletionSource<int>
-        {
-            private const long NoResult = 0;
-            private const long ResultSuccess = (long)1 << 32;
-            private const long ResultError = (long)2 << 32;
-            private const long RegisteringCancellation = (long)4 << 32;
-            private const long CompletedCallback = (long)8 << 32;
-            private const ulong ResultMask = ((ulong)uint.MaxValue) << 32;
-
-            private static Action<object> s_cancelCallback;
-
-            private readonly FileStream _stream;
-            private readonly int _numBufferedBytes;
-            private readonly CancellationToken _cancellationToken;
-            private CancellationTokenRegistration _cancellationRegistration;
-#if DEBUG
-            private bool _cancellationHasBeenRegistered;
-#endif
-            private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs
-            private long _result; // Using long since this needs to be used in Interlocked APIs
-
-            // Using RunContinuationsAsynchronously for compat reasons (old API used Task.Factory.StartNew for continuations)
-            internal FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[] bytes, CancellationToken cancellationToken)
-                : base(TaskCreationOptions.RunContinuationsAsynchronously)
-            {
-                _numBufferedBytes = numBufferedBytes;
-                _stream = stream;
-                _result = NoResult;
-                _cancellationToken = cancellationToken;
-
-                // Create the native overlapped. We try to use the preallocated overlapped if possible: 
-                // it's possible if the byte buffer is the same one that's associated with the preallocated overlapped 
-                // and if no one else is currently using the preallocated overlapped.  This is the fast-path for cases 
-                // where the user-provided buffer is smaller than the FileStream's buffer (such that the FileStream's 
-                // buffer is used) and where operations on the FileStream are not being performed concurrently.
-                _overlapped = ReferenceEquals(bytes, _stream._buffer) && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ?
-                    _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(_stream._preallocatedOverlapped) :
-                    _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(s_ioCallback, this, bytes);
-                Debug.Assert(_overlapped != null, "AllocateNativeOverlapped returned null");
-            }
-
-            internal NativeOverlapped* Overlapped
-            {
-                get { return _overlapped; }
-            }
-
-            public void SetCompletedSynchronously(int numBytes)
-            {
-                ReleaseNativeResource();
-                TrySetResult(numBytes + _numBufferedBytes);
-            }
-
-            public void RegisterForCancellation()
-            {
-#if DEBUG
-                Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice");
-                _cancellationHasBeenRegistered = true;
-#endif
-
-                // Quick check to make sure that the cancellation token supports cancellation, and that the IO hasn't completed
-                if ((_cancellationToken.CanBeCanceled) && (_overlapped != null))
-                {
-                    var cancelCallback = s_cancelCallback;
-                    if (cancelCallback == null) s_cancelCallback = cancelCallback = Cancel;
-
-                    // Register the cancellation only if the IO hasn't completed
-                    long packedResult = Interlocked.CompareExchange(ref _result, RegisteringCancellation, NoResult);
-                    if (packedResult == NoResult)
-                    {
-                        _cancellationRegistration = _cancellationToken.Register(cancelCallback, this);
-
-                        // Switch the result, just in case IO completed while we were setting the registration
-                        packedResult = Interlocked.Exchange(ref _result, NoResult);
-                    }
-                    else if (packedResult != CompletedCallback)
-                    {
-                        // Failed to set the result, IO is in the process of completing
-                        // Attempt to take the packed result
-                        packedResult = Interlocked.Exchange(ref _result, NoResult);
-                    }
-
-                    // If we have a callback that needs to be completed
-                    if ((packedResult != NoResult) && (packedResult != CompletedCallback) && (packedResult != RegisteringCancellation))
-                    {
-                        CompleteCallback((ulong)packedResult);
-                    }
-                }
-            }
-
-            internal void ReleaseNativeResource()
-            {
-                // Ensure that cancellation has been completed and cleaned up.
-                _cancellationRegistration.Dispose();
-
-                // Free the overlapped.
-                // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory
-                // (this is why we disposed the registration above).
-                if (_overlapped != null)
-                {
-                    _stream._fileHandle.ThreadPoolBinding.FreeNativeOverlapped(_overlapped);
-                    _overlapped = null;
-                }
-
-                // Ensure we're no longer set as the current completion source (we may not have been to begin with).
-                // Only one operation at a time is eligible to use the preallocated overlapped, 
-                _stream.CompareExchangeCurrentOverlappedOwner(null, this);
-            }
-
-            // When doing IO asynchronously (i.e. _isAsync==true), this callback is 
-            // called by a free thread in the threadpool when the IO operation 
-            // completes.  
-            internal static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
-            {
-                // Extract the completion source from the overlapped.  The state in the overlapped
-                // will either be a Win32FileStream (in the case where the preallocated overlapped was used),
-                // in which case the operation being completed is its _currentOverlappedOwner, or it'll
-                // be directly the FileStreamCompletion that's completing (in the case where the preallocated
-                // overlapped was already in use by another operation).
-                object state = ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
-                FileStream fs = state as FileStream;
-                FileStreamCompletionSource completionSource = fs != null ?
-                    fs._currentOverlappedOwner :
-                    (FileStreamCompletionSource)state;
-                Debug.Assert(completionSource._overlapped == pOverlapped, "Overlaps don't match");
-
-                // 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.
-                ulong packedResult;
-                if (errorCode != 0 && errorCode != ERROR_BROKEN_PIPE && errorCode != ERROR_NO_DATA)
-                {
-                    packedResult = ((ulong)ResultError | errorCode);
-                }
-                else
-                {
-                    packedResult = ((ulong)ResultSuccess | numBytes);
-                }
-
-                // Stow the result so that other threads can observe it
-                // And, if no other thread is registering cancellation, continue
-                if (NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult))
-                {
-                    // Successfully set the state, attempt to take back the callback
-                    if (Interlocked.Exchange(ref completionSource._result, CompletedCallback) != NoResult)
-                    {
-                        // Successfully got the callback, finish the callback
-                        completionSource.CompleteCallback(packedResult);
-                    }
-                    // else: Some other thread stole the result, so now it is responsible to finish the callback
-                }
-                // else: Some other thread is registering a cancellation, so it *must* finish the callback
-            }
-
-            private void CompleteCallback(ulong packedResult)
-            {
-                // Free up the native resource and cancellation registration
-                ReleaseNativeResource();
-
-                // Unpack the result and send it to the user
-                long result = (long)(packedResult & ResultMask);
-                if (result == ResultError)
-                {
-                    int errorCode = unchecked((int)(packedResult & uint.MaxValue));
-                    if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED)
-                    {
-                        TrySetCanceled(_cancellationToken.IsCancellationRequested ? _cancellationToken : new CancellationToken(true));
-                    }
-                    else
-                    {
-                        TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode));
-                    }
-                }
-                else
-                {
-                    Debug.Assert(result == ResultSuccess, "Unknown result");
-                    TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes);
-                }
-            }
-
-            private static void Cancel(object state)
-            {
-                // WARNING: This may potentially be called under a lock (during cancellation registration)
-
-                FileStreamCompletionSource completionSource = state as FileStreamCompletionSource;
-                Debug.Assert(completionSource != null, "Unknown state passed to cancellation");
-                Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet");
-
-                // If the handle is still valid, attempt to cancel the IO
-                if (!completionSource._stream._fileHandle.IsInvalid &&
-                    !Interop.Kernel32.CancelIoEx(completionSource._stream._fileHandle, completionSource._overlapped))
-                {
-                    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 != Interop.Errors.ERROR_NOT_FOUND)
-                    {
-                        throw Win32Marshal.GetExceptionForWin32Error(errorCode);
-                    }
-                }
-            }
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/Path.Unix.cs b/src/mscorlib/corefx/System/IO/Path.Unix.cs
deleted file mode 100644 (file)
index c566fa0..0000000
+++ /dev/null
@@ -1,216 +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.
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace System.IO
-{
-    public static partial class Path
-    {
-        public static char[] GetInvalidFileNameChars() => new char[] { '\0', '/' };
-
-        public static char[] GetInvalidPathChars() => new char[] { '\0' };
-
-        internal static int MaxPath => Interop.Sys.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 > Interop.Sys.MaxPath)
-            {
-                throw new PathTooLongException(SR.IO_PathTooLong);
-            }
-
-            string result = collapsedString.Length == 0 ? PathInternal.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 > Interop.Sys.MaxName)
-                {
-                    throw new PathTooLongException(SR.IO_PathTooLong);
-                }
-
-                // Normalize the directory separator if needed
-                if (c != PathInternal.DirectorySeparatorChar && c == PathInternal.AltDirectorySeparatorChar)
-                {
-                    c = PathInternal.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 + PathInternal.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] == PathInternal.DirectorySeparatorChar;
-        }
-
-        public static string GetPathRoot(string path)
-        {
-            if (path == null) return null;
-            return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : String.Empty;
-        }
-
-        private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount)
-        {
-            // 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);
-        }
-
-        /// <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
deleted file mode 100644 (file)
index 8a9e62e..0000000
+++ /dev/null
@@ -1,36 +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.
-
-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
deleted file mode 100644 (file)
index 0f8e3b3..0000000
+++ /dev/null
@@ -1,155 +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.
-
-using System.Diagnostics;
-using System.Text;
-
-namespace System.IO
-{
-    public static partial class Path
-    {
-        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, ':', '*', '?', '\\', '/'
-        };
-
-        public 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
-        };
-
-        // 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 const int MaxPath = 260;
-
-        // 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] == PathInternal.VolumeSeparatorChar)
-                    || (path.Length >= startIndex && path[startIndex - 1] == PathInternal.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2]))
-                    || (path.Length > startIndex && path.IndexOf(PathInternal.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.Kernel32.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.Kernel32.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] == PathInternal.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
deleted file mode 100644 (file)
index ce80424..0000000
+++ /dev/null
@@ -1,574 +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.
-
-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
-    {
-        // Public static readonly variant of the separators. The Path implementation itself is using
-        // internal const variant of the separators for better performance.
-        public static readonly char DirectorySeparatorChar = PathInternal.DirectorySeparatorChar;
-        public static readonly char AltDirectorySeparatorChar = PathInternal.AltDirectorySeparatorChar;
-        public static readonly char VolumeSeparatorChar = PathInternal.VolumeSeparatorChar;
-        public static readonly char PathSeparator = PathInternal.PathSeparator;
-
-        // For generating random file names
-        // 8 random bytes provides 12 chars in our encoding for the 8.3 name.
-        private 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;
-        }
-
-        // 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(PathInternal.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 + PathInternal.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 + PathInternal.DirectorySeparatorCharAsString + path3;
-            }
-            else if (hasSep2)
-            {
-                return path1 + PathInternal.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(PathInternal.DirectorySeparatorChar)
-                  .Append(path2)
-                  .Append(PathInternal.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(PathInternal.DirectorySeparatorChar);
-                }
-
-                sb.Append(path2);
-                if (!hasSep2)
-                {
-                    sb.Append(PathInternal.DirectorySeparatorChar);
-                }
-
-                sb.Append(path3);
-                if (!hasSep3)
-                {
-                    sb.Append(PathInternal.DirectorySeparatorChar);
-                }
-
-                sb.Append(path4);
-
-                return StringBuilderCache.GetStringAndRelease(sb);
-            }
-        }
-
-        private static readonly char[] s_base32Char = {
-                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
-                'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
-                'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
-                'y', 'z', '0', '1', '2', '3', '4', '5'};
-
-        private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, char* chars, int charCount)
-        {
-            Debug.Assert(bytes != null);
-            Debug.Assert(chars != null);
-
-            // This method requires bytes of length 8 and chars of length 12.
-            Debug.Assert(byteCount == 8, $"Unexpected {nameof(byteCount)}");
-            Debug.Assert(charCount == 12, $"Unexpected {nameof(charCount)}");
-
-            byte b0 = bytes[0];
-            byte b1 = bytes[1];
-            byte b2 = bytes[2];
-            byte b3 = bytes[3];
-            byte b4 = bytes[4];
-
-            // Consume the 5 Least significant bits of the first 5 bytes
-            chars[0] = s_base32Char[b0 & 0x1F];
-            chars[1] = s_base32Char[b1 & 0x1F];
-            chars[2] = s_base32Char[b2 & 0x1F];
-            chars[3] = s_base32Char[b3 & 0x1F];
-            chars[4] = s_base32Char[b4 & 0x1F];
-
-            // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
-            chars[5] = s_base32Char[(
-                    ((b0 & 0xE0) >> 5) |
-                    ((b3 & 0x60) >> 2))];
-
-            chars[6] = s_base32Char[(
-                    ((b1 & 0xE0) >> 5) |
-                    ((b4 & 0x60) >> 2))];
-
-            // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
-            b2 >>= 5;
-
-            Debug.Assert(((b2 & 0xF8) == 0), "Unexpected set bits");
-
-            if ((b3 & 0x80) != 0)
-                b2 |= 0x08;
-            if ((b4 & 0x80) != 0)
-                b2 |= 0x10;
-
-            chars[7] = s_base32Char[b2];
-
-            // Set the file extension separator
-            chars[8] = '.';
-
-            // Consume the 5 Least significant bits of the remaining 3 bytes
-            chars[9] = s_base32Char[(bytes[5] & 0x1F)];
-            chars[10] = s_base32Char[(bytes[6] & 0x1F)];
-            chars[11] = s_base32Char[(bytes[7] & 0x1F)];
-        }
-
-        /// <summary>
-        /// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
-        /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
-        /// </summary>
-        /// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param>
-        /// <param name="path">The destination path.</param>
-        /// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns>
-        /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
-        public static string GetRelativePath(string relativeTo, string path)
-        {
-            return GetRelativePath(relativeTo, path, StringComparison);
-        }
-
-        private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
-        {
-            if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo));
-            if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path));
-            Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
-
-            relativeTo = GetFullPath(relativeTo);
-            path = GetFullPath(path);
-
-            // Need to check if the roots are different- if they are we need to return the "to" path.
-            if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
-                return path;
-
-            int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);
-
-            // If there is nothing in common they can't share the same root, return the "to" path as is.
-            if (commonLength == 0)
-                return path;
-
-            // Trailing separators aren't significant for comparison
-            int relativeToLength = relativeTo.Length;
-            if (PathInternal.EndsInDirectorySeparator(relativeTo))
-                relativeToLength--;
-
-            bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path);
-            int pathLength = path.Length;
-            if (pathEndsInSeparator)
-                pathLength--;
-
-            // If we have effectively the same path, return "."
-            if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";
-
-            // We have the same root, we need to calculate the difference now using the
-            // common Length and Segment count past the length.
-            //
-            // Some examples:
-            //
-            //  C:\Foo C:\Bar L3, S1 -> ..\Bar
-            //  C:\Foo C:\Foo\Bar L6, S0 -> Bar
-            //  C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
-            //  C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
-
-            StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
-
-            // Add parent segments for segments past the common on the "from" path
-            if (commonLength < relativeToLength)
-            {
-                sb.Append(PathInternal.ParentDirectoryPrefix);
-
-                for (int i = commonLength; i < relativeToLength; i++)
-                {
-                    if (PathInternal.IsDirectorySeparator(relativeTo[i]))
-                    {
-                        sb.Append(PathInternal.ParentDirectoryPrefix);
-                    }
-                }
-            }
-            else if (PathInternal.IsDirectorySeparator(path[commonLength]))
-            {
-                // No parent segments and we need to eat the initial separator
-                //  (C:\Foo C:\Foo\Bar case)
-                commonLength++;
-            }
-
-            // Now add the rest of the "to" path, adding back the trailing separator
-            int count = pathLength - commonLength;
-            if (pathEndsInSeparator)
-                count++;
-
-            sb.Append(path, commonLength, count);
-            return StringBuilderCache.GetStringAndRelease(sb);
-        }
-
-        // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are
-        // too low in System.Runtime.Extensions to use it (no FileStream, etc.)
-
-        /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
-        internal static StringComparison StringComparison
-        {
-            get
-            {
-                return IsCaseSensitive ?
-                    StringComparison.Ordinal :
-                    StringComparison.OrdinalIgnoreCase;
-            }
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs b/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs
deleted file mode 100644 (file)
index e2ead93..0000000
+++ /dev/null
@@ -1,398 +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.
-
-using System.Diagnostics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-namespace System.IO
-{
-    /// <summary>
-    /// Wrapper to help with path normalization.
-    /// </summary>
-    internal class PathHelper
-    {
-        // Can't be over 8.3 and be a short name
-        private const int MaxShortName = 12;
-
-        private const char LastAnsi = (char)255;
-        private const char Delete = (char)127;
-
-        /// <summary>
-        /// Normalize the given path.
-        /// </summary>
-        /// <remarks>
-        /// Normalizes via Win32 GetFullPathName(). It will also trim all "typical" whitespace at the end of the path (see s_trimEndChars). Will also trim initial
-        /// spaces if the path is determined to be rooted.
-        /// 
-        /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt)
-        /// </remarks>
-        /// <param name="path">Path to normalize</param>
-        /// <param name="checkInvalidCharacters">True to check for invalid characters</param>
-        /// <param name="expandShortPaths">Attempt to expand short paths if true</param>
-        /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception>
-        /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception>
-        /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
-        /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
-        /// <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>
-        internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths)
-        {
-            // Get the full path
-            StringBuffer fullPath = new StringBuffer(PathInternal.MaxShortPath);
-
-            try
-            {
-                GetFullPathName(path, ref fullPath);
-
-                // Trim whitespace off the end of the string. Win32 normalization trims only U+0020.
-                fullPath.TrimEnd(PathInternal.s_trimEndChars);
-
-                if (fullPath.Length >= PathInternal.MaxLongPath)
-                {
-                    // Fullpath is genuinely too long
-                    throw new PathTooLongException(SR.IO_PathTooLong);
-                }
-
-                // Checking path validity used to happen before getting the full path name. To avoid additional input allocation
-                // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that
-                // used to get kicked back (notably segments with invalid characters might get removed via "..").
-                //
-                // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to
-                // expand short file names.
-
-                // Scan the path for:
-                //
-                //  - Illegal path characters.
-                //  - Invalid UNC paths like \\, \\server, \\server\.
-                //  - Segments that are too long (over MaxComponentLength)
-
-                // 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;
-                bool foundTilde = false;
-
-                // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't
-                // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device
-                // path that contains UNC or  to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\,
-                // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc.
-                bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\';
-                bool isDevice = PathInternal.IsDevice(ref fullPath);
-                bool possibleBadUnc = specialPath && !isDevice;
-                int index = specialPath ? 2 : 0;
-                int lastSeparator = specialPath ? 1 : 0;
-                int segmentLength;
-                char current;
-
-                while (index < fullPath.Length)
-                {
-                    current = fullPath[index];
-
-                    // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~'
-                    if (current < '?' || current == '\\' || current == '|' || current == '~')
-                    {
-                        switch (current)
-                        {
-                            case '|':
-                            case '>':
-                            case '<':
-                            case '\"':
-                                if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars);
-                                foundTilde = false;
-                                break;
-                            case '~':
-                                foundTilde = true;
-                                break;
-                            case '\\':
-                                segmentLength = index - lastSeparator - 1;
-                                if (segmentLength > PathInternal.MaxComponentLength)
-                                    throw new PathTooLongException(SR.IO_PathTooLong + fullPath.ToString());
-                                lastSeparator = index;
-
-                                if (foundTilde)
-                                {
-                                    if (segmentLength <= MaxShortName)
-                                    {
-                                        // Possibly a short path.
-                                        possibleShortPath = true;
-                                    }
-
-                                    foundTilde = false;
-                                }
-
-                                if (possibleBadUnc)
-                                {
-                                    // 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(SR.Arg_PathIllegalUNC);
-                                    else
-                                        possibleBadUnc = false;
-                                }
-
-                                break;
-
-                            default:
-                                if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
-                                break;
-                        }
-                    }
-
-                    index++;
-                }
-
-                if (possibleBadUnc)
-                    throw new ArgumentException(SR.Arg_PathIllegalUNC);
-
-                segmentLength = fullPath.Length - lastSeparator - 1;
-                if (segmentLength > PathInternal.MaxComponentLength)
-                    throw new PathTooLongException(SR.IO_PathTooLong);
-
-                if (foundTilde && segmentLength <= MaxShortName)
-                    possibleShortPath = true;
-
-                // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but
-                // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide.
-                if (expandShortPaths && possibleShortPath)
-                {
-                    return TryExpandShortFileName(ref fullPath, originalPath: path);
-                }
-                else
-                {
-                    if (fullPath.Length == path.Length && fullPath.StartsWith(path))
-                    {
-                        // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder.
-                        return path;
-                    }
-                    else
-                    {
-                        return fullPath.ToString();
-                    }
-                }
-            }
-            finally
-            {
-                // Clear the buffer
-                fullPath.Free();
-            }
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static bool IsDosUnc(ref StringBuffer buffer)
-        {
-            return !PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\';
-        }
-
-        private static unsafe void GetFullPathName(string path, ref 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.
-            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);
-
-            fixed (char* pathStart = path)
-            {
-                uint result = 0;
-                while ((result = Interop.Kernel32.GetFullPathNameW(pathStart + startIndex, (uint)fullPath.Capacity, fullPath.UnderlyingArray, IntPtr.Zero)) > fullPath.Capacity)
-                {
-                    // Reported size is greater than the buffer size. Increase the capacity.
-                    fullPath.EnsureCapacity(checked((int)result));
-                }
-
-                if (result == 0)
-                {
-                    // Failure, get the error and throw
-                    int errorCode = Marshal.GetLastWin32Error();
-                    if (errorCode == 0)
-                        errorCode = Interop.Errors.ERROR_BAD_PATHNAME;
-                    throw Win32Marshal.GetExceptionForWin32Error(errorCode, path);
-                }
-
-                fullPath.Length = checked((int)result);
-            }
-        }
-
-        private static int GetInputBuffer(ref StringBuffer content, bool isDosUnc, ref StringBuffer buffer)
-        {
-            int length = content.Length;
-
-            length += isDosUnc
-                ? PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength
-                : PathInternal.DevicePrefixLength;
-
-            buffer.EnsureCapacity(length + 1);
-
-            if (isDosUnc)
-            {
-                // Put the extended UNC prefix (\\?\UNC\) in front of the path
-                buffer.CopyFrom(bufferIndex: 0, source: PathInternal.UncExtendedPathPrefix);
-
-                // Copy the source buffer over after the existing UNC prefix
-                content.CopyTo(
-                    bufferIndex: PathInternal.UncPrefixLength,
-                    destination: ref buffer,
-                    destinationIndex: PathInternal.UncExtendedPrefixLength,
-                    count: content.Length - PathInternal.UncPrefixLength);
-
-                // Return the prefix difference
-                return PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength;
-            }
-            else
-            {
-                int prefixSize = PathInternal.ExtendedPathPrefix.Length;
-                buffer.CopyFrom(bufferIndex: 0, source: PathInternal.ExtendedPathPrefix);
-                content.CopyTo(bufferIndex: 0, destination: ref buffer, destinationIndex: prefixSize, count: content.Length);
-                return prefixSize;
-            }
-        }
-
-        private static string TryExpandShortFileName(ref 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(ref 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:\)
-            //  2. Dos UNC (\\Server\Share)
-            //  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).
-
-            int rootLength = PathInternal.GetRootLength(ref outputBuffer);
-            bool isDevice = PathInternal.IsDevice(ref outputBuffer);
-
-            StringBuffer inputBuffer = new StringBuffer(0);
-            try
-            {
-                bool isDosUnc = false;
-                int rootDifference = 0;
-                bool wasDotDevice = false;
-
-                // Add the extended prefix before expanding to allow growth over MAX_PATH
-                if (isDevice)
-                {
-                    // We have one of the following (\\?\ or \\.\)
-                    inputBuffer.Append(ref outputBuffer);
-
-                    if (outputBuffer[2] == '.')
-                    {
-                        wasDotDevice = true;
-                        inputBuffer[2] = '?';
-                    }
-                }
-                else
-                {
-                    isDosUnc = IsDosUnc(ref outputBuffer);
-                    rootDifference = GetInputBuffer(ref outputBuffer, isDosUnc, ref inputBuffer);
-                }
-
-                rootLength += rootDifference;
-                int inputLength = inputBuffer.Length;
-
-                bool success = false;
-                int foundIndex = inputBuffer.Length - 1;
-
-                while (!success)
-                {
-                    uint result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);
-
-                    // 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 != Interop.Errors.ERROR_FILE_NOT_FOUND && error != Interop.Errors.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.Capacity)
-                    {
-                        // Not enough space. The result count for this API does not include the null terminator.
-                        outputBuffer.EnsureCapacity(checked((int)result));
-                        result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);
-                    }
-                    else
-                    {
-                        // Found the path
-                        success = true;
-                        outputBuffer.Length = checked((int)result);
-                        if (foundIndex < inputLength - 1)
-                        {
-                            // It was a partial find, put the non-existent part of the path back
-                            outputBuffer.Append(ref inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
-                        }
-                    }
-                }
-
-                // Strip out the prefix and return the string
-                ref StringBuffer bufferToUse = ref Choose(success, ref outputBuffer, ref inputBuffer);
-
-                // Switch back from \\?\ to \\.\ if necessary
-                if (wasDotDevice)
-                    bufferToUse[2] = '.';
-
-                string returnValue = null;
-
-                int newLength = (int)(bufferToUse.Length - rootDifference);
-                if (isDosUnc)
-                {
-                    // Need to go from \\?\UNC\ to \\?\UN\\
-                    bufferToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\';
-                }
-
-                // We now need to strip out any added characters at the front of the string
-                if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength))
-                {
-                    // Use the original path to avoid allocating
-                    returnValue = originalPath;
-                }
-                else
-                {
-                    returnValue = bufferToUse.Substring(rootDifference, newLength);
-                }
-
-                return returnValue;
-            }
-            finally
-            {
-                inputBuffer.Free();
-            }
-        }
-
-        // Helper method to workaround lack of operator ? support for ref values
-        private static ref StringBuffer Choose(bool condition, ref StringBuffer s1, ref StringBuffer s2)
-        {
-            if (condition) return ref s1;
-            else return ref s2;
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs b/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs
deleted file mode 100644 (file)
index 08dc1d0..0000000
+++ /dev/null
@@ -1,104 +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.
-
-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
-    {
-        internal const char DirectorySeparatorChar = '/';
-        internal const char AltDirectorySeparatorChar = '/';
-        internal const char VolumeSeparatorChar = '/';
-        internal const char PathSeparator = ':';
-
-        internal const string DirectorySeparatorCharAsString = "/";
-
-        // There is only one invalid path character in Unix
-        private const char InvalidPathChar = '\0';
-
-        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(DirectorySeparatorChar == AltDirectorySeparatorChar);
-            return c == DirectorySeparatorChar;
-        }
-
-        /// <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(DirectorySeparatorChar == AltDirectorySeparatorChar);
-            Debug.Assert(DirectorySeparatorChar == VolumeSeparatorChar);
-            return ch == DirectorySeparatorChar;
-        }
-
-        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
deleted file mode 100644 (file)
index 84953df..0000000
+++ /dev/null
@@ -1,93 +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.
-
-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(ref 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 int GetRootLength(ref StringBuffer path)
-        {
-            if (path.Length == 0) return 0;
-
-            fixed (char* value = path.UnderlyingArray)
-            {
-                return GetRootLength(value, path.Length);
-            }
-        }
-
-        /// <summary>
-        /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
-        /// </summary>
-        internal static bool IsDevice(ref 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(ref 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(ref 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] == VolumeSeparatorChar)
-                && IsDirectorySeparator(path[2]));
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs b/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs
deleted file mode 100644 (file)
index ee0dd54..0000000
+++ /dev/null
@@ -1,442 +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.
-
-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 char DirectorySeparatorChar = '\\';
-        internal const char AltDirectorySeparatorChar = '/';
-        internal const char VolumeSeparatorChar = ':';
-        internal const char PathSeparator = ';';
-
-        internal const string DirectorySeparatorCharAsString = "\\";
-
-        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;
-
-        /// <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>
-        /// 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 <= '|') // fast path for common case - '|' is highest illegal character
-                {
-                    if (c <= '\u001f' || c == '|')
-                    {
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Check for known wildcard characters. '*' and '?' are the most common ones.
-        /// </summary>
-        internal 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;
-
-            // [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
-            for (int i = startIndex; i < path.Length; i++)
-            {
-                char c = path[i];
-                if (c <= '?') // fast path for common case - '?' is highest wildcard character
-                {
-                    if (c == '\"' || c == '<' || c == '>' || c == '*' || c == '?')
-                        return true;
-                }
-            }
-
-            return false;
-        }
-
-        /// <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 GetRootLength(value, path.Length);
-            }
-        }
-
-        private unsafe static int GetRootLength(char* path, int pathLength)
-        {
-            int i = 0;
-            int volumeSeparatorLength = 2;  // Length to the colon "C:"
-            int 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 = UncExtendedPathPrefix.Length;
-                }
-                else
-                {
-                    // "C:" -> "\\?\C:"
-                    volumeSeparatorLength += 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] == 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, int sourceLength, string value)
-        {
-            if (sourceLength < 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] == 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 == DirectorySeparatorChar || c == 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 != 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(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 = 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) || VolumeSeparatorChar == ch;
-        }
-    }
-}
diff --git a/src/mscorlib/corefx/System/IO/PathInternal.cs b/src/mscorlib/corefx/System/IO/PathInternal.cs
deleted file mode 100644 (file)
index 0dab5b9..0000000
+++ /dev/null
@@ -1,171 +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.
-
-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 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/IO/Win32Marshal.cs b/src/mscorlib/corefx/System/IO/Win32Marshal.cs
deleted file mode 100644 (file)
index ef76c27..0000000
+++ /dev/null
@@ -1,109 +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.
-
-using System;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace System.IO
-{
-    /// <summary>
-    ///     Provides static methods for converting from Win32 errors codes to exceptions, HRESULTS and error messages.
-    /// </summary>
-    internal static class Win32Marshal
-    {
-        /// <summary>
-        ///     Converts, resetting it, the last Win32 error into a corresponding <see cref="Exception"/> object.
-        /// </summary>
-        internal static Exception GetExceptionForLastWin32Error()
-        {
-            int errorCode = Marshal.GetLastWin32Error();
-            return GetExceptionForWin32Error(errorCode, string.Empty);
-        }
-
-        /// <summary>
-        ///     Converts the specified Win32 error into a corresponding <see cref="Exception"/> object.
-        /// </summary>
-        internal static Exception GetExceptionForWin32Error(int errorCode)
-        {
-            return GetExceptionForWin32Error(errorCode, string.Empty);
-        }
-
-        /// <summary>
-        ///     Converts the specified Win32 error into a corresponding <see cref="Exception"/> object, optionally 
-        ///     including the specified path in the error message.
-        /// </summary>
-        internal static Exception GetExceptionForWin32Error(int errorCode, string path)
-        {
-            switch (errorCode)
-            {
-                case Interop.Errors.ERROR_FILE_NOT_FOUND:
-                    if (path.Length == 0)
-                        return new FileNotFoundException(SR.IO_FileNotFound);
-                    else
-                        return new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path);
-
-                case Interop.Errors.ERROR_PATH_NOT_FOUND:
-                    if (path.Length == 0)
-                        return new DirectoryNotFoundException(SR.IO_PathNotFound_NoPathName);
-                    else
-                        return new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path));
-
-                case Interop.Errors.ERROR_ACCESS_DENIED:
-                    if (path.Length == 0)
-                        return new UnauthorizedAccessException(SR.UnauthorizedAccess_IODenied_NoPathName);
-                    else
-                        return new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
-
-                case Interop.Errors.ERROR_ALREADY_EXISTS:
-                    if (path.Length == 0)
-                        goto default;
-
-                    return new IOException(SR.Format(SR.IO_AlreadyExists_Name, path), MakeHRFromErrorCode(errorCode));
-
-                case Interop.Errors.ERROR_FILENAME_EXCED_RANGE:
-                    return new PathTooLongException(SR.IO_PathTooLong);
-
-                case Interop.Errors.ERROR_INVALID_PARAMETER:
-                    return new IOException(GetMessage(errorCode), MakeHRFromErrorCode(errorCode));
-
-                case Interop.Errors.ERROR_SHARING_VIOLATION:
-                    if (path.Length == 0)
-                        return new IOException(SR.IO_SharingViolation_NoFileName, MakeHRFromErrorCode(errorCode));
-                    else
-                        return new IOException(SR.Format(SR.IO_SharingViolation_File, path), MakeHRFromErrorCode(errorCode));
-
-                case Interop.Errors.ERROR_FILE_EXISTS:
-                    if (path.Length == 0)
-                        goto default;
-
-                    return new IOException(SR.Format(SR.IO_FileExists_Name, path), MakeHRFromErrorCode(errorCode));
-
-                case Interop.Errors.ERROR_OPERATION_ABORTED:
-                    return new OperationCanceledException();
-
-                default:
-                    return new IOException(GetMessage(errorCode), MakeHRFromErrorCode(errorCode));
-            }
-        }
-
-        /// <summary>
-        ///     Returns a HRESULT for the specified Win32 error code.
-        /// </summary>
-        internal static int MakeHRFromErrorCode(int errorCode)
-        {
-            Debug.Assert((0xFFFF0000 & errorCode) == 0, "This is an HRESULT, not an error code!");
-
-            return unchecked(((int)0x80070000) | errorCode);
-        }
-
-        /// <summary>
-        ///     Returns a string message for the specified Win32 error code.
-        /// </summary>
-        internal static string GetMessage(int errorCode)
-        {
-            return Interop.Kernel32.GetMessage(errorCode);
-        }
-    }
-}
diff --git a/src/mscorlib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs b/src/mscorlib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Unix.cs
new file mode 100644 (file)
index 0000000..d13b536
--- /dev/null
@@ -0,0 +1,119 @@
+// 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;
+
+namespace Microsoft.Win32.SafeHandles
+{
+    public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
+    {
+        /// <summary>A handle value of -1.</summary>
+        private static readonly IntPtr s_invalidHandle = new IntPtr(-1);
+
+        private SafeFileHandle() : this(ownsHandle: true)
+        {
+        }
+
+        private SafeFileHandle(bool ownsHandle)
+            : base(ownsHandle)
+        {
+            SetHandle(s_invalidHandle);
+        }
+
+        public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : this(ownsHandle)
+        {
+            SetHandle(preexistingHandle);
+        }
+
+        internal bool? IsAsync { get; set; }
+
+        /// <summary>Opens the specified file with the requested flags and mode.</summary>
+        /// <param name="path">The path to the file.</param>
+        /// <param name="flags">The flags with which to open the file.</param>
+        /// <param name="mode">The mode for opening the file.</param>
+        /// <returns>A SafeFileHandle for the opened file.</returns>
+        internal static SafeFileHandle Open(string path, Interop.Sys.OpenFlags flags, int mode)
+        {
+            Debug.Assert(path != null);
+
+            // If we fail to open the file due to a path not existing, we need to know whether to blame
+            // the file itself or its directory.  If we're creating the file, then we blame the directory,
+            // otherwise we blame the file.
+            bool enoentDueToDirectory = (flags & Interop.Sys.OpenFlags.O_CREAT) != 0;
+
+            // Open the file. 
+            SafeFileHandle handle = Interop.CheckIo(
+                Interop.Sys.Open(path, flags, mode),
+                path, 
+                isDirectory: enoentDueToDirectory,
+                errorRewriter: e => (e.Error == Interop.Error.EISDIR) ? Interop.Error.EACCES.Info() : e);
+
+            // Make sure it's not a directory; we do this after opening it once we have a file descriptor 
+            // to avoid race conditions.
+            Interop.Sys.FileStatus status;
+            if (Interop.Sys.FStat(handle, out status) != 0)
+            {
+                handle.Dispose();
+                throw Interop.GetExceptionForIoErrno(Interop.Sys.GetLastErrorInfo(), path);
+            }
+            if ((status.Mode & Interop.Sys.FileTypes.S_IFMT) == Interop.Sys.FileTypes.S_IFDIR)
+            {
+                handle.Dispose();
+                throw Interop.GetExceptionForIoErrno(Interop.Error.EACCES.Info(), path, isDirectory: true);
+            }
+
+            return handle;
+        }
+
+        /// <summary>Opens a SafeFileHandle for a file descriptor created by a provided delegate.</summary>
+        /// <param name="fdFunc">
+        /// The function that creates the file descriptor. Returns the file descriptor on success, or an invalid
+        /// file descriptor on error with Marshal.GetLastWin32Error() set to the error code.
+        /// </param>
+        /// <returns>The created SafeFileHandle.</returns>
+        internal static SafeFileHandle Open(Func<SafeFileHandle> fdFunc)
+        {
+            SafeFileHandle handle = Interop.CheckIo(fdFunc());
+
+            Debug.Assert(!handle.IsInvalid, "File descriptor is invalid");
+            return handle;
+        }
+
+        protected override bool ReleaseHandle()
+        {
+            // When the SafeFileHandle was opened, we likely issued an flock on the created descriptor in order to add 
+            // an advisory lock.  This lock should be removed via closing the file descriptor, but close can be
+            // interrupted, and we don't retry closes.  As such, we could end up leaving the file locked,
+            // which could prevent subsequent usage of the file until this process dies.  To avoid that, we proactively
+            // try to release the lock before we close the handle. (If it's not locked, there's no behavioral
+            // problem trying to unlock it.)
+            Interop.Sys.FLock(handle, Interop.Sys.LockOperations.LOCK_UN); // ignore any errors
+
+            // Close the descriptor. Although close is documented to potentially fail with EINTR, we never want
+            // to retry, as the descriptor could actually have been closed, been subsequently reassigned, and
+            // be in use elsewhere in the process.  Instead, we simply check whether the call was successful.
+            int result = Interop.Sys.Close(handle);
+#if DEBUG
+            if (result != 0)
+            {
+                Debug.Fail(string.Format(
+                    "Close failed with result {0} and error {1}", 
+                    result, Interop.Sys.GetLastErrorInfo()));
+            }
+#endif
+            return result == 0;
+        }
+
+        public override bool IsInvalid
+        {
+            get
+            {
+                long h = (long)handle;
+                return h < 0 || h > int.MaxValue;
+            }
+        }
+    }
+}
diff --git a/src/mscorlib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs b/src/mscorlib/shared/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs
new file mode 100644 (file)
index 0000000..4eabe8f
--- /dev/null
@@ -0,0 +1,50 @@
+// 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.Security;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Microsoft.Win32;
+
+namespace Microsoft.Win32.SafeHandles
+{
+    public sealed class SafeFileHandle : SafeHandleZeroOrMinusOneIsInvalid
+    {
+        private bool? _isAsync;
+
+        private SafeFileHandle() : base(true)
+        {
+            _isAsync = null;
+        }
+
+        public SafeFileHandle(IntPtr preexistingHandle, bool ownsHandle) : base(ownsHandle)
+        {
+            SetHandle(preexistingHandle);
+
+            _isAsync = null;
+        }
+
+        internal bool? IsAsync
+        {
+            get
+            {
+                return _isAsync;
+            }
+
+            set
+            {
+                _isAsync = value;
+            }
+        }
+
+        internal ThreadPoolBoundHandle ThreadPoolBinding { get; set; }
+
+        override protected bool ReleaseHandle()
+        {
+            return Interop.Kernel32.CloseHandle(handle);
+        }
+    }
+}
+
diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.sources b/src/mscorlib/shared/System.Private.CoreLib.Shared.sources
new file mode 100644 (file)
index 0000000..211b25e
--- /dev/null
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <TargetsWindows Condition="'$(TargetsWindows)' != 'true'">false</TargetsWindows>
+    <TargetsUnix Condition="'$(TargetsUnix)' != 'true'">false</TargetsUnix>
+    <TargetsOSX Condition="'$(TargetsOSX)' != 'true'">false</TargetsOSX>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\Buffers\ArrayPool.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\Buffers\ArrayPoolEventSource.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\Buffers\ConfigurableArrayPool.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\Buffers\TlsOverPerCoreLockedStacksArrayPool.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\Buffers\Utilities.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\Error.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\FileStream.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\Path.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\PathInternal.cs"/>
+  </ItemGroup>
+  <ItemGroup Condition="$(TargetsWindows)">
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\FileStream.Win32.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\FileStreamCompletionSource.Win32.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\Path.Win32.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\Path.Windows.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\PathHelper.Windows.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\PathInternal.Windows.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\PathInternal.Windows.StringBuffer.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\Win32Marshal.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\Microsoft\Win32\SafeHandles\SafeFileHandle.Windows.cs"/>
+  </ItemGroup>
+  <ItemGroup Condition="$(TargetsUnix)">
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\FileStream.Unix.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\Path.Unix.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\PathInternal.Unix.cs"/>
+    <Compile Include="$(MSBuildThisFileDirectory)\Microsoft\Win32\SafeHandles\SafeFileHandle.Unix.cs"/>
+  </ItemGroup>
+  <ItemGroup Condition="$(TargetsUnix) and $(TargetsOSX)">
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\FileStream.OSX.cs"/>
+  </ItemGroup>
+  <ItemGroup Condition="$(TargetsUnix) and !$(TargetsOSX)">
+    <Compile Include="$(MSBuildThisFileDirectory)\System\IO\FileStream.Linux.cs"/>
+  </ItemGroup>
+</Project>
diff --git a/src/mscorlib/shared/System/Buffers/ArrayPool.cs b/src/mscorlib/shared/System/Buffers/ArrayPool.cs
new file mode 100644 (file)
index 0000000..77a07f7
--- /dev/null
@@ -0,0 +1,100 @@
+// 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.Buffers
+{
+    /// <summary>
+    /// Provides a resource pool that enables reusing instances of type <see cref="T:T[]"/>. 
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Renting and returning buffers with an <see cref="ArrayPool{T}"/> can increase performance
+    /// in situations where arrays are created and destroyed frequently, resulting in significant
+    /// memory pressure on the garbage collector.
+    /// </para>
+    /// <para>
+    /// This class is thread-safe.  All members may be used by multiple threads concurrently.
+    /// </para>
+    /// </remarks>
+    public abstract class ArrayPool<T>
+    {
+        /// <summary>
+        /// Retrieves a shared <see cref="ArrayPool{T}"/> instance.
+        /// </summary>
+        /// <remarks>
+        /// The shared pool provides a default implementation of <see cref="ArrayPool{T}"/>
+        /// that's intended for general applicability.  It maintains arrays of multiple sizes, and 
+        /// may hand back a larger array than was actually requested, but will never hand back a smaller 
+        /// array than was requested. Renting a buffer from it with <see cref="Rent"/> will result in an 
+        /// existing buffer being taken from the pool if an appropriate buffer is available or in a new 
+        /// buffer being allocated if one is not available.
+        /// byte[] and char[] are the most commonly pooled array types. For these we use a special pool type
+        /// optimized for very fast access speeds, at the expense of more memory consumption.
+        /// The shared pool instance is created lazily on first access.
+        /// </remarks>
+        public static ArrayPool<T> Shared { get; } =
+            typeof(T) == typeof(byte) || typeof(T) == typeof(char) ? new TlsOverPerCoreLockedStacksArrayPool<T>() :
+            Create();
+
+        /// <summary>
+        /// Creates a new <see cref="ArrayPool{T}"/> instance using default configuration options.
+        /// </summary>
+        /// <returns>A new <see cref="ArrayPool{T}"/> instance.</returns>
+        public static ArrayPool<T> Create() => new ConfigurableArrayPool<T>();
+
+        /// <summary>
+        /// Creates a new <see cref="ArrayPool{T}"/> instance using custom configuration options.
+        /// </summary>
+        /// <param name="maxArrayLength">The maximum length of array instances that may be stored in the pool.</param>
+        /// <param name="maxArraysPerBucket">
+        /// The maximum number of array instances that may be stored in each bucket in the pool.  The pool
+        /// groups arrays of similar lengths into buckets for faster access.
+        /// </param>
+        /// <returns>A new <see cref="ArrayPool{T}"/> instance with the specified configuration options.</returns>
+        /// <remarks>
+        /// The created pool will group arrays into buckets, with no more than <paramref name="maxArraysPerBucket"/>
+        /// in each bucket and with those arrays not exceeding <paramref name="maxArrayLength"/> in length.
+        /// </remarks>
+        public static ArrayPool<T> Create(int maxArrayLength, int maxArraysPerBucket) =>
+            new ConfigurableArrayPool<T>(maxArrayLength, maxArraysPerBucket);
+
+        /// <summary>
+        /// Retrieves a buffer that is at least the requested length.
+        /// </summary>
+        /// <param name="minimumLength">The minimum length of the array needed.</param>
+        /// <returns>
+        /// An <see cref="T:T[]"/> that is at least <paramref name="minimumLength"/> in length.
+        /// </returns>
+        /// <remarks>
+        /// This buffer is loaned to the caller and should be returned to the same pool via 
+        /// <see cref="Return"/> so that it may be reused in subsequent usage of <see cref="Rent"/>.  
+        /// It is not a fatal error to not return a rented buffer, but failure to do so may lead to 
+        /// decreased application performance, as the pool may need to create a new buffer to replace
+        /// the one lost.
+        /// </remarks>
+        public abstract T[] Rent(int minimumLength);
+
+        /// <summary>
+        /// Returns to the pool an array that was previously obtained via <see cref="Rent"/> on the same 
+        /// <see cref="ArrayPool{T}"/> instance.
+        /// </summary>
+        /// <param name="array">
+        /// The buffer previously obtained from <see cref="Rent"/> to return to the pool.
+        /// </param>
+        /// <param name="clearArray">
+        /// If <c>true</c> and if the pool will store the buffer to enable subsequent reuse, <see cref="Return"/>
+        /// will clear <paramref name="array"/> of its contents so that a subsequent consumer via <see cref="Rent"/> 
+        /// will not see the previous consumer's content.  If <c>false</c> or if the pool will release the buffer,
+        /// the array's contents are left unchanged.
+        /// </param>
+        /// <remarks>
+        /// Once a buffer has been returned to the pool, the caller gives up all ownership of the buffer 
+        /// and must not use it. The reference returned from a given call to <see cref="Rent"/> must only be
+        /// returned via <see cref="Return"/> once.  The default <see cref="ArrayPool{T}"/>
+        /// may hold onto the returned buffer in order to rent it again, or it may release the returned buffer
+        /// if it's determined that the pool already has enough buffers stored.
+        /// </remarks>
+        public abstract void Return(T[] array, bool clearArray = false);
+    }
+}
diff --git a/src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs b/src/mscorlib/shared/System/Buffers/ArrayPoolEventSource.cs
new file mode 100644 (file)
index 0000000..9482744
--- /dev/null
@@ -0,0 +1,78 @@
+// 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.Tracing;
+
+namespace System.Buffers
+{
+    [EventSource(Name = "System.Buffers.ArrayPoolEventSource")]
+    internal sealed class ArrayPoolEventSource : EventSource
+    {
+        internal readonly static ArrayPoolEventSource Log = new ArrayPoolEventSource();
+
+        /// <summary>The reason for a BufferAllocated event.</summary>
+        internal enum BufferAllocatedReason : int
+        {
+            /// <summary>The pool is allocating a buffer to be pooled in a bucket.</summary>
+            Pooled,
+            /// <summary>The requested buffer size was too large to be pooled.</summary>
+            OverMaximumSize,
+            /// <summary>The pool has already allocated for pooling as many buffers of a particular size as it's allowed.</summary>
+            PoolExhausted
+        }
+
+        /// <summary>
+        /// Event for when a buffer is rented.  This is invoked once for every successful call to Rent,
+        /// regardless of whether a buffer is allocated or a buffer is taken from the pool.  In a
+        /// perfect situation where all rented buffers are returned, we expect to see the number
+        /// of BufferRented events exactly match the number of BuferReturned events, with the number
+        /// of BufferAllocated events being less than or equal to those numbers (ideally significantly
+        /// less than).
+        /// </summary>
+        [Event(1, Level = EventLevel.Verbose)]
+        internal unsafe void BufferRented(int bufferId, int bufferSize, int poolId, int bucketId)
+        {
+            EventData* payload = stackalloc EventData[4];
+            payload[0].Size = sizeof(int);
+            payload[0].DataPointer = ((IntPtr)(&bufferId));
+            payload[1].Size = sizeof(int);
+            payload[1].DataPointer = ((IntPtr)(&bufferSize));
+            payload[2].Size = sizeof(int);
+            payload[2].DataPointer = ((IntPtr)(&poolId));
+            payload[3].Size = sizeof(int);
+            payload[3].DataPointer = ((IntPtr)(&bucketId));
+            WriteEventCore(1, 4, payload);
+        }
+
+        /// <summary>
+        /// Event for when a buffer is allocated by the pool.  In an ideal situation, the number
+        /// of BufferAllocated events is significantly smaller than the number of BufferRented and
+        /// BufferReturned events.
+        /// </summary>
+        [Event(2, Level = EventLevel.Informational)]
+        internal unsafe void BufferAllocated(int bufferId, int bufferSize, int poolId, int bucketId, BufferAllocatedReason reason)
+        {
+            EventData* payload = stackalloc EventData[5];
+            payload[0].Size = sizeof(int);
+            payload[0].DataPointer = ((IntPtr)(&bufferId));
+            payload[1].Size = sizeof(int);
+            payload[1].DataPointer = ((IntPtr)(&bufferSize));
+            payload[2].Size = sizeof(int);
+            payload[2].DataPointer = ((IntPtr)(&poolId));
+            payload[3].Size = sizeof(int);
+            payload[3].DataPointer = ((IntPtr)(&bucketId));
+            payload[4].Size = sizeof(BufferAllocatedReason);
+            payload[4].DataPointer = ((IntPtr)(&reason));
+            WriteEventCore(2, 5, payload);
+        }
+
+        /// <summary>
+        /// Event raised when a buffer is returned to the pool.  This event is raised regardless of whether
+        /// the returned buffer is stored or dropped.  In an ideal situation, the number of BufferReturned
+        /// events exactly matches the number of BufferRented events.
+        /// </summary>
+        [Event(3, Level = EventLevel.Verbose)]
+        internal void BufferReturned(int bufferId, int bufferSize, int poolId) => WriteEvent(3, bufferId, bufferSize, poolId);
+    }
+}
diff --git a/src/mscorlib/shared/System/Buffers/ConfigurableArrayPool.cs b/src/mscorlib/shared/System/Buffers/ConfigurableArrayPool.cs
new file mode 100644 (file)
index 0000000..f7b6034
--- /dev/null
@@ -0,0 +1,265 @@
+// 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.Threading;
+
+namespace System.Buffers
+{
+    internal sealed partial class ConfigurableArrayPool<T> : ArrayPool<T>
+    {
+        /// <summary>The default maximum length of each array in the pool (2^20).</summary>
+        private const int DefaultMaxArrayLength = 1024 * 1024;
+        /// <summary>The default maximum number of arrays per bucket that are available for rent.</summary>
+        private const int DefaultMaxNumberOfArraysPerBucket = 50;
+
+        private readonly Bucket[] _buckets;
+
+        internal ConfigurableArrayPool() : this(DefaultMaxArrayLength, DefaultMaxNumberOfArraysPerBucket)
+        {
+        }
+
+        internal ConfigurableArrayPool(int maxArrayLength, int maxArraysPerBucket)
+        {
+            if (maxArrayLength <= 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(maxArrayLength));
+            }
+            if (maxArraysPerBucket <= 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(maxArraysPerBucket));
+            }
+
+            // Our bucketing algorithm has a min length of 2^4 and a max length of 2^30.
+            // Constrain the actual max used to those values.
+            const int MinimumArrayLength = 0x10, MaximumArrayLength = 0x40000000;
+            if (maxArrayLength > MaximumArrayLength)
+            {
+                maxArrayLength = MaximumArrayLength;
+            }
+            else if (maxArrayLength < MinimumArrayLength)
+            {
+                maxArrayLength = MinimumArrayLength;
+            }
+
+            // Create the buckets.
+            int poolId = Id;
+            int maxBuckets = Utilities.SelectBucketIndex(maxArrayLength);
+            var buckets = new Bucket[maxBuckets + 1];
+            for (int i = 0; i < buckets.Length; i++)
+            {
+                buckets[i] = new Bucket(Utilities.GetMaxSizeForBucket(i), maxArraysPerBucket, poolId);
+            }
+            _buckets = buckets;
+        }
+
+        /// <summary>Gets an ID for the pool to use with events.</summary>
+        private int Id => GetHashCode();
+
+        public override T[] Rent(int minimumLength)
+        {
+            // Arrays can't be smaller than zero.  We allow requesting zero-length arrays (even though
+            // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
+            // to be usable in general instead of using `new`, even for computed lengths.
+            if (minimumLength < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(minimumLength));
+            }
+            else if (minimumLength == 0)
+            {
+                // No need for events with the empty array.  Our pool is effectively infinite
+                // and we'll never allocate for rents and never store for returns.
+                return Array.Empty<T>();
+            }
+
+            var log = ArrayPoolEventSource.Log;
+            T[] buffer = null;
+
+            int index = Utilities.SelectBucketIndex(minimumLength);
+            if (index < _buckets.Length)
+            {
+                // Search for an array starting at the 'index' bucket. If the bucket is empty, bump up to the
+                // next higher bucket and try that one, but only try at most a few buckets.
+                const int MaxBucketsToTry = 2;
+                int i = index;
+                do
+                {
+                    // Attempt to rent from the bucket.  If we get a buffer from it, return it.
+                    buffer = _buckets[i].Rent();
+                    if (buffer != null)
+                    {
+                        if (log.IsEnabled())
+                        {
+                            log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, _buckets[i].Id);
+                        }
+                        return buffer;
+                    }
+                }
+                while (++i < _buckets.Length && i != index + MaxBucketsToTry);
+
+                // The pool was exhausted for this buffer size.  Allocate a new buffer with a size corresponding
+                // to the appropriate bucket.
+                buffer = new T[_buckets[index]._bufferLength];
+            }
+            else
+            {
+                // The request was for a size too large for the pool.  Allocate an array of exactly the requested length.
+                // When it's returned to the pool, we'll simply throw it away.
+                buffer = new T[minimumLength];
+            }
+
+            if (log.IsEnabled())
+            {
+                int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer
+                log.BufferRented(bufferId, buffer.Length, Id, bucketId);
+                log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, index >= _buckets.Length ?
+                    ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize : ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
+            }
+
+            return buffer;
+        }
+
+        public override void Return(T[] array, bool clearArray = false)
+        {
+            if (array == null)
+            {
+                throw new ArgumentNullException(nameof(array));
+            }
+            else if (array.Length == 0)
+            {
+                // Ignore empty arrays.  When a zero-length array is rented, we return a singleton
+                // rather than actually taking a buffer out of the lowest bucket.
+                return;
+            }
+
+            // Determine with what bucket this array length is associated
+            int bucket = Utilities.SelectBucketIndex(array.Length);
+
+            // If we can tell that the buffer was allocated, drop it. Otherwise, check if we have space in the pool
+            if (bucket < _buckets.Length)
+            {
+                // Clear the array if the user requests
+                if (clearArray)
+                {
+                    Array.Clear(array, 0, array.Length);
+                }
+
+                // Return the buffer to its bucket.  In the future, we might consider having Return return false
+                // instead of dropping a bucket, in which case we could try to return to a lower-sized bucket,
+                // just as how in Rent we allow renting from a higher-sized bucket.
+                _buckets[bucket].Return(array);
+            }
+
+            // Log that the buffer was returned
+            var log = ArrayPoolEventSource.Log;
+            if (log.IsEnabled())
+            {
+                log.BufferReturned(array.GetHashCode(), array.Length, Id);
+            }
+        }
+
+        /// <summary>Provides a thread-safe bucket containing buffers that can be Rent'd and Return'd.</summary>
+        private sealed class Bucket
+        {
+            internal readonly int _bufferLength;
+            private readonly T[][] _buffers;
+            private readonly int _poolId;
+
+            private SpinLock _lock; // do not make this readonly; it's a mutable struct
+            private int _index;
+
+            /// <summary>
+            /// Creates the pool with numberOfBuffers arrays where each buffer is of bufferLength length.
+            /// </summary>
+            internal Bucket(int bufferLength, int numberOfBuffers, int poolId)
+            {
+                _lock = new SpinLock(Debugger.IsAttached); // only enable thread tracking if debugger is attached; it adds non-trivial overheads to Enter/Exit
+                _buffers = new T[numberOfBuffers][];
+                _bufferLength = bufferLength;
+                _poolId = poolId;
+            }
+
+            /// <summary>Gets an ID for the bucket to use with events.</summary>
+            internal int Id => GetHashCode();
+
+            /// <summary>Takes an array from the bucket.  If the bucket is empty, returns null.</summary>
+            internal T[] Rent()
+            {
+                T[][] buffers = _buffers;
+                T[] buffer = null;
+
+                // While holding the lock, grab whatever is at the next available index and
+                // update the index.  We do as little work as possible while holding the spin
+                // lock to minimize contention with other threads.  The try/finally is
+                // necessary to properly handle thread aborts on platforms which have them.
+                bool lockTaken = false, allocateBuffer = false;
+                try
+                {
+                    _lock.Enter(ref lockTaken);
+
+                    if (_index < buffers.Length)
+                    {
+                        buffer = buffers[_index];
+                        buffers[_index++] = null;
+                        allocateBuffer = buffer == null;
+                    }
+                }
+                finally
+                {
+                    if (lockTaken) _lock.Exit(false);
+                }
+
+                // While we were holding the lock, we grabbed whatever was at the next available index, if
+                // there was one.  If we tried and if we got back null, that means we hadn't yet allocated
+                // for that slot, in which case we should do so now.
+                if (allocateBuffer)
+                {
+                    buffer = new T[_bufferLength];
+
+                    var log = ArrayPoolEventSource.Log;
+                    if (log.IsEnabled())
+                    {
+                        log.BufferAllocated(buffer.GetHashCode(), _bufferLength, _poolId, Id,
+                            ArrayPoolEventSource.BufferAllocatedReason.Pooled);
+                    }
+                }
+
+                return buffer;
+            }
+
+            /// <summary>
+            /// Attempts to return the buffer to the bucket.  If successful, the buffer will be stored
+            /// in the bucket and true will be returned; otherwise, the buffer won't be stored, and false
+            /// will be returned.
+            /// </summary>
+            internal void Return(T[] array)
+            {
+                // Check to see if the buffer is the correct size for this bucket
+                if (array.Length != _bufferLength)
+                {
+                    throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array));
+                }
+
+                // While holding the spin lock, if there's room available in the bucket,
+                // put the buffer into the next available slot.  Otherwise, we just drop it.
+                // The try/finally is necessary to properly handle thread aborts on platforms
+                // which have them.
+                bool lockTaken = false;
+                try
+                {
+                    _lock.Enter(ref lockTaken);
+
+                    if (_index != 0)
+                    {
+                        _buffers[--_index] = array;
+                    }
+                }
+                finally
+                {
+                    if (lockTaken) _lock.Exit(false);
+                }
+            }
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs b/src/mscorlib/shared/System/Buffers/TlsOverPerCoreLockedStacksArrayPool.cs
new file mode 100644 (file)
index 0000000..64c5ceb
--- /dev/null
@@ -0,0 +1,292 @@
+// 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 Microsoft.Win32;
+using System.Runtime.CompilerServices;
+using System.Threading;
+
+namespace System.Buffers
+{
+    /// <summary>
+    /// Provides an ArrayPool implementation meant to be used as the singleton returned from ArrayPool.Shared.
+    /// </summary>
+    /// <remarks>
+    /// The implementation uses a tiered caching scheme, with a small per-thread cache for each array size, followed
+    /// by a cache per array size shared by all threads, split into per-core stacks meant to be used by threads
+    /// running on that core.  Locks are used to protect each per-core stack, because a thread can migrate after
+    /// checking its processor number, because multiple threads could interleave on the same core, and because
+    /// a thread is allowed to check other core's buckets if its core's bucket is empty/full.
+    /// </remarks>
+    internal sealed partial class TlsOverPerCoreLockedStacksArrayPool<T> : ArrayPool<T>
+    {
+        // TODO: #7747: "Investigate optimizing ArrayPool heuristics"
+        // - Explore caching in TLS more than one array per size per thread, and moving stale buffers to the global queue.
+        // - Explore dumping stale buffers from the global queue, similar to PinnableBufferCache (maybe merging them).
+        // - Explore changing the size of each per-core bucket, potentially dynamically or based on other factors like array size.
+        // - Explore changing number of buckets and what sizes of arrays are cached.
+        // - Investigate whether false sharing is causing any issues, in particular on LockedStack's count and the contents of its array.
+        // ...
+
+        /// <summary>The number of buckets (array sizes) in the pool, one for each array length, starting from length 16.</summary>
+        private const int NumBuckets = 17; // Utilities.SelectBucketIndex(2*1024*1024)
+        /// <summary>Maximum number of per-core stacks to use per array size.</summary>
+        private const int MaxPerCorePerArraySizeStacks = 64; // selected to avoid needing to worry about processor groups
+        /// <summary>The maximum number of buffers to store in a bucket's global queue.</summary>
+        private const int MaxBuffersPerArraySizePerCore = 8;
+
+        /// <summary>The length of arrays stored in the corresponding indices in <see cref="_buckets"/> and <see cref="t_tlsBuckets"/>.</summary>
+        private readonly int[] _bucketArraySizes;
+        /// <summary>
+        /// An array of per-core array stacks. The slots are lazily initialized to avoid creating
+        /// lots of overhead for unused array sizes.
+        /// </summary>
+        private readonly PerCoreLockedStacks[] _buckets = new PerCoreLockedStacks[NumBuckets];
+        /// <summary>A per-thread array of arrays, to cache one array per array size per thread.</summary>
+        [ThreadStatic]
+        private static T[][] t_tlsBuckets;
+
+        /// <summary>Initialize the pool.</summary>
+        public TlsOverPerCoreLockedStacksArrayPool()
+        {
+            var sizes = new int[NumBuckets];
+            for (int i = 0; i < sizes.Length; i++)
+            {
+                sizes[i] = Utilities.GetMaxSizeForBucket(i);
+            }
+            _bucketArraySizes = sizes;
+        }
+
+        /// <summary>Allocate a new PerCoreLockedStacks and try to store it into the <see cref="_buckets"/> array.</summary>
+        private PerCoreLockedStacks CreatePerCoreLockedStacks(int bucketIndex)
+        {
+            var inst = new PerCoreLockedStacks();
+            return Interlocked.CompareExchange(ref _buckets[bucketIndex], inst, null) ?? inst;
+        }
+
+        /// <summary>Gets an ID for the pool to use with events.</summary>
+        private int Id => GetHashCode();
+
+        public override T[] Rent(int minimumLength)
+        {
+            // Arrays can't be smaller than zero.  We allow requesting zero-length arrays (even though
+            // pooling such an array isn't valuable) as it's a valid length array, and we want the pool
+            // to be usable in general instead of using `new`, even for computed lengths.
+            if (minimumLength < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(minimumLength));
+            }
+            else if (minimumLength == 0)
+            {
+                // No need to log the empty array.  Our pool is effectively infinite
+                // and we'll never allocate for rents and never store for returns.
+                return Array.Empty<T>();
+            }
+
+            ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+            T[] buffer;
+
+            // Get the bucket number for the array length
+            int bucketIndex = Utilities.SelectBucketIndex(minimumLength);
+
+            // If the array could come from a bucket...
+            if (bucketIndex < _buckets.Length)
+            {
+                // First try to get it from TLS if possible.
+                T[][] tlsBuckets = t_tlsBuckets;
+                if (tlsBuckets != null)
+                {
+                    buffer = tlsBuckets[bucketIndex];
+                    if (buffer != null)
+                    {
+                        tlsBuckets[bucketIndex] = null;
+                        if (log.IsEnabled())
+                        {
+                            log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex);
+                        }
+                        return buffer;
+                    }
+                }
+
+                // We couldn't get a buffer from TLS, so try the global stack.
+                PerCoreLockedStacks b = _buckets[bucketIndex];
+                if (b != null)
+                {
+                    buffer = b.TryPop();
+                    if (buffer != null)
+                    {
+                        if (log.IsEnabled())
+                        {
+                            log.BufferRented(buffer.GetHashCode(), buffer.Length, Id, bucketIndex);
+                        }
+                        return buffer;
+                    }
+                }
+
+                // No buffer available.  Allocate a new buffer with a size corresponding to the appropriate bucket.
+                buffer = new T[_bucketArraySizes[bucketIndex]];
+            }
+            else
+            {
+                // The request was for a size too large for the pool.  Allocate an array of exactly the requested length.
+                // When it's returned to the pool, we'll simply throw it away.
+                buffer = new T[minimumLength];
+            }
+
+            if (log.IsEnabled())
+            {
+                int bufferId = buffer.GetHashCode(), bucketId = -1; // no bucket for an on-demand allocated buffer
+                log.BufferRented(bufferId, buffer.Length, Id, bucketId);
+                log.BufferAllocated(bufferId, buffer.Length, Id, bucketId, bucketIndex >= _buckets.Length ?
+                    ArrayPoolEventSource.BufferAllocatedReason.OverMaximumSize :
+                    ArrayPoolEventSource.BufferAllocatedReason.PoolExhausted);
+            }
+
+            return buffer;
+        }
+
+        public override void Return(T[] array, bool clearArray = false)
+        {
+            if (array == null)
+            {
+                throw new ArgumentNullException(nameof(array));
+            }
+
+            // Determine with what bucket this array length is associated
+            int bucketIndex = Utilities.SelectBucketIndex(array.Length);
+
+            // If we can tell that the buffer was allocated (or empty), drop it. Otherwise, check if we have space in the pool.
+            if (bucketIndex < _buckets.Length)
+            {
+                // Clear the array if the user requests.
+                if (clearArray)
+                {
+                    Array.Clear(array, 0, array.Length);
+                }
+
+                // Check to see if the buffer is the correct size for this bucket
+                if (array.Length != _bucketArraySizes[bucketIndex])
+                {
+                    throw new ArgumentException(SR.ArgumentException_BufferNotFromPool, nameof(array));
+                }
+
+                // Write through the TLS bucket.  If there weren't any buckets, create them
+                // and store this array into it.  If there were, store this into it, and
+                // if there was a previous one there, push that to the global stack.  This
+                // helps to keep LIFO access such that the most recently pushed stack will
+                // be in TLS and the first to be popped next.
+                T[][] tlsBuckets = t_tlsBuckets;
+                if (tlsBuckets == null)
+                {
+                    t_tlsBuckets = tlsBuckets = new T[NumBuckets][];
+                    tlsBuckets[bucketIndex] = array;
+                }
+                else
+                {
+                    T[] prev = tlsBuckets[bucketIndex];
+                    tlsBuckets[bucketIndex] = array;
+                    if (prev != null)
+                    {
+                        PerCoreLockedStacks bucket = _buckets[bucketIndex] ?? CreatePerCoreLockedStacks(bucketIndex);
+                        bucket.TryPush(prev);
+                    }
+                }
+            }
+
+            // Log that the buffer was returned
+            ArrayPoolEventSource log = ArrayPoolEventSource.Log;
+            if (log.IsEnabled())
+            {
+                log.BufferReturned(array.GetHashCode(), array.Length, Id);
+            }
+        }
+
+        /// <summary>
+        /// Stores a set of stacks of arrays, with one stack per core.
+        /// </summary>
+        private sealed class PerCoreLockedStacks
+        {
+            /// <summary>The stacks.</summary>
+            private readonly LockedStack[] _perCoreStacks;
+
+            /// <summary>Initializes the stacks.</summary>
+            public PerCoreLockedStacks()
+            {
+                // Create the stacks.  We create as many as there are processors, limited by our max.
+                var stacks = new LockedStack[Math.Min(Environment.ProcessorCount, MaxPerCorePerArraySizeStacks)];
+                for (int i = 0; i < stacks.Length; i++)
+                {
+                    stacks[i] = new LockedStack();
+                }
+                _perCoreStacks = stacks;
+            }
+
+            /// <summary>Try to push the array into the stacks. If each is full when it's tested, the array will be dropped.</summary>
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            public void TryPush(T[] array)
+            {
+                // Try to push on to the associated stack first.  If that fails,
+                // round-robin through the other stacks.
+                LockedStack[] stacks = _perCoreStacks;
+                int index = Environment.CurrentExecutionId % stacks.Length;
+                for (int i = 0; i < stacks.Length; i++)
+                {
+                    if (stacks[index].TryPush(array)) return;
+                    if (++index == stacks.Length) index = 0;
+                }
+            }
+
+            /// <summary>Try to get an array from the stacks.  If each is empty when it's tested, null will be returned.</summary>
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            public T[] TryPop()
+            {
+                // Try to pop from the associated stack first.  If that fails,
+                // round-robin through the other stacks.
+                T[] arr;
+                LockedStack[] stacks = _perCoreStacks;
+                int index = Environment.CurrentExecutionId % stacks.Length;
+                for (int i = 0; i < stacks.Length; i++)
+                {
+                    if ((arr = stacks[index].TryPop()) != null) return arr;
+                    if (++index == stacks.Length) index = 0;
+                }
+                return null;
+            }
+        }
+
+        /// <summary>Provides a simple stack of arrays, protected by a lock.</summary>
+        private sealed class LockedStack
+        {
+            private readonly T[][] _arrays = new T[MaxBuffersPerArraySizePerCore][];
+            private int _count;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            public bool TryPush(T[] array)
+            {
+                bool enqueued = false;
+                Monitor.Enter(this);
+                if (_count < MaxBuffersPerArraySizePerCore)
+                {
+                    _arrays[_count++] = array;
+                    enqueued = true;
+                }
+                Monitor.Exit(this);
+                return enqueued;
+            }
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            public T[] TryPop()
+            {
+                T[] arr = null;
+                Monitor.Enter(this);
+                if (_count > 0)
+                {
+                    arr = _arrays[--_count];
+                    _arrays[_count] = null;
+                }
+                Monitor.Exit(this);
+                return arr;
+            }
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/Buffers/Utilities.cs b/src/mscorlib/shared/System/Buffers/Utilities.cs
new file mode 100644 (file)
index 0000000..4f115fe
--- /dev/null
@@ -0,0 +1,35 @@
+// 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;
+
+namespace System.Buffers
+{
+    internal static class Utilities
+    {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static int SelectBucketIndex(int bufferSize)
+        {
+            uint bitsRemaining = ((uint)bufferSize - 1) >> 4;
+
+            int poolIndex = 0;
+            if (bitsRemaining > 0xFFFF) { bitsRemaining >>= 16; poolIndex = 16; }
+            if (bitsRemaining > 0xFF) { bitsRemaining >>= 8; poolIndex += 8; }
+            if (bitsRemaining > 0xF) { bitsRemaining >>= 4; poolIndex += 4; }
+            if (bitsRemaining > 0x3) { bitsRemaining >>= 2; poolIndex += 2; }
+            if (bitsRemaining > 0x1) { bitsRemaining >>= 1; poolIndex += 1; }
+
+            return poolIndex + (int)bitsRemaining;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static int GetMaxSizeForBucket(int binIndex)
+        {
+            int maxSize = 16 << binIndex;
+            Debug.Assert(maxSize >= 0);
+            return maxSize;
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/Error.cs b/src/mscorlib/shared/System/IO/Error.cs
new file mode 100644 (file)
index 0000000..acf5b35
--- /dev/null
@@ -0,0 +1,49 @@
+// 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;
+using System.Text;
+using System.Globalization;
+using System.Diagnostics.Contracts;
+
+namespace System.IO
+{
+    /// <summary>
+    ///     Provides centralized methods for creating exceptions for System.IO.FileSystem.
+    /// </summary>
+    [Pure]
+    internal static class Error
+    {
+        internal static Exception GetStreamIsClosed()
+        {
+            return new ObjectDisposedException(null, SR.ObjectDisposed_StreamIsClosed);
+        }
+
+        internal static Exception GetEndOfFile()
+        {
+            return new EndOfStreamException(SR.IO_EOF_ReadBeyondEOF);
+        }
+
+        internal static Exception GetFileNotOpen()
+        {
+            return new ObjectDisposedException(null, SR.ObjectDisposed_FileClosed);
+        }
+
+        internal static Exception GetReadNotSupported()
+        {
+            return new NotSupportedException(SR.NotSupported_UnreadableStream);
+        }
+
+        internal static Exception GetSeekNotSupported()
+        {
+            return new NotSupportedException(SR.NotSupported_UnseekableStream);
+        }
+
+        internal static Exception GetWriteNotSupported()
+        {
+            return new NotSupportedException(SR.NotSupported_UnwritableStream);
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/FileStream.Linux.cs b/src/mscorlib/shared/System/IO/FileStream.Linux.cs
new file mode 100644 (file)
index 0000000..873c4eb
--- /dev/null
@@ -0,0 +1,30 @@
+// 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 Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+    public partial class FileStream : Stream
+    {
+        /// <summary>Prevents other processes from reading from or writing to the FileStream.</summary>
+        /// <param name="position">The beginning of the range to lock.</param>
+        /// <param name="length">The range to be locked.</param>
+        private void LockInternal(long position, long length)
+        {
+            CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_WRLCK));
+        }
+
+        /// <summary>Allows access by other processes to all or part of a file that was previously locked.</summary>
+        /// <param name="position">The beginning of the range to unlock.</param>
+        /// <param name="length">The range to be unlocked.</param>
+        private void UnlockInternal(long position, long length)
+        {
+            CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_UNLCK));
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/FileStream.OSX.cs b/src/mscorlib/shared/System/IO/FileStream.OSX.cs
new file mode 100644 (file)
index 0000000..a1167bf
--- /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.
+
+namespace System.IO
+{
+    public partial class FileStream : Stream
+    {
+        private void LockInternal(long position, long length)
+        {
+            throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_OSXFileLocking"));
+        }
+
+        private void UnlockInternal(long position, long length)
+        {
+            throw new PlatformNotSupportedException(Environment.GetResourceString("PlatformNotSupported_OSXFileLocking"));
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/FileStream.Unix.cs b/src/mscorlib/shared/System/IO/FileStream.Unix.cs
new file mode 100644 (file)
index 0000000..87bcc6c
--- /dev/null
@@ -0,0 +1,918 @@
+// 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 Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.IO
+{
+    /// <summary>Provides an implementation of a file stream for Unix files.</summary>
+    public partial class FileStream : Stream
+    {
+        /// <summary>File mode.</summary>
+        private FileMode _mode;
+
+        /// <summary>Advanced options requested when opening the file.</summary>
+        private FileOptions _options;
+
+        /// <summary>If the file was opened with FileMode.Append, the length of the file when opened; otherwise, -1.</summary>
+        private long _appendStart = -1;
+
+        /// <summary>
+        /// Extra state used by the file stream when _useAsyncIO is true.  This includes
+        /// the semaphore used to serialize all operation, the buffer/offset/count provided by the
+        /// caller for ReadAsync/WriteAsync operations, and the last successful task returned
+        /// synchronously from ReadAsync which can be reused if the count matches the next request.
+        /// Only initialized when <see cref="_useAsyncIO"/> is true.
+        /// </summary>
+        private AsyncState _asyncState;
+
+        /// <summary>Lazily-initialized value for whether the file supports seeking.</summary>
+        private bool? _canSeek;
+
+        private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
+        {
+            // FileStream performs most of the general argument validation.  We can assume here that the arguments
+            // are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.)
+            // Store the arguments
+            _mode = mode;
+            _options = options;
+
+            if (_useAsyncIO)
+                _asyncState = new AsyncState();
+
+            // Translate the arguments into arguments for an open call.
+            Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, _access, options); // FileShare currently ignored
+
+            // If the file gets created a new, we'll select the permissions for it.  Most Unix utilities by default use 666 (read and 
+            // write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
+            // a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
+            // actual permissions will typically be less than what we select here.
+            const Interop.Sys.Permissions OpenPermissions =
+                Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
+                Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
+                Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
+
+            // Open the file and store the safe handle.
+            return SafeFileHandle.Open(_path, openFlags, (int)OpenPermissions);
+        }
+
+        /// <summary>Initializes a stream for reading or writing a Unix file.</summary>
+        /// <param name="mode">How the file should be opened.</param>
+        /// <param name="share">What other access to the file should be allowed.  This is currently ignored.</param>
+        private void Init(FileMode mode, FileShare share)
+        {
+            _fileHandle.IsAsync = _useAsyncIO;
+
+            // Lock the file if requested via FileShare.  This is only advisory locking. FileShare.None implies an exclusive 
+            // lock on the file and all other modes use a shared lock.  While this is not as granular as Windows, not mandatory, 
+            // and not atomic with file opening, it's better than nothing.
+            Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
+            if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
+            {
+                // The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
+                // else and we would block trying to access it.  Other errors, such as ENOTSUP (locking isn't supported) or
+                // EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
+                // given again that this is only advisory / best-effort.
+                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+                if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
+                {
+                    throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
+                }
+            }
+
+            // These provide hints around how the file will be accessed.  Specifying both RandomAccess
+            // and Sequential together doesn't make sense as they are two competing options on the same spectrum,
+            // so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
+            Interop.Sys.FileAdvice fadv =
+                (_options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
+                (_options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
+                0;
+            if (fadv != 0)
+            {
+                CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv),
+                    ignoreNotSupported: true); // just a hint.
+            }
+
+            // Jump to the end of the file if opened as Append.
+            if (_mode == FileMode.Append)
+            {
+                _appendStart = SeekCore(0, SeekOrigin.End);
+            }
+        }
+
+        /// <summary>Initializes a stream from an already open file handle (file descriptor).</summary>
+        /// <param name="handle">The handle to the file.</param>
+        /// <param name="bufferSize">The size of the buffer to use when buffering.</param>
+        /// <param name="useAsyncIO">Whether access to the stream is performed asynchronously.</param>
+        private void InitFromHandle(SafeFileHandle handle)
+        {
+            if (_useAsyncIO)
+                _asyncState = new AsyncState();
+
+            if (CanSeekCore) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor
+                SeekCore(0, SeekOrigin.Current);
+        }
+
+        /// <summary>Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.</summary>
+        /// <param name="mode">The FileMode provided to the stream's constructor.</param>
+        /// <param name="access">The FileAccess provided to the stream's constructor</param>
+        /// <param name="options">The FileOptions provided to the stream's constructor</param>
+        /// <returns>The flags value to be passed to the open system call.</returns>
+        private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileOptions options)
+        {
+            // Translate FileMode.  Most of the values map cleanly to one or more options for open.
+            Interop.Sys.OpenFlags flags = default(Interop.Sys.OpenFlags);
+            switch (mode)
+            {
+                default:
+                case FileMode.Open: // Open maps to the default behavior for open(...).  No flags needed.
+                    break;
+
+                case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
+                case FileMode.OpenOrCreate:
+                    flags |= Interop.Sys.OpenFlags.O_CREAT;
+                    break;
+
+                case FileMode.Create:
+                    flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_TRUNC);
+                    break;
+
+                case FileMode.CreateNew:
+                    flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
+                    break;
+
+                case FileMode.Truncate:
+                    flags |= Interop.Sys.OpenFlags.O_TRUNC;
+                    break;
+            }
+
+            // Translate FileAccess.  All possible values map cleanly to corresponding values for open.
+            switch (access)
+            {
+                case FileAccess.Read:
+                    flags |= Interop.Sys.OpenFlags.O_RDONLY;
+                    break;
+
+                case FileAccess.ReadWrite:
+                    flags |= Interop.Sys.OpenFlags.O_RDWR;
+                    break;
+
+                case FileAccess.Write:
+                    flags |= Interop.Sys.OpenFlags.O_WRONLY;
+                    break;
+            }
+
+            // Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
+            // - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
+            // - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
+            // - Encrypted: No equivalent on Unix and is ignored
+            // - RandomAccess: Implemented after open if posix_fadvise is available
+            // - SequentialScan: Implemented after open if posix_fadvise is available
+            // - WriteThrough: Handled here
+            if ((options & FileOptions.WriteThrough) != 0)
+            {
+                flags |= Interop.Sys.OpenFlags.O_SYNC;
+            }
+
+            return flags;
+        }
+
+        /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
+        public override bool CanSeek => CanSeekCore;
+
+        /// <summary>Gets a value indicating whether the current stream supports seeking.</summary>
+        /// <remarks>Separated out of CanSeek to enable making non-virtual call to this logic.</remarks>
+        private bool CanSeekCore
+        {
+            get
+            {
+                if (_fileHandle.IsClosed)
+                {
+                    return false;
+                }
+
+                if (!_canSeek.HasValue)
+                {
+                    // Lazily-initialize whether we're able to seek, tested by seeking to our current location.
+                    _canSeek = Interop.Sys.LSeek(_fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;
+                }
+                return _canSeek.Value;
+            }
+        }
+
+        private long GetLengthInternal()
+        {
+            // Get the length of the file as reported by the OS
+            Interop.Sys.FileStatus status;
+            CheckFileCall(Interop.Sys.FStat(_fileHandle, out status));
+            long length = status.Size;
+
+            // But we may have buffered some data to be written that puts our length
+            // beyond what the OS is aware of.  Update accordingly.
+            if (_writePos > 0 && _filePosition + _writePos > length)
+            {
+                length = _writePos + _filePosition;
+            }
+
+            return length;
+        }
+
+        /// <summary>Releases the unmanaged resources used by the stream.</summary>
+        /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
+        protected override void Dispose(bool disposing)
+        {
+            try
+            {
+                if (_fileHandle != null && !_fileHandle.IsClosed)
+                {
+                    // Flush any remaining data in the file
+                    FlushWriteBuffer();
+
+                    // If DeleteOnClose was requested when constructed, delete the file now.
+                    // (Unix doesn't directly support DeleteOnClose, so we mimic it here.)
+                    if (_path != null && (_options & FileOptions.DeleteOnClose) != 0)
+                    {
+                        // Since we still have the file open, this will end up deleting
+                        // it (assuming we're the only link to it) once it's closed, but the
+                        // name will be removed immediately.
+                        Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist
+                    }
+                }
+            }
+            finally
+            {
+                if (_fileHandle != null && !_fileHandle.IsClosed)
+                {
+                    _fileHandle.Dispose();
+                }
+                base.Dispose(disposing);
+            }
+        }
+
+        /// <summary>Flushes the OS buffer.  This does not flush the internal read/write buffer.</summary>
+        private void FlushOSBuffer()
+        {
+            if (Interop.Sys.FSync(_fileHandle) < 0)
+            {
+                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+                switch (errorInfo.Error)
+                {
+                    case Interop.Error.EROFS:
+                    case Interop.Error.EINVAL:
+                    case Interop.Error.ENOTSUP:
+                        // Ignore failures due to the FileStream being bound to a special file that
+                        // doesn't support synchronization.  In such cases there's nothing to flush.
+                        break;
+                    default:
+                        throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
+                }
+            }
+        }
+
+        /// <summary>Writes any data in the write buffer to the underlying stream and resets the buffer.</summary>
+        private void FlushWriteBuffer()
+        {
+            AssertBufferInvariants();
+            if (_writePos > 0)
+            {
+                WriteNative(GetBuffer(), 0, _writePos);
+                _writePos = 0;
+            }
+        }
+
+        /// <summary>Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device.</summary>
+        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+        /// <returns>A task that represents the asynchronous flush operation.</returns>
+        private Task FlushAsyncInternal(CancellationToken cancellationToken)
+        {
+            if (cancellationToken.IsCancellationRequested)
+            {
+                return Task.FromCanceled(cancellationToken);
+            }
+            if (_fileHandle.IsClosed)
+            {
+                throw Error.GetFileNotOpen();
+            }
+
+            // As with Win32FileStream, flush the buffers synchronously to avoid race conditions.
+            try
+            {
+                FlushInternalBuffer();
+            }
+            catch (Exception e)
+            {
+                return Task.FromException(e);
+            }
+
+            // We then separately flush to disk asynchronously.  This is only 
+            // necessary if we support writing; otherwise, we're done.
+            if (CanWrite)
+            {
+                return Task.Factory.StartNew(
+                    state => ((FileStream)state).FlushOSBuffer(),
+                    this,
+                    cancellationToken,
+                    TaskCreationOptions.DenyChildAttach,
+                    TaskScheduler.Default);
+            }
+            else
+            {
+                return Task.CompletedTask;
+            }
+        }
+
+        /// <summary>Sets the length of this stream to the given value.</summary>
+        /// <param name="value">The new length of the stream.</param>
+        private void SetLengthInternal(long value)
+        {
+            FlushInternalBuffer();
+
+            if (_appendStart != -1 && value < _appendStart)
+            {
+                throw new IOException(SR.IO_SetLengthAppendTruncate);
+            }
+
+            long origPos = _filePosition;
+
+            VerifyOSHandlePosition();
+
+            if (_filePosition != value)
+            {
+                SeekCore(value, SeekOrigin.Begin);
+            }
+
+            CheckFileCall(Interop.Sys.FTruncate(_fileHandle, value));
+
+            // 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);
+                }
+            }
+        }
+
+        /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
+        /// <param name="array">
+        /// When this method returns, contains the specified byte array with the values between offset and 
+        /// (offset + count - 1) replaced by the bytes read from the current source.
+        /// </param>
+        /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
+        /// <param name="count">The maximum number of bytes to read. </param>
+        /// <returns>
+        /// The total number of bytes read into the buffer. This might be less than the number of bytes requested 
+        /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
+        /// </returns>
+        public override int Read(byte[] array, int offset, int count)
+        {
+            ValidateReadWriteArgs(array, offset, count);
+
+            if (_useAsyncIO)
+            {
+                _asyncState.Wait();
+                try { return ReadCore(array, offset, count); }
+                finally { _asyncState.Release(); }
+            }
+            else
+            {
+                return ReadCore(array, offset, count);
+            }
+        }
+
+        /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary>
+        /// <param name="array">
+        /// When this method returns, contains the specified byte array with the values between offset and 
+        /// (offset + count - 1) replaced by the bytes read from the current source.
+        /// </param>
+        /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
+        /// <param name="count">The maximum number of bytes to read. </param>
+        /// <returns>
+        /// The total number of bytes read into the buffer. This might be less than the number of bytes requested 
+        /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
+        /// </returns>
+        private int ReadCore(byte[] array, int offset, int count)
+        {
+            PrepareForReading();
+
+            // Are there any bytes available in the read buffer? If yes,
+            // we can just return from the buffer.  If the buffer is empty
+            // or has no more available data in it, we can either refill it
+            // (and then read from the buffer into the user's buffer) or
+            // we can just go directly into the user's buffer, if they asked
+            // for more data than we'd otherwise buffer.
+            int numBytesAvailable = _readLength - _readPos;
+            bool readFromOS = false;
+            if (numBytesAvailable == 0)
+            {
+                // If we're not able to seek, then we're not able to rewind the stream (i.e. flushing
+                // a read buffer), in which case we don't want to use a read buffer.  Similarly, if
+                // the user has asked for more data than we can buffer, we also want to skip the buffer.
+                if (!CanSeek || (count >= _bufferLength))
+                {
+                    // Read directly into the user's buffer
+                    _readPos = _readLength = 0;
+                    return ReadNative(array, offset, count);
+                }
+                else
+                {
+                    // Read into our buffer.
+                    _readLength = numBytesAvailable = ReadNative(GetBuffer(), 0, _bufferLength);
+                    _readPos = 0;
+                    if (numBytesAvailable == 0)
+                    {
+                        return 0;
+                    }
+
+                    // Note that we did an OS read as part of this Read, so that later
+                    // we don't try to do one again if what's in the buffer doesn't
+                    // meet the user's request.
+                    readFromOS = true;
+                }
+            }
+
+            // Now that we know there's data in the buffer, read from it into the user's buffer.
+            Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here");
+            int bytesRead = Math.Min(numBytesAvailable, count);
+            Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, bytesRead);
+            _readPos += bytesRead;
+
+            // We may not have had enough data in the buffer to completely satisfy the user's request.
+            // While Read doesn't require that we return as much data as the user requested (any amount
+            // up to the requested count is fine), FileStream on Windows tries to do so by doing a 
+            // subsequent read from the file if we tried to satisfy the request with what was in the 
+            // buffer but the buffer contained less than the requested count. To be consistent with that 
+            // behavior, we do the same thing here on Unix.  Note that we may still get less the requested 
+            // amount, as the OS may give us back fewer than we request, either due to reaching the end of 
+            // file, or due to its own whims.
+            if (!readFromOS && bytesRead < count)
+            {
+                Debug.Assert(_readPos == _readLength, "bytesToRead should only be < count if numBytesAvailable < count");
+                _readPos = _readLength = 0; // no data left in the read buffer
+                bytesRead += ReadNative(array, offset + bytesRead, count - bytesRead);
+            }
+
+            return bytesRead;
+        }
+
+        /// <summary>Unbuffered, reads a block of bytes from the stream and writes the data in a given buffer.</summary>
+        /// <param name="array">
+        /// When this method returns, contains the specified byte array with the values between offset and 
+        /// (offset + count - 1) replaced by the bytes read from the current source.
+        /// </param>
+        /// <param name="offset">The byte offset in array at which the read bytes will be placed.</param>
+        /// <param name="count">The maximum number of bytes to read. </param>
+        /// <returns>
+        /// The total number of bytes read into the buffer. This might be less than the number of bytes requested 
+        /// if that number of bytes are not currently available, or zero if the end of the stream is reached.
+        /// </returns>
+        private unsafe int ReadNative(byte[] array, int offset, int count)
+        {
+            FlushWriteBuffer(); // we're about to read; dump the write buffer
+
+            VerifyOSHandlePosition();
+
+            int bytesRead;
+            fixed (byte* bufPtr = array)
+            {
+                bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr + offset, count));
+                Debug.Assert(bytesRead <= count);
+            }
+            _filePosition += bytesRead;
+            return bytesRead;
+        }
+
+        /// <summary>
+        /// Asynchronously reads a sequence of bytes from the current stream and advances
+        /// the position within the stream by the number of bytes read.
+        /// </summary>
+        /// <param name="buffer">The buffer to write the data into.</param>
+        /// <param name="offset">The byte offset in buffer at which to begin writing data from the stream.</param>
+        /// <param name="count">The maximum number of bytes to read.</param>
+        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+        /// <returns>A task that represents the asynchronous read operation.</returns>
+        private Task<int> ReadAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (_useAsyncIO)
+            {
+                if (!CanRead) // match Windows behavior; this gets thrown synchronously
+                {
+                    throw Error.GetReadNotSupported();
+                }
+
+                // Serialize operations using the semaphore.
+                Task waitTask = _asyncState.WaitAsync();
+
+                // If we got ownership immediately, and if there's enough data in our buffer
+                // to satisfy the full request of the caller, hand back the buffered data.
+                // While it would be a legal implementation of the Read contract, we don't
+                // hand back here less than the amount requested so as to match the behavior
+                // in ReadCore that will make a native call to try to fulfill the remainder
+                // of the request.
+                if (waitTask.Status == TaskStatus.RanToCompletion)
+                {
+                    int numBytesAvailable = _readLength - _readPos;
+                    if (numBytesAvailable >= count)
+                    {
+                        try
+                        {
+                            PrepareForReading();
+
+                            Buffer.BlockCopy(GetBuffer(), _readPos, buffer, offset, count);
+                            _readPos += count;
+
+                            return _asyncState._lastSuccessfulReadTask != null && _asyncState._lastSuccessfulReadTask.Result == count ?
+                                _asyncState._lastSuccessfulReadTask :
+                                (_asyncState._lastSuccessfulReadTask = Task.FromResult(count));
+                        }
+                        catch (Exception exc)
+                        {
+                            return Task.FromException<int>(exc);
+                        }
+                        finally
+                        {
+                            _asyncState.Release();
+                        }
+                    }
+                }
+
+                // Otherwise, issue the whole request asynchronously.
+                _asyncState.Update(buffer, offset, count);
+                return waitTask.ContinueWith((t, s) =>
+                {
+                    // The options available on Unix for writing asynchronously to an arbitrary file 
+                    // handle typically amount to just using another thread to do the synchronous write, 
+                    // which is exactly  what this implementation does. This does mean there are subtle
+                    // differences in certain FileStream behaviors between Windows and Unix when multiple 
+                    // asynchronous operations are issued against the stream to execute concurrently; on 
+                    // Unix the operations will be serialized due to the usage of a semaphore, but the 
+                    // position /length information won't be updated until after the write has completed, 
+                    // whereas on Windows it may happen before the write has completed.
+
+                    Debug.Assert(t.Status == TaskStatus.RanToCompletion);
+                    var thisRef = (FileStream)s;
+                    try
+                    {
+                        byte[] b = thisRef._asyncState._buffer;
+                        thisRef._asyncState._buffer = null; // remove reference to user's buffer
+                        return thisRef.ReadCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
+                    }
+                    finally { thisRef._asyncState.Release(); }
+                }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
+            }
+            else
+            {
+                return base.ReadAsync(buffer, offset, count, cancellationToken);
+            }
+        }
+
+        /// <summary>
+        /// Reads a byte from the stream and advances the position within the stream
+        /// by one byte, or returns -1 if at the end of the stream.
+        /// </summary>
+        /// <returns>The unsigned byte cast to an Int32, or -1 if at the end of the stream.</returns>
+        public override int ReadByte()
+        {
+            if (_useAsyncIO)
+            {
+                _asyncState.Wait();
+                try { return ReadByteCore(); }
+                finally { _asyncState.Release(); }
+            }
+            else
+            {
+                return ReadByteCore();
+            }
+        }
+
+        /// <summary>Writes a block of bytes to the file stream.</summary>
+        /// <param name="array">The buffer containing data to write to the stream.</param>
+        /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
+        /// <param name="count">The maximum number of bytes to write.</param>
+        public override void Write(byte[] array, int offset, int count)
+        {
+            ValidateReadWriteArgs(array, offset, count);
+
+            if (_useAsyncIO)
+            {
+                _asyncState.Wait();
+                try { WriteCore(array, offset, count); }
+                finally { _asyncState.Release(); }
+            }
+            else
+            {
+                WriteCore(array, offset, count);
+            }
+        }
+
+        /// <summary>Writes a block of bytes to the file stream.</summary>
+        /// <param name="array">The buffer containing data to write to the stream.</param>
+        /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
+        /// <param name="count">The maximum number of bytes to write.</param>
+        private void WriteCore(byte[] array, int offset, int count)
+        {
+            PrepareForWriting();
+
+            // If no data is being written, nothing more to do.
+            if (count == 0)
+            {
+                return;
+            }
+
+            // If there's already data in our write buffer, then we need to go through
+            // our buffer to ensure data isn't corrupted.
+            if (_writePos > 0)
+            {
+                // If there's space remaining in the buffer, then copy as much as
+                // we can from the user's buffer into ours.
+                int spaceRemaining = _bufferLength - _writePos;
+                if (spaceRemaining > 0)
+                {
+                    int bytesToCopy = Math.Min(spaceRemaining, count);
+                    Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, bytesToCopy);
+                    _writePos += bytesToCopy;
+
+                    // If we've successfully copied all of the user's data, we're done.
+                    if (count == bytesToCopy)
+                    {
+                        return;
+                    }
+
+                    // Otherwise, keep track of how much more data needs to be handled.
+                    offset += bytesToCopy;
+                    count -= bytesToCopy;
+                }
+
+                // At this point, the buffer is full, so flush it out.
+                FlushWriteBuffer();
+            }
+
+            // Our buffer is now empty.  If using the buffer would slow things down (because
+            // the user's looking to write more data than we can store in the buffer),
+            // skip the buffer.  Otherwise, put the remaining data into the buffer.
+            Debug.Assert(_writePos == 0);
+            if (count >= _bufferLength)
+            {
+                WriteNative(array, offset, count);
+            }
+            else
+            {
+                Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count);
+                _writePos = count;
+            }
+        }
+
+        /// <summary>Unbuffered, writes a block of bytes to the file stream.</summary>
+        /// <param name="array">The buffer containing data to write to the stream.</param>
+        /// <param name="offset">The zero-based byte offset in array from which to begin copying bytes to the stream.</param>
+        /// <param name="count">The maximum number of bytes to write.</param>
+        private unsafe void WriteNative(byte[] array, int offset, int count)
+        {
+            VerifyOSHandlePosition();
+
+            fixed (byte* bufPtr = array)
+            {
+                while (count > 0)
+                {
+                    int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count));
+                    Debug.Assert(bytesWritten <= count);
+
+                    _filePosition += bytesWritten;
+                    count -= bytesWritten;
+                    offset += bytesWritten;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously writes a sequence of bytes to the current stream, advances
+        /// the current position within this stream by the number of bytes written, and
+        /// monitors cancellation requests.
+        /// </summary>
+        /// <param name="buffer">The buffer to write data from.</param>
+        /// <param name="offset">The zero-based byte offset in buffer from which to begin copying bytes to the stream.</param>
+        /// <param name="count">The maximum number of bytes to write.</param>
+        /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+        /// <returns>A task that represents the asynchronous write operation.</returns>
+        private Task WriteAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (cancellationToken.IsCancellationRequested)
+                return Task.FromCanceled(cancellationToken);
+
+            if (_fileHandle.IsClosed)
+                throw Error.GetFileNotOpen();
+
+            if (_useAsyncIO)
+            {
+                if (!CanWrite) // match Windows behavior; this gets thrown synchronously
+                {
+                    throw Error.GetWriteNotSupported();
+                }
+
+                // Serialize operations using the semaphore.
+                Task waitTask = _asyncState.WaitAsync();
+
+                // If we got ownership immediately, and if there's enough space in our buffer
+                // to buffer the entire write request, then do so and we're done.
+                if (waitTask.Status == TaskStatus.RanToCompletion)
+                {
+                    int spaceRemaining = _bufferLength - _writePos;
+                    if (spaceRemaining >= count)
+                    {
+                        try
+                        {
+                            PrepareForWriting();
+
+                            Buffer.BlockCopy(buffer, offset, GetBuffer(), _writePos, count);
+                            _writePos += count;
+
+                            return Task.CompletedTask;
+                        }
+                        catch (Exception exc)
+                        {
+                            return Task.FromException(exc);
+                        }
+                        finally
+                        {
+                            _asyncState.Release();
+                        }
+                    }
+                }
+
+                // Otherwise, issue the whole request asynchronously.
+                _asyncState.Update(buffer, offset, count);
+                return waitTask.ContinueWith((t, s) =>
+                {
+                    // The options available on Unix for writing asynchronously to an arbitrary file 
+                    // handle typically amount to just using another thread to do the synchronous write, 
+                    // which is exactly  what this implementation does. This does mean there are subtle
+                    // differences in certain FileStream behaviors between Windows and Unix when multiple 
+                    // asynchronous operations are issued against the stream to execute concurrently; on 
+                    // Unix the operations will be serialized due to the usage of a semaphore, but the 
+                    // position /length information won't be updated until after the write has completed, 
+                    // whereas on Windows it may happen before the write has completed.
+
+                    Debug.Assert(t.Status == TaskStatus.RanToCompletion);
+                    var thisRef = (FileStream)s;
+                    try
+                    {
+                        byte[] b = thisRef._asyncState._buffer;
+                        thisRef._asyncState._buffer = null; // remove reference to user's buffer
+                        thisRef.WriteCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
+                    }
+                    finally { thisRef._asyncState.Release(); }
+                }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
+            }
+            else
+            {
+                return base.WriteAsync(buffer, offset, count, cancellationToken);
+            }
+        }
+
+        /// <summary>
+        /// Writes a byte to the current position in the stream and advances the position
+        /// within the stream by one byte.
+        /// </summary>
+        /// <param name="value">The byte to write to the stream.</param>
+        public override void WriteByte(byte value) // avoids an array allocation in the base implementation
+        {
+            if (_useAsyncIO)
+            {
+                _asyncState.Wait();
+                try { WriteByteCore(value); }
+                finally { _asyncState.Release(); }
+            }
+            else
+            {
+                WriteByteCore(value);
+            }
+        }
+
+        /// <summary>Sets the current position of this stream to the given value.</summary>
+        /// <param name="offset">The point relative to origin from which to begin seeking. </param>
+        /// <param name="origin">
+        /// Specifies the beginning, the end, or the current position as a reference 
+        /// point for offset, using a value of type SeekOrigin.
+        /// </param>
+        /// <returns>The new position in the stream.</returns>
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
+            {
+                throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
+            }
+            if (_fileHandle.IsClosed)
+            {
+                throw Error.GetFileNotOpen();
+            }
+            if (!CanSeek)
+            {
+                throw Error.GetSeekNotSupported();
+            }
+
+            VerifyOSHandlePosition();
+
+            // Flush our write/read buffer.  FlushWrite will output any write buffer we have and reset _bufferWritePos.
+            // We don't call FlushRead, as that will do an unnecessary seek to rewind the read buffer, and since we're 
+            // about to seek and update our position, we can simply update the offset as necessary and reset our read 
+            // position and length to 0. (In the future, for some simple cases we could potentially add an optimization 
+            // here to just move data around in the buffer for short jumps, to avoid re-reading the data from disk.)
+            FlushWriteBuffer();
+            if (origin == SeekOrigin.Current)
+            {
+                offset -= (_readLength - _readPos);
+            }
+            _readPos = _readLength = 0;
+
+            // Keep track of where we were, in case we're in append mode and need to verify
+            long oldPos = 0;
+            if (_appendStart >= 0)
+            {
+                oldPos = SeekCore(0, SeekOrigin.Current);
+            }
+
+            // Jump to the new location
+            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(SR.IO_SeekAppendOverwrite);
+            }
+
+            // Return the new position
+            return pos;
+        }
+
+        /// <summary>Sets the current position of this stream to the given value.</summary>
+        /// <param name="offset">The point relative to origin from which to begin seeking. </param>
+        /// <param name="origin">
+        /// Specifies the beginning, the end, or the current position as a reference 
+        /// point for offset, using a value of type SeekOrigin.
+        /// </param>
+        /// <returns>The new position in the stream.</returns>
+        private long SeekCore(long offset, SeekOrigin origin)
+        {
+            Debug.Assert(!_fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeek)); // verify that we can seek, but only if CanSeek won't be a virtual call (which could happen in the ctor)
+            Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End);
+
+            long pos = CheckFileCall(Interop.Sys.LSeek(_fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values
+            _filePosition = pos;
+            return pos;
+        }
+
+        private long CheckFileCall(long result, bool ignoreNotSupported = false)
+        {
+            if (result < 0)
+            {
+                Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
+                if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
+                {
+                    throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
+                }
+            }
+
+            return result;
+        }
+
+        private int CheckFileCall(int result, bool ignoreNotSupported = false)
+        {
+            CheckFileCall((long)result, ignoreNotSupported);
+
+            return result;
+        }
+
+        /// <summary>State used when the stream is in async mode.</summary>
+        private sealed class AsyncState : SemaphoreSlim
+        {
+            /// <summary>The caller's buffer currently being used by the active async operation.</summary>
+            internal byte[] _buffer;
+            /// <summary>The caller's offset currently being used by the active async operation.</summary>
+            internal int _offset;
+            /// <summary>The caller's count currently being used by the active async operation.</summary>
+            internal int _count;
+            /// <summary>The last task successfully, synchronously returned task from ReadAsync.</summary>
+            internal Task<int> _lastSuccessfulReadTask;
+
+            /// <summary>Initialize the AsyncState.</summary>
+            internal AsyncState() : base(initialCount: 1, maxCount: 1) { }
+
+            /// <summary>Sets the active buffer, offset, and count.</summary>
+            internal void Update(byte[] buffer, int offset, int count)
+            {
+                _buffer = buffer;
+                _offset = offset;
+                _count = count;
+            }
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/FileStream.Win32.cs b/src/mscorlib/shared/System/IO/FileStream.Win32.cs
new file mode 100644 (file)
index 0000000..82e7473
--- /dev/null
@@ -0,0 +1,1770 @@
+// 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.Buffers;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
+using System.Runtime.CompilerServices;
+
+/*
+ * Win32FileStream supports different modes of accessing the disk - async mode
+ * and sync mode.  They are two completely different codepaths in the
+ * sync & async methods (i.e. Read/Write vs. ReadAsync/WriteAsync).  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 Win32FileStream as well. Folded in the
+ * code from BufferedStream, so all the comments about it being mostly
+ * aggressive (and the possible perf improvement) apply to Win32FileStream 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
+{
+    public partial class FileStream : Stream
+    {
+        private bool _canSeek;
+        private bool _isPipe;      // Whether to disable async buffering code.
+        private long _appendStart; // When appending, prevent overwriting file.
+
+        private static unsafe IOCompletionCallback s_ioCallback = FileStreamCompletionSource.IOCallback;
+
+        private Task<int> _lastSynchronouslyCompletedTask = null;   // cached task for read ops that complete synchronously
+        private Task _activeBufferOperation = null;                 // tracks in-progress async ops using the buffer
+        private PreAllocatedOverlapped _preallocatedOverlapped;     // optimization for async ops to avoid per-op allocations
+        private FileStreamCompletionSource _currentOverlappedOwner; // async op currently using the preallocated overlapped
+
+        private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
+        {
+            Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share);
+
+            int fAccess =
+                ((_access & FileAccess.Read) == FileAccess.Read ? GENERIC_READ : 0) |
+                ((_access & FileAccess.Write) == FileAccess.Write ? GENERIC_WRITE : 0);
+
+            // 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;
+
+            // Must use a valid Win32 constant here...
+            if (mode == FileMode.Append)
+                mode = FileMode.OpenOrCreate;
+
+            int flagsAndAttributes = (int)options;
+
+            // 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 |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS);
+
+            // Don't pop up a dialog for reading from an empty floppy drive
+            uint oldMode = Interop.Kernel32.SetErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS);
+            try
+            {
+                SafeFileHandle fileHandle = Interop.Kernel32.SafeCreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero);
+                fileHandle.IsAsync = _useAsyncIO;
+
+                if (fileHandle.IsInvalid)
+                {
+                    // Return a meaningful exception with the full path.
+
+                    // NT5 oddity - when trying to open "C:\" as a Win32FileStream,
+                    // 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 == Interop.Errors.ERROR_PATH_NOT_FOUND && _path.Equals(Directory.InternalGetDirectoryRoot(_path)))
+                        errorCode = Interop.Errors.ERROR_ACCESS_DENIED;
+
+                    throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path);
+                }
+
+                return fileHandle;
+            }
+            finally
+            {
+                Interop.Kernel32.SetErrorMode(oldMode);
+            }
+        }
+
+        private void Init(FileMode mode, FileShare share)
+        {
+            // Disallow access to all non-file devices from the Win32FileStream
+            // 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 = Interop.Kernel32.GetFileType(_fileHandle);
+            if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK)
+            {
+                _fileHandle.Dispose();
+                throw new NotSupportedException(SR.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 (_useAsyncIO)
+            {
+                try
+                {
+                    _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle);
+                }
+                catch (ArgumentException ex)
+                {
+                    throw new IOException(SR.IO_BindHandleFailed, ex);
+                }
+                finally
+                {
+                    if (_fileHandle.ThreadPoolBinding == null)
+                    {
+                        // We should close the handle so that the handle is not open until SafeFileHandle GC
+                        Debug.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?");
+                        _fileHandle.Dispose();
+                    }
+                }
+            }
+
+            _canSeek = true;
+
+            // For Append mode...
+            if (mode == FileMode.Append)
+            {
+                _appendStart = SeekCore(0, SeekOrigin.End);
+            }
+            else
+            {
+                _appendStart = -1;
+            }
+        }
+
+        private void InitFromHandle(SafeFileHandle handle)
+        {
+            int handleType = Interop.Kernel32.GetFileType(_fileHandle);
+            Debug.Assert(handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE || handleType == Interop.Kernel32.FileTypes.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!");
+
+            _canSeek = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_DISK;
+            _isPipe = handleType == Interop.Kernel32.FileTypes.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, however, we've already bound this file handle to our completion port,
+            // don't try to bind it again because it will fail.  A handle can only be
+            // bound to a single completion port at a time.
+            if (_useAsyncIO && !GetSuppressBindHandle(handle))
+            {
+                try
+                {
+                    _fileHandle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(_fileHandle);
+                }
+                catch (Exception ex)
+                {
+                    // If you passed in a synchronous handle and told us to use
+                    // it asynchronously, throw here.
+                    throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle), ex);
+                }
+            }
+            else if (!_useAsyncIO)
+            {
+                if (handleType != Interop.Kernel32.FileTypes.FILE_TYPE_PIPE)
+                    VerifyHandleIsSync();
+            }
+
+            if (_canSeek)
+                SeekCore(0, SeekOrigin.Current);
+            else
+                _filePosition = 0;
+        }
+
+        private static bool GetSuppressBindHandle(SafeFileHandle handle)
+        {
+            return handle.IsAsync.HasValue ? handle.IsAsync.Value : false;
+        }
+
+        private unsafe static Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share)
+        {
+            Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default(Interop.Kernel32.SECURITY_ATTRIBUTES);
+            if ((share & FileShare.Inheritable) != 0)
+            {
+                secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES();
+                secAttrs.nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES);
+
+                secAttrs.bInheritHandle = Interop.BOOL.TRUE;
+            }
+            return secAttrs;
+        }
+
+        // Verifies that this handle supports synchronous IO operations (unless you
+        // didn't open it for either reading or writing).
+        private unsafe void VerifyHandleIsSync()
+        {
+            Debug.Assert(!_useAsyncIO);
+
+            // 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
+            // Win32FileStream's thread).
+            Debug.Assert(Interop.Kernel32.GetFileType(_fileHandle) != Interop.Kernel32.FileTypes.FILE_TYPE_PIPE);
+
+            byte* bytes = stackalloc byte[1];
+            int numBytesReadWritten;
+            int r = -1;
+
+            // 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 ((_access & FileAccess.Read) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor
+            {
+                r = Interop.Kernel32.ReadFile(_fileHandle, bytes, 0, out numBytesReadWritten, IntPtr.Zero);
+            }
+            else if ((_access & FileAccess.Write) != 0) // don't use the virtual CanRead or CanWrite, as this may be used in the ctor
+            {
+                r = Interop.Kernel32.WriteFile(_fileHandle, bytes, 0, out numBytesReadWritten, IntPtr.Zero);
+            }
+
+            if (r == 0)
+            {
+                int errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(throwIfInvalidHandle: true);
+                if (errorCode == ERROR_INVALID_PARAMETER)
+                    throw new ArgumentException(SR.Arg_HandleNotSync, "handle");
+            }
+        }
+
+        private bool HasActiveBufferOperation
+        {
+            get { return _activeBufferOperation != null && !_activeBufferOperation.IsCompleted; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return _canSeek; }
+        }
+
+        private long GetLengthInternal()
+        {
+            Interop.Kernel32.FILE_STANDARD_INFO info = new Interop.Kernel32.FILE_STANDARD_INFO();
+
+            if (!Interop.Kernel32.GetFileInformationByHandleEx(_fileHandle, Interop.Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, out info, (uint)Marshal.SizeOf<Interop.Kernel32.FILE_STANDARD_INFO>()))
+                throw Win32Marshal.GetExceptionForLastWin32Error();
+            long len = info.EndOfFile;
+            // 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 && _filePosition + _writePos > len)
+                len = _writePos + _filePosition;
+            return len;
+        }
+
+        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 Win32FileStream, which would often "just work" when 
+            // finalized.
+            try
+            {
+                if (_fileHandle != null && !_fileHandle.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)
+                    {
+                        FlushWriteBuffer(!disposing);
+                    }
+                }
+            }
+            finally
+            {
+                if (_fileHandle != null && !_fileHandle.IsClosed)
+                {
+                    if (_fileHandle.ThreadPoolBinding != null)
+                        _fileHandle.ThreadPoolBinding.Dispose();
+
+                    _fileHandle.Dispose();
+                }
+
+                if (_preallocatedOverlapped != null)
+                    _preallocatedOverlapped.Dispose();
+
+                _canSeek = false;
+
+                // Don't set the buffer to null, to avoid a NullReferenceException
+                // when users have a race condition in their code (i.e. they call
+                // Close when calling another method on Stream like Read).
+                //_buffer = null;
+                base.Dispose(disposing);
+            }
+        }
+
+        private void FlushOSBuffer()
+        {
+            if (!Interop.Kernel32.FlushFileBuffers(_fileHandle))
+            {
+                throw Win32Marshal.GetExceptionForLastWin32Error();
+            }
+        }
+
+        // Returns a task that flushes the internal write buffer
+        private Task FlushWriteAsync(CancellationToken cancellationToken)
+        {
+            Debug.Assert(_useAsyncIO);
+            Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWriteAsync!");
+
+            // If the buffer is already flushed, don't spin up the OS write
+            if (_writePos == 0) return Task.CompletedTask;
+
+            Task flushTask = WriteInternalCoreAsync(GetBuffer(), 0, _writePos, cancellationToken);
+            _writePos = 0;
+
+            // Update the active buffer operation
+            _activeBufferOperation = HasActiveBufferOperation ?
+                Task.WhenAll(_activeBufferOperation, flushTask) :
+                flushTask;
+
+            return flushTask;
+        }
+
+        // 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 FlushWriteBuffer(bool calledFromFinalizer = false)
+        {
+            if (_writePos == 0) return;
+            Debug.Assert(_readPos == 0 && _readLength == 0, "FileStream: Read buffer must be empty in FlushWrite!");
+
+            if (_useAsyncIO)
+            {
+                Task writeTask = FlushWriteAsync(CancellationToken.None);
+                // 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 don'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)
+                {
+                    writeTask.GetAwaiter().GetResult();
+                }
+            }
+            else
+            {
+                WriteCore(GetBuffer(), 0, _writePos);
+            }
+
+            _writePos = 0;
+        }
+
+        private void SetLengthInternal(long value)
+        {
+            // Handle buffering updates.
+            if (_writePos > 0)
+            {
+                FlushWriteBuffer();
+            }
+            else if (_readPos < _readLength)
+            {
+                FlushReadBuffer();
+            }
+            _readPos = 0;
+            _readLength = 0;
+
+            if (_appendStart != -1 && value < _appendStart)
+                throw new IOException(SR.IO_SetLengthAppendTruncate);
+            SetLengthCore(value);
+        }
+
+        // We absolutely need this method broken out so that WriteInternalCoreAsync can call
+        // a method without having to go through buffering code that might call FlushWrite.
+        private void SetLengthCore(long value)
+        {
+            Debug.Assert(value >= 0, "value >= 0");
+            long origPos = _filePosition;
+
+            VerifyOSHandlePosition();
+            if (_filePosition != value)
+                SeekCore(value, SeekOrigin.Begin);
+            if (!Interop.Kernel32.SetEndOfFile(_fileHandle))
+            {
+                int errorCode = Marshal.GetLastWin32Error();
+                if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER)
+                    throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_FileLengthTooBig);
+                throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+            }
+            // 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);
+            }
+        }
+
+        // Instance method to help code external to this MarshalByRefObject avoid
+        // accessing its fields by ref.  This avoids a compiler warning.
+        private FileStreamCompletionSource CompareExchangeCurrentOverlappedOwner(FileStreamCompletionSource newSource, FileStreamCompletionSource existingSource) => Interlocked.CompareExchange(ref _currentOverlappedOwner, newSource, existingSource);
+
+        public override int Read(byte[] array, int offset, int count)
+        {
+            ValidateReadWriteArgs(array, offset, count);
+            return ReadCore(array, offset, count);
+        }
+
+        private int ReadCore(byte[] array, int offset, int count)
+        {
+            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength),
+                "We're either reading or writing, but not both.");
+
+            bool isBlocked = false;
+            int n = _readLength - _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) throw Error.GetReadNotSupported();
+                if (_writePos > 0) FlushWriteBuffer();
+                if (!CanSeek || (count >= _bufferLength))
+                {
+                    n = ReadNative(array, offset, count);
+                    // Throw away read buffer.
+                    _readPos = 0;
+                    _readLength = 0;
+                    return n;
+                }
+                n = ReadNative(GetBuffer(), 0, _bufferLength);
+                if (n == 0) return 0;
+                isBlocked = n < _bufferLength;
+                _readPos = 0;
+                _readLength = n;
+            }
+            // Now copy min of count or numBytesAvailable (i.e. near EOF) to array.
+            if (n > count) n = count;
+            Buffer.BlockCopy(GetBuffer(), _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 (i.e. we're 
+                // probably blocked), don't ask for more bytes.
+                if (n < count && !isBlocked)
+                {
+                    Debug.Assert(_readPos == _readLength, "Read buffer should be empty!");
+                    int moreBytesRead = ReadNative(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;
+                    _readLength = 0;
+                }
+            }
+
+            return n;
+        }
+
+        [Conditional("DEBUG")]
+        private void AssertCanRead(byte[] buffer, int offset, int count)
+        {
+            Debug.Assert(!_fileHandle.IsClosed, "!_fileHandle.IsClosed");
+            Debug.Assert(CanRead, "CanRead");
+            Debug.Assert(buffer != null, "buffer != null");
+            Debug.Assert(_writePos == 0, "_writePos == 0");
+            Debug.Assert(offset >= 0, "offset is negative");
+            Debug.Assert(count >= 0, "count is negative");
+        }
+
+        private unsafe int ReadNative(byte[] buffer, int offset, int count)
+        {
+            AssertCanRead(buffer, offset, count);
+
+            if (_useAsyncIO)
+                return ReadNativeAsync(buffer, offset, count, 0, CancellationToken.None).GetAwaiter().GetResult();
+
+            // Make sure we are reading from the right spot
+            VerifyOSHandlePosition();
+
+            int errorCode = 0;
+            int r = ReadFileNative(_fileHandle, buffer, offset, count, null, out errorCode);
+
+            if (r == -1)
+            {
+                // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe.
+                if (errorCode == ERROR_BROKEN_PIPE)
+                {
+                    r = 0;
+                }
+                else
+                {
+                    if (errorCode == ERROR_INVALID_PARAMETER)
+                        throw new ArgumentException(SR.Arg_HandleNotSync, "_fileHandle");
+
+                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+                }
+            }
+            Debug.Assert(r >= 0, "FileStream's ReadNative is likely broken.");
+            _filePosition += r;
+
+            return r;
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
+                throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
+            if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
+            if (!CanSeek) throw Error.GetSeekNotSupported();
+
+            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "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)
+            {
+                FlushWriteBuffer();
+            }
+            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 -= (_readLength - _readPos);
+            }
+            _readPos = _readLength = 0;
+
+            // Verify that internal position is in sync with the handle
+            VerifyOSHandlePosition();
+
+            long oldPos = _filePosition + (_readPos - _readLength);
+            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(SR.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 (_readLength > 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.BlockCopy(GetBuffer(), _readPos, GetBuffer(), 0, _readLength - _readPos);
+                        _readLength -= _readPos;
+                        _readPos = 0;
+                    }
+                    // If we still have buffered data, we must update the stream's 
+                    // position so our Position property is correct.
+                    if (_readLength > 0)
+                        SeekCore(_readLength, SeekOrigin.Current);
+                }
+                else if (oldPos - _readPos < pos && pos < oldPos + _readLength - _readPos)
+                {
+                    int diff = (int)(pos - oldPos);
+                    //Console.WriteLine("Seek: diff was "+diff+", readpos was "+_readPos+"  adjusting buffer - shrinking by "+ (_readPos + diff));
+                    Buffer.BlockCopy(GetBuffer(), _readPos + diff, GetBuffer(), 0, _readLength - (_readPos + diff));
+                    _readLength -= (_readPos + diff);
+                    _readPos = 0;
+                    if (_readLength > 0)
+                        SeekCore(_readLength, SeekOrigin.Current);
+                }
+                else
+                {
+                    // Lose the read buffer.
+                    _readPos = 0;
+                    _readLength = 0;
+                }
+                Debug.Assert(_readLength >= 0 && _readPos <= _readLength, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen");
+                Debug.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
+        // This is called during construction so it should avoid any virtual
+        // calls
+        private long SeekCore(long offset, SeekOrigin origin)
+        {
+            Debug.Assert(!_fileHandle.IsClosed && _canSeek, "!_handle.IsClosed && _parent.CanSeek");
+            Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End");
+            long ret = 0;
+
+            if (!Interop.Kernel32.SetFilePointerEx(_fileHandle, offset, out ret, (uint)origin))
+            {
+                int errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
+                throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+            }
+
+            _filePosition = ret;
+            return ret;
+        }
+
+        partial void OnBufferAllocated()
+        {
+            Debug.Assert(_buffer != null);
+            Debug.Assert(_preallocatedOverlapped == null);
+
+            if (_useAsyncIO)
+                _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer);
+        }
+
+        public override void Write(byte[] array, int offset, int count)
+        {
+            ValidateReadWriteArgs(array, offset, count);
+
+            if (_writePos == 0)
+            {
+                // Ensure we can write to the stream, and ready buffer for writing.
+                if (!CanWrite) throw Error.GetWriteNotSupported();
+                if (_readPos < _readLength) FlushReadBuffer();
+                _readPos = 0;
+                _readLength = 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 (i.e. write 1 byte, then write for the buffer 
+            // size repeatedly)
+            if (_writePos > 0)
+            {
+                int numBytes = _bufferLength - _writePos;   // space left in buffer
+                if (numBytes > 0)
+                {
+                    if (numBytes > count)
+                        numBytes = count;
+                    Buffer.BlockCopy(array, offset, GetBuffer(), _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 (_useAsyncIO)
+                {
+                    WriteInternalCoreAsync(GetBuffer(), 0, _writePos, CancellationToken.None).GetAwaiter().GetResult();
+                }
+                else
+                {
+                    WriteCore(GetBuffer(), 0, _writePos);
+                }
+                _writePos = 0;
+            }
+            // If the buffer would slow writes down, avoid buffer completely.
+            if (count >= _bufferLength)
+            {
+                Debug.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.
+            }
+
+            // Copy remaining bytes into buffer, to write at a later date.
+            Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count);
+            _writePos = count;
+            return;
+        }
+
+        private unsafe void WriteCore(byte[] buffer, int offset, int count)
+        {
+            Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
+            Debug.Assert(CanWrite, "_parent.CanWrite");
+
+            Debug.Assert(buffer != null, "buffer != null");
+            Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
+            Debug.Assert(offset >= 0, "offset is negative");
+            Debug.Assert(count >= 0, "count is negative");
+            if (_useAsyncIO)
+            {
+                WriteInternalCoreAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
+                return;
+            }
+
+            // Make sure we are writing to the position that we think we are
+            VerifyOSHandlePosition();
+
+            int errorCode = 0;
+            int r = WriteFileNative(_fileHandle, buffer, offset, count, null, out errorCode);
+
+            if (r == -1)
+            {
+                // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing.
+                if (errorCode == ERROR_NO_DATA)
+                {
+                    r = 0;
+                }
+                else
+                {
+                    // ERROR_INVALID_PARAMETER may be returned for writes
+                    // where the position is too large (i.e. writing at Int64.MaxValue 
+                    // on Win9x) OR for synchronous writes to a handle opened 
+                    // asynchronously.
+                    if (errorCode == ERROR_INVALID_PARAMETER)
+                        throw new IOException(SR.IO_FileTooLongOrHandleNotSync);
+                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+                }
+            }
+            Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken.");
+            _filePosition += r;
+            return;
+        }
+
+        private Task<int> ReadAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken)
+        {
+            // If async IO is not supported on this platform or 
+            // if this Win32FileStream was not opened with FileOptions.Asynchronous.
+            if (!_useAsyncIO)
+            {
+                return base.ReadAsync(array, offset, numBytes, cancellationToken);
+            }
+
+            if (!CanRead) throw Error.GetReadNotSupported();
+
+            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
+
+            if (_isPipe)
+            {
+                // Pipes are tricky, at least when you have 2 different pipes
+                // that you want to use simultaneously.  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 
+                // Win32FileStream's ReadAsync 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 < _readLength)
+                {
+                    int n = _readLength - _readPos;
+                    if (n > numBytes) n = numBytes;
+                    Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n);
+                    _readPos += n;
+
+                    // Return a completed task
+                    return TaskFromResultOrCache(n);
+                }
+                else
+                {
+                    Debug.Assert(_writePos == 0, "Win32FileStream must not have buffered write data here!  Pipes should be unidirectional.");
+                    return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken);
+                }
+            }
+
+            Debug.Assert(!_isPipe, "Should not be a pipe.");
+
+            // Handle buffering.
+            if (_writePos > 0) FlushWriteBuffer();
+            if (_readPos == _readLength)
+            {
+                // 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 < _bufferLength)
+                {
+                    Task<int> readTask = ReadNativeAsync(GetBuffer(), 0, _bufferLength, 0, cancellationToken);
+                    _readLength = readTask.GetAwaiter().GetResult();
+                    int n = _readLength;
+                    if (n > numBytes) n = numBytes;
+                    Buffer.BlockCopy(GetBuffer(), 0, array, offset, n);
+                    _readPos = n;
+
+                    // Return a completed task (recycling the one above if possible)
+                    return (_readLength == n ? readTask : TaskFromResultOrCache(n));
+                }
+                else
+                {
+                    // Here we're making our position pointer inconsistent
+                    // with our read buffer.  Throw away the read buffer's contents.
+                    _readPos = 0;
+                    _readLength = 0;
+                    return ReadNativeAsync(array, offset, numBytes, 0, cancellationToken);
+                }
+            }
+            else
+            {
+                int n = _readLength - _readPos;
+                if (n > numBytes) n = numBytes;
+                Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, n);
+                _readPos += n;
+
+                if (n >= numBytes)
+                {
+                    // Return a completed task
+                    return TaskFromResultOrCache(n);
+                }
+                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;
+                    _readLength = 0;
+                    return ReadNativeAsync(array, offset + n, numBytes - n, n, cancellationToken);
+                }
+            }
+        }
+
+        unsafe private Task<int> ReadNativeAsync(byte[] bytes, int offset, int numBytes, int numBufferedBytesRead, CancellationToken cancellationToken)
+        {
+            AssertCanRead(bytes, offset, numBytes);
+            Debug.Assert(_useAsyncIO, "ReadNativeAsync doesn't work on synchronous file streams!");
+
+            // Create and store async stream class library specific data in the async result
+
+            FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, numBufferedBytesRead, bytes, cancellationToken);
+            NativeOverlapped* intOverlapped = completionSource.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
+                VerifyOSHandlePosition();
+
+                if (_filePosition + numBytes > len)
+                {
+                    if (_filePosition <= len)
+                        numBytes = (int)(len - _filePosition);
+                    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)_filePosition);
+                intOverlapped->OffsetHigh = (int)(_filePosition >> 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);
+            }
+
+            // queue an async ReadFile operation and pass in a packed overlapped
+            int errorCode = 0;
+            int r = ReadFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
+            // 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/ errorCode==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 (errorCode == 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;
+                    completionSource.SetCompletedSynchronously(0);
+                }
+                else if (errorCode != ERROR_IO_PENDING)
+                {
+                    if (!_fileHandle.IsClosed && CanSeek)  // Update Position - It could be anywhere.
+                    {
+                        SeekCore(0, SeekOrigin.Current);
+                    }
+
+                    completionSource.ReleaseNativeResource();
+
+                    if (errorCode == ERROR_HANDLE_EOF)
+                    {
+                        throw Error.GetEndOfFile();
+                    }
+                    else
+                    {
+                        throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+                    }
+                }
+                else
+                {
+                    // Only once the IO is pending do we register for cancellation
+                    completionSource.RegisterForCancellation();
+                }
+            }
+            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 completionSource.Task;
+        }
+
+        // Reads a byte from the file stream.  Returns the byte cast to an int
+        // or -1 if reading from the end of the stream.
+        public override int ReadByte()
+        {
+            return ReadByteCore();
+        }
+
+        private Task WriteAsyncInternal(byte[] array, int offset, int numBytes, CancellationToken cancellationToken)
+        {
+            // If async IO is not supported on this platform or 
+            // if this Win32FileStream was not opened with FileOptions.Asynchronous.
+            if (!_useAsyncIO)
+            {
+                return base.WriteAsync(array, offset, numBytes, cancellationToken);
+            }
+
+            if (!CanWrite) throw Error.GetWriteNotSupported();
+
+            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
+            Debug.Assert(!_isPipe || (_readPos == 0 && _readLength == 0), "Win32FileStream must not have buffered data here!  Pipes should be unidirectional.");
+
+            bool writeDataStoredInBuffer = false;
+            if (!_isPipe) // avoid async buffering with pipes, as doing so can lead to deadlocks (see comments in ReadInternalAsyncCore)
+            {
+                // Ensure the buffer is clear for writing
+                if (_writePos == 0)
+                {
+                    if (_readPos < _readLength)
+                    {
+                        FlushReadBuffer();
+                    }
+                    _readPos = 0;
+                    _readLength = 0;
+                }
+
+                // Determine how much space remains in the buffer
+                int remainingBuffer = _bufferLength - _writePos;
+                Debug.Assert(remainingBuffer >= 0);
+
+                // Simple/common case:
+                // - The write is smaller than our buffer, such that it's worth considering buffering it.
+                // - There's no active flush operation, such that we don't have to worry about the existing buffer being in use.
+                // - And the data we're trying to write fits in the buffer, meaning it wasn't already filled by previous writes.
+                // In that case, just store it in the buffer.
+                if (numBytes < _bufferLength && !HasActiveBufferOperation && numBytes <= remainingBuffer)
+                {
+                    Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, numBytes);
+                    _writePos += numBytes;
+                    writeDataStoredInBuffer = true;
+
+                    // There is one special-but-common case, common because devs often use
+                    // byte[] sizes that are powers of 2 and thus fit nicely into our buffer, which is
+                    // also a power of 2. If after our write the buffer still has remaining space,
+                    // then we're done and can return a completed task now.  But if we filled the buffer
+                    // completely, we want to do the asynchronous flush/write as part of this operation 
+                    // rather than waiting until the next write that fills the buffer.
+                    if (numBytes != remainingBuffer)
+                        return Task.CompletedTask;
+
+                    Debug.Assert(_writePos == _bufferLength);
+                }
+            }
+
+            // At this point, at least one of the following is true:
+            // 1. There was an active flush operation (it could have completed by now, though).
+            // 2. The data doesn't fit in the remaining buffer (or it's a pipe and we chose not to try).
+            // 3. We wrote all of the data to the buffer, filling it.
+            //
+            // If there's an active operation, we can't touch the current buffer because it's in use.
+            // That gives us a choice: we can either allocate a new buffer, or we can skip the buffer
+            // entirely (even if the data would otherwise fit in it).  For now, for simplicity, we do
+            // the latter; it could also have performance wins due to OS-level optimizations, and we could
+            // potentially add support for PreAllocatedOverlapped due to having a single buffer. (We can
+            // switch to allocating a new buffer, potentially experimenting with buffer pooling, should
+            // performance data suggest it's appropriate.)
+            //
+            // If the data doesn't fit in the remaining buffer, it could be because it's so large
+            // it's greater than the entire buffer size, in which case we'd always skip the buffer,
+            // or it could be because there's more data than just the space remaining.  For the latter
+            // case, we need to issue an asynchronous write to flush that data, which then turns this into
+            // the first case above with an active operation.
+            //
+            // If we already stored the data, then we have nothing additional to write beyond what
+            // we need to flush.
+            //
+            // In any of these cases, we have the same outcome:
+            // - If there's data in the buffer, flush it by writing it out asynchronously.
+            // - Then, if there's any data to be written, issue a write for it concurrently.
+            // We return a Task that represents one or both.
+
+            // Flush the buffer asynchronously if there's anything to flush
+            Task flushTask = null;
+            if (_writePos > 0)
+            {
+                flushTask = FlushWriteAsync(cancellationToken);
+
+                // If we already copied all of the data into the buffer,
+                // simply return the flush task here.  Same goes for if the task has 
+                // already completed and was unsuccessful.
+                if (writeDataStoredInBuffer ||
+                    flushTask.IsFaulted ||
+                    flushTask.IsCanceled)
+                {
+                    return flushTask;
+                }
+            }
+
+            Debug.Assert(!writeDataStoredInBuffer);
+            Debug.Assert(_writePos == 0);
+
+            // Finally, issue the write asynchronously, and return a Task that logically
+            // represents the write operation, including any flushing done.
+            Task writeTask = WriteInternalCoreAsync(array, offset, numBytes, cancellationToken);
+            return
+                (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask :
+                (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask :
+                Task.WhenAll(flushTask, writeTask);
+        }
+
+        private unsafe Task WriteInternalCoreAsync(byte[] bytes, int offset, int numBytes, CancellationToken cancellationToken)
+        {
+            Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
+            Debug.Assert(CanWrite, "_parent.CanWrite");
+            Debug.Assert(bytes != null, "bytes != null");
+            Debug.Assert(_readPos == _readLength, "_readPos == _readLen");
+            Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!");
+            Debug.Assert(offset >= 0, "offset is negative");
+            Debug.Assert(numBytes >= 0, "numBytes is negative");
+
+            // Create and store async stream class library specific data in the async result
+            FileStreamCompletionSource completionSource = new FileStreamCompletionSource(this, 0, bytes, cancellationToken);
+            NativeOverlapped* intOverlapped = completionSource.Overlapped;
+
+            if (CanSeek)
+            {
+                // Make sure we set the length of the file appropriately.
+                long len = Length;
+                //Console.WriteLine("WriteInternalCoreAsync - Calculating end pos.  pos: "+pos+"  len: "+len+"  numBytes: "+numBytes);
+
+                // Make sure we are writing to the position that we think we are
+                VerifyOSHandlePosition();
+
+                if (_filePosition + numBytes > len)
+                {
+                    //Console.WriteLine("WriteInternalCoreAsync - Setting length to: "+(pos + numBytes));
+                    SetLengthCore(_filePosition + 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)_filePosition;
+                intOverlapped->OffsetHigh = (int)(_filePosition >> 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("WriteInternalCoreAsync finishing.  pos: "+pos+"  numBytes: "+numBytes+"  _pos: "+_pos+"  Position: "+Position);
+
+            int errorCode = 0;
+            // queue an async WriteFile operation and pass in a packed overlapped
+            int r = WriteFileNative(_fileHandle, bytes, offset, numBytes, intOverlapped, out errorCode);
+
+            // 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/ errorCode==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 errorCode==3e5)  errorCode: 0x{0:x}", errorCode);
+
+                // For pipes, when they are closed on the other side, they will come here.
+                if (errorCode == ERROR_NO_DATA)
+                {
+                    // Not an error, but EOF. AsyncFSCallback will NOT be called.
+                    // Completing TCS and return cached task allowing the GC to collect TCS.
+                    completionSource.SetCompletedSynchronously(0);
+                    return Task.CompletedTask;
+                }
+                else if (errorCode != ERROR_IO_PENDING)
+                {
+                    if (!_fileHandle.IsClosed && CanSeek)  // Update Position - It could be anywhere.
+                    {
+                        SeekCore(0, SeekOrigin.Current);
+                    }
+
+                    completionSource.ReleaseNativeResource();
+
+                    if (errorCode == ERROR_HANDLE_EOF)
+                    {
+                        throw Error.GetEndOfFile();
+                    }
+                    else
+                    {
+                        throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+                    }
+                }
+                else // ERROR_IO_PENDING
+                {
+                    // Only once the IO is pending do we register for cancellation
+                    completionSource.RegisterForCancellation();
+                }
+            }
+            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 completionSource.Task;
+        }
+
+        public override void WriteByte(byte value)
+        {
+            WriteByteCore(value);
+        }
+
+        // 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. 
+        private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode)
+        {
+            Debug.Assert(handle != null, "handle != null");
+            Debug.Assert(offset >= 0, "offset >= 0");
+            Debug.Assert(count >= 0, "count >= 0");
+            Debug.Assert(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(SR.IndexOutOfRange_IORaceCondition);
+
+            Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to ReadFileNative.");
+
+            // You can't use the fixed statement on an array of length 0.
+            if (bytes.Length == 0)
+            {
+                errorCode = 0;
+                return 0;
+            }
+
+            int r = 0;
+            int numBytesRead = 0;
+
+            fixed (byte* p = &bytes[0])
+            {
+                if (_useAsyncIO)
+                    r = Interop.Kernel32.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped);
+                else
+                    r = Interop.Kernel32.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero);
+            }
+
+            if (r == 0)
+            {
+                errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
+                return -1;
+            }
+            else
+            {
+                errorCode = 0;
+                return numBytesRead;
+            }
+        }
+
+        private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int errorCode)
+        {
+            Debug.Assert(handle != null, "handle != null");
+            Debug.Assert(offset >= 0, "offset >= 0");
+            Debug.Assert(count >= 0, "count >= 0");
+            Debug.Assert(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(SR.IndexOutOfRange_IORaceCondition);
+
+            Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to WriteFileNative.");
+
+            // You can't use the fixed statement on an array of length 0.
+            if (bytes.Length == 0)
+            {
+                errorCode = 0;
+                return 0;
+            }
+
+            int numBytesWritten = 0;
+            int r = 0;
+
+            fixed (byte* p = &bytes[0])
+            {
+                if (_useAsyncIO)
+                    r = Interop.Kernel32.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped);
+                else
+                    r = Interop.Kernel32.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero);
+            }
+
+            if (r == 0)
+            {
+                errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid();
+                return -1;
+            }
+            else
+            {
+                errorCode = 0;
+                return numBytesWritten;
+            }
+        }
+
+        private int GetLastWin32ErrorAndDisposeHandleIfInvalid(bool throwIfInvalidHandle = false)
+        {
+            int errorCode = Marshal.GetLastWin32Error();
+
+            // 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 Win32FileStream
+            // 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 _parent.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 (errorCode == Interop.Errors.ERROR_INVALID_HANDLE)
+            {
+                _fileHandle.Dispose();
+
+                if (throwIfInvalidHandle)
+                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+            }
+
+            return errorCode;
+        }
+
+        public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+        {
+            // If we're in sync mode, just use the shared CopyToAsync implementation that does
+            // typical read/write looping.  We also need to take this path if this is a derived
+            // instance from FileStream, as a derived type could have overridden ReadAsync, in which
+            // case our custom CopyToAsync implementation isn't necessarily correct.
+            if (!_useAsyncIO || GetType() != typeof(FileStream))
+            {
+                return base.CopyToAsync(destination, bufferSize, cancellationToken);
+            }
+
+            StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize);
+
+            // Bail early for cancellation if cancellation has been requested
+            if (cancellationToken.IsCancellationRequested)
+            {
+                return Task.FromCanceled<int>(cancellationToken);
+            }
+
+            // Fail if the file was closed
+            if (_fileHandle.IsClosed)
+            {
+                throw Error.GetFileNotOpen();
+            }
+
+            // Do the async copy, with differing implementations based on whether the FileStream was opened as async or sync
+            Debug.Assert((_readPos == 0 && _readLength == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLength), "We're either reading or writing, but not both.");
+            return AsyncModeCopyToAsync(destination, bufferSize, cancellationToken);
+        }
+
+        private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
+        {
+            Debug.Assert(_useAsyncIO, "This implementation is for async mode only");
+            Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed");
+            Debug.Assert(CanRead, "_parent.CanRead");
+
+            // Make sure any pending writes have been flushed before we do a read.
+            if (_writePos > 0)
+            {
+                await FlushWriteAsync(cancellationToken).ConfigureAwait(false);
+            }
+
+            // Typically CopyToAsync would be invoked as the only "read" on the stream, but it's possible some reading is
+            // done and then the CopyToAsync is issued.  For that case, see if we have any data available in the buffer.
+            if (GetBuffer() != null)
+            {
+                int bufferedBytes = _readLength - _readPos;
+                if (bufferedBytes > 0)
+                {
+                    await destination.WriteAsync(GetBuffer(), _readPos, bufferedBytes, cancellationToken).ConfigureAwait(false);
+                    _readPos = _readLength = 0;
+                }
+            }
+
+            // For efficiency, we avoid creating a new task and associated state for each asynchronous read.
+            // Instead, we create a single reusable awaitable object that will be triggered when an await completes
+            // and reset before going again.
+            var readAwaitable = new AsyncCopyToAwaitable(this);
+
+            // Make sure we are reading from the position that we think we are.
+            // Only set the position in the awaitable if we can seek (e.g. not for pipes).
+            bool canSeek = CanSeek;
+            if (canSeek)
+            {
+                VerifyOSHandlePosition();
+                readAwaitable._position = _filePosition;
+            }
+
+            // Get the buffer to use for the copy operation, as the base CopyToAsync does. We don't try to use
+            // _buffer here, even if it's not null, as concurrent operations are allowed, and another operation may
+            // actually be using the buffer already. Plus, it'll be rare for _buffer to be non-null, as typically
+            // CopyToAsync is used as the only operation performed on the stream, and the buffer is lazily initialized.
+            // Further, typically the CopyToAsync buffer size will be larger than that used by the FileStream, such that
+            // we'd likely be unable to use it anyway.  Instead, we rent the buffer from a pool.
+            byte[] copyBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
+            bufferSize = 0; // repurpose bufferSize to be the high water mark for the buffer, to avoid an extra field in the state machine
+
+            // Allocate an Overlapped we can use repeatedly for all operations
+            var awaitableOverlapped = new PreAllocatedOverlapped(AsyncCopyToAwaitable.s_callback, readAwaitable, copyBuffer);
+            var cancellationReg = default(CancellationTokenRegistration);
+            try
+            {
+                // Register for cancellation.  We do this once for the whole copy operation, and just try to cancel
+                // whatever read operation may currently be in progress, if there is one.  It's possible the cancellation
+                // request could come in between operations, in which case we flag that with explicit calls to ThrowIfCancellationRequested
+                // in the read/write copy loop.
+                if (cancellationToken.CanBeCanceled)
+                {
+                    cancellationReg = cancellationToken.Register(s =>
+                    {
+                        var innerAwaitable = (AsyncCopyToAwaitable)s;
+                        unsafe
+                        {
+                            lock (innerAwaitable.CancellationLock) // synchronize with cleanup of the overlapped
+                            {
+                                if (innerAwaitable._nativeOverlapped != null)
+                                {
+                                    // Try to cancel the I/O.  We ignore the return value, as cancellation is opportunistic and we
+                                    // don't want to fail the operation because we couldn't cancel it.
+                                    Interop.Kernel32.CancelIoEx(innerAwaitable._fileStream._fileHandle, innerAwaitable._nativeOverlapped);
+                                }
+                            }
+                        }
+                    }, readAwaitable);
+                }
+
+                // Repeatedly read from this FileStream and write the results to the destination stream.
+                while (true)
+                {
+                    cancellationToken.ThrowIfCancellationRequested();
+                    readAwaitable.ResetForNextOperation();
+
+                    try
+                    {
+                        bool synchronousSuccess;
+                        int errorCode;
+                        unsafe
+                        {
+                            // Allocate a native overlapped for our reusable overlapped, and set position to read based on the next
+                            // desired address stored in the awaitable.  (This position may be 0, if either we're at the beginning or
+                            // if the stream isn't seekable.)
+                            readAwaitable._nativeOverlapped = _fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(awaitableOverlapped);
+                            if (canSeek)
+                            {
+                                readAwaitable._nativeOverlapped->OffsetLow = unchecked((int)readAwaitable._position);
+                                readAwaitable._nativeOverlapped->OffsetHigh = (int)(readAwaitable._position >> 32);
+                            }
+
+                            // Kick off the read.
+                            synchronousSuccess = ReadFileNative(_fileHandle, copyBuffer, 0, copyBuffer.Length, readAwaitable._nativeOverlapped, out errorCode) >= 0;
+                        }
+
+                        // If the operation did not synchronously succeed, it either failed or initiated the asynchronous operation.
+                        if (!synchronousSuccess)
+                        {
+                            switch (errorCode)
+                            {
+                                case ERROR_IO_PENDING:
+                                    // Async operation in progress.
+                                    break;
+                                case ERROR_BROKEN_PIPE:
+                                case ERROR_HANDLE_EOF:
+                                    // We're at or past the end of the file, and the overlapped callback
+                                    // won't be raised in these cases. Mark it as completed so that the await
+                                    // below will see it as such.
+                                    readAwaitable.MarkCompleted();
+                                    break;
+                                default:
+                                    // Everything else is an error (and there won't be a callback).
+                                    throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+                            }
+                        }
+
+                        // Wait for the async operation (which may or may not have already completed), then throw if it failed.
+                        await readAwaitable;
+                        switch (readAwaitable._errorCode)
+                        {
+                            case 0: // success
+                                Debug.Assert(readAwaitable._numBytes >= 0, $"Expected non-negative numBytes, got {readAwaitable._numBytes}");
+                                break;
+                            case ERROR_BROKEN_PIPE: // logically success with 0 bytes read (write end of pipe closed)
+                            case ERROR_HANDLE_EOF:  // logically success with 0 bytes read (read at end of file)
+                                Debug.Assert(readAwaitable._numBytes == 0, $"Expected 0 bytes read, got {readAwaitable._numBytes}");
+                                break;
+                            case Interop.Errors.ERROR_OPERATION_ABORTED: // canceled
+                                throw new OperationCanceledException(cancellationToken.IsCancellationRequested ? cancellationToken : new CancellationToken(true));
+                            default: // error
+                                throw Win32Marshal.GetExceptionForWin32Error((int)readAwaitable._errorCode);
+                        }
+
+                        // Successful operation.  If we got zero bytes, we're done: exit the read/write loop.
+                        int numBytesRead = (int)readAwaitable._numBytes;
+                        if (numBytesRead == 0)
+                        {
+                            break;
+                        }
+
+                        // Otherwise, update the read position for next time accordingly.
+                        if (canSeek)
+                        {
+                            readAwaitable._position += numBytesRead;
+                        }
+
+                        // (and keep track of the maximum number of bytes in the buffer we used, to avoid excessive and unnecessary
+                        // clearing of the buffer before we return it to the pool)
+                        if (numBytesRead > bufferSize)
+                        {
+                            bufferSize = numBytesRead;
+                        }
+                    }
+                    finally
+                    {
+                        // Free the resources for this read operation
+                        unsafe
+                        {
+                            NativeOverlapped* overlapped;
+                            lock (readAwaitable.CancellationLock) // just an Exchange, but we need this to be synchronized with cancellation, so using the same lock
+                            {
+                                overlapped = readAwaitable._nativeOverlapped;
+                                readAwaitable._nativeOverlapped = null;
+                            }
+                            if (overlapped != null)
+                            {
+                                _fileHandle.ThreadPoolBinding.FreeNativeOverlapped(overlapped);
+                            }
+                        }
+                    }
+
+                    // Write out the read data.
+                    await destination.WriteAsync(copyBuffer, 0, (int)readAwaitable._numBytes, cancellationToken).ConfigureAwait(false);
+                }
+            }
+            finally
+            {
+                // Cleanup from the whole copy operation
+                cancellationReg.Dispose();
+                awaitableOverlapped.Dispose();
+
+                Array.Clear(copyBuffer, 0, bufferSize);
+                ArrayPool<byte>.Shared.Return(copyBuffer, clearArray: false);
+
+                // Make sure the stream's current position reflects where we ended up
+                if (!_fileHandle.IsClosed && CanSeek)
+                {
+                    SeekCore(0, SeekOrigin.End);
+                }
+            }
+        }
+
+        /// <summary>Used by CopyToAsync to enable awaiting the result of an overlapped I/O operation with minimal overhead.</summary>
+        private sealed unsafe class AsyncCopyToAwaitable : ICriticalNotifyCompletion
+        {
+            /// <summary>Sentinel object used to indicate that the I/O operation has completed before being awaited.</summary>
+            private readonly static Action s_sentinel = () => { };
+            /// <summary>Cached delegate to IOCallback.</summary>
+            internal static readonly IOCompletionCallback s_callback = IOCallback;
+
+            /// <summary>The FileStream that owns this instance.</summary>
+            internal readonly FileStream _fileStream;
+
+            /// <summary>Tracked position representing the next location from which to read.</summary>
+            internal long _position;
+            /// <summary>The current native overlapped pointer.  This changes for each operation.</summary>
+            internal NativeOverlapped* _nativeOverlapped;
+            /// <summary>
+            /// null if the operation is still in progress,
+            /// s_sentinel if the I/O operation completed before the await,
+            /// s_callback if it completed after the await yielded.
+            /// </summary>
+            internal Action _continuation;
+            /// <summary>Last error code from completed operation.</summary>
+            internal uint _errorCode;
+            /// <summary>Last number of read bytes from completed operation.</summary>
+            internal uint _numBytes;
+
+            /// <summary>Lock object used to protect cancellation-related access to _nativeOverlapped.</summary>
+            internal object CancellationLock => this;
+
+            /// <summary>Initialize the awaitable.</summary>
+            internal unsafe AsyncCopyToAwaitable(FileStream fileStream)
+            {
+                _fileStream = fileStream;
+            }
+
+            /// <summary>Reset state to prepare for the next read operation.</summary>
+            internal void ResetForNextOperation()
+            {
+                Debug.Assert(_position >= 0, $"Expected non-negative position, got {_position}");
+                _continuation = null;
+                _errorCode = 0;
+                _numBytes = 0;
+            }
+
+            /// <summary>Overlapped callback: store the results, then invoke the continuation delegate.</summary>
+            internal unsafe static void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP)
+            {
+                var awaitable = (AsyncCopyToAwaitable)ThreadPoolBoundHandle.GetNativeOverlappedState(pOVERLAP);
+
+                Debug.Assert(awaitable._continuation != s_sentinel, "Sentinel must not have already been set as the continuation");
+                awaitable._errorCode = errorCode;
+                awaitable._numBytes = numBytes;
+
+                (awaitable._continuation ?? Interlocked.CompareExchange(ref awaitable._continuation, s_sentinel, null))?.Invoke();
+            }
+
+            /// <summary>
+            /// Called when it's known that the I/O callback for an operation will not be invoked but we'll
+            /// still be awaiting the awaitable.
+            /// </summary>
+            internal void MarkCompleted()
+            {
+                Debug.Assert(_continuation == null, "Expected null continuation");
+                _continuation = s_sentinel;
+            }
+
+            public AsyncCopyToAwaitable GetAwaiter() => this;
+            public bool IsCompleted => _continuation == s_sentinel;
+            public void GetResult() { }
+            public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation);
+            public void UnsafeOnCompleted(Action continuation)
+            {
+                if (_continuation == s_sentinel ||
+                    Interlocked.CompareExchange(ref _continuation, continuation, null) != null)
+                {
+                    Debug.Assert(_continuation == s_sentinel, $"Expected continuation set to s_sentinel, got ${_continuation}");
+                    Task.Run(continuation);
+                }
+            }
+        }
+
+        // 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.
+        private Task FlushAsyncInternal(CancellationToken cancellationToken)
+        {
+            if (cancellationToken.IsCancellationRequested)
+                return Task.FromCanceled(cancellationToken);
+
+            if (_fileHandle.IsClosed)
+                throw Error.GetFileNotOpen();
+
+            // 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;
+            }
+        }
+
+        private Task<int> TaskFromResultOrCache(int result)
+        {
+            Task<int> completedTask = _lastSynchronouslyCompletedTask;
+            Debug.Assert(completedTask == null || completedTask.Status == TaskStatus.RanToCompletion, "Cached task should have completed successfully");
+
+            if ((completedTask == null) || (completedTask.Result != result))
+            {
+                completedTask = Task.FromResult(result);
+                _lastSynchronouslyCompletedTask = completedTask;
+            }
+
+            return completedTask;
+        }
+
+        private void LockInternal(long position, long length)
+        {
+            int positionLow = unchecked((int)(position));
+            int positionHigh = unchecked((int)(position >> 32));
+            int lengthLow = unchecked((int)(length));
+            int lengthHigh = unchecked((int)(length >> 32));
+
+            if (!Interop.Kernel32.LockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
+            {
+                throw Win32Marshal.GetExceptionForLastWin32Error();
+            }
+        }
+
+        private void UnlockInternal(long position, long length)
+        {
+            int positionLow = unchecked((int)(position));
+            int positionHigh = unchecked((int)(position >> 32));
+            int lengthLow = unchecked((int)(length));
+            int lengthHigh = unchecked((int)(length >> 32));
+
+            if (!Interop.Kernel32.UnlockFile(_fileHandle, positionLow, positionHigh, lengthLow, lengthHigh))
+            {
+                throw Win32Marshal.GetExceptionForLastWin32Error();
+            }
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/FileStream.cs b/src/mscorlib/shared/System/IO/FileStream.cs
new file mode 100644 (file)
index 0000000..7db8518
--- /dev/null
@@ -0,0 +1,684 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.Win32.SafeHandles;
+using System.Diagnostics;
+
+namespace System.IO
+{
+    public partial class FileStream : Stream
+    {
+        private const FileShare DefaultShare = FileShare.Read;
+        private const bool DefaultIsAsync = false;
+        internal const int DefaultBufferSize = 4096;
+
+        private byte[] _buffer;
+        private int _bufferLength;
+        private readonly SafeFileHandle _fileHandle;
+
+        /// <summary>Whether the file is opened for reading, writing, or both.</summary>
+        private readonly FileAccess _access;
+
+        /// <summary>The path to the opened file.</summary>
+        private readonly string _path;
+
+        /// <summary>The next available byte to be read from the _buffer.</summary>
+        private int _readPos;
+
+        /// <summary>The number of valid bytes in _buffer.</summary>
+        private int _readLength;
+
+        /// <summary>The next location in which a write should occur to the buffer.</summary>
+        private int _writePos;
+
+        /// <summary>
+        /// Whether asynchronous read/write/flush operations should be performed using async I/O.
+        /// On Windows FileOptions.Asynchronous controls how the file handle is configured, 
+        /// and then as a result how operations are issued against that file handle.  On Unix, 
+        /// there isn't any distinction around how file descriptors are created for async vs 
+        /// sync, but we still differentiate how the operations are issued in order to provide
+        /// similar behavioral semantics and performance characteristics as on Windows.  On
+        /// Windows, if non-async, async read/write requests just delegate to the base stream,
+        /// and no attempt is made to synchronize between sync and async operations on the stream;
+        /// if async, then async read/write requests are implemented specially, and sync read/write
+        /// requests are coordinated with async ones by implementing the sync ones over the async
+        /// ones.  On Unix, we do something similar.  If non-async, async read/write requests just
+        /// delegate to the base stream, and no attempt is made to synchronize.  If async, we use
+        /// a semaphore to coordinate both sync and async operations.
+        /// </summary>
+        private readonly bool _useAsyncIO;
+
+        /// <summary>
+        /// Currently cached position in the stream.  This should always mirror the underlying file's actual position,
+        /// and should only ever be out of sync if another stream with access to this same file manipulates it, at which
+        /// point we attempt to error out.
+        /// </summary>
+        private long _filePosition;
+
+        /// <summary>Whether the file stream's handle has been exposed.</summary>
+        private bool _exposedHandle;
+
+        [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)
+        {
+        }
+
+        [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")]
+        public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
+            : this(new SafeFileHandle(handle, ownsHandle), access, bufferSize, isAsync)
+        {
+        }
+
+        public FileStream(SafeFileHandle handle, FileAccess access)
+            : this(handle, access, DefaultBufferSize)
+        {
+        }
+
+        public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
+            : this(handle, access, bufferSize, GetDefaultIsAsync(handle))
+        {
+        }
+
+        public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
+        {
+            if (handle.IsInvalid)
+                throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle));
+
+            if (access < FileAccess.Read || access > FileAccess.ReadWrite)
+                throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
+            if (bufferSize <= 0)
+                throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+
+            if (handle.IsClosed)
+                throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
+            if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.Value)
+                throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle));
+
+            _access = access;
+            _useAsyncIO = isAsync;
+            _exposedHandle = true;
+            _bufferLength = bufferSize;
+            _fileHandle = handle;
+
+            InitFromHandle(handle);
+        }
+
+        public FileStream(string path, FileMode mode) :
+            this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), DefaultShare, DefaultBufferSize, DefaultIsAsync)
+        { }
+
+        public FileStream(string path, FileMode mode, FileAccess access) :
+            this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync)
+        { }
+
+        public FileStream(string path, FileMode mode, FileAccess access, FileShare share) :
+            this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync)
+        { }
+
+        public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) :
+            this(path, mode, access, share, bufferSize, DefaultIsAsync)
+        { }
+
+        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)
+        { }
+
+        public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
+        {
+            if (path == null)
+                throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path);
+            if (path.Length == 0)
+                throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
+
+            // 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 (access < FileAccess.Read || access > FileAccess.ReadWrite)
+                badArg = nameof(access);
+            else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
+                badArg = nameof(share);
+
+            if (badArg != null)
+                throw new ArgumentOutOfRangeException(badArg, SR.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), SR.ArgumentOutOfRange_Enum);
+
+            if (bufferSize <= 0)
+                throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
+
+            // Write access validation
+            if ((access & FileAccess.Write) == 0)
+            {
+                if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
+                {
+                    // No write access, mode and access disagree but flag access since mode comes first
+                    throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access));
+                }
+            }
+
+            if ((access & FileAccess.Read) != 0 && mode == FileMode.Append)
+                throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
+
+            string fullPath = Path.GetFullPath(path);
+
+            _path = fullPath;
+            _access = access;
+            _bufferLength = bufferSize;
+
+            if ((options & FileOptions.Asynchronous) != 0)
+                _useAsyncIO = true;
+
+            _fileHandle = OpenHandle(mode, share, options);
+
+            try
+            {
+                Init(mode, share);
+            }
+            catch
+            {
+                // If anything goes wrong while setting up the stream, make sure we deterministically dispose
+                // of the opened handle.
+                _fileHandle.Dispose();
+                _fileHandle = null;
+                throw;
+            }
+        }
+
+        private static bool GetDefaultIsAsync(SafeFileHandle handle)
+        {
+            // This will eventually get more complicated as we can actually check the underlying handle type on Windows
+            return handle.IsAsync.HasValue ? handle.IsAsync.Value : false;
+        }
+
+        [Obsolete("This property has been deprecated.  Please use FileStream's SafeFileHandle property instead.  http://go.microsoft.com/fwlink/?linkid=14202")]
+        public virtual IntPtr Handle { get { return SafeFileHandle.DangerousGetHandle(); } }
+
+        public virtual void Lock(long position, long length)
+        {
+            if (position < 0 || length < 0)
+            {
+                throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+            }
+
+            if (_fileHandle.IsClosed)
+            {
+                throw Error.GetFileNotOpen();
+            }
+
+            LockInternal(position, length);
+        }
+
+        public virtual void Unlock(long position, long length)
+        {
+            if (position < 0 || length < 0)
+            {
+                throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
+            }
+
+            if (_fileHandle.IsClosed)
+            {
+                throw Error.GetFileNotOpen();
+            }
+
+            UnlockInternal(position, length);
+        }
+
+        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 overridden.  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 (GetType() != typeof(FileStream))
+                return base.FlushAsync(cancellationToken);
+
+            return FlushAsyncInternal(cancellationToken);
+        }
+
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (buffer == null)
+                throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+            if (offset < 0)
+                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (buffer.Length - offset < count)
+                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
+
+            // If we have been inherited into a subclass, the following implementation could be incorrect
+            // since it does not call through to Read() or ReadAsync() which a subclass might have overridden.  
+            // 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/ReadAsync) when we are not sure.
+            if (GetType() != typeof(FileStream))
+                return base.ReadAsync(buffer, offset, count, cancellationToken);
+
+            if (cancellationToken.IsCancellationRequested)
+                return Task.FromCanceled<int>(cancellationToken);
+
+            if (IsClosed)
+                throw Error.GetFileNotOpen();
+
+            return ReadAsyncInternal(buffer, offset, count, cancellationToken);
+        }
+
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (buffer == null)
+                throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
+            if (offset < 0)
+                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (buffer.Length - offset < count)
+                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
+
+            // If we have been inherited into a subclass, the following implementation could be incorrect
+            // since it does not call through to Write() or WriteAsync() which a subclass might have overridden.  
+            // 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/WriteAsync) when we are not sure.
+            if (GetType() != typeof(FileStream))
+                return base.WriteAsync(buffer, offset, count, cancellationToken);
+
+            if (cancellationToken.IsCancellationRequested)
+                return Task.FromCanceled(cancellationToken);
+
+            if (IsClosed)
+                throw Error.GetFileNotOpen();
+
+            return WriteAsyncInternal(buffer, offset, count, cancellationToken);
+        }
+
+        /// <summary>
+        /// Clears buffers for this stream and causes any buffered data to be written to the file.
+        /// </summary>
+        public override void Flush()
+        {
+            // Make sure that we call through the public virtual API
+            Flush(flushToDisk: false);
+        }
+
+        /// <summary>
+        /// Clears buffers for this stream, and if <param name="flushToDisk"/> is true, 
+        /// causes any buffered data to be written to the file.
+        /// </summary>
+        public virtual void Flush(bool flushToDisk)
+        {
+            if (IsClosed) throw Error.GetFileNotOpen();
+
+            FlushInternalBuffer();
+
+            if (flushToDisk && CanWrite)
+            {
+                FlushOSBuffer();
+            }
+        }
+
+        /// <summary>Gets a value indicating whether the current stream supports reading.</summary>
+        public override bool CanRead
+        {
+            get { return !_fileHandle.IsClosed && (_access & FileAccess.Read) != 0; }
+        }
+
+        /// <summary>Gets a value indicating whether the current stream supports writing.</summary>
+        public override bool CanWrite
+        {
+            get { return !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0; }
+        }
+
+        /// <summary>Validates arguments to Read and Write and throws resulting exceptions.</summary>
+        /// <param name="array">The buffer to read from or write to.</param>
+        /// <param name="offset">The zero-based offset into the array.</param>
+        /// <param name="count">The maximum number of bytes to read or write.</param>
+        private void ValidateReadWriteArgs(byte[] array, int offset, int count)
+        {
+            if (array == null)
+                throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
+            if (offset < 0)
+                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (count < 0)
+                throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (array.Length - offset < count)
+                throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
+            if (_fileHandle.IsClosed)
+                throw Error.GetFileNotOpen();
+        }
+
+        /// <summary>Sets the length of this stream to the given value.</summary>
+        /// <param name="value">The new length of the stream.</param>
+        public override void SetLength(long value)
+        {
+            if (value < 0)
+                throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (_fileHandle.IsClosed)
+                throw Error.GetFileNotOpen();
+            if (!CanSeek)
+                throw Error.GetSeekNotSupported();
+            if (!CanWrite)
+                throw Error.GetWriteNotSupported();
+
+            SetLengthInternal(value);
+        }
+
+        public virtual SafeFileHandle SafeFileHandle
+        {
+            get
+            {
+                Flush();
+                _exposedHandle = true;
+                return _fileHandle;
+            }
+        }
+
+        /// <summary>Gets the path that was passed to the constructor.</summary>
+        public virtual string Name { get { return _path ?? SR.IO_UnknownFileName; } }
+
+        /// <summary>Gets a value indicating whether the stream was opened for I/O to be performed synchronously or asynchronously.</summary>
+        public virtual bool IsAsync
+        {
+            get { return _useAsyncIO; }
+        }
+
+        /// <summary>Gets the length of the stream in bytes.</summary>
+        public override long Length
+        {
+            get
+            {
+                if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
+                if (!CanSeek) throw Error.GetSeekNotSupported();
+                return GetLengthInternal();
+            }
+        }
+
+        /// <summary>
+        /// Verify that the actual position of the OS's handle equals what we expect it to.
+        /// This will fail if someone else moved the UnixFileStream's handle or if
+        /// our position updating code is incorrect.
+        /// </summary>
+        private void VerifyOSHandlePosition()
+        {
+            bool verifyPosition = _exposedHandle; // in release, only verify if we've given out the handle such that someone else could be manipulating it
+#if DEBUG
+            verifyPosition = true; // in debug, always make sure our position matches what the OS says it should be
+#endif
+            if (verifyPosition && CanSeek)
+            {
+                long oldPos = _filePosition; // SeekCore will override the current _position, so save it now
+                long curPos = SeekCore(0, SeekOrigin.Current);
+                if (oldPos != curPos)
+                {
+                    // For reads, this is non-fatal but we still could have returned corrupted 
+                    // data in some cases, so discard the internal buffer. For writes, 
+                    // this is a problem; discard the buffer and error out.
+                    _readPos = _readLength = 0;
+                    if (_writePos > 0)
+                    {
+                        _writePos = 0;
+                        throw new IOException(SR.IO_FileStreamHandlePosition);
+                    }
+                }
+            }
+        }
+
+        /// <summary>Verifies that state relating to the read/write buffer is consistent.</summary>
+        [Conditional("DEBUG")]
+        private void AssertBufferInvariants()
+        {
+            // Read buffer values must be in range: 0 <= _bufferReadPos <= _bufferReadLength <= _bufferLength
+            Debug.Assert(0 <= _readPos && _readPos <= _readLength && _readLength <= _bufferLength);
+
+            // Write buffer values must be in range: 0 <= _bufferWritePos <= _bufferLength
+            Debug.Assert(0 <= _writePos && _writePos <= _bufferLength);
+
+            // Read buffering and write buffering can't both be active
+            Debug.Assert((_readPos == 0 && _readLength == 0) || _writePos == 0);
+        }
+
+        /// <summary>Validates that we're ready to read from the stream.</summary>
+        private void PrepareForReading()
+        {
+            if (_fileHandle.IsClosed)
+                throw Error.GetFileNotOpen();
+            if (_readLength == 0 && !CanRead)
+                throw Error.GetReadNotSupported();
+
+            AssertBufferInvariants();
+        }
+
+        /// <summary>Gets or sets the position within the current stream</summary>
+        public override long Position
+        {
+            get
+            {
+                if (_fileHandle.IsClosed)
+                    throw Error.GetFileNotOpen();
+
+                if (!CanSeek)
+                    throw Error.GetSeekNotSupported();
+
+                AssertBufferInvariants();
+                VerifyOSHandlePosition();
+
+                // We may have read data into our buffer from the handle, such that the handle position
+                // is artificially further along than the consumer's view of the stream's position.
+                // Thus, when reading, our position is really starting from the handle position negatively
+                // offset by the number of bytes in the buffer and positively offset by the number of
+                // bytes into that buffer we've read.  When writing, both the read length and position
+                // must be zero, and our position is just the handle position offset positive by how many
+                // bytes we've written into the buffer.
+                return (_filePosition - _readLength) + _readPos + _writePos;
+            }
+            set
+            {
+                if (value < 0)
+                    throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
+
+                Seek(value, SeekOrigin.Begin);
+            }
+        }
+
+        internal virtual bool IsClosed => _fileHandle.IsClosed;
+
+        /// <summary>
+        /// Gets the array used for buffering reading and writing.  
+        /// If the array hasn't been allocated, this will lazily allocate it.
+        /// </summary>
+        /// <returns>The buffer.</returns>
+        private byte[] GetBuffer()
+        {
+            Debug.Assert(_buffer == null || _buffer.Length == _bufferLength);
+            if (_buffer == null)
+            {
+                _buffer = new byte[_bufferLength];
+                OnBufferAllocated();
+            }
+
+            return _buffer;
+        }
+
+        partial void OnBufferAllocated();
+
+        /// <summary>
+        /// Flushes the internal read/write buffer for this stream.  If write data has been buffered,
+        /// that data is written out to the underlying file.  Or if data has been buffered for 
+        /// reading from the stream, the data is dumped and our position in the underlying file 
+        /// is rewound as necessary.  This does not flush the OS buffer.
+        /// </summary>
+        private void FlushInternalBuffer()
+        {
+            AssertBufferInvariants();
+            if (_writePos > 0)
+            {
+                FlushWriteBuffer();
+            }
+            else if (_readPos < _readLength && CanSeek)
+            {
+                FlushReadBuffer();
+            }
+        }
+
+        /// <summary>Dumps any read data in the buffer and rewinds our position in the stream, accordingly, as necessary.</summary>
+        private void FlushReadBuffer()
+        {
+            // 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.
+
+            AssertBufferInvariants();
+            Debug.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushReadBuffer!");
+
+            int rewind = _readPos - _readLength;
+            if (rewind != 0)
+            {
+                Debug.Assert(CanSeek, "FileStream will lose buffered read data now.");
+                SeekCore(rewind, SeekOrigin.Current);
+            }
+            _readPos = _readLength = 0;
+        }
+
+        private int ReadByteCore()
+        {
+            PrepareForReading();
+
+            byte[] buffer = GetBuffer();
+            if (_readPos == _readLength)
+            {
+                FlushWriteBuffer();
+                Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
+
+                _readLength = ReadNative(buffer, 0, _bufferLength);
+                _readPos = 0;
+                if (_readLength == 0)
+                {
+                    return -1;
+                }
+            }
+
+            return buffer[_readPos++];
+        }
+
+        private void WriteByteCore(byte value)
+        {
+            PrepareForWriting();
+
+            // Flush the write buffer if it's full
+            if (_writePos == _bufferLength)
+                FlushWriteBuffer();
+
+            // We now have space in the buffer. Store the byte.
+            GetBuffer()[_writePos++] = value;
+        }
+
+        /// <summary>
+        /// Validates that we're ready to write to the stream,
+        /// including flushing a read buffer if necessary.
+        /// </summary>
+        private void PrepareForWriting()
+        {
+            if (_fileHandle.IsClosed)
+                throw Error.GetFileNotOpen();
+
+            // Make sure we're good to write.  We only need to do this if there's nothing already
+            // in our write buffer, since if there is something in the buffer, we've already done 
+            // this checking and flushing.
+            if (_writePos == 0)
+            {
+                if (!CanWrite) throw Error.GetWriteNotSupported();
+                FlushReadBuffer();
+                Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
+            }
+        }
+
+        ~FileStream()
+        {
+            // Preserved for compatibility since FileStream has defined a 
+            // finalizer in past releases and derived classes may depend
+            // on Dispose(false) call.
+            Dispose(false);
+        }
+
+        public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
+        {
+            if (array == null)
+                throw new ArgumentNullException(nameof(array));
+            if (offset < 0)
+                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (numBytes < 0)
+                throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (array.Length - offset < numBytes)
+                throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+            if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
+            if (!CanRead) throw new NotSupportedException(SR.NotSupported_UnreadableStream);
+
+            if (!IsAsync)
+                return base.BeginRead(array, offset, numBytes, callback, state);
+            else
+                return TaskToApm.Begin(ReadAsyncInternal(array, offset, numBytes, CancellationToken.None), callback, state);
+        }
+
+        public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
+        {
+            if (array == null)
+                throw new ArgumentNullException(nameof(array));
+            if (offset < 0)
+                throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (numBytes < 0)
+                throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
+            if (array.Length - offset < numBytes)
+                throw new ArgumentException(SR.Argument_InvalidOffLen);
+
+            if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
+            if (!CanWrite) throw new NotSupportedException(SR.NotSupported_UnwritableStream);
+
+            if (!IsAsync)
+                return base.BeginWrite(array, offset, numBytes, callback, state);
+            else
+                return TaskToApm.Begin(WriteAsyncInternal(array, offset, numBytes, CancellationToken.None), callback, state);
+        }
+
+        public override int EndRead(IAsyncResult asyncResult)
+        {
+            if (asyncResult == null)
+                throw new ArgumentNullException(nameof(asyncResult));
+
+            if (!IsAsync)
+                return base.EndRead(asyncResult);
+            else
+                return TaskToApm.End<int>(asyncResult);
+        }
+
+        public override void EndWrite(IAsyncResult asyncResult)
+        {
+            if (asyncResult == null)
+                throw new ArgumentNullException(nameof(asyncResult));
+
+            if (!IsAsync)
+                base.EndWrite(asyncResult);
+            else
+                TaskToApm.End(asyncResult);
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/FileStreamCompletionSource.Win32.cs b/src/mscorlib/shared/System/IO/FileStreamCompletionSource.Win32.cs
new file mode 100644 (file)
index 0000000..7dca133
--- /dev/null
@@ -0,0 +1,222 @@
+// 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.Security;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.InteropServices;
+using System.Diagnostics;
+
+namespace System.IO
+{
+    public partial class FileStream : Stream
+    {
+        // This is an internal object extending TaskCompletionSource with fields
+        // for all of the relevant data necessary to complete the IO operation.
+        // This is used by IOCallback and all of the async methods.
+        unsafe private sealed class FileStreamCompletionSource : TaskCompletionSource<int>
+        {
+            private const long NoResult = 0;
+            private const long ResultSuccess = (long)1 << 32;
+            private const long ResultError = (long)2 << 32;
+            private const long RegisteringCancellation = (long)4 << 32;
+            private const long CompletedCallback = (long)8 << 32;
+            private const ulong ResultMask = ((ulong)uint.MaxValue) << 32;
+
+            private static Action<object> s_cancelCallback;
+
+            private readonly FileStream _stream;
+            private readonly int _numBufferedBytes;
+            private readonly CancellationToken _cancellationToken;
+            private CancellationTokenRegistration _cancellationRegistration;
+#if DEBUG
+            private bool _cancellationHasBeenRegistered;
+#endif
+            private NativeOverlapped* _overlapped; // Overlapped class responsible for operations in progress when an appdomain unload occurs
+            private long _result; // Using long since this needs to be used in Interlocked APIs
+
+            // Using RunContinuationsAsynchronously for compat reasons (old API used Task.Factory.StartNew for continuations)
+            internal FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[] bytes, CancellationToken cancellationToken)
+                : base(TaskCreationOptions.RunContinuationsAsynchronously)
+            {
+                _numBufferedBytes = numBufferedBytes;
+                _stream = stream;
+                _result = NoResult;
+                _cancellationToken = cancellationToken;
+
+                // Create the native overlapped. We try to use the preallocated overlapped if possible: 
+                // it's possible if the byte buffer is the same one that's associated with the preallocated overlapped 
+                // and if no one else is currently using the preallocated overlapped.  This is the fast-path for cases 
+                // where the user-provided buffer is smaller than the FileStream's buffer (such that the FileStream's 
+                // buffer is used) and where operations on the FileStream are not being performed concurrently.
+                _overlapped = ReferenceEquals(bytes, _stream._buffer) && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ?
+                    _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(_stream._preallocatedOverlapped) :
+                    _stream._fileHandle.ThreadPoolBinding.AllocateNativeOverlapped(s_ioCallback, this, bytes);
+                Debug.Assert(_overlapped != null, "AllocateNativeOverlapped returned null");
+            }
+
+            internal NativeOverlapped* Overlapped
+            {
+                get { return _overlapped; }
+            }
+
+            public void SetCompletedSynchronously(int numBytes)
+            {
+                ReleaseNativeResource();
+                TrySetResult(numBytes + _numBufferedBytes);
+            }
+
+            public void RegisterForCancellation()
+            {
+#if DEBUG
+                Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice");
+                _cancellationHasBeenRegistered = true;
+#endif
+
+                // Quick check to make sure that the cancellation token supports cancellation, and that the IO hasn't completed
+                if ((_cancellationToken.CanBeCanceled) && (_overlapped != null))
+                {
+                    var cancelCallback = s_cancelCallback;
+                    if (cancelCallback == null) s_cancelCallback = cancelCallback = Cancel;
+
+                    // Register the cancellation only if the IO hasn't completed
+                    long packedResult = Interlocked.CompareExchange(ref _result, RegisteringCancellation, NoResult);
+                    if (packedResult == NoResult)
+                    {
+                        _cancellationRegistration = _cancellationToken.Register(cancelCallback, this);
+
+                        // Switch the result, just in case IO completed while we were setting the registration
+                        packedResult = Interlocked.Exchange(ref _result, NoResult);
+                    }
+                    else if (packedResult != CompletedCallback)
+                    {
+                        // Failed to set the result, IO is in the process of completing
+                        // Attempt to take the packed result
+                        packedResult = Interlocked.Exchange(ref _result, NoResult);
+                    }
+
+                    // If we have a callback that needs to be completed
+                    if ((packedResult != NoResult) && (packedResult != CompletedCallback) && (packedResult != RegisteringCancellation))
+                    {
+                        CompleteCallback((ulong)packedResult);
+                    }
+                }
+            }
+
+            internal void ReleaseNativeResource()
+            {
+                // Ensure that cancellation has been completed and cleaned up.
+                _cancellationRegistration.Dispose();
+
+                // Free the overlapped.
+                // NOTE: The cancellation must *NOT* be running at this point, or it may observe freed memory
+                // (this is why we disposed the registration above).
+                if (_overlapped != null)
+                {
+                    _stream._fileHandle.ThreadPoolBinding.FreeNativeOverlapped(_overlapped);
+                    _overlapped = null;
+                }
+
+                // Ensure we're no longer set as the current completion source (we may not have been to begin with).
+                // Only one operation at a time is eligible to use the preallocated overlapped, 
+                _stream.CompareExchangeCurrentOverlappedOwner(null, this);
+            }
+
+            // When doing IO asynchronously (i.e. _isAsync==true), this callback is 
+            // called by a free thread in the threadpool when the IO operation 
+            // completes.  
+            internal static unsafe void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped)
+            {
+                // Extract the completion source from the overlapped.  The state in the overlapped
+                // will either be a Win32FileStream (in the case where the preallocated overlapped was used),
+                // in which case the operation being completed is its _currentOverlappedOwner, or it'll
+                // be directly the FileStreamCompletion that's completing (in the case where the preallocated
+                // overlapped was already in use by another operation).
+                object state = ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped);
+                FileStream fs = state as FileStream;
+                FileStreamCompletionSource completionSource = fs != null ?
+                    fs._currentOverlappedOwner :
+                    (FileStreamCompletionSource)state;
+                Debug.Assert(completionSource._overlapped == pOverlapped, "Overlaps don't match");
+
+                // 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.
+                ulong packedResult;
+                if (errorCode != 0 && errorCode != ERROR_BROKEN_PIPE && errorCode != ERROR_NO_DATA)
+                {
+                    packedResult = ((ulong)ResultError | errorCode);
+                }
+                else
+                {
+                    packedResult = ((ulong)ResultSuccess | numBytes);
+                }
+
+                // Stow the result so that other threads can observe it
+                // And, if no other thread is registering cancellation, continue
+                if (NoResult == Interlocked.Exchange(ref completionSource._result, (long)packedResult))
+                {
+                    // Successfully set the state, attempt to take back the callback
+                    if (Interlocked.Exchange(ref completionSource._result, CompletedCallback) != NoResult)
+                    {
+                        // Successfully got the callback, finish the callback
+                        completionSource.CompleteCallback(packedResult);
+                    }
+                    // else: Some other thread stole the result, so now it is responsible to finish the callback
+                }
+                // else: Some other thread is registering a cancellation, so it *must* finish the callback
+            }
+
+            private void CompleteCallback(ulong packedResult)
+            {
+                // Free up the native resource and cancellation registration
+                ReleaseNativeResource();
+
+                // Unpack the result and send it to the user
+                long result = (long)(packedResult & ResultMask);
+                if (result == ResultError)
+                {
+                    int errorCode = unchecked((int)(packedResult & uint.MaxValue));
+                    if (errorCode == Interop.Errors.ERROR_OPERATION_ABORTED)
+                    {
+                        TrySetCanceled(_cancellationToken.IsCancellationRequested ? _cancellationToken : new CancellationToken(true));
+                    }
+                    else
+                    {
+                        TrySetException(Win32Marshal.GetExceptionForWin32Error(errorCode));
+                    }
+                }
+                else
+                {
+                    Debug.Assert(result == ResultSuccess, "Unknown result");
+                    TrySetResult((int)(packedResult & uint.MaxValue) + _numBufferedBytes);
+                }
+            }
+
+            private static void Cancel(object state)
+            {
+                // WARNING: This may potentially be called under a lock (during cancellation registration)
+
+                FileStreamCompletionSource completionSource = state as FileStreamCompletionSource;
+                Debug.Assert(completionSource != null, "Unknown state passed to cancellation");
+                Debug.Assert(completionSource._overlapped != null && !completionSource.Task.IsCompleted, "IO should not have completed yet");
+
+                // If the handle is still valid, attempt to cancel the IO
+                if (!completionSource._stream._fileHandle.IsInvalid &&
+                    !Interop.Kernel32.CancelIoEx(completionSource._stream._fileHandle, completionSource._overlapped))
+                {
+                    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 != Interop.Errors.ERROR_NOT_FOUND)
+                    {
+                        throw Win32Marshal.GetExceptionForWin32Error(errorCode);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/Path.Unix.cs b/src/mscorlib/shared/System/IO/Path.Unix.cs
new file mode 100644 (file)
index 0000000..c566fa0
--- /dev/null
@@ -0,0 +1,216 @@
+// 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 char[] GetInvalidFileNameChars() => new char[] { '\0', '/' };
+
+        public static char[] GetInvalidPathChars() => new char[] { '\0' };
+
+        internal static int MaxPath => Interop.Sys.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 > Interop.Sys.MaxPath)
+            {
+                throw new PathTooLongException(SR.IO_PathTooLong);
+            }
+
+            string result = collapsedString.Length == 0 ? PathInternal.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 > Interop.Sys.MaxName)
+                {
+                    throw new PathTooLongException(SR.IO_PathTooLong);
+                }
+
+                // Normalize the directory separator if needed
+                if (c != PathInternal.DirectorySeparatorChar && c == PathInternal.AltDirectorySeparatorChar)
+                {
+                    c = PathInternal.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 + PathInternal.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] == PathInternal.DirectorySeparatorChar;
+        }
+
+        public static string GetPathRoot(string path)
+        {
+            if (path == null) return null;
+            return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : String.Empty;
+        }
+
+        private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount)
+        {
+            // 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);
+        }
+
+        /// <summary>Gets whether the system is case-sensitive.</summary>
+        internal static bool IsCaseSensitive { get { return !s_isMac; } }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/Path.Win32.cs b/src/mscorlib/shared/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/shared/System/IO/Path.Windows.cs b/src/mscorlib/shared/System/IO/Path.Windows.cs
new file mode 100644 (file)
index 0000000..0f8e3b3
--- /dev/null
@@ -0,0 +1,155 @@
+// 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 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, ':', '*', '?', '\\', '/'
+        };
+
+        public 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
+        };
+
+        // 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 const int MaxPath = 260;
+
+        // 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] == PathInternal.VolumeSeparatorChar)
+                    || (path.Length >= startIndex && path[startIndex - 1] == PathInternal.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2]))
+                    || (path.Length > startIndex && path.IndexOf(PathInternal.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.Kernel32.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.Kernel32.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] == PathInternal.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/shared/System/IO/Path.cs b/src/mscorlib/shared/System/IO/Path.cs
new file mode 100644 (file)
index 0000000..ce80424
--- /dev/null
@@ -0,0 +1,574 @@
+// 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
+    {
+        // Public static readonly variant of the separators. The Path implementation itself is using
+        // internal const variant of the separators for better performance.
+        public static readonly char DirectorySeparatorChar = PathInternal.DirectorySeparatorChar;
+        public static readonly char AltDirectorySeparatorChar = PathInternal.AltDirectorySeparatorChar;
+        public static readonly char VolumeSeparatorChar = PathInternal.VolumeSeparatorChar;
+        public static readonly char PathSeparator = PathInternal.PathSeparator;
+
+        // For generating random file names
+        // 8 random bytes provides 12 chars in our encoding for the 8.3 name.
+        private 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;
+        }
+
+        // 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(PathInternal.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 + PathInternal.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 + PathInternal.DirectorySeparatorCharAsString + path3;
+            }
+            else if (hasSep2)
+            {
+                return path1 + PathInternal.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(PathInternal.DirectorySeparatorChar)
+                  .Append(path2)
+                  .Append(PathInternal.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(PathInternal.DirectorySeparatorChar);
+                }
+
+                sb.Append(path2);
+                if (!hasSep2)
+                {
+                    sb.Append(PathInternal.DirectorySeparatorChar);
+                }
+
+                sb.Append(path3);
+                if (!hasSep3)
+                {
+                    sb.Append(PathInternal.DirectorySeparatorChar);
+                }
+
+                sb.Append(path4);
+
+                return StringBuilderCache.GetStringAndRelease(sb);
+            }
+        }
+
+        private static readonly char[] s_base32Char = {
+                'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+                'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+                'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+                'y', 'z', '0', '1', '2', '3', '4', '5'};
+
+        private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, char* chars, int charCount)
+        {
+            Debug.Assert(bytes != null);
+            Debug.Assert(chars != null);
+
+            // This method requires bytes of length 8 and chars of length 12.
+            Debug.Assert(byteCount == 8, $"Unexpected {nameof(byteCount)}");
+            Debug.Assert(charCount == 12, $"Unexpected {nameof(charCount)}");
+
+            byte b0 = bytes[0];
+            byte b1 = bytes[1];
+            byte b2 = bytes[2];
+            byte b3 = bytes[3];
+            byte b4 = bytes[4];
+
+            // Consume the 5 Least significant bits of the first 5 bytes
+            chars[0] = s_base32Char[b0 & 0x1F];
+            chars[1] = s_base32Char[b1 & 0x1F];
+            chars[2] = s_base32Char[b2 & 0x1F];
+            chars[3] = s_base32Char[b3 & 0x1F];
+            chars[4] = s_base32Char[b4 & 0x1F];
+
+            // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4
+            chars[5] = s_base32Char[(
+                    ((b0 & 0xE0) >> 5) |
+                    ((b3 & 0x60) >> 2))];
+
+            chars[6] = s_base32Char[(
+                    ((b1 & 0xE0) >> 5) |
+                    ((b4 & 0x60) >> 2))];
+
+            // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4
+            b2 >>= 5;
+
+            Debug.Assert(((b2 & 0xF8) == 0), "Unexpected set bits");
+
+            if ((b3 & 0x80) != 0)
+                b2 |= 0x08;
+            if ((b4 & 0x80) != 0)
+                b2 |= 0x10;
+
+            chars[7] = s_base32Char[b2];
+
+            // Set the file extension separator
+            chars[8] = '.';
+
+            // Consume the 5 Least significant bits of the remaining 3 bytes
+            chars[9] = s_base32Char[(bytes[5] & 0x1F)];
+            chars[10] = s_base32Char[(bytes[6] & 0x1F)];
+            chars[11] = s_base32Char[(bytes[7] & 0x1F)];
+        }
+
+        /// <summary>
+        /// Create a relative path from one path to another. Paths will be resolved before calculating the difference.
+        /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix).
+        /// </summary>
+        /// <param name="relativeTo">The source path the output should be relative to. This path is always considered to be a directory.</param>
+        /// <param name="path">The destination path.</param>
+        /// <returns>The relative path or <paramref name="path"/> if the paths don't share the same root.</returns>
+        /// <exception cref="ArgumentNullException">Thrown if <paramref name="relativeTo"/> or <paramref name="path"/> is <c>null</c> or an empty string.</exception>
+        public static string GetRelativePath(string relativeTo, string path)
+        {
+            return GetRelativePath(relativeTo, path, StringComparison);
+        }
+
+        private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType)
+        {
+            if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo));
+            if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path));
+            Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase);
+
+            relativeTo = GetFullPath(relativeTo);
+            path = GetFullPath(path);
+
+            // Need to check if the roots are different- if they are we need to return the "to" path.
+            if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType))
+                return path;
+
+            int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase);
+
+            // If there is nothing in common they can't share the same root, return the "to" path as is.
+            if (commonLength == 0)
+                return path;
+
+            // Trailing separators aren't significant for comparison
+            int relativeToLength = relativeTo.Length;
+            if (PathInternal.EndsInDirectorySeparator(relativeTo))
+                relativeToLength--;
+
+            bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path);
+            int pathLength = path.Length;
+            if (pathEndsInSeparator)
+                pathLength--;
+
+            // If we have effectively the same path, return "."
+            if (relativeToLength == pathLength && commonLength >= relativeToLength) return ".";
+
+            // We have the same root, we need to calculate the difference now using the
+            // common Length and Segment count past the length.
+            //
+            // Some examples:
+            //
+            //  C:\Foo C:\Bar L3, S1 -> ..\Bar
+            //  C:\Foo C:\Foo\Bar L6, S0 -> Bar
+            //  C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar
+            //  C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar
+
+            StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length));
+
+            // Add parent segments for segments past the common on the "from" path
+            if (commonLength < relativeToLength)
+            {
+                sb.Append(PathInternal.ParentDirectoryPrefix);
+
+                for (int i = commonLength; i < relativeToLength; i++)
+                {
+                    if (PathInternal.IsDirectorySeparator(relativeTo[i]))
+                    {
+                        sb.Append(PathInternal.ParentDirectoryPrefix);
+                    }
+                }
+            }
+            else if (PathInternal.IsDirectorySeparator(path[commonLength]))
+            {
+                // No parent segments and we need to eat the initial separator
+                //  (C:\Foo C:\Foo\Bar case)
+                commonLength++;
+            }
+
+            // Now add the rest of the "to" path, adding back the trailing separator
+            int count = pathLength - commonLength;
+            if (pathEndsInSeparator)
+                count++;
+
+            sb.Append(path, commonLength, count);
+            return StringBuilderCache.GetStringAndRelease(sb);
+        }
+
+        // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are
+        // too low in System.Runtime.Extensions to use it (no FileStream, etc.)
+
+        /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
+        internal static StringComparison StringComparison
+        {
+            get
+            {
+                return IsCaseSensitive ?
+                    StringComparison.Ordinal :
+                    StringComparison.OrdinalIgnoreCase;
+            }
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/PathHelper.Windows.cs b/src/mscorlib/shared/System/IO/PathHelper.Windows.cs
new file mode 100644 (file)
index 0000000..e2ead93
--- /dev/null
@@ -0,0 +1,398 @@
+// 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.Runtime.InteropServices;
+
+namespace System.IO
+{
+    /// <summary>
+    /// Wrapper to help with path normalization.
+    /// </summary>
+    internal class PathHelper
+    {
+        // Can't be over 8.3 and be a short name
+        private const int MaxShortName = 12;
+
+        private const char LastAnsi = (char)255;
+        private const char Delete = (char)127;
+
+        /// <summary>
+        /// Normalize the given path.
+        /// </summary>
+        /// <remarks>
+        /// Normalizes via Win32 GetFullPathName(). It will also trim all "typical" whitespace at the end of the path (see s_trimEndChars). Will also trim initial
+        /// spaces if the path is determined to be rooted.
+        /// 
+        /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt)
+        /// </remarks>
+        /// <param name="path">Path to normalize</param>
+        /// <param name="checkInvalidCharacters">True to check for invalid characters</param>
+        /// <param name="expandShortPaths">Attempt to expand short paths if true</param>
+        /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception>
+        /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception>
+        /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+        /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+        /// <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>
+        internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths)
+        {
+            // Get the full path
+            StringBuffer fullPath = new StringBuffer(PathInternal.MaxShortPath);
+
+            try
+            {
+                GetFullPathName(path, ref fullPath);
+
+                // Trim whitespace off the end of the string. Win32 normalization trims only U+0020.
+                fullPath.TrimEnd(PathInternal.s_trimEndChars);
+
+                if (fullPath.Length >= PathInternal.MaxLongPath)
+                {
+                    // Fullpath is genuinely too long
+                    throw new PathTooLongException(SR.IO_PathTooLong);
+                }
+
+                // Checking path validity used to happen before getting the full path name. To avoid additional input allocation
+                // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that
+                // used to get kicked back (notably segments with invalid characters might get removed via "..").
+                //
+                // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to
+                // expand short file names.
+
+                // Scan the path for:
+                //
+                //  - Illegal path characters.
+                //  - Invalid UNC paths like \\, \\server, \\server\.
+                //  - Segments that are too long (over MaxComponentLength)
+
+                // 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;
+                bool foundTilde = false;
+
+                // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't
+                // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device
+                // path that contains UNC or  to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\,
+                // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc.
+                bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\';
+                bool isDevice = PathInternal.IsDevice(ref fullPath);
+                bool possibleBadUnc = specialPath && !isDevice;
+                int index = specialPath ? 2 : 0;
+                int lastSeparator = specialPath ? 1 : 0;
+                int segmentLength;
+                char current;
+
+                while (index < fullPath.Length)
+                {
+                    current = fullPath[index];
+
+                    // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~'
+                    if (current < '?' || current == '\\' || current == '|' || current == '~')
+                    {
+                        switch (current)
+                        {
+                            case '|':
+                            case '>':
+                            case '<':
+                            case '\"':
+                                if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars);
+                                foundTilde = false;
+                                break;
+                            case '~':
+                                foundTilde = true;
+                                break;
+                            case '\\':
+                                segmentLength = index - lastSeparator - 1;
+                                if (segmentLength > PathInternal.MaxComponentLength)
+                                    throw new PathTooLongException(SR.IO_PathTooLong + fullPath.ToString());
+                                lastSeparator = index;
+
+                                if (foundTilde)
+                                {
+                                    if (segmentLength <= MaxShortName)
+                                    {
+                                        // Possibly a short path.
+                                        possibleShortPath = true;
+                                    }
+
+                                    foundTilde = false;
+                                }
+
+                                if (possibleBadUnc)
+                                {
+                                    // 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(SR.Arg_PathIllegalUNC);
+                                    else
+                                        possibleBadUnc = false;
+                                }
+
+                                break;
+
+                            default:
+                                if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
+                                break;
+                        }
+                    }
+
+                    index++;
+                }
+
+                if (possibleBadUnc)
+                    throw new ArgumentException(SR.Arg_PathIllegalUNC);
+
+                segmentLength = fullPath.Length - lastSeparator - 1;
+                if (segmentLength > PathInternal.MaxComponentLength)
+                    throw new PathTooLongException(SR.IO_PathTooLong);
+
+                if (foundTilde && segmentLength <= MaxShortName)
+                    possibleShortPath = true;
+
+                // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but
+                // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide.
+                if (expandShortPaths && possibleShortPath)
+                {
+                    return TryExpandShortFileName(ref fullPath, originalPath: path);
+                }
+                else
+                {
+                    if (fullPath.Length == path.Length && fullPath.StartsWith(path))
+                    {
+                        // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder.
+                        return path;
+                    }
+                    else
+                    {
+                        return fullPath.ToString();
+                    }
+                }
+            }
+            finally
+            {
+                // Clear the buffer
+                fullPath.Free();
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static bool IsDosUnc(ref StringBuffer buffer)
+        {
+            return !PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\';
+        }
+
+        private static unsafe void GetFullPathName(string path, ref 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.
+            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);
+
+            fixed (char* pathStart = path)
+            {
+                uint result = 0;
+                while ((result = Interop.Kernel32.GetFullPathNameW(pathStart + startIndex, (uint)fullPath.Capacity, fullPath.UnderlyingArray, IntPtr.Zero)) > fullPath.Capacity)
+                {
+                    // Reported size is greater than the buffer size. Increase the capacity.
+                    fullPath.EnsureCapacity(checked((int)result));
+                }
+
+                if (result == 0)
+                {
+                    // Failure, get the error and throw
+                    int errorCode = Marshal.GetLastWin32Error();
+                    if (errorCode == 0)
+                        errorCode = Interop.Errors.ERROR_BAD_PATHNAME;
+                    throw Win32Marshal.GetExceptionForWin32Error(errorCode, path);
+                }
+
+                fullPath.Length = checked((int)result);
+            }
+        }
+
+        private static int GetInputBuffer(ref StringBuffer content, bool isDosUnc, ref StringBuffer buffer)
+        {
+            int length = content.Length;
+
+            length += isDosUnc
+                ? PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength
+                : PathInternal.DevicePrefixLength;
+
+            buffer.EnsureCapacity(length + 1);
+
+            if (isDosUnc)
+            {
+                // Put the extended UNC prefix (\\?\UNC\) in front of the path
+                buffer.CopyFrom(bufferIndex: 0, source: PathInternal.UncExtendedPathPrefix);
+
+                // Copy the source buffer over after the existing UNC prefix
+                content.CopyTo(
+                    bufferIndex: PathInternal.UncPrefixLength,
+                    destination: ref buffer,
+                    destinationIndex: PathInternal.UncExtendedPrefixLength,
+                    count: content.Length - PathInternal.UncPrefixLength);
+
+                // Return the prefix difference
+                return PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength;
+            }
+            else
+            {
+                int prefixSize = PathInternal.ExtendedPathPrefix.Length;
+                buffer.CopyFrom(bufferIndex: 0, source: PathInternal.ExtendedPathPrefix);
+                content.CopyTo(bufferIndex: 0, destination: ref buffer, destinationIndex: prefixSize, count: content.Length);
+                return prefixSize;
+            }
+        }
+
+        private static string TryExpandShortFileName(ref 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(ref 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:\)
+            //  2. Dos UNC (\\Server\Share)
+            //  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).
+
+            int rootLength = PathInternal.GetRootLength(ref outputBuffer);
+            bool isDevice = PathInternal.IsDevice(ref outputBuffer);
+
+            StringBuffer inputBuffer = new StringBuffer(0);
+            try
+            {
+                bool isDosUnc = false;
+                int rootDifference = 0;
+                bool wasDotDevice = false;
+
+                // Add the extended prefix before expanding to allow growth over MAX_PATH
+                if (isDevice)
+                {
+                    // We have one of the following (\\?\ or \\.\)
+                    inputBuffer.Append(ref outputBuffer);
+
+                    if (outputBuffer[2] == '.')
+                    {
+                        wasDotDevice = true;
+                        inputBuffer[2] = '?';
+                    }
+                }
+                else
+                {
+                    isDosUnc = IsDosUnc(ref outputBuffer);
+                    rootDifference = GetInputBuffer(ref outputBuffer, isDosUnc, ref inputBuffer);
+                }
+
+                rootLength += rootDifference;
+                int inputLength = inputBuffer.Length;
+
+                bool success = false;
+                int foundIndex = inputBuffer.Length - 1;
+
+                while (!success)
+                {
+                    uint result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);
+
+                    // 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 != Interop.Errors.ERROR_FILE_NOT_FOUND && error != Interop.Errors.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.Capacity)
+                    {
+                        // Not enough space. The result count for this API does not include the null terminator.
+                        outputBuffer.EnsureCapacity(checked((int)result));
+                        result = Interop.Kernel32.GetLongPathNameW(inputBuffer.UnderlyingArray, outputBuffer.UnderlyingArray, (uint)outputBuffer.Capacity);
+                    }
+                    else
+                    {
+                        // Found the path
+                        success = true;
+                        outputBuffer.Length = checked((int)result);
+                        if (foundIndex < inputLength - 1)
+                        {
+                            // It was a partial find, put the non-existent part of the path back
+                            outputBuffer.Append(ref inputBuffer, foundIndex, inputBuffer.Length - foundIndex);
+                        }
+                    }
+                }
+
+                // Strip out the prefix and return the string
+                ref StringBuffer bufferToUse = ref Choose(success, ref outputBuffer, ref inputBuffer);
+
+                // Switch back from \\?\ to \\.\ if necessary
+                if (wasDotDevice)
+                    bufferToUse[2] = '.';
+
+                string returnValue = null;
+
+                int newLength = (int)(bufferToUse.Length - rootDifference);
+                if (isDosUnc)
+                {
+                    // Need to go from \\?\UNC\ to \\?\UN\\
+                    bufferToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\';
+                }
+
+                // We now need to strip out any added characters at the front of the string
+                if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength))
+                {
+                    // Use the original path to avoid allocating
+                    returnValue = originalPath;
+                }
+                else
+                {
+                    returnValue = bufferToUse.Substring(rootDifference, newLength);
+                }
+
+                return returnValue;
+            }
+            finally
+            {
+                inputBuffer.Free();
+            }
+        }
+
+        // Helper method to workaround lack of operator ? support for ref values
+        private static ref StringBuffer Choose(bool condition, ref StringBuffer s1, ref StringBuffer s2)
+        {
+            if (condition) return ref s1;
+            else return ref s2;
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/PathInternal.Unix.cs b/src/mscorlib/shared/System/IO/PathInternal.Unix.cs
new file mode 100644 (file)
index 0000000..08dc1d0
--- /dev/null
@@ -0,0 +1,104 @@
+// 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
+    {
+        internal const char DirectorySeparatorChar = '/';
+        internal const char AltDirectorySeparatorChar = '/';
+        internal const char VolumeSeparatorChar = '/';
+        internal const char PathSeparator = ':';
+
+        internal const string DirectorySeparatorCharAsString = "/";
+
+        // There is only one invalid path character in Unix
+        private const char InvalidPathChar = '\0';
+
+        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(DirectorySeparatorChar == AltDirectorySeparatorChar);
+            return c == DirectorySeparatorChar;
+        }
+
+        /// <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(DirectorySeparatorChar == AltDirectorySeparatorChar);
+            Debug.Assert(DirectorySeparatorChar == VolumeSeparatorChar);
+            return ch == DirectorySeparatorChar;
+        }
+
+        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/shared/System/IO/PathInternal.Windows.StringBuffer.cs b/src/mscorlib/shared/System/IO/PathInternal.Windows.StringBuffer.cs
new file mode 100644 (file)
index 0000000..84953df
--- /dev/null
@@ -0,0 +1,93 @@
+// 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(ref 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 int GetRootLength(ref StringBuffer path)
+        {
+            if (path.Length == 0) return 0;
+
+            fixed (char* value = path.UnderlyingArray)
+            {
+                return GetRootLength(value, path.Length);
+            }
+        }
+
+        /// <summary>
+        /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
+        /// </summary>
+        internal static bool IsDevice(ref 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(ref 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(ref 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] == VolumeSeparatorChar)
+                && IsDirectorySeparator(path[2]));
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/PathInternal.Windows.cs b/src/mscorlib/shared/System/IO/PathInternal.Windows.cs
new file mode 100644 (file)
index 0000000..ee0dd54
--- /dev/null
@@ -0,0 +1,442 @@
+// 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 char DirectorySeparatorChar = '\\';
+        internal const char AltDirectorySeparatorChar = '/';
+        internal const char VolumeSeparatorChar = ':';
+        internal const char PathSeparator = ';';
+
+        internal const string DirectorySeparatorCharAsString = "\\";
+
+        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;
+
+        /// <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>
+        /// 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 <= '|') // fast path for common case - '|' is highest illegal character
+                {
+                    if (c <= '\u001f' || c == '|')
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Check for known wildcard characters. '*' and '?' are the most common ones.
+        /// </summary>
+        internal 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;
+
+            // [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
+            for (int i = startIndex; i < path.Length; i++)
+            {
+                char c = path[i];
+                if (c <= '?') // fast path for common case - '?' is highest wildcard character
+                {
+                    if (c == '\"' || c == '<' || c == '>' || c == '*' || c == '?')
+                        return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <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 GetRootLength(value, path.Length);
+            }
+        }
+
+        private unsafe static int GetRootLength(char* path, int pathLength)
+        {
+            int i = 0;
+            int volumeSeparatorLength = 2;  // Length to the colon "C:"
+            int 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 = UncExtendedPathPrefix.Length;
+                }
+                else
+                {
+                    // "C:" -> "\\?\C:"
+                    volumeSeparatorLength += 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] == 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, int sourceLength, string value)
+        {
+            if (sourceLength < 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] == 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 == DirectorySeparatorChar || c == 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 != 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(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 = 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) || VolumeSeparatorChar == ch;
+        }
+    }
+}
diff --git a/src/mscorlib/shared/System/IO/PathInternal.cs b/src/mscorlib/shared/System/IO/PathInternal.cs
new file mode 100644 (file)
index 0000000..0dab5b9
--- /dev/null
@@ -0,0 +1,171 @@
+// 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 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/shared/System/IO/Win32Marshal.cs b/src/mscorlib/shared/System/IO/Win32Marshal.cs
new file mode 100644 (file)
index 0000000..ef76c27
--- /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.
+
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+
+namespace System.IO
+{
+    /// <summary>
+    ///     Provides static methods for converting from Win32 errors codes to exceptions, HRESULTS and error messages.
+    /// </summary>
+    internal static class Win32Marshal
+    {
+        /// <summary>
+        ///     Converts, resetting it, the last Win32 error into a corresponding <see cref="Exception"/> object.
+        /// </summary>
+        internal static Exception GetExceptionForLastWin32Error()
+        {
+            int errorCode = Marshal.GetLastWin32Error();
+            return GetExceptionForWin32Error(errorCode, string.Empty);
+        }
+
+        /// <summary>
+        ///     Converts the specified Win32 error into a corresponding <see cref="Exception"/> object.
+        /// </summary>
+        internal static Exception GetExceptionForWin32Error(int errorCode)
+        {
+            return GetExceptionForWin32Error(errorCode, string.Empty);
+        }
+
+        /// <summary>
+        ///     Converts the specified Win32 error into a corresponding <see cref="Exception"/> object, optionally 
+        ///     including the specified path in the error message.
+        /// </summary>
+        internal static Exception GetExceptionForWin32Error(int errorCode, string path)
+        {
+            switch (errorCode)
+            {
+                case Interop.Errors.ERROR_FILE_NOT_FOUND:
+                    if (path.Length == 0)
+                        return new FileNotFoundException(SR.IO_FileNotFound);
+                    else
+                        return new FileNotFoundException(SR.Format(SR.IO_FileNotFound_FileName, path), path);
+
+                case Interop.Errors.ERROR_PATH_NOT_FOUND:
+                    if (path.Length == 0)
+                        return new DirectoryNotFoundException(SR.IO_PathNotFound_NoPathName);
+                    else
+                        return new DirectoryNotFoundException(SR.Format(SR.IO_PathNotFound_Path, path));
+
+                case Interop.Errors.ERROR_ACCESS_DENIED:
+                    if (path.Length == 0)
+                        return new UnauthorizedAccessException(SR.UnauthorizedAccess_IODenied_NoPathName);
+                    else
+                        return new UnauthorizedAccessException(SR.Format(SR.UnauthorizedAccess_IODenied_Path, path));
+
+                case Interop.Errors.ERROR_ALREADY_EXISTS:
+                    if (path.Length == 0)
+                        goto default;
+
+                    return new IOException(SR.Format(SR.IO_AlreadyExists_Name, path), MakeHRFromErrorCode(errorCode));
+
+                case Interop.Errors.ERROR_FILENAME_EXCED_RANGE:
+                    return new PathTooLongException(SR.IO_PathTooLong);
+
+                case Interop.Errors.ERROR_INVALID_PARAMETER:
+                    return new IOException(GetMessage(errorCode), MakeHRFromErrorCode(errorCode));
+
+                case Interop.Errors.ERROR_SHARING_VIOLATION:
+                    if (path.Length == 0)
+                        return new IOException(SR.IO_SharingViolation_NoFileName, MakeHRFromErrorCode(errorCode));
+                    else
+                        return new IOException(SR.Format(SR.IO_SharingViolation_File, path), MakeHRFromErrorCode(errorCode));
+
+                case Interop.Errors.ERROR_FILE_EXISTS:
+                    if (path.Length == 0)
+                        goto default;
+
+                    return new IOException(SR.Format(SR.IO_FileExists_Name, path), MakeHRFromErrorCode(errorCode));
+
+                case Interop.Errors.ERROR_OPERATION_ABORTED:
+                    return new OperationCanceledException();
+
+                default:
+                    return new IOException(GetMessage(errorCode), MakeHRFromErrorCode(errorCode));
+            }
+        }
+
+        /// <summary>
+        ///     Returns a HRESULT for the specified Win32 error code.
+        /// </summary>
+        internal static int MakeHRFromErrorCode(int errorCode)
+        {
+            Debug.Assert((0xFFFF0000 & errorCode) == 0, "This is an HRESULT, not an error code!");
+
+            return unchecked(((int)0x80070000) | errorCode);
+        }
+
+        /// <summary>
+        ///     Returns a string message for the specified Win32 error code.
+        /// </summary>
+        internal static string GetMessage(int errorCode)
+        {
+            return Interop.Kernel32.GetMessage(errorCode);
+        }
+    }
+}