diff options
author | Jiyoung Yun <jy910.yun@samsung.com> | 2017-04-13 14:17:19 +0900 |
---|---|---|
committer | Jiyoung Yun <jy910.yun@samsung.com> | 2017-04-13 14:17:19 +0900 |
commit | a56e30c8d33048216567753d9d3fefc2152af8ac (patch) | |
tree | 7e5d979695fc4a431740982eb1cfecc2898b23a5 /src/mscorlib/corefx/System/IO | |
parent | 4b11dc566a5bbfa1378d6266525c281b028abcc8 (diff) | |
download | coreclr-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.cs | 44 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/FileStream.Unix.cs | 934 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/FileStream.Win32.cs | 1770 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/FileStream.cs | 684 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/FileStreamCompletionSource.Win32.cs | 221 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/Path.Unix.cs | 216 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/Path.Win32.cs | 36 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/Path.Windows.cs | 155 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/Path.cs | 575 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/PathHelper.Windows.cs | 398 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/PathInternal.Unix.cs | 104 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/PathInternal.Windows.StringBuffer.cs | 93 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/PathInternal.Windows.cs | 442 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/PathInternal.cs | 172 | ||||
-rw-r--r-- | src/mscorlib/corefx/System/IO/Win32Marshal.cs | 109 |
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 (", <, >, | - /// 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); - } - } -} |