summaryrefslogtreecommitdiff
path: root/src/mscorlib/corefx/System/IO
diff options
context:
space:
mode:
authorJiyoung Yun <jy910.yun@samsung.com>2017-04-13 14:17:19 +0900
committerJiyoung Yun <jy910.yun@samsung.com>2017-04-13 14:17:19 +0900
commita56e30c8d33048216567753d9d3fefc2152af8ac (patch)
tree7e5d979695fc4a431740982eb1cfecc2898b23a5 /src/mscorlib/corefx/System/IO
parent4b11dc566a5bbfa1378d6266525c281b028abcc8 (diff)
downloadcoreclr-a56e30c8d33048216567753d9d3fefc2152af8ac.tar.gz
coreclr-a56e30c8d33048216567753d9d3fefc2152af8ac.tar.bz2
coreclr-a56e30c8d33048216567753d9d3fefc2152af8ac.zip
Imported Upstream version 2.0.0.11353upstream/2.0.0.11353
Diffstat (limited to 'src/mscorlib/corefx/System/IO')
-rw-r--r--src/mscorlib/corefx/System/IO/Error.cs44
-rw-r--r--src/mscorlib/corefx/System/IO/FileStream.Unix.cs934
-rw-r--r--src/mscorlib/corefx/System/IO/FileStream.Win32.cs1770
-rw-r--r--src/mscorlib/corefx/System/IO/FileStream.cs684
-rw-r--r--src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs221
-rw-r--r--src/mscorlib/corefx/System/IO/Path.Unix.cs216
-rw-r--r--src/mscorlib/corefx/System/IO/Path.Win32.cs36
-rw-r--r--src/mscorlib/corefx/System/IO/Path.Windows.cs155
-rw-r--r--src/mscorlib/corefx/System/IO/Path.cs575
-rw-r--r--src/mscorlib/corefx/System/IO/PathHelper.Windows.cs398
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.Unix.cs104
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs93
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.Windows.cs442
-rw-r--r--src/mscorlib/corefx/System/IO/PathInternal.cs172
-rw-r--r--src/mscorlib/corefx/System/IO/Win32Marshal.cs109
15 files changed, 0 insertions, 5953 deletions
diff --git a/src/mscorlib/corefx/System/IO/Error.cs b/src/mscorlib/corefx/System/IO/Error.cs
deleted file mode 100644
index c8434ffad8..0000000000
--- a/src/mscorlib/corefx/System/IO/Error.cs
+++ /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.Unix.cs b/src/mscorlib/corefx/System/IO/FileStream.Unix.cs
deleted file mode 100644
index f83fc84259..0000000000
--- a/src/mscorlib/corefx/System/IO/FileStream.Unix.cs
+++ /dev/null
@@ -1,934 +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>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));
- }
-
- /// <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
index 683eef5e43..0000000000
--- a/src/mscorlib/corefx/System/IO/FileStream.Win32.cs
+++ /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
index 7db8518435..0000000000
--- a/src/mscorlib/corefx/System/IO/FileStream.cs
+++ /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
index 159e416e63..0000000000
--- a/src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs
+++ /dev/null
@@ -1,221 +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
index c566fa0066..0000000000
--- a/src/mscorlib/corefx/System/IO/Path.Unix.cs
+++ /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
index 8a9e62e6e5..0000000000
--- a/src/mscorlib/corefx/System/IO/Path.Win32.cs
+++ /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
index ce867efd2c..0000000000
--- a/src/mscorlib/corefx/System/IO/Path.Windows.cs
+++ /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
index 77b213968b..0000000000
--- a/src/mscorlib/corefx/System/IO/Path.cs
+++ /dev/null
@@ -1,575 +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.
- 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
index e2ead93185..0000000000
--- a/src/mscorlib/corefx/System/IO/PathHelper.Windows.cs
+++ /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
index 08dc1d0251..0000000000
--- a/src/mscorlib/corefx/System/IO/PathInternal.Unix.cs
+++ /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
index 84953df37b..0000000000
--- a/src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs
+++ /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
index 0ec9b30f99..0000000000
--- a/src/mscorlib/corefx/System/IO/PathInternal.Windows.cs
+++ /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
index 6b4c3b2d30..0000000000
--- a/src/mscorlib/corefx/System/IO/PathInternal.cs
+++ /dev/null
@@ -1,172 +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
index ef76c27010..0000000000
--- a/src/mscorlib/corefx/System/IO/Win32Marshal.cs
+++ /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);
- }
- }
-}