diff options
Diffstat (limited to 'netcore/System.Private.CoreLib/shared/System/IO')
46 files changed, 0 insertions, 16302 deletions
diff --git a/netcore/System.Private.CoreLib/shared/System/IO/BinaryReader.cs b/netcore/System.Private.CoreLib/shared/System/IO/BinaryReader.cs deleted file mode 100644 index cf8e4954593..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/BinaryReader.cs +++ /dev/null @@ -1,611 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** -** -** Purpose: Wraps a stream and provides convenient read functionality -** for strings and primitive types. -** -** -============================================================*/ - -using System.Buffers.Binary; -using System.Diagnostics; -using System.Runtime.CompilerServices; -using System.Text; - -namespace System.IO -{ - public class BinaryReader : IDisposable - { - private const int MaxCharBytesSize = 128; - - private readonly Stream _stream; - private readonly byte[] _buffer; - private readonly Decoder _decoder; - private byte[]? _charBytes; - private char[]? _charBuffer; - private readonly int _maxCharsSize; // From MaxCharBytesSize & Encoding - - // Performance optimization for Read() w/ Unicode. Speeds us up by ~40% - private readonly bool _2BytesPerChar; - private readonly bool _isMemoryStream; // "do we sit on MemoryStream?" for Read/ReadInt32 perf - private readonly bool _leaveOpen; - private bool _disposed; - - public BinaryReader(Stream input) : this(input, Encoding.UTF8, false) - { - } - - public BinaryReader(Stream input, Encoding encoding) : this(input, encoding, false) - { - } - - public BinaryReader(Stream input, Encoding encoding, bool leaveOpen) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - if (encoding == null) - { - throw new ArgumentNullException(nameof(encoding)); - } - if (!input.CanRead) - { - throw new ArgumentException(SR.Argument_StreamNotReadable); - } - - _stream = input; - _decoder = encoding.GetDecoder(); - _maxCharsSize = encoding.GetMaxCharCount(MaxCharBytesSize); - int minBufferSize = encoding.GetMaxByteCount(1); // max bytes per one char - if (minBufferSize < 16) - { - minBufferSize = 16; - } - - _buffer = new byte[minBufferSize]; - // _charBuffer and _charBytes will be left null. - - // For Encodings that always use 2 bytes per char (or more), - // special case them here to make Read() & Peek() faster. - _2BytesPerChar = encoding is UnicodeEncoding; - // check if BinaryReader is based on MemoryStream, and keep this for it's life - // we cannot use "as" operator, since derived classes are not allowed - _isMemoryStream = (_stream.GetType() == typeof(MemoryStream)); - _leaveOpen = leaveOpen; - - Debug.Assert(_decoder != null, "[BinaryReader.ctor]_decoder!=null"); - } - - public virtual Stream BaseStream => _stream; - - protected virtual void Dispose(bool disposing) - { - if (!_disposed) - { - if (disposing && !_leaveOpen) - { - _stream.Close(); - } - _disposed = true; - } - } - - public void Dispose() - { - Dispose(true); - } - - /// <remarks> - /// Override Dispose(bool) instead of Close(). This API exists for compatibility purposes. - /// </remarks> - public virtual void Close() - { - Dispose(true); - } - - private void ThrowIfDisposed() - { - if (_disposed) - { - throw Error.GetFileNotOpen(); - } - } - - public virtual int PeekChar() - { - ThrowIfDisposed(); - - if (!_stream.CanSeek) - { - return -1; - } - - long origPos = _stream.Position; - int ch = Read(); - _stream.Position = origPos; - return ch; - } - - public virtual int Read() - { - ThrowIfDisposed(); - - int charsRead = 0; - int numBytes; - long posSav = 0; - - if (_stream.CanSeek) - { - posSav = _stream.Position; - } - - _charBytes ??= new byte[MaxCharBytesSize]; - - Span<char> singleChar = stackalloc char[1]; - - while (charsRead == 0) - { - // We really want to know what the minimum number of bytes per char - // is for our encoding. Otherwise for UnicodeEncoding we'd have to - // do ~1+log(n) reads to read n characters. - // Assume 1 byte can be 1 char unless _2BytesPerChar is true. - numBytes = _2BytesPerChar ? 2 : 1; - - int r = _stream.ReadByte(); - _charBytes[0] = (byte)r; - if (r == -1) - { - numBytes = 0; - } - if (numBytes == 2) - { - r = _stream.ReadByte(); - _charBytes[1] = (byte)r; - if (r == -1) - { - numBytes = 1; - } - } - - if (numBytes == 0) - { - return -1; - } - - Debug.Assert(numBytes == 1 || numBytes == 2, "BinaryReader::ReadOneChar assumes it's reading one or 2 bytes only."); - - try - { - charsRead = _decoder.GetChars(new ReadOnlySpan<byte>(_charBytes, 0, numBytes), singleChar, flush: false); - } - catch - { - // Handle surrogate char - - if (_stream.CanSeek) - { - _stream.Seek(posSav - _stream.Position, SeekOrigin.Current); - } - // else - we can't do much here - - throw; - } - - Debug.Assert(charsRead < 2, "BinaryReader::ReadOneChar - assuming we only got 0 or 1 char, not 2!"); - } - Debug.Assert(charsRead > 0); - return singleChar[0]; - } - - public virtual byte ReadByte() => InternalReadByte(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] // Inlined to avoid some method call overhead with InternalRead. - private byte InternalReadByte() - { - ThrowIfDisposed(); - - int b = _stream.ReadByte(); - if (b == -1) - { - throw Error.GetEndOfFile(); - } - - return (byte)b; - } - - [CLSCompliant(false)] - public virtual sbyte ReadSByte() => (sbyte)InternalReadByte(); - public virtual bool ReadBoolean() => InternalReadByte() != 0; - - public virtual char ReadChar() - { - int value = Read(); - if (value == -1) - { - throw Error.GetEndOfFile(); - } - return (char)value; - } - - public virtual short ReadInt16() => BinaryPrimitives.ReadInt16LittleEndian(InternalRead(2)); - - [CLSCompliant(false)] - public virtual ushort ReadUInt16() => BinaryPrimitives.ReadUInt16LittleEndian(InternalRead(2)); - - public virtual int ReadInt32() => BinaryPrimitives.ReadInt32LittleEndian(InternalRead(4)); - [CLSCompliant(false)] - public virtual uint ReadUInt32() => BinaryPrimitives.ReadUInt32LittleEndian(InternalRead(4)); - public virtual long ReadInt64() => BinaryPrimitives.ReadInt64LittleEndian(InternalRead(8)); - [CLSCompliant(false)] - public virtual ulong ReadUInt64() => BinaryPrimitives.ReadUInt64LittleEndian(InternalRead(8)); - public virtual unsafe float ReadSingle() => BitConverter.Int32BitsToSingle(BinaryPrimitives.ReadInt32LittleEndian(InternalRead(4))); - public virtual unsafe double ReadDouble() => BitConverter.Int64BitsToDouble(BinaryPrimitives.ReadInt64LittleEndian(InternalRead(8))); - - public virtual decimal ReadDecimal() - { - ReadOnlySpan<byte> span = InternalRead(16); - try - { - return decimal.ToDecimal(span); - } - catch (ArgumentException e) - { - // ReadDecimal cannot leak out ArgumentException - throw new IOException(SR.Arg_DecBitCtor, e); - } - } - - public virtual string ReadString() - { - ThrowIfDisposed(); - - int currPos = 0; - int n; - int stringLength; - int readLength; - int charsRead; - - // Length of the string in bytes, not chars - stringLength = Read7BitEncodedInt(); - if (stringLength < 0) - { - throw new IOException(SR.Format(SR.IO_InvalidStringLen_Len, stringLength)); - } - - if (stringLength == 0) - { - return string.Empty; - } - - _charBytes ??= new byte[MaxCharBytesSize]; - _charBuffer ??= new char[_maxCharsSize]; - - StringBuilder? sb = null; - do - { - readLength = ((stringLength - currPos) > MaxCharBytesSize) ? MaxCharBytesSize : (stringLength - currPos); - - n = _stream.Read(_charBytes, 0, readLength); - if (n == 0) - { - throw Error.GetEndOfFile(); - } - - charsRead = _decoder.GetChars(_charBytes, 0, n, _charBuffer, 0); - - if (currPos == 0 && n == stringLength) - { - return new string(_charBuffer, 0, charsRead); - } - - sb ??= StringBuilderCache.Acquire(stringLength); // Actual string length in chars may be smaller. - sb.Append(_charBuffer, 0, charsRead); - currPos += n; - } while (currPos < stringLength); - - return StringBuilderCache.GetStringAndRelease(sb); - } - - public virtual int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - ThrowIfDisposed(); - - return InternalReadChars(new Span<char>(buffer, index, count)); - } - - public virtual int Read(Span<char> buffer) - { - ThrowIfDisposed(); - return InternalReadChars(buffer); - } - - private int InternalReadChars(Span<char> buffer) - { - Debug.Assert(!_disposed); - - int totalCharsRead = 0; - - while (!buffer.IsEmpty) - { - int numBytes = buffer.Length; - - // We really want to know what the minimum number of bytes per char - // is for our encoding. Otherwise for UnicodeEncoding we'd have to - // do ~1+log(n) reads to read n characters. - if (_2BytesPerChar) - { - numBytes <<= 1; - } - - // We do not want to read even a single byte more than necessary. - // - // Subtract pending bytes that the decoder may be holding onto. This assumes that each - // decoded char corresponds to one or more bytes. Note that custom encodings or encodings with - // a custom replacement sequence may violate this assumption. - if (numBytes > 1) - { - DecoderNLS? decoder = _decoder as DecoderNLS; - // For internal decoders, we can check whether the decoder has any pending state. - // For custom decoders, assume that the decoder has pending state. - if (decoder == null || decoder.HasState) - { - numBytes--; - - // The worst case is charsRemaining = 2 and UTF32Decoder holding onto 3 pending bytes. We need to read just - // one byte in this case. - if (_2BytesPerChar && numBytes > 2) - numBytes -= 2; - } - } - - ReadOnlySpan<byte> byteBuffer; - if (_isMemoryStream) - { - Debug.Assert(_stream is MemoryStream); - MemoryStream mStream = (MemoryStream)_stream; - - int position = mStream.InternalGetPosition(); - numBytes = mStream.InternalEmulateRead(numBytes); - byteBuffer = new ReadOnlySpan<byte>(mStream.InternalGetBuffer(), position, numBytes); - } - else - { - _charBytes ??= new byte[MaxCharBytesSize]; - - if (numBytes > MaxCharBytesSize) - { - numBytes = MaxCharBytesSize; - } - - numBytes = _stream.Read(_charBytes, 0, numBytes); - byteBuffer = new ReadOnlySpan<byte>(_charBytes, 0, numBytes); - } - - if (byteBuffer.IsEmpty) - { - break; - } - - int charsRead = _decoder.GetChars(byteBuffer, buffer, flush: false); - buffer = buffer.Slice(charsRead); - - totalCharsRead += charsRead; - } - - // we may have read fewer than the number of characters requested if end of stream reached - // or if the encoding makes the char count too big for the buffer (e.g. fallback sequence) - return totalCharsRead; - } - - public virtual char[] ReadChars(int count) - { - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - ThrowIfDisposed(); - - if (count == 0) - { - return Array.Empty<char>(); - } - - char[] chars = new char[count]; - int n = InternalReadChars(new Span<char>(chars)); - if (n != count) - { - char[] copy = new char[n]; - Buffer.BlockCopy(chars, 0, copy, 0, 2 * n); // sizeof(char) - chars = copy; - } - - return chars; - } - - public virtual int Read(byte[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - ThrowIfDisposed(); - - return _stream.Read(buffer, index, count); - } - - public virtual int Read(Span<byte> buffer) - { - ThrowIfDisposed(); - return _stream.Read(buffer); - } - - public virtual byte[] ReadBytes(int count) - { - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - ThrowIfDisposed(); - - if (count == 0) - { - return Array.Empty<byte>(); - } - - byte[] result = new byte[count]; - int numRead = 0; - do - { - int n = _stream.Read(result, numRead, count); - if (n == 0) - { - break; - } - - numRead += n; - count -= n; - } while (count > 0); - - if (numRead != result.Length) - { - // Trim array. This should happen on EOF & possibly net streams. - byte[] copy = new byte[numRead]; - Buffer.BlockCopy(result, 0, copy, 0, numRead); - result = copy; - } - - return result; - } - - private ReadOnlySpan<byte> InternalRead(int numBytes) - { - Debug.Assert(numBytes >= 2 && numBytes <= 16, "value of 1 should use ReadByte. value > 16 requires to change the minimal _buffer size"); - - if (_isMemoryStream) - { - // read directly from MemoryStream buffer - Debug.Assert(_stream is MemoryStream); - return ((MemoryStream)_stream).InternalReadSpan(numBytes); - } - else - { - ThrowIfDisposed(); - - int bytesRead = 0; - do - { - int n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead); - if (n == 0) - { - throw Error.GetEndOfFile(); - } - bytesRead += n; - } while (bytesRead < numBytes); - - return _buffer; - } - } - - // FillBuffer is not performing well when reading from MemoryStreams as it is using the public Stream interface. - // We introduced new function InternalRead which can work directly on the MemoryStream internal buffer or using the public Stream - // interface when working with all other streams. This function is not needed anymore but we decided not to delete it for compatibility - // reasons. More about the subject in: https://github.com/dotnet/coreclr/pull/22102 - protected virtual void FillBuffer(int numBytes) - { - if (numBytes < 0 || numBytes > _buffer.Length) - { - throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_BinaryReaderFillBuffer); - } - - int bytesRead = 0; - int n = 0; - - ThrowIfDisposed(); - - // Need to find a good threshold for calling ReadByte() repeatedly - // vs. calling Read(byte[], int, int) for both buffered & unbuffered - // streams. - if (numBytes == 1) - { - n = _stream.ReadByte(); - if (n == -1) - { - throw Error.GetEndOfFile(); - } - - _buffer[0] = (byte)n; - return; - } - - do - { - n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead); - if (n == 0) - { - throw Error.GetEndOfFile(); - } - bytesRead += n; - } while (bytesRead < numBytes); - } - - protected internal int Read7BitEncodedInt() - { - // Read out an Int32 7 bits at a time. The high bit - // of the byte when on means to continue reading more bytes. - int count = 0; - int shift = 0; - byte b; - do - { - // Check for a corrupted stream. Read a max of 5 bytes. - // In a future version, add a DataFormatException. - if (shift == 5 * 7) // 5 bytes max per Int32, shift += 7 - { - throw new FormatException(SR.Format_Bad7BitInt32); - } - - // ReadByte handles end of stream cases for us. - b = ReadByte(); - count |= (b & 0x7F) << shift; - shift += 7; - } while ((b & 0x80) != 0); - return count; - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs b/netcore/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs deleted file mode 100644 index fde451656a5..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/BinaryWriter.cs +++ /dev/null @@ -1,469 +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.Text; -using System.Diagnostics; -using System.Buffers; -using System.Threading.Tasks; - -namespace System.IO -{ - // This abstract base class represents a writer that can write - // primitives to an arbitrary stream. A subclass can override methods to - // give unique encodings. - // - public class BinaryWriter : IDisposable, IAsyncDisposable - { - public static readonly BinaryWriter Null = new BinaryWriter(); - - protected Stream OutStream; - private readonly byte[] _buffer; // temp space for writing primitives to. - private readonly Encoding _encoding; - private readonly Encoder _encoder; - - private readonly bool _leaveOpen; - - // Perf optimization stuff - private byte[]? _largeByteBuffer; // temp space for writing chars. - private int _maxChars; // max # of chars we can put in _largeByteBuffer - // Size should be around the max number of chars/string * Encoding's max bytes/char - private const int LargeByteBufferSize = 256; - - // Protected default constructor that sets the output stream - // to a null stream (a bit bucket). - protected BinaryWriter() - { - OutStream = Stream.Null; - _buffer = new byte[16]; - _encoding = EncodingCache.UTF8NoBOM; - _encoder = _encoding.GetEncoder(); - } - - public BinaryWriter(Stream output) : this(output, EncodingCache.UTF8NoBOM, false) - { - } - - public BinaryWriter(Stream output, Encoding encoding) : this(output, encoding, false) - { - } - - public BinaryWriter(Stream output, Encoding encoding, bool leaveOpen) - { - if (output == null) - throw new ArgumentNullException(nameof(output)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); - if (!output.CanWrite) - throw new ArgumentException(SR.Argument_StreamNotWritable); - - OutStream = output; - _buffer = new byte[16]; - _encoding = encoding; - _encoder = _encoding.GetEncoder(); - _leaveOpen = leaveOpen; - } - - // Closes this writer and releases any system resources associated with the - // writer. Following a call to Close, any operations on the writer - // may raise exceptions. - public virtual void Close() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - if (_leaveOpen) - OutStream.Flush(); - else - OutStream.Close(); - } - } - - public void Dispose() - { - Dispose(true); - } - - public virtual ValueTask DisposeAsync() - { - try - { - if (GetType() == typeof(BinaryWriter)) - { - if (_leaveOpen) - { - return new ValueTask(OutStream.FlushAsync()); - } - - OutStream.Close(); - } - else - { - // Since this is a derived BinaryWriter, delegate to whatever logic - // the derived implementation already has in Dispose. - Dispose(); - } - - return default; - } - catch (Exception exc) - { - return new ValueTask(Task.FromException(exc)); - } - } - - // Returns the stream associated with the writer. It flushes all pending - // writes before returning. All subclasses should override Flush to - // ensure that all buffered data is sent to the stream. - public virtual Stream BaseStream - { - get - { - Flush(); - return OutStream; - } - } - - // Clears all buffers for this writer and causes any buffered data to be - // written to the underlying device. - public virtual void Flush() - { - OutStream.Flush(); - } - - public virtual long Seek(int offset, SeekOrigin origin) - { - return OutStream.Seek(offset, origin); - } - - // Writes a boolean to this stream. A single byte is written to the stream - // with the value 0 representing false or the value 1 representing true. - // - public virtual void Write(bool value) - { - _buffer[0] = (byte)(value ? 1 : 0); - OutStream.Write(_buffer, 0, 1); - } - - // Writes a byte to this stream. The current position of the stream is - // advanced by one. - // - public virtual void Write(byte value) - { - OutStream.WriteByte(value); - } - - // Writes a signed byte to this stream. The current position of the stream - // is advanced by one. - // - [CLSCompliant(false)] - public virtual void Write(sbyte value) - { - OutStream.WriteByte((byte)value); - } - - // Writes a byte array to this stream. - // - // This default implementation calls the Write(Object, int, int) - // method to write the byte array. - // - public virtual void Write(byte[] buffer) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - OutStream.Write(buffer, 0, buffer.Length); - } - - // Writes a section of a byte array to this stream. - // - // This default implementation calls the Write(Object, int, int) - // method to write the byte array. - // - public virtual void Write(byte[] buffer, int index, int count) - { - OutStream.Write(buffer, index, count); - } - - - // Writes a character to this stream. The current position of the stream is - // advanced by two. - // Note this method cannot handle surrogates properly in UTF-8. - // - public virtual unsafe void Write(char ch) - { - if (char.IsSurrogate(ch)) - throw new ArgumentException(SR.Arg_SurrogatesNotAllowedAsSingleChar); - - Debug.Assert(_encoding.GetMaxByteCount(1) <= 16, "_encoding.GetMaxByteCount(1) <= 16)"); - int numBytes = 0; - fixed (byte* pBytes = &_buffer[0]) - { - numBytes = _encoder.GetBytes(&ch, 1, pBytes, _buffer.Length, flush: true); - } - OutStream.Write(_buffer, 0, numBytes); - } - - // Writes a character array to this stream. - // - // This default implementation calls the Write(Object, int, int) - // method to write the character array. - // - public virtual void Write(char[] chars) - { - if (chars == null) - throw new ArgumentNullException(nameof(chars)); - - byte[] bytes = _encoding.GetBytes(chars, 0, chars.Length); - OutStream.Write(bytes, 0, bytes.Length); - } - - // Writes a section of a character array to this stream. - // - // This default implementation calls the Write(Object, int, int) - // method to write the character array. - // - public virtual void Write(char[] chars, int index, int count) - { - byte[] bytes = _encoding.GetBytes(chars, index, count); - OutStream.Write(bytes, 0, bytes.Length); - } - - - // Writes a double to this stream. The current position of the stream is - // advanced by eight. - // - public virtual unsafe void Write(double value) - { - ulong TmpValue = *(ulong*)&value; - _buffer[0] = (byte)TmpValue; - _buffer[1] = (byte)(TmpValue >> 8); - _buffer[2] = (byte)(TmpValue >> 16); - _buffer[3] = (byte)(TmpValue >> 24); - _buffer[4] = (byte)(TmpValue >> 32); - _buffer[5] = (byte)(TmpValue >> 40); - _buffer[6] = (byte)(TmpValue >> 48); - _buffer[7] = (byte)(TmpValue >> 56); - OutStream.Write(_buffer, 0, 8); - } - - public virtual void Write(decimal value) - { - decimal.GetBytes(value, _buffer); - OutStream.Write(_buffer, 0, 16); - } - - // Writes a two-byte signed integer to this stream. The current position of - // the stream is advanced by two. - // - public virtual void Write(short value) - { - _buffer[0] = (byte)value; - _buffer[1] = (byte)(value >> 8); - OutStream.Write(_buffer, 0, 2); - } - - // Writes a two-byte unsigned integer to this stream. The current position - // of the stream is advanced by two. - // - [CLSCompliant(false)] - public virtual void Write(ushort value) - { - _buffer[0] = (byte)value; - _buffer[1] = (byte)(value >> 8); - OutStream.Write(_buffer, 0, 2); - } - - // Writes a four-byte signed integer to this stream. The current position - // of the stream is advanced by four. - // - public virtual void Write(int value) - { - _buffer[0] = (byte)value; - _buffer[1] = (byte)(value >> 8); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 24); - OutStream.Write(_buffer, 0, 4); - } - - // Writes a four-byte unsigned integer to this stream. The current position - // of the stream is advanced by four. - // - [CLSCompliant(false)] - public virtual void Write(uint value) - { - _buffer[0] = (byte)value; - _buffer[1] = (byte)(value >> 8); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 24); - OutStream.Write(_buffer, 0, 4); - } - - // Writes an eight-byte signed integer to this stream. The current position - // of the stream is advanced by eight. - // - public virtual void Write(long value) - { - _buffer[0] = (byte)value; - _buffer[1] = (byte)(value >> 8); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 24); - _buffer[4] = (byte)(value >> 32); - _buffer[5] = (byte)(value >> 40); - _buffer[6] = (byte)(value >> 48); - _buffer[7] = (byte)(value >> 56); - OutStream.Write(_buffer, 0, 8); - } - - // Writes an eight-byte unsigned integer to this stream. The current - // position of the stream is advanced by eight. - // - [CLSCompliant(false)] - public virtual void Write(ulong value) - { - _buffer[0] = (byte)value; - _buffer[1] = (byte)(value >> 8); - _buffer[2] = (byte)(value >> 16); - _buffer[3] = (byte)(value >> 24); - _buffer[4] = (byte)(value >> 32); - _buffer[5] = (byte)(value >> 40); - _buffer[6] = (byte)(value >> 48); - _buffer[7] = (byte)(value >> 56); - OutStream.Write(_buffer, 0, 8); - } - - // Writes a float to this stream. The current position of the stream is - // advanced by four. - // - public virtual unsafe void Write(float value) - { - uint TmpValue = *(uint*)&value; - _buffer[0] = (byte)TmpValue; - _buffer[1] = (byte)(TmpValue >> 8); - _buffer[2] = (byte)(TmpValue >> 16); - _buffer[3] = (byte)(TmpValue >> 24); - OutStream.Write(_buffer, 0, 4); - } - - - // Writes a length-prefixed string to this stream in the BinaryWriter's - // current Encoding. This method first writes the length of the string as - // a four-byte unsigned integer, and then writes that many characters - // to the stream. - // - public virtual unsafe void Write(string value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - - int len = _encoding.GetByteCount(value); - Write7BitEncodedInt(len); - - if (_largeByteBuffer == null) - { - _largeByteBuffer = new byte[LargeByteBufferSize]; - _maxChars = _largeByteBuffer.Length / _encoding.GetMaxByteCount(1); - } - - if (len <= _largeByteBuffer.Length) - { - _encoding.GetBytes(value, 0, value.Length, _largeByteBuffer, 0); - OutStream.Write(_largeByteBuffer, 0, len); - } - else - { - // Aggressively try to not allocate memory in this loop for - // runtime performance reasons. Use an Encoder to write out - // the string correctly (handling surrogates crossing buffer - // boundaries properly). - int charStart = 0; - int numLeft = value.Length; -#if DEBUG - int totalBytes = 0; -#endif - while (numLeft > 0) - { - // Figure out how many chars to process this round. - int charCount = (numLeft > _maxChars) ? _maxChars : numLeft; - int byteLen; - - checked - { - if (charStart < 0 || charCount < 0 || charStart > value.Length - charCount) - { - throw new ArgumentOutOfRangeException(nameof(charCount)); - } - fixed (char* pChars = value) - { - fixed (byte* pBytes = &_largeByteBuffer[0]) - { - byteLen = _encoder.GetBytes(pChars + charStart, charCount, pBytes, _largeByteBuffer.Length, charCount == numLeft); - } - } - } -#if DEBUG - totalBytes += byteLen; - Debug.Assert(totalBytes <= len && byteLen <= _largeByteBuffer.Length, "BinaryWriter::Write(String) - More bytes encoded than expected!"); -#endif - OutStream.Write(_largeByteBuffer, 0, byteLen); - charStart += charCount; - numLeft -= charCount; - } -#if DEBUG - Debug.Assert(totalBytes == len, "BinaryWriter::Write(String) - Didn't write out all the bytes!"); -#endif - } - } - - public virtual void Write(ReadOnlySpan<byte> buffer) - { - if (GetType() == typeof(BinaryWriter)) - { - OutStream.Write(buffer); - } - else - { - byte[] array = ArrayPool<byte>.Shared.Rent(buffer.Length); - try - { - buffer.CopyTo(array); - Write(array, 0, buffer.Length); - } - finally - { - ArrayPool<byte>.Shared.Return(array); - } - } - } - - public virtual void Write(ReadOnlySpan<char> chars) - { - byte[] bytes = ArrayPool<byte>.Shared.Rent(_encoding.GetMaxByteCount(chars.Length)); - try - { - int bytesWritten = _encoding.GetBytes(chars, bytes); - Write(bytes, 0, bytesWritten); - } - finally - { - ArrayPool<byte>.Shared.Return(bytes); - } - } - - protected void Write7BitEncodedInt(int value) - { - // Write out an int 7 bits at a time. The high bit of the byte, - // when on, tells reader to continue reading more bytes. - uint v = (uint)value; // support negative numbers - while (v >= 0x80) - { - Write((byte)(v | 0x80)); - v >>= 7; - } - Write((byte)v); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/DirectoryNotFoundException.cs b/netcore/System.Private.CoreLib/shared/System/IO/DirectoryNotFoundException.cs deleted file mode 100644 index 2b0f5026919..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/DirectoryNotFoundException.cs +++ /dev/null @@ -1,42 +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.Runtime.Serialization; - -namespace System.IO -{ - /* - * Thrown when trying to access a directory that doesn't exist on disk. - * From COM Interop, this exception is thrown for 2 HRESULTS: - * the Win32 errorcode-as-HRESULT ERROR_PATH_NOT_FOUND (0x80070003) - * and STG_E_PATHNOTFOUND (0x80030003). - */ - [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public class DirectoryNotFoundException : IOException - { - public DirectoryNotFoundException() - : base(SR.Arg_DirectoryNotFoundException) - { - HResult = HResults.COR_E_DIRECTORYNOTFOUND; - } - - public DirectoryNotFoundException(string? message) - : base(message) - { - HResult = HResults.COR_E_DIRECTORYNOTFOUND; - } - - public DirectoryNotFoundException(string? message, Exception? innerException) - : base(message, innerException) - { - HResult = HResults.COR_E_DIRECTORYNOTFOUND; - } - - protected DirectoryNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/DisableMediaInsertionPrompt.cs b/netcore/System.Private.CoreLib/shared/System/IO/DisableMediaInsertionPrompt.cs deleted file mode 100644 index d14043c8304..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/DisableMediaInsertionPrompt.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#if MS_IO_REDIST -using System; - -namespace Microsoft.IO -#else -namespace System.IO -#endif -{ - /// <summary> - /// Simple wrapper to safely disable the normal media insertion prompt for - /// removable media (floppies, cds, memory cards, etc.) - /// </summary> - /// <remarks> - /// Note that removable media file systems lazily load. After starting the OS - /// they won't be loaded until you have media in the drive- and as such the - /// prompt won't happen. You have to have had media in at least once to get - /// the file system to load and then have removed it. - /// </remarks> - internal struct DisableMediaInsertionPrompt : IDisposable - { - private bool _disableSuccess; - private uint _oldMode; - - public static DisableMediaInsertionPrompt Create() - { - DisableMediaInsertionPrompt prompt = default; - prompt._disableSuccess = Interop.Kernel32.SetThreadErrorMode(Interop.Kernel32.SEM_FAILCRITICALERRORS, out prompt._oldMode); - return prompt; - } - - public void Dispose() - { - if (_disableSuccess) - Interop.Kernel32.SetThreadErrorMode(_oldMode, out _); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/DriveInfoInternal.Unix.cs b/netcore/System.Private.CoreLib/shared/System/IO/DriveInfoInternal.Unix.cs deleted file mode 100644 index 78ef95704fe..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/DriveInfoInternal.Unix.cs +++ /dev/null @@ -1,15 +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 volume helpers that are shared between many projects.</summary> - internal static partial class DriveInfoInternal - { - internal static string[] GetLogicalDrives() => Interop.Sys.GetAllMountPoints(); - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/DriveInfoInternal.Windows.cs b/netcore/System.Private.CoreLib/shared/System/IO/DriveInfoInternal.Windows.cs deleted file mode 100644 index c811a876920..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/DriveInfoInternal.Windows.cs +++ /dev/null @@ -1,87 +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. - -#nullable enable -using System.Diagnostics; - -namespace System.IO -{ - /// <summary>Contains internal volume helpers that are shared between many projects.</summary> - internal static partial class DriveInfoInternal - { - public static string[] GetLogicalDrives() - { - int drives = Interop.Kernel32.GetLogicalDrives(); - if (drives == 0) - { - throw Win32Marshal.GetExceptionForLastWin32Error(); - } - - // GetLogicalDrives returns a bitmask starting from - // position 0 "A" indicating whether a drive is present. - // Loop over each bit, creating a string for each one - // that is set. - - uint d = (uint)drives; - int count = 0; - while (d != 0) - { - if (((int)d & 1) != 0) count++; - d >>= 1; - } - - string[] result = new string[count]; - Span<char> root = stackalloc char[] { 'A', ':', '\\' }; - d = (uint)drives; - count = 0; - while (d != 0) - { - if (((int)d & 1) != 0) - { - result[count++] = root.ToString(); - } - d >>= 1; - root[0]++; - } - return result; - } - - public static string NormalizeDriveName(string driveName) - { - Debug.Assert(driveName != null); - - string? name; - - if (driveName.Length == 1) - { - name = driveName + ":\\"; - } - else - { - name = Path.GetPathRoot(driveName); - // Disallow null or empty drive letters and UNC paths - if (string.IsNullOrEmpty(name) || name.StartsWith("\\\\", StringComparison.Ordinal)) - { - throw new ArgumentException(SR.Arg_MustBeDriveLetterOrRootDir, nameof(driveName)); - } - } - // We want to normalize to have a trailing backslash so we don't have two equivalent forms and - // because some Win32 API don't work without it. - if (name.Length == 2 && name[1] == ':') - { - name += "\\"; - } - - // Now verify that the drive letter could be a real drive name. - // On Windows this means it's between A and Z, ignoring case. - char letter = driveName[0]; - if (!((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'))) - { - throw new ArgumentException(SR.Arg_MustBeDriveLetterOrRootDir, nameof(driveName)); - } - - return name; - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/EncodingCache.cs b/netcore/System.Private.CoreLib/shared/System/IO/EncodingCache.cs deleted file mode 100644 index 53379bc77f3..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/EncodingCache.cs +++ /dev/null @@ -1,13 +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.Text; - -namespace System.IO -{ - internal static class EncodingCache - { - internal static readonly Encoding UTF8NoBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/EndOfStreamException.cs b/netcore/System.Private.CoreLib/shared/System/IO/EndOfStreamException.cs deleted file mode 100644 index c965b6a1c20..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/EndOfStreamException.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.Runtime.Serialization; - -namespace System.IO -{ - [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public class EndOfStreamException : IOException - { - public EndOfStreamException() - : base(SR.Arg_EndOfStreamException) - { - HResult = HResults.COR_E_ENDOFSTREAM; - } - - public EndOfStreamException(string? message) - : base(message) - { - HResult = HResults.COR_E_ENDOFSTREAM; - } - - public EndOfStreamException(string? message, Exception? innerException) - : base(message, innerException) - { - HResult = HResults.COR_E_ENDOFSTREAM; - } - - protected EndOfStreamException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/Error.cs b/netcore/System.Private.CoreLib/shared/System/IO/Error.cs deleted file mode 100644 index fd39f166cf9..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/Error.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - /// <summary> - /// Provides centralized methods for creating exceptions for System.IO.FileSystem. - /// </summary> - internal static class Error - { - internal static Exception GetStreamIsClosed() - { - return new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed); - } - - 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/netcore/System.Private.CoreLib/shared/System/IO/FileAccess.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileAccess.cs deleted file mode 100644 index c0b75374522..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileAccess.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - // Contains constants for specifying the access you want for a file. - // You can have Read, Write or ReadWrite access. - // - [Flags] - public enum FileAccess - { - // Specifies read access to the file. Data can be read from the file and - // the file pointer can be moved. Combine with WRITE for read-write access. - Read = 1, - - // Specifies write access to the file. Data can be written to the file and - // the file pointer can be moved. Combine with READ for read-write access. - Write = 2, - - // Specifies read and write access to the file. Data can be written to the - // file and the file pointer can be moved. Data can also be read from the - // file. - ReadWrite = 3, - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileLoadException.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileLoadException.cs deleted file mode 100644 index f7ef7c4c050..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileLoadException.cs +++ /dev/null @@ -1,85 +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.Runtime.Serialization; - -namespace System.IO -{ - [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public partial class FileLoadException : IOException - { - public FileLoadException() - : base(SR.IO_FileLoad) - { - HResult = HResults.COR_E_FILELOAD; - } - - public FileLoadException(string? message) - : base(message) - { - HResult = HResults.COR_E_FILELOAD; - } - - public FileLoadException(string? message, Exception? inner) - : base(message, inner) - { - HResult = HResults.COR_E_FILELOAD; - } - - public FileLoadException(string? message, string? fileName) : base(message) - { - HResult = HResults.COR_E_FILELOAD; - FileName = fileName; - } - - public FileLoadException(string? message, string? fileName, Exception? inner) - : base(message, inner) - { - HResult = HResults.COR_E_FILELOAD; - FileName = fileName; - } - - public override string Message => _message ??= FormatFileLoadExceptionMessage(FileName, HResult); - - public string? FileName { get; } - public string? FusionLog { get; } - - public override string ToString() - { - string s = GetType().ToString() + ": " + Message; - - if (!string.IsNullOrEmpty(FileName)) - s += Environment.NewLineConst + SR.Format(SR.IO_FileName_Name, FileName); - - if (InnerException != null) - s += Environment.NewLineConst + InnerExceptionPrefix + InnerException.ToString(); - - if (StackTrace != null) - s += Environment.NewLineConst + StackTrace; - - if (FusionLog != null) - { - s ??= " "; - s += Environment.NewLineConst + Environment.NewLineConst + FusionLog; - } - - return s; - } - - protected FileLoadException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - FileName = info.GetString("FileLoad_FileName"); - FusionLog = info.GetString("FileLoad_FusionLog"); - } - - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue("FileLoad_FileName", FileName, typeof(string)); - info.AddValue("FileLoad_FusionLog", FusionLog, typeof(string)); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileMode.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileMode.cs deleted file mode 100644 index 83f48229958..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileMode.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - // Contains constants for specifying how the OS should open a file. - // These will control whether you overwrite a file, open an existing - // file, or some combination thereof. - // - // To append to a file, use Append (which maps to OpenOrCreate then we seek - // to the end of the file). To truncate a file or create it if it doesn't - // exist, use Create. - // - public enum FileMode - { - // Creates a new file. An exception is raised if the file already exists. - CreateNew = 1, - - // Creates a new file. If the file already exists, it is overwritten. - Create = 2, - - // Opens an existing file. An exception is raised if the file does not exist. - Open = 3, - - // Opens the file if it exists. Otherwise, creates a new file. - OpenOrCreate = 4, - - // Opens an existing file. Once opened, the file is truncated so that its - // size is zero bytes. The calling process must open the file with at least - // WRITE access. An exception is raised if the file does not exist. - Truncate = 5, - - // Opens the file if it exists and seeks to the end. Otherwise, - // creates a new file. - Append = 6, - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileNotFoundException.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileNotFoundException.cs deleted file mode 100644 index 48c513b7f37..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileNotFoundException.cs +++ /dev/null @@ -1,107 +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.Serialization; - -namespace System.IO -{ - // Thrown when trying to access a file that doesn't exist on disk. - [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public partial class FileNotFoundException : IOException - { - public FileNotFoundException() - : base(SR.IO_FileNotFound) - { - HResult = HResults.COR_E_FILENOTFOUND; - } - - public FileNotFoundException(string? message) - : base(message) - { - HResult = HResults.COR_E_FILENOTFOUND; - } - - public FileNotFoundException(string? message, Exception? innerException) - : base(message, innerException) - { - HResult = HResults.COR_E_FILENOTFOUND; - } - - public FileNotFoundException(string? message, string? fileName) - : base(message) - { - HResult = HResults.COR_E_FILENOTFOUND; - FileName = fileName; - } - - public FileNotFoundException(string? message, string? fileName, Exception? innerException) - : base(message, innerException) - { - HResult = HResults.COR_E_FILENOTFOUND; - FileName = fileName; - } - - public override string Message - { - get - { - SetMessageField(); - Debug.Assert(_message != null, "_message was null after calling SetMessageField"); - return _message; - } - } - - private void SetMessageField() - { - if (_message == null) - { - if ((FileName == null) && - (HResult == System.HResults.COR_E_EXCEPTION)) - _message = SR.IO_FileNotFound; - else if (FileName != null) - _message = FileLoadException.FormatFileLoadExceptionMessage(FileName, HResult); - } - } - - public string? FileName { get; } - public string? FusionLog { get; } - - public override string ToString() - { - string s = GetType().ToString() + ": " + Message; - - if (!string.IsNullOrEmpty(FileName)) - s += Environment.NewLineConst + SR.Format(SR.IO_FileName_Name, FileName); - - if (InnerException != null) - s += Environment.NewLineConst + InnerExceptionPrefix + InnerException.ToString(); - - if (StackTrace != null) - s += Environment.NewLineConst + StackTrace; - - if (FusionLog != null) - { - s ??= " "; - s += Environment.NewLineConst + Environment.NewLineConst + FusionLog; - } - return s; - } - - protected FileNotFoundException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - FileName = info.GetString("FileNotFound_FileName"); - FusionLog = info.GetString("FileNotFound_FusionLog"); - } - - public override void GetObjectData(SerializationInfo info, StreamingContext context) - { - base.GetObjectData(info, context); - info.AddValue("FileNotFound_FileName", FileName, typeof(string)); - info.AddValue("FileNotFound_FusionLog", FusionLog, typeof(string)); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileOptions.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileOptions.cs deleted file mode 100644 index eed14c86b05..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileOptions.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - // Maps to FILE_FLAG_DELETE_ON_CLOSE and similar values from winbase.h. - // We didn't expose a number of these values because we didn't believe - // a number of them made sense in managed code, at least not yet. - [Flags] - public enum FileOptions - { - // NOTE: any change to FileOptions enum needs to be - // matched in the FileStream ctor for error validation - None = 0, - WriteThrough = unchecked((int)0x80000000), - Asynchronous = unchecked((int)0x40000000), // FILE_FLAG_OVERLAPPED - // NoBuffering = 0x20000000, - RandomAccess = 0x10000000, - DeleteOnClose = 0x04000000, - SequentialScan = 0x08000000, - // AllowPosix = 0x01000000, // FILE_FLAG_POSIX_SEMANTICS - // BackupOrRestore, - // DisallowReparsePoint = 0x00200000, // FILE_FLAG_OPEN_REPARSE_POINT - // NoRemoteRecall = 0x00100000, // FILE_FLAG_OPEN_NO_RECALL - // FirstPipeInstance = 0x00080000, // FILE_FLAG_FIRST_PIPE_INSTANCE - Encrypted = 0x00004000, // FILE_ATTRIBUTE_ENCRYPTED - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileShare.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileShare.cs deleted file mode 100644 index 24742a71b3f..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileShare.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - // Contains constants for controlling file sharing options while - // opening files. You can specify what access other processes trying - // to open the same file concurrently can have. - // - // Note these values currently match the values for FILE_SHARE_READ, - // FILE_SHARE_WRITE, and FILE_SHARE_DELETE in winnt.h - // - [Flags] - public enum FileShare - { - // No sharing. Any request to open the file (by this process or another - // process) will fail until the file is closed. - None = 0, - - // Allows subsequent opening of the file for reading. If this flag is not - // specified, any request to open the file for reading (by this process or - // another process) will fail until the file is closed. - Read = 1, - - // Allows subsequent opening of the file for writing. If this flag is not - // specified, any request to open the file for writing (by this process or - // another process) will fail until the file is closed. - Write = 2, - - // Allows subsequent opening of the file for writing or reading. If this flag - // is not specified, any request to open the file for writing or reading (by - // this process or another process) will fail until the file is closed. - ReadWrite = 3, - - // Open the file, but allow someone else to delete the file. - Delete = 4, - - // Whether the file handle should be inheritable by child processes. - // Note this is not directly supported like this by Win32. - Inheritable = 0x10, - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Linux.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Linux.cs deleted file mode 100644 index 873c4eb5599..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Linux.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Win32.SafeHandles; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO -{ - public partial class FileStream : Stream - { - /// <summary>Prevents other processes from reading from or writing to the FileStream.</summary> - /// <param name="position">The beginning of the range to lock.</param> - /// <param name="length">The range to be locked.</param> - private void LockInternal(long position, long length) - { - CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_WRLCK)); - } - - /// <summary>Allows access by other processes to all or part of a file that was previously locked.</summary> - /// <param name="position">The beginning of the range to unlock.</param> - /// <param name="length">The range to be unlocked.</param> - private void UnlockInternal(long position, long length) - { - CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_UNLCK)); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.OSX.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileStream.OSX.cs deleted file mode 100644 index f29e9223373..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.OSX.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - public partial class FileStream : Stream - { - private void LockInternal(long position, long length) - { - throw new PlatformNotSupportedException(SR.PlatformNotSupported_OSXFileLocking); - } - - private void UnlockInternal(long position, long length) - { - throw new PlatformNotSupportedException(SR.PlatformNotSupported_OSXFileLocking); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs deleted file mode 100644 index c1f0fcfb57f..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Unix.cs +++ /dev/null @@ -1,852 +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.Runtime.InteropServices; -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, share, options); - - // 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); - } - - private static bool GetDefaultIsAsync(SafeFileHandle handle) => handle.IsAsync ?? DefaultIsAsync; - - /// <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, string originalPath) - { - _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. - } - - if (_mode == FileMode.Append) - { - // Jump to the end of the file if opened as Append. - _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End); - } - else if (mode == FileMode.Create || mode == FileMode.Truncate) - { - // Truncate the file now if the file mode requires it. This ensures that the file only will be truncated - // if opened successfully. - if (Interop.Sys.FTruncate(_fileHandle, 0) < 0) - { - Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); - if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL) - { - // We know the file descriptor is valid and we know the size argument to FTruncate is correct, - // so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be - // truncated. Ignore the error in such cases; in all others, throw. - throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false); - } - } - } - } - - /// <summary>Initializes a stream from an already open file handle (file descriptor).</summary> - private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAsyncIO) - { - if (useAsyncIO) - _asyncState = new AsyncState(); - - if (CanSeekCore(handle)) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor - SeekCore(handle, 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="share">The FileShare 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, FileShare share, 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. - case FileMode.Truncate: // We truncate the file after getting the lock - break; - - case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later - case FileMode.OpenOrCreate: - case FileMode.Create: // We truncate the file after getting the lock - flags |= Interop.Sys.OpenFlags.O_CREAT; - break; - - case FileMode.CreateNew: - flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL); - 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; - } - - // Handle Inheritable, other FileShare flags are handled by Init - if ((share & FileShare.Inheritable) == 0) - { - flags |= Interop.Sys.OpenFlags.O_CLOEXEC; - } - - // 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(_fileHandle); - - /// <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. - /// We also pass in the file handle to allow the constructor to use this before it stashes the handle. - /// </remarks> - private bool CanSeekCore(SafeFileHandle fileHandle) - { - 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.GetValueOrDefault(); - } - - 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 - try - { - FlushWriteBuffer(); - } - catch (Exception e) when (IsIoRelatedException(e) && !disposing) - { - // On finalization, ignore failures from trying to flush the write buffer, - // e.g. if this stream is wrapping a pipe and the pipe is now broken. - } - - // 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); - } - } - - public override ValueTask DisposeAsync() - { - // On Unix, we don't have any special support for async I/O, simply queueing writes - // rather than doing them synchronously. As such, if we're "using async I/O" and we - // have something to flush, queue the call to Dispose, so that we end up queueing whatever - // write work happens to flush the buffer. Otherwise, just delegate to the base implementation, - // which will synchronously invoke Dispose. We don't need to factor in the current type - // as we're using the virtual Dispose either way, and therefore factoring in whatever - // override may already exist on a derived type. - if (_useAsyncIO && _writePos > 0) - { - return new ValueTask(Task.Factory.StartNew(s => ((FileStream)s!).Dispose(), this, - CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default)); - } - - return base.DisposeAsync(); - } - - /// <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); - } - } - } - - private void FlushWriteBufferForWriteByte() - { - _asyncState?.Wait(); - try { FlushWriteBuffer(); } - finally { _asyncState?.Release(); } - } - - /// <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(new ReadOnlySpan<byte>(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(_fileHandle, 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(_fileHandle, origPos, SeekOrigin.Begin); - } - else - { - SeekCore(_fileHandle, 0, SeekOrigin.End); - } - } - } - - /// <summary>Reads a block of bytes from the stream and writes the data in a given buffer.</summary> - private int ReadSpan(Span<byte> destination) - { - 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 || (destination.Length >= _bufferLength)) - { - // Read directly into the user's buffer - _readPos = _readLength = 0; - return ReadNative(destination); - } - else - { - // Read into our buffer. - _readLength = numBytesAvailable = ReadNative(GetBuffer()); - _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, destination.Length); - new Span<byte>(GetBuffer(), _readPos, bytesRead).CopyTo(destination); - _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 < destination.Length) - { - Debug.Assert(_readPos == _readLength, "bytesToRead should only be < destination.Length if numBytesAvailable < destination.Length"); - _readPos = _readLength = 0; // no data left in the read buffer - bytesRead += ReadNative(destination.Slice(bytesRead)); - } - - return bytesRead; - } - - /// <summary>Unbuffered, reads a block of bytes from the file handle into the given buffer.</summary> - /// <param name="buffer">The buffer into which data from the file is 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(Span<byte> buffer) - { - FlushWriteBuffer(); // we're about to read; dump the write buffer - - VerifyOSHandlePosition(); - - int bytesRead; - fixed (byte* bufPtr = &MemoryMarshal.GetReference(buffer)) - { - bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr, buffer.Length)); - Debug.Assert(bytesRead <= buffer.Length); - } - _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="destination">The buffer to write the data into.</param> - /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> - /// <param name="synchronousResult">If the operation completes synchronously, the number of bytes read.</param> - /// <returns>A task that represents the asynchronous read operation.</returns> - private Task<int>? ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken, out int synchronousResult) - { - Debug.Assert(_useAsyncIO); - Debug.Assert(_asyncState != null); - - 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 >= destination.Length) - { - try - { - PrepareForReading(); - - new Span<byte>(GetBuffer(), _readPos, destination.Length).CopyTo(destination.Span); - _readPos += destination.Length; - - synchronousResult = destination.Length; - return null; - } - catch (Exception exc) - { - synchronousResult = 0; - return Task.FromException<int>(exc); - } - finally - { - _asyncState.Release(); - } - } - } - - // Otherwise, issue the whole request asynchronously. - synchronousResult = 0; - _asyncState.Memory = destination; - 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!; - Debug.Assert(thisRef._asyncState != null); - try - { - Memory<byte> memory = thisRef._asyncState.Memory; - thisRef._asyncState.Memory = default(Memory<byte>); - return thisRef.ReadSpan(memory.Span); - } - finally { thisRef._asyncState.Release(); } - }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default); - } - - /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary> - private int FillReadBufferForReadByte() - { - _asyncState?.Wait(); - try { return ReadNative(_buffer); } - finally { _asyncState?.Release(); } - } - - /// <summary>Writes a block of bytes to the file stream.</summary> - /// <param name="source">The buffer containing data to write to the stream.</param> - private void WriteSpan(ReadOnlySpan<byte> source) - { - PrepareForWriting(); - - // If no data is being written, nothing more to do. - if (source.Length == 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 >= source.Length) - { - source.CopyTo(GetBuffer().AsSpan(_writePos)); - _writePos += source.Length; - return; - } - else if (spaceRemaining > 0) - { - source.Slice(0, spaceRemaining).CopyTo(GetBuffer().AsSpan(_writePos)); - _writePos += spaceRemaining; - source = source.Slice(spaceRemaining); - } - - // 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 (source.Length >= _bufferLength) - { - WriteNative(source); - } - else - { - source.CopyTo(new Span<byte>(GetBuffer())); - _writePos = source.Length; - } - } - - /// <summary>Unbuffered, writes a block of bytes to the file stream.</summary> - /// <param name="source">The buffer containing data to write to the stream.</param> - private unsafe void WriteNative(ReadOnlySpan<byte> source) - { - VerifyOSHandlePosition(); - - fixed (byte* bufPtr = &MemoryMarshal.GetReference(source)) - { - int offset = 0; - int count = source.Length; - while (count > 0) - { - int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count)); - _filePosition += bytesWritten; - offset += bytesWritten; - count -= 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="source">The buffer to write data from.</param> - /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> - /// <returns>A task that represents the asynchronous write operation.</returns> - private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) - { - Debug.Assert(_useAsyncIO); - Debug.Assert(_asyncState != null); - - if (cancellationToken.IsCancellationRequested) - return new ValueTask(Task.FromCanceled(cancellationToken)); - - if (_fileHandle.IsClosed) - throw Error.GetFileNotOpen(); - - 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 >= source.Length) - { - try - { - PrepareForWriting(); - - source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length)); - _writePos += source.Length; - - return default; - } - catch (Exception exc) - { - return new ValueTask(Task.FromException(exc)); - } - finally - { - _asyncState.Release(); - } - } - } - - // Otherwise, issue the whole request asynchronously. - _asyncState.ReadOnlyMemory = source; - return new ValueTask(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!; - Debug.Assert(thisRef._asyncState != null); - try - { - ReadOnlyMemory<byte> readOnlyMemory = thisRef._asyncState.ReadOnlyMemory; - thisRef._asyncState.ReadOnlyMemory = default(ReadOnlyMemory<byte>); - thisRef.WriteSpan(readOnlyMemory.Span); - } - finally { thisRef._asyncState.Release(); } - }, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default)); - } - - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) => - // Windows version overrides this method, so the Unix version does as well, but it doesn't - // currently have any special optimizations to be done and so just calls to the base. - base.CopyToAsync(destination, bufferSize, cancellationToken); - - /// <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(_fileHandle, 0, SeekOrigin.Current); - } - - // Jump to the new location - long pos = SeekCore(_fileHandle, offset, origin); - - // Prevent users from overwriting data in a file that was opened in append mode. - if (_appendStart != -1 && pos < _appendStart) - { - SeekCore(_fileHandle, 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(SafeFileHandle fileHandle, long offset, SeekOrigin origin) - { - Debug.Assert(!fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeekCore(fileHandle))); // 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 - { - internal ReadOnlyMemory<byte> ReadOnlyMemory; - internal Memory<byte> Memory; - - /// <summary>Initialize the AsyncState.</summary> - internal AsyncState() : base(initialCount: 1, maxCount: 1) { } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Win32.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Win32.cs deleted file mode 100644 index 310d5d2d1fb..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Win32.cs +++ /dev/null @@ -1,107 +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 Microsoft.Win32.SafeHandles; - -namespace System.IO -{ - public partial class FileStream : Stream - { - private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options) - { - return CreateFileOpenHandle(mode, share, options); - } - - private unsafe SafeFileHandle CreateFileOpenHandle(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 - // (note that this is the effective default on CreateFile2) - flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS); - - using (DisableMediaInsertionPrompt.Create()) - { - Debug.Assert(_path != null); - return ValidateFileHandle( - Interop.Kernel32.CreateFile(_path, fAccess, share, ref secAttrs, mode, flagsAndAttributes, IntPtr.Zero)); - } - } - - private static bool GetDefaultIsAsync(SafeFileHandle handle) - { - return handle.IsAsync ?? !IsHandleSynchronous(handle, ignoreInvalid: true) ?? DefaultIsAsync; - } - - private static unsafe bool? IsHandleSynchronous(SafeFileHandle fileHandle, bool ignoreInvalid) - { - if (fileHandle.IsInvalid) - return null; - - uint fileMode; - - - int status = Interop.NtDll.NtQueryInformationFile( - FileHandle: fileHandle, - IoStatusBlock: out _, - FileInformation: &fileMode, - Length: sizeof(uint), - FileInformationClass: Interop.NtDll.FileModeInformation); - - switch (status) - { - case 0: - // We were successful - break; - case Interop.NtDll.STATUS_INVALID_HANDLE: - if (!ignoreInvalid) - { - throw Win32Marshal.GetExceptionForWin32Error(Interop.Errors.ERROR_INVALID_HANDLE); - } - else - { - return null; - } - default: - // Something else is preventing access - Debug.Fail("Unable to get the file mode information, status was" + status.ToString()); - return null; - } - - // If either of these two flags are set, the file handle is synchronous (not overlapped) - return (fileMode & (Interop.NtDll.FILE_SYNCHRONOUS_IO_ALERT | Interop.NtDll.FILE_SYNCHRONOUS_IO_NONALERT)) > 0; - } - - private static void VerifyHandleIsSync(SafeFileHandle handle, int fileType, FileAccess access) - { - // As we can accurately check the handle type when we have access to NtQueryInformationFile we don't need to skip for - // any particular file handle type. - - // If the handle was passed in without an explicit async setting, we already looked it up in GetDefaultIsAsync - if (!handle.IsAsync.HasValue) - return; - - // If we can't check the handle, just assume it is ok. - if (!(IsHandleSynchronous(handle, ignoreInvalid: false) ?? true)) - throw new ArgumentException(SR.Arg_HandleNotSync, nameof(handle)); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs deleted file mode 100644 index cc6da307f6a..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.Windows.cs +++ /dev/null @@ -1,1627 +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 readonly unsafe IOCompletionCallback s_ioCallback = FileStreamCompletionSource.IOCallback; - - private Task _activeBufferOperation = Task.CompletedTask; // 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 void Init(FileMode mode, FileShare share, string originalPath) - { - if (!PathInternal.IsExtended(originalPath)) - { - // To help avoid stumbling into opening COM/LPT ports by accident, we will block on non file handles unless - // we were explicitly passed a path that has \\?\. GetFullPath() will turn paths like C:\foo\con.txt into - // \\.\CON, so we'll only allow the \\?\ syntax. - - int fileType = Interop.Kernel32.GetFileType(_fileHandle); - if (fileType != Interop.Kernel32.FileTypes.FILE_TYPE_DISK) - { - int errorCode = fileType == Interop.Kernel32.FileTypes.FILE_TYPE_UNKNOWN - ? Marshal.GetLastWin32Error() - : Interop.Errors.ERROR_SUCCESS; - - _fileHandle.Dispose(); - - if (errorCode != Interop.Errors.ERROR_SUCCESS) - { - throw Win32Marshal.GetExceptionForWin32Error(errorCode); - } - 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(_fileHandle, 0, SeekOrigin.End); - } - else - { - _appendStart = -1; - } - } - - private void InitFromHandle(SafeFileHandle handle, FileAccess access, bool useAsyncIO) - { -#if DEBUG - bool hadBinding = handle.ThreadPoolBinding != null; - - try - { -#endif - InitFromHandleImpl(handle, access, useAsyncIO); -#if DEBUG - } - catch - { - Debug.Assert(hadBinding || handle.ThreadPoolBinding == null, "We should never error out with a ThreadPoolBinding we've added"); - throw; - } -#endif - } - - private void InitFromHandleImpl(SafeFileHandle handle, FileAccess access, bool useAsyncIO) - { - int handleType = Interop.Kernel32.GetFileType(handle); - 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 && !(handle.IsAsync ?? false)) - { - try - { - handle.ThreadPoolBinding = ThreadPoolBoundHandle.BindHandle(handle); - } - 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) - { - VerifyHandleIsSync(handle, handleType, access); - } - - if (_canSeek) - SeekCore(handle, 0, SeekOrigin.Current); - else - _filePosition = 0; - } - - private static unsafe Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share) - { - Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = default; - if ((share & FileShare.Inheritable) != 0) - { - secAttrs = new Interop.Kernel32.SECURITY_ATTRIBUTES - { - nLength = (uint)sizeof(Interop.Kernel32.SECURITY_ATTRIBUTES), - bInheritHandle = Interop.BOOL.TRUE - }; - } - return secAttrs; - } - - private bool HasActiveBufferOperation => !_activeBufferOperation.IsCompleted; - - public override bool CanSeek => _canSeek; - - private unsafe long GetLengthInternal() - { - Interop.Kernel32.FILE_STANDARD_INFO info; - - if (!Interop.Kernel32.GetFileInformationByHandleEx(_fileHandle, Interop.Kernel32.FILE_INFO_BY_HANDLE_CLASS.FileStandardInfo, out info, (uint)sizeof(Interop.Kernel32.FILE_STANDARD_INFO))) - throw Win32Marshal.GetExceptionForLastWin32Error(_path); - 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 && _writePos > 0) - { - // 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. - try - { - FlushWriteBuffer(!disposing); - } - catch (Exception e) when (IsIoRelatedException(e) && !disposing) - { - // On finalization, ignore failures from trying to flush the write buffer, - // e.g. if this stream is wrapping a pipe and the pipe is now broken. - } - } - } - finally - { - if (_fileHandle != null && !_fileHandle.IsClosed) - { - _fileHandle.ThreadPoolBinding?.Dispose(); - _fileHandle.Dispose(); - } - - _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). - } - } - - public override ValueTask DisposeAsync() => - GetType() == typeof(FileStream) ? - DisposeAsyncCore() : - base.DisposeAsync(); - - private async ValueTask DisposeAsyncCore() - { - // Same logic as in Dispose(), except with async counterparts. - // TODO: https://github.com/dotnet/corefx/issues/32837: FlushAsync does synchronous work. - try - { - if (_fileHandle != null && !_fileHandle.IsClosed && _writePos > 0) - { - await FlushAsyncInternal(default).ConfigureAwait(false); - } - } - finally - { - if (_fileHandle != null && !_fileHandle.IsClosed) - { - _fileHandle.ThreadPoolBinding?.Dispose(); - _fileHandle.Dispose(); - } - - _preallocatedOverlapped?.Dispose(); - _canSeek = false; - GC.SuppressFinalize(this); // the handle is closed; nothing further for the finalizer to do - } - } - - private void FlushOSBuffer() - { - if (!Interop.Kernel32.FlushFileBuffers(_fileHandle)) - { - throw Win32Marshal.GetExceptionForLastWin32Error(_path); - } - } - - // 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 = WriteAsyncInternalCore(new ReadOnlyMemory<byte>(GetBuffer(), 0, _writePos), cancellationToken); - _writePos = 0; - - // Update the active buffer operation - _activeBufferOperation = HasActiveBufferOperation ? - Task.WhenAll(_activeBufferOperation, flushTask) : - flushTask; - - return flushTask; - } - - private void FlushWriteBufferForWriteByte() => FlushWriteBuffer(); - - // 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(new ReadOnlySpan<byte>(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(_fileHandle, 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, _path); - } - // Return file pointer to where it was before setting length - if (origPos != value) - { - if (origPos < value) - SeekCore(_fileHandle, origPos, SeekOrigin.Begin); - else - SeekCore(_fileHandle, 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); - - private int ReadSpan(Span<byte> destination) - { - Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode"); - 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 || (destination.Length >= _bufferLength)) - { - n = ReadNative(destination); - // Throw away read buffer. - _readPos = 0; - _readLength = 0; - return n; - } - n = ReadNative(GetBuffer()); - 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 > destination.Length) n = destination.Length; - new ReadOnlySpan<byte>(GetBuffer(), _readPos, n).CopyTo(destination); - _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 < destination.Length && !isBlocked) - { - Debug.Assert(_readPos == _readLength, "Read buffer should be empty!"); - int moreBytesRead = ReadNative(destination.Slice(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() - { - Debug.Assert(!_fileHandle.IsClosed, "!_fileHandle.IsClosed"); - Debug.Assert(CanRead, "CanRead"); - } - - /// <summary>Reads from the file handle into the buffer, overwriting anything in it.</summary> - private int FillReadBufferForReadByte() => - _useAsyncIO ? - ReadNativeAsync(new Memory<byte>(_buffer), 0, CancellationToken.None).GetAwaiter().GetResult() : - ReadNative(_buffer); - - private unsafe int ReadNative(Span<byte> buffer) - { - Debug.Assert(!_useAsyncIO, $"{nameof(ReadNative)} doesn't work on asynchronous file streams."); - AssertCanRead(); - - // Make sure we are reading from the right spot - VerifyOSHandlePosition(); - - int r = ReadFileNative(_fileHandle, buffer, null, out int 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, _path); - } - } - 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(_fileHandle, offset, origin); - - // Prevent users from overwriting data in a file that was opened in - // append mode. - if (_appendStart != -1 && pos < _appendStart) - { - SeekCore(_fileHandle, 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) - { - 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(_fileHandle, _readLength, SeekOrigin.Current); - } - else if (oldPos - _readPos < pos && pos < oldPos + _readLength - _readPos) - { - int diff = (int)(pos - oldPos); - Buffer.BlockCopy(GetBuffer(), _readPos + diff, GetBuffer(), 0, _readLength - (_readPos + diff)); - _readLength -= (_readPos + diff); - _readPos = 0; - if (_readLength > 0) - SeekCore(_fileHandle, _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 - private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, bool closeInvalidHandle = false) - { - Debug.Assert(!fileHandle.IsClosed && _canSeek, "!fileHandle.IsClosed && _canSeek"); - Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End, "origin >= SeekOrigin.Begin && origin <= SeekOrigin.End"); - - if (!Interop.Kernel32.SetFilePointerEx(fileHandle, offset, out long ret, (uint)origin)) - { - if (closeInvalidHandle) - { - throw Win32Marshal.GetExceptionForWin32Error(GetLastWin32ErrorAndDisposeHandleIfInvalid(), _path); - } - else - { - throw Win32Marshal.GetExceptionForLastWin32Error(_path); - } - } - - _filePosition = ret; - return ret; - } - - partial void OnBufferAllocated() - { - Debug.Assert(_buffer != null); - Debug.Assert(_preallocatedOverlapped == null); - - if (_useAsyncIO) - _preallocatedOverlapped = new PreAllocatedOverlapped(s_ioCallback, this, _buffer); - } - - private void WriteSpan(ReadOnlySpan<byte> source) - { - Debug.Assert(!_useAsyncIO, "Must only be used when in synchronous mode"); - - 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 >= source.Length) - { - source.CopyTo(GetBuffer().AsSpan(_writePos)); - _writePos += source.Length; - return; - } - else - { - source.Slice(0, numBytes).CopyTo(GetBuffer().AsSpan(_writePos)); - _writePos += numBytes; - source = source.Slice(numBytes); - } - } - // Reset our buffer. We essentially want to call FlushWrite - // without calling Flush on the underlying Stream. - - WriteCore(new ReadOnlySpan<byte>(GetBuffer(), 0, _writePos)); - _writePos = 0; - } - - // If the buffer would slow writes down, avoid buffer completely. - if (source.Length >= _bufferLength) - { - Debug.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted."); - WriteCore(source); - return; - } - else if (source.Length == 0) - { - return; // Don't allocate a buffer then call memcpy for 0 bytes. - } - - // Copy remaining bytes into buffer, to write at a later date. - source.CopyTo(GetBuffer().AsSpan(_writePos)); - _writePos = source.Length; - return; - } - - private unsafe void WriteCore(ReadOnlySpan<byte> source) - { - Debug.Assert(!_useAsyncIO); - Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); - Debug.Assert(CanWrite, "_parent.CanWrite"); - Debug.Assert(_readPos == _readLength, "_readPos == _readLen"); - - // Make sure we are writing to the position that we think we are - VerifyOSHandlePosition(); - - int errorCode = 0; - int r = WriteFileNative(_fileHandle, source, 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 or for synchronous writes - // to a handle opened asynchronously. - if (errorCode == ERROR_INVALID_PARAMETER) - throw new IOException(SR.IO_FileTooLongOrHandleNotSync); - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); - } - } - Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken."); - _filePosition += r; - return; - } - - private Task<int>? ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken, out int synchronousResult) - { - Debug.Assert(_useAsyncIO); - 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 = Math.Min(_readLength - _readPos, destination.Length); - new Span<byte>(GetBuffer(), _readPos, n).CopyTo(destination.Span); - _readPos += n; - synchronousResult = n; - return null; - } - else - { - Debug.Assert(_writePos == 0, "Win32FileStream must not have buffered write data here! Pipes should be unidirectional."); - synchronousResult = 0; - return ReadNativeAsync(destination, 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 (destination.Length < _bufferLength) - { - Task<int> readTask = ReadNativeAsync(new Memory<byte>(GetBuffer()), 0, cancellationToken); - _readLength = readTask.GetAwaiter().GetResult(); - int n = Math.Min(_readLength, destination.Length); - new Span<byte>(GetBuffer(), 0, n).CopyTo(destination.Span); - _readPos = n; - - synchronousResult = n; - return null; - } - else - { - // Here we're making our position pointer inconsistent - // with our read buffer. Throw away the read buffer's contents. - _readPos = 0; - _readLength = 0; - synchronousResult = 0; - return ReadNativeAsync(destination, 0, cancellationToken); - } - } - else - { - int n = Math.Min(_readLength - _readPos, destination.Length); - new Span<byte>(GetBuffer(), _readPos, n).CopyTo(destination.Span); - _readPos += n; - - if (n == destination.Length) - { - // Return a completed task - synchronousResult = n; - return null; - } - 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; - synchronousResult = 0; - return ReadNativeAsync(destination.Slice(n), n, cancellationToken); - } - } - } - - private unsafe Task<int> ReadNativeAsync(Memory<byte> destination, int numBufferedBytesRead, CancellationToken cancellationToken) - { - AssertCanRead(); - 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 = FileStreamCompletionSource.Create(this, numBufferedBytesRead, destination); - 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 + destination.Length > len) - { - if (_filePosition <= len) - { - destination = destination.Slice(0, (int)(len - _filePosition)); - } - else - { - destination = default; - } - } - - // 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(_fileHandle, destination.Length, SeekOrigin.Current); - } - - // queue an async ReadFile operation and pass in a packed overlapped - int errorCode = 0; - int r = ReadFileNative(_fileHandle, destination.Span, 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) - { - // 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(_fileHandle, 0, SeekOrigin.Current); - } - - completionSource.ReleaseNativeResource(); - - if (errorCode == ERROR_HANDLE_EOF) - { - throw Error.GetEndOfFile(); - } - else - { - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); - } - } - else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING - { - // Only once the IO is pending do we register for cancellation - completionSource.RegisterForCancellation(cancellationToken); - } - } - 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. - } - - return completionSource.Task; - } - - private ValueTask WriteAsyncInternal(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) - { - Debug.Assert(_useAsyncIO); - 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."); - - if (!CanWrite) throw Error.GetWriteNotSupported(); - - 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 (source.Length < _bufferLength && !HasActiveBufferOperation && source.Length <= remainingBuffer) - { - source.Span.CopyTo(new Span<byte>(GetBuffer(), _writePos, source.Length)); - _writePos += source.Length; - 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 (source.Length != remainingBuffer) - return default; - - 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 new ValueTask(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 = WriteAsyncInternalCore(source, cancellationToken); - return new ValueTask( - (flushTask == null || flushTask.Status == TaskStatus.RanToCompletion) ? writeTask : - (writeTask.Status == TaskStatus.RanToCompletion) ? flushTask : - Task.WhenAll(flushTask, writeTask)); - } - - private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory<byte> source, CancellationToken cancellationToken) - { - Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); - Debug.Assert(CanWrite, "_parent.CanWrite"); - Debug.Assert(_readPos == _readLength, "_readPos == _readLen"); - Debug.Assert(_useAsyncIO, "WriteInternalCoreAsync doesn't work on synchronous file streams!"); - - // Create and store async stream class library specific data in the async result - FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, 0, source); - NativeOverlapped* intOverlapped = completionSource.Overlapped; - - if (CanSeek) - { - // Make sure we set the length of the file appropriately. - long len = Length; - - // Make sure we are writing to the position that we think we are - VerifyOSHandlePosition(); - - if (_filePosition + source.Length > len) - { - SetLengthCore(_filePosition + source.Length); - } - - // 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(_fileHandle, source.Length, SeekOrigin.Current); - } - - int errorCode = 0; - // queue an async WriteFile operation and pass in a packed overlapped - int r = WriteFileNative(_fileHandle, source.Span, 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) - { - // 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(_fileHandle, 0, SeekOrigin.Current); - } - - completionSource.ReleaseNativeResource(); - - if (errorCode == ERROR_HANDLE_EOF) - { - throw Error.GetEndOfFile(); - } - else - { - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); - } - } - else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING - { - // Only once the IO is pending do we register for cancellation - completionSource.RegisterForCancellation(cancellationToken); - } - } - 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. - } - - return completionSource.Task; - } - - // Windows API definitions, from winbase.h and others - - internal const int GENERIC_READ = unchecked((int)0x80000000); - private const int GENERIC_WRITE = 0x40000000; - - // 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, Span<byte> bytes, NativeOverlapped* overlapped, out int errorCode) - { - Debug.Assert(handle != null, "handle != null"); - Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to ReadFileNative."); - - int r; - int numBytesRead = 0; - - fixed (byte* p = &MemoryMarshal.GetReference(bytes)) - { - r = _useAsyncIO ? - Interop.Kernel32.ReadFile(handle, p, bytes.Length, IntPtr.Zero, overlapped) : - Interop.Kernel32.ReadFile(handle, p, bytes.Length, out numBytesRead, IntPtr.Zero); - } - - if (r == 0) - { - errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(); - return -1; - } - else - { - errorCode = 0; - return numBytesRead; - } - } - - private unsafe int WriteFileNative(SafeFileHandle handle, ReadOnlySpan<byte> buffer, NativeOverlapped* overlapped, out int errorCode) - { - Debug.Assert(handle != null, "handle != null"); - Debug.Assert((_useAsyncIO && overlapped != null) || (!_useAsyncIO && overlapped == null), "Async IO and overlapped parameters inconsistent in call to WriteFileNative."); - - int numBytesWritten = 0; - int r; - - fixed (byte* p = &MemoryMarshal.GetReference(buffer)) - { - r = _useAsyncIO ? - Interop.Kernel32.WriteFile(handle, p, buffer.Length, IntPtr.Zero, overlapped) : - Interop.Kernel32.WriteFile(handle, p, buffer.Length, out numBytesWritten, IntPtr.Zero); - } - - if (r == 0) - { - errorCode = GetLastWin32ErrorAndDisposeHandleIfInvalid(); - return -1; - } - else - { - errorCode = 0; - return numBytesWritten; - } - } - - private int GetLastWin32ErrorAndDisposeHandleIfInvalid() - { - 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(); - } - - 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(new ReadOnlyMemory<byte>(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); - - // 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.UnsafeRegister(s => - { - Debug.Assert(s is AsyncCopyToAwaitable); - 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, 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, _path); - } - } - - // 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 - 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, _path); - } - - // 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; - } - } - 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(new ReadOnlyMemory<byte>(copyBuffer, 0, (int)readAwaitable._numBytes), cancellationToken).ConfigureAwait(false); - } - } - finally - { - // Cleanup from the whole copy operation - cancellationReg.Dispose(); - awaitableOverlapped.Dispose(); - - ArrayPool<byte>.Shared.Return(copyBuffer); - - // Make sure the stream's current position reflects where we ended up - if (!_fileHandle.IsClosed && CanSeek) - { - SeekCore(_fileHandle, 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 static readonly 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 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 static void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP) - { - var awaitable = (AsyncCopyToAwaitable?)ThreadPoolBoundHandle.GetNativeOverlappedState(pOVERLAP); - Debug.Assert(awaitable != null); - - Debug.Assert(!ReferenceEquals(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 => ReferenceEquals(_continuation, s_sentinel); - public void GetResult() { } - public void OnCompleted(Action continuation) => UnsafeOnCompleted(continuation); - public void UnsafeOnCompleted(Action continuation) - { - if (ReferenceEquals(_continuation, s_sentinel) || - Interlocked.CompareExchange(ref _continuation, continuation, null) != null) - { - Debug.Assert(ReferenceEquals(_continuation, s_sentinel), $"Expected continuation set to s_sentinel, got ${_continuation}"); - Task.Run(continuation); - } - } - } - - private Task FlushAsyncInternal(CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - if (_fileHandle.IsClosed) - throw Error.GetFileNotOpen(); - - // TODO: https://github.com/dotnet/corefx/issues/32837 (stop doing this synchronous work!!). - // 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); - } - - return Task.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(_path); - } - } - - 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(_path); - } - } - - private SafeFileHandle ValidateFileHandle(SafeFileHandle fileHandle) - { - 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!.Length == PathInternal.GetRootLength(_path)) - errorCode = Interop.Errors.ERROR_ACCESS_DENIED; - - throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); - } - - fileHandle.IsAsync = _useAsyncIO; - return fileHandle; - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileStream.cs deleted file mode 100644 index 05b97e1bcc6..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileStream.cs +++ /dev/null @@ -1,915 +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.Runtime.Serialization; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Win32.SafeHandles; - -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; // only ever null if ctor throws - - /// <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>cached task for read ops that complete synchronously</summary> - private Task<int>? _lastSynchronouslyCompletedTask = null; - - /// <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; - - /// <summary>Caches whether Serialization Guard has been disabled for file writes</summary> - private static int s_cachedSerializationSwitch = 0; - - [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. https://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. https://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. https://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. https://go.microsoft.com/fwlink/?linkid=14202")] - public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync) - { - SafeFileHandle safeHandle = new SafeFileHandle(handle, ownsHandle: ownsHandle); - try - { - ValidateAndInitFromHandle(safeHandle, access, bufferSize, isAsync); - } - catch - { - // We don't want to take ownership of closing passed in handles - // *unless* the constructor completes successfully. - GC.SuppressFinalize(safeHandle); - - // This would also prevent Close from being called, but is unnecessary - // as we've removed the object from the finalizer queue. - // - // safeHandle.SetHandleAsInvalid(); - throw; - } - - // Note: Cleaner to set the following fields in ValidateAndInitFromHandle, - // but we can't as they're readonly. - _access = access; - _useAsyncIO = isAsync; - - // As the handle was passed in, we must set the handle field at the very end to - // avoid the finalizer closing the handle when we throw errors. - _fileHandle = safeHandle; - } - - public FileStream(SafeFileHandle handle, FileAccess access) - : this(handle, access, DefaultBufferSize) - { - } - - public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) - : this(handle, access, bufferSize, GetDefaultIsAsync(handle)) - { - } - - private void ValidateAndInitFromHandle(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.GetValueOrDefault()) - throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle)); - - _exposedHandle = true; - _bufferLength = bufferSize; - - InitFromHandle(handle, access, isAsync); - } - - public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) - { - ValidateAndInitFromHandle(handle, access, bufferSize, isAsync); - - // Note: Cleaner to set the following fields in ValidateAndInitFromHandle, - // but we can't as they're readonly. - _access = access; - _useAsyncIO = isAsync; - - // As the handle was passed in, we must set the handle field at the very end to - // avoid the finalizer closing the handle when we throw errors. - _fileHandle = 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; - - if ((access & FileAccess.Write) == FileAccess.Write) - { - SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch); - } - - _fileHandle = OpenHandle(mode, share, options); - - try - { - Init(mode, share, path); - } - catch - { - // If anything goes wrong while setting up the stream, make sure we deterministically dispose - // of the opened handle. - _fileHandle.Dispose(); - _fileHandle = null!; - throw; - } - } - - [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. https://go.microsoft.com/fwlink/?linkid=14202")] - public virtual IntPtr Handle => 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 int Read(byte[] array, int offset, int count) - { - ValidateReadWriteArgs(array, offset, count); - return _useAsyncIO ? - ReadAsyncTask(array, offset, count, CancellationToken.None).GetAwaiter().GetResult() : - ReadSpan(new Span<byte>(array, offset, count)); - } - - public override int Read(Span<byte> buffer) - { - if (GetType() == typeof(FileStream) && !_useAsyncIO) - { - if (_fileHandle.IsClosed) - { - throw Error.GetFileNotOpen(); - } - return ReadSpan(buffer); - } - else - { - // This type is derived from FileStream and/or the stream is in async mode. If this is a - // derived type, it may have overridden Read(byte[], int, int) prior to this Read(Span<byte>) - // overload being introduced. In that case, this Read(Span<byte>) overload should use the behavior - // of Read(byte[],int,int) overload. Or if the stream is in async mode, we can't call the - // synchronous ReadSpan, so we similarly call the base Read, which will turn delegate to - // Read(byte[],int,int), which will do the right thing if we're in async mode. - return base.Read(buffer); - } - } - - 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 (GetType() != typeof(FileStream)) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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. - return base.ReadAsync(buffer, offset, count, cancellationToken); - } - - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled<int>(cancellationToken); - - if (IsClosed) - throw Error.GetFileNotOpen(); - - if (!_useAsyncIO) - { - // If we weren't opened for asynchronous I/O, we still call to the base implementation so that - // Read is invoked asynchronously. But we can do so using the base Stream's internal helper - // that bypasses delegating to BeginRead, since we already know this is FileStream rather - // than something derived from it and what our BeginRead implementation is going to do. - return (Task<int>)base.BeginReadInternal(buffer, offset, count, null, null, serializeAsynchronously: true, apm: false); - } - - return ReadAsyncTask(buffer, offset, count, cancellationToken); - } - - public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) - { - if (GetType() != typeof(FileStream)) - { - // If this isn't a concrete FileStream, a derived type may have overridden ReadAsync(byte[],...), - // which was introduced first, so delegate to the base which will delegate to that. - return base.ReadAsync(buffer, cancellationToken); - } - - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)); - } - - if (IsClosed) - { - throw Error.GetFileNotOpen(); - } - - if (!_useAsyncIO) - { - // If we weren't opened for asynchronous I/O, we still call to the base implementation so that - // Read is invoked asynchronously. But if we have a byte[], we can do so using the base Stream's - // internal helper that bypasses delegating to BeginRead, since we already know this is FileStream - // rather than something derived from it and what our BeginRead implementation is going to do. - return MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment) ? - new ValueTask<int>((Task<int>)base.BeginReadInternal(segment.Array!, segment.Offset, segment.Count, null, null, serializeAsynchronously: true, apm: false)) : - base.ReadAsync(buffer, cancellationToken); - } - - Task<int>? t = ReadAsyncInternal(buffer, cancellationToken, out int synchronousResult); - return t != null ? - new ValueTask<int>(t) : - new ValueTask<int>(synchronousResult); - } - - private Task<int> ReadAsyncTask(byte[] array, int offset, int count, CancellationToken cancellationToken) - { - Task<int>? t = ReadAsyncInternal(new Memory<byte>(array, offset, count), cancellationToken, out int synchronousResult); - - if (t == null) - { - t = _lastSynchronouslyCompletedTask; - Debug.Assert(t == null || t.IsCompletedSuccessfully, "Cached task should have completed successfully"); - - if (t == null || t.Result != synchronousResult) - { - _lastSynchronouslyCompletedTask = t = Task.FromResult(synchronousResult); - } - } - - return t; - } - - public override void Write(byte[] array, int offset, int count) - { - ValidateReadWriteArgs(array, offset, count); - if (_useAsyncIO) - { - WriteAsyncInternal(new ReadOnlyMemory<byte>(array, offset, count), CancellationToken.None).GetAwaiter().GetResult(); - } - else - { - WriteSpan(new ReadOnlySpan<byte>(array, offset, count)); - } - } - - public override void Write(ReadOnlySpan<byte> buffer) - { - if (GetType() == typeof(FileStream) && !_useAsyncIO) - { - if (_fileHandle.IsClosed) - { - throw Error.GetFileNotOpen(); - } - WriteSpan(buffer); - } - else - { - // This type is derived from FileStream and/or the stream is in async mode. If this is a - // derived type, it may have overridden Write(byte[], int, int) prior to this Write(ReadOnlySpan<byte>) - // overload being introduced. In that case, this Write(ReadOnlySpan<byte>) overload should use the behavior - // of Write(byte[],int,int) overload. Or if the stream is in async mode, we can't call the - // synchronous WriteSpan, so we similarly call the base Write, which will turn delegate to - // Write(byte[],int,int), which will do the right thing if we're in async mode. - base.Write(buffer); - } - } - - 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 (GetType() != typeof(FileStream)) - { - // 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. - return base.WriteAsync(buffer, offset, count, cancellationToken); - } - - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - if (IsClosed) - throw Error.GetFileNotOpen(); - - if (!_useAsyncIO) - { - // If we weren't opened for asynchronous I/O, we still call to the base implementation so that - // Write is invoked asynchronously. But we can do so using the base Stream's internal helper - // that bypasses delegating to BeginWrite, since we already know this is FileStream rather - // than something derived from it and what our BeginWrite implementation is going to do. - return (Task)base.BeginWriteInternal(buffer, offset, count, null, null, serializeAsynchronously: true, apm: false); - } - - return WriteAsyncInternal(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask(); - } - - public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) - { - if (GetType() != typeof(FileStream)) - { - // If this isn't a concrete FileStream, a derived type may have overridden WriteAsync(byte[],...), - // which was introduced first, so delegate to the base which will delegate to that. - return base.WriteAsync(buffer, cancellationToken); - } - - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask(Task.FromCanceled<int>(cancellationToken)); - } - - if (IsClosed) - { - throw Error.GetFileNotOpen(); - } - - if (!_useAsyncIO) - { - // If we weren't opened for asynchronous I/O, we still call to the base implementation so that - // Write is invoked asynchronously. But if we have a byte[], we can do so using the base Stream's - // internal helper that bypasses delegating to BeginWrite, since we already know this is FileStream - // rather than something derived from it and what our BeginWrite implementation is going to do. - return MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> segment) ? - new ValueTask((Task)BeginWriteInternal(segment.Array!, segment.Offset, segment.Count, null, null, serializeAsynchronously: true, apm: false)) : - base.WriteAsync(buffer, cancellationToken); - } - - return WriteAsyncInternal(buffer, 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 => !_fileHandle.IsClosed && (_access & FileAccess.Read) != 0; - - /// <summary>Gets a value indicating whether the current stream supports writing.</summary> - public override bool CanWrite => !_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 => _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 => _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(_fileHandle, 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; - - private static bool IsIoRelatedException(Exception e) => - // These all derive from IOException - // DirectoryNotFoundException - // DriveNotFoundException - // EndOfStreamException - // FileLoadException - // FileNotFoundException - // PathTooLongException - // PipeException - e is IOException || - // Note that SecurityException is only thrown on runtimes that support CAS - // e is SecurityException || - e is UnauthorizedAccessException || - e is NotSupportedException || - (e is ArgumentException && !(e is ArgumentNullException)); - - /// <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(_fileHandle, rewind, SeekOrigin.Current); - } - _readPos = _readLength = 0; - } - - /// <summary> - /// Reads a byte from the file stream. Returns the byte cast to an int - /// or -1 if reading from the end of the stream. - /// </summary> - public override int ReadByte() - { - PrepareForReading(); - - byte[] buffer = GetBuffer(); - if (_readPos == _readLength) - { - FlushWriteBuffer(); - _readLength = FillReadBufferForReadByte(); - _readPos = 0; - if (_readLength == 0) - { - return -1; - } - } - - return buffer[_readPos++]; - } - - /// <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) - { - PrepareForWriting(); - - // Flush the write buffer if it's full - if (_writePos == _bufferLength) - FlushWriteBufferForWriteByte(); - - // 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(ReadAsyncTask(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(new ReadOnlyMemory<byte>(array, offset, numBytes), CancellationToken.None).AsTask(), 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/netcore/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs b/netcore/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs deleted file mode 100644 index 7345afec3f1..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/FileStreamCompletionSource.Win32.cs +++ /dev/null @@ -1,259 +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.ExceptionServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -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. - private unsafe 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 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) - protected FileStreamCompletionSource(FileStream stream, int numBufferedBytes, byte[]? bytes) - : base(TaskCreationOptions.RunContinuationsAsynchronously) - { - _numBufferedBytes = numBufferedBytes; - _stream = stream; - _result = NoResult; - - // Create the native overlapped. We try to use the preallocated overlapped if possible: it's possible if the byte - // buffer is null (there's nothing to pin) or the same one that's associated with the preallocated overlapped (and - // thus is already pinned) 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. - Debug.Assert(bytes == null || ReferenceEquals(bytes, _stream._buffer)); - - // The _preallocatedOverlapped is null if the internal buffer was never created, so we check for - // a non-null bytes before using the stream's _preallocatedOverlapped - _overlapped = bytes != null && _stream.CompareExchangeCurrentOverlappedOwner(this, null) == null ? - _stream._fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(_stream._preallocatedOverlapped!) : // allocated when buffer was created, and buffer is non-null - _stream._fileHandle.ThreadPoolBinding!.AllocateNativeOverlapped(s_ioCallback, this, bytes); - Debug.Assert(_overlapped != null, "AllocateNativeOverlapped returned null"); - } - - internal NativeOverlapped* Overlapped => _overlapped; - - public void SetCompletedSynchronously(int numBytes) - { - ReleaseNativeResource(); - TrySetResult(numBytes + _numBufferedBytes); - } - - public void RegisterForCancellation(CancellationToken cancellationToken) - { -#if DEBUG - Debug.Assert(cancellationToken.CanBeCanceled); - Debug.Assert(!_cancellationHasBeenRegistered, "Cannot register for cancellation twice"); - _cancellationHasBeenRegistered = true; -#endif - - // Quick check to make sure the IO hasn't completed - if (_overlapped != null) - { - Action<object?>? cancelCallback = s_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.UnsafeRegister(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 virtual 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 void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) - { - // Extract the completion source from the overlapped. The state in the overlapped - // will either be a FileStream (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 FileStreamCompletionSource that's completing (in the case where the preallocated - // overlapped was already in use by another operation). - object? state = ThreadPoolBoundHandle.GetNativeOverlappedState(pOverlapped); - Debug.Assert(state is FileStream || state is FileStreamCompletionSource); - FileStreamCompletionSource completionSource = state is FileStream fs ? - fs._currentOverlappedOwner! : // must be owned - (FileStreamCompletionSource)state!; - Debug.Assert(completionSource != null); - 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 - CancellationToken cancellationToken = _cancellationRegistration.Token; // access before disposing 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 - { - Exception e = Win32Marshal.GetExceptionForWin32Error(errorCode); - e.SetCurrentStackTrace(); - TrySetException(e); - } - } - 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) - - Debug.Assert(state is FileStreamCompletionSource, "Unknown state passed to cancellation"); - FileStreamCompletionSource completionSource = (FileStreamCompletionSource)state; - 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); - } - } - } - - public static FileStreamCompletionSource Create(FileStream stream, int numBufferedBytesRead, ReadOnlyMemory<byte> memory) - { - // If the memory passed in is the stream's internal buffer, we can use the base FileStreamCompletionSource, - // which has a PreAllocatedOverlapped with the memory already pinned. Otherwise, we use the derived - // MemoryFileStreamCompletionSource, which Retains the memory, which will result in less pinning in the case - // where the underlying memory is backed by pre-pinned buffers. - return MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> buffer) && ReferenceEquals(buffer.Array, stream._buffer) ? - new FileStreamCompletionSource(stream, numBufferedBytesRead, buffer.Array) : - new MemoryFileStreamCompletionSource(stream, numBufferedBytesRead, memory); - } - } - - /// <summary> - /// Extends <see cref="FileStreamCompletionSource"/> with to support disposing of a - /// <see cref="MemoryHandle"/> when the operation has completed. This should only be used - /// when memory doesn't wrap a byte[]. - /// </summary> - private sealed class MemoryFileStreamCompletionSource : FileStreamCompletionSource - { - private MemoryHandle _handle; // mutable struct; do not make this readonly - - internal MemoryFileStreamCompletionSource(FileStream stream, int numBufferedBytes, ReadOnlyMemory<byte> memory) : - base(stream, numBufferedBytes, bytes: null) // this type handles the pinning, so null is passed for bytes - { - _handle = memory.Pin(); - } - - internal override void ReleaseNativeResource() - { - _handle.Dispose(); - base.ReleaseNativeResource(); - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/IOException.cs b/netcore/System.Private.CoreLib/shared/System/IO/IOException.cs deleted file mode 100644 index a22cc3bc48f..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/IOException.cs +++ /dev/null @@ -1,41 +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.Runtime.Serialization; - -namespace System.IO -{ - [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public class IOException : SystemException - { - public IOException() - : base(SR.Arg_IOException) - { - HResult = HResults.COR_E_IO; - } - - public IOException(string? message) - : base(message) - { - HResult = HResults.COR_E_IO; - } - - public IOException(string? message, int hresult) - : base(message) - { - HResult = hresult; - } - - public IOException(string? message, Exception? innerException) - : base(message, innerException) - { - HResult = HResults.COR_E_IO; - } - - protected IOException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/MemoryStream.cs b/netcore/System.Private.CoreLib/shared/System/IO/MemoryStream.cs deleted file mode 100644 index b8ff126e895..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/MemoryStream.cs +++ /dev/null @@ -1,868 +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.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO -{ - // A MemoryStream represents a Stream in memory (ie, it has no backing store). - // This stream may reduce the need for temporary buffers and files in - // an application. - // - // There are two ways to create a MemoryStream. You can initialize one - // from an unsigned byte array, or you can create an empty one. Empty - // memory streams are resizable, while ones created with a byte array provide - // a stream "view" of the data. - public class MemoryStream : Stream - { - private byte[] _buffer; // Either allocated internally or externally. - private readonly int _origin; // For user-provided arrays, start at this origin - private int _position; // read/write head. - private int _length; // Number of bytes within the memory stream - private int _capacity; // length of usable portion of buffer for stream - // Note that _capacity == _buffer.Length for non-user-provided byte[]'s - - private bool _expandable; // User-provided buffers aren't expandable. - private bool _writable; // Can user write to this stream? - private readonly bool _exposable; // Whether the array can be returned to the user. - private bool _isOpen; // Is this stream open or closed? - - private Task<int>? _lastReadTask; // The last successful task returned from ReadAsync - - private const int MemStreamMaxLength = int.MaxValue; - - public MemoryStream() - : this(0) - { - } - - public MemoryStream(int capacity) - { - if (capacity < 0) - throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NegativeCapacity); - - _buffer = capacity != 0 ? new byte[capacity] : Array.Empty<byte>(); - _capacity = capacity; - _expandable = true; - _writable = true; - _exposable = true; - _origin = 0; // Must be 0 for byte[]'s created by MemoryStream - _isOpen = true; - } - - public MemoryStream(byte[] buffer) - : this(buffer, true) - { - } - - public MemoryStream(byte[] buffer, bool writable) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - - _buffer = buffer; - _length = _capacity = buffer.Length; - _writable = writable; - _exposable = false; - _origin = 0; - _isOpen = true; - } - - public MemoryStream(byte[] buffer, int index, int count) - : this(buffer, index, count, true, false) - { - } - - public MemoryStream(byte[] buffer, int index, int count, bool writable) - : this(buffer, index, count, writable, false) - { - } - - public MemoryStream(byte[] buffer, int index, int count, bool writable, bool publiclyVisible) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - if (count < 0) - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - if (buffer.Length - index < count) - throw new ArgumentException(SR.Argument_InvalidOffLen); - - _buffer = buffer; - _origin = _position = index; - _length = _capacity = index + count; - _writable = writable; - _exposable = publiclyVisible; // Can TryGetBuffer/GetBuffer return the array? - _expandable = false; - _isOpen = true; - } - - public override bool CanRead => _isOpen; - - public override bool CanSeek => _isOpen; - - public override bool CanWrite => _writable; - - private void EnsureNotClosed() - { - if (!_isOpen) - throw Error.GetStreamIsClosed(); - } - - private void EnsureWriteable() - { - if (!CanWrite) - throw Error.GetWriteNotSupported(); - } - - protected override void Dispose(bool disposing) - { - try - { - if (disposing) - { - _isOpen = false; - _writable = false; - _expandable = false; - // Don't set buffer to null - allow TryGetBuffer, GetBuffer & ToArray to work. - _lastReadTask = null; - } - } - finally - { - // Call base.Close() to cleanup async IO resources - base.Dispose(disposing); - } - } - - // returns a bool saying whether we allocated a new array. - private bool EnsureCapacity(int value) - { - // Check for overflow - if (value < 0) - throw new IOException(SR.IO_StreamTooLong); - - if (value > _capacity) - { - int newCapacity = Math.Max(value, 256); - - // We are ok with this overflowing since the next statement will deal - // with the cases where _capacity*2 overflows. - if (newCapacity < _capacity * 2) - { - newCapacity = _capacity * 2; - } - - // We want to expand the array up to Array.MaxByteArrayLength - // And we want to give the user the value that they asked for - if ((uint)(_capacity * 2) > Array.MaxByteArrayLength) - { - newCapacity = Math.Max(value, Array.MaxByteArrayLength); - } - - Capacity = newCapacity; - return true; - } - return false; - } - - public override void Flush() - { - } - - public override Task FlushAsync(CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - try - { - Flush(); - return Task.CompletedTask; - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - - public virtual byte[] GetBuffer() - { - if (!_exposable) - throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer); - return _buffer; - } - - public virtual bool TryGetBuffer(out ArraySegment<byte> buffer) - { - if (!_exposable) - { - buffer = default; - return false; - } - - buffer = new ArraySegment<byte>(_buffer, offset: _origin, count: _length - _origin); - return true; - } - - // -------------- PERF: Internal functions for fast direct access of MemoryStream buffer (cf. BinaryReader for usage) --------------- - - // PERF: Internal sibling of GetBuffer, always returns a buffer (cf. GetBuffer()) - internal byte[] InternalGetBuffer() - { - return _buffer; - } - - // PERF: True cursor position, we don't need _origin for direct access - internal int InternalGetPosition() - { - return _position; - } - - // PERF: Expose internal buffer for BinaryReader instead of going via the regular Stream interface which requires to copy the data out - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ReadOnlySpan<byte> InternalReadSpan(int count) - { - EnsureNotClosed(); - - int origPos = _position; - int newPos = origPos + count; - - if ((uint)newPos > (uint)_length) - { - _position = _length; - throw Error.GetEndOfFile(); - } - - var span = new ReadOnlySpan<byte>(_buffer, origPos, count); - _position = newPos; - return span; - } - - // PERF: Get actual length of bytes available for read; do sanity checks; shift position - i.e. everything except actual copying bytes - internal int InternalEmulateRead(int count) - { - EnsureNotClosed(); - - int n = _length - _position; - if (n > count) - n = count; - if (n < 0) - n = 0; - - Debug.Assert(_position + n >= 0, "_position + n >= 0"); // len is less than 2^31 -1. - _position += n; - return n; - } - - // Gets & sets the capacity (number of bytes allocated) for this stream. - // The capacity cannot be set to a value less than the current length - // of the stream. - // - public virtual int Capacity - { - get - { - EnsureNotClosed(); - return _capacity - _origin; - } - set - { - // Only update the capacity if the MS is expandable and the value is different than the current capacity. - // Special behavior if the MS isn't expandable: we don't throw if value is the same as the current capacity - if (value < Length) - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_SmallCapacity); - - EnsureNotClosed(); - - if (!_expandable && (value != Capacity)) - throw new NotSupportedException(SR.NotSupported_MemStreamNotExpandable); - - // MemoryStream has this invariant: _origin > 0 => !expandable (see ctors) - if (_expandable && value != _capacity) - { - if (value > 0) - { - byte[] newBuffer = new byte[value]; - if (_length > 0) - { - Buffer.BlockCopy(_buffer, 0, newBuffer, 0, _length); - } - _buffer = newBuffer; - } - else - { - _buffer = Array.Empty<byte>(); - } - _capacity = value; - } - } - } - - public override long Length - { - get - { - EnsureNotClosed(); - return _length - _origin; - } - } - - public override long Position - { - get - { - EnsureNotClosed(); - return _position - _origin; - } - set - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum); - - EnsureNotClosed(); - - if (value > MemStreamMaxLength) - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength); - _position = _origin + (int)value; - } - } - - public override int Read(byte[] buffer, int offset, int count) - { - 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); - - EnsureNotClosed(); - - int n = _length - _position; - if (n > count) - n = count; - if (n <= 0) - return 0; - - Debug.Assert(_position + n >= 0, "_position + n >= 0"); // len is less than 2^31 -1. - - if (n <= 8) - { - int byteCount = n; - while (--byteCount >= 0) - buffer[offset + byteCount] = _buffer[_position + byteCount]; - } - else - Buffer.BlockCopy(_buffer, _position, buffer, offset, n); - _position += n; - - return n; - } - - public override int Read(Span<byte> buffer) - { - if (GetType() != typeof(MemoryStream)) - { - // MemoryStream is not sealed, and a derived type may have overridden Read(byte[], int, int) prior - // to this Read(Span<byte>) overload being introduced. In that case, this Read(Span<byte>) overload - // should use the behavior of Read(byte[],int,int) overload. - return base.Read(buffer); - } - - EnsureNotClosed(); - - int n = Math.Min(_length - _position, buffer.Length); - if (n <= 0) - return 0; - - // TODO https://github.com/dotnet/coreclr/issues/15076: - // Read(byte[], int, int) has an n <= 8 optimization, presumably based - // on benchmarking. Determine if/where such a cut-off is here and add - // an equivalent optimization if necessary. - new Span<byte>(_buffer, _position, n).CopyTo(buffer); - - _position += n; - return n; - } - - 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); - - // If cancellation was requested, bail early - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled<int>(cancellationToken); - - try - { - int n = Read(buffer, offset, count); - Task<int>? t = _lastReadTask; - Debug.Assert(t == null || t.Status == TaskStatus.RanToCompletion, - "Expected that a stored last task completed successfully"); - return (t != null && t.Result == n) ? t : (_lastReadTask = Task.FromResult<int>(n)); - } - catch (OperationCanceledException oce) - { - return Task.FromCanceled<int>(oce); - } - catch (Exception exception) - { - return Task.FromException<int>(exception); - } - } - - public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)); - } - - try - { - // ReadAsync(Memory<byte>,...) needs to delegate to an existing virtual to do the work, in case an existing derived type - // has changed or augmented the logic associated with reads. If the Memory wraps an array, we could delegate to - // ReadAsync(byte[], ...), but that would defeat part of the purpose, as ReadAsync(byte[], ...) often needs to allocate - // a Task<int> for the return value, so we want to delegate to one of the synchronous methods. We could always - // delegate to the Read(Span<byte>) method, and that's the most efficient solution when dealing with a concrete - // MemoryStream, but if we're dealing with a type derived from MemoryStream, Read(Span<byte>) will end up delegating - // to Read(byte[], ...), which requires it to get a byte[] from ArrayPool and copy the data. So, we special-case the - // very common case of the Memory<byte> wrapping an array: if it does, we delegate to Read(byte[], ...) with it, - // as that will be efficient in both cases, and we fall back to Read(Span<byte>) if the Memory<byte> wrapped something - // else; if this is a concrete MemoryStream, that'll be efficient, and only in the case where the Memory<byte> wrapped - // something other than an array and this is a MemoryStream-derived type that doesn't override Read(Span<byte>) will - // it then fall back to doing the ArrayPool/copy behavior. - return new ValueTask<int>( - MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> destinationArray) ? - Read(destinationArray.Array!, destinationArray.Offset, destinationArray.Count) : - Read(buffer.Span)); - } - catch (OperationCanceledException oce) - { - return new ValueTask<int>(Task.FromCanceled<int>(oce)); - } - catch (Exception exception) - { - return new ValueTask<int>(Task.FromException<int>(exception)); - } - } - - public override int ReadByte() - { - EnsureNotClosed(); - - if (_position >= _length) - return -1; - - return _buffer[_position++]; - } - - public override void CopyTo(Stream destination, int bufferSize) - { - // Since we did not originally override this method, validate the arguments - // the same way Stream does for back-compat. - StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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) when we are not sure. - if (GetType() != typeof(MemoryStream)) - { - base.CopyTo(destination, bufferSize); - return; - } - - int originalPosition = _position; - - // Seek to the end of the MemoryStream. - int remaining = InternalEmulateRead(_length - originalPosition); - - // If we were already at or past the end, there's no copying to do so just quit. - if (remaining > 0) - { - // Call Write() on the other Stream, using our internal buffer and avoiding any - // intermediary allocations. - destination.Write(_buffer, originalPosition, remaining); - } - } - - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - // This implementation offers better performance compared to the base class version. - - StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to 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 ReadAsync) when we are not sure. - if (GetType() != typeof(MemoryStream)) - return base.CopyToAsync(destination, bufferSize, cancellationToken); - - // If canceled - return fast: - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - // Avoid copying data from this buffer into a temp buffer: - // (require that InternalEmulateRead does not throw, - // otherwise it needs to be wrapped into try-catch-Task.FromException like memStrDest.Write below) - - int pos = _position; - int n = InternalEmulateRead(_length - _position); - - // If we were already at or past the end, there's no copying to do so just quit. - if (n == 0) - return Task.CompletedTask; - - // If destination is not a memory stream, write there asynchronously: - if (!(destination is MemoryStream memStrDest)) - return destination.WriteAsync(_buffer, pos, n, cancellationToken); - - try - { - // If destination is a MemoryStream, CopyTo synchronously: - memStrDest.Write(_buffer, pos, n); - return Task.CompletedTask; - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - - public override void CopyTo(ReadOnlySpanAction<byte, object?> callback, object? state, int bufferSize) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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) when we are not sure. - if (GetType() != typeof(MemoryStream)) - { - base.CopyTo(callback, state, bufferSize); - return; - } - - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - // Retrieve a span until the end of the MemoryStream. - ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(_buffer, _position, _length - _position); - _position = _length; - - // Invoke the callback, using our internal span and avoiding any - // intermediary allocations. - callback(span, state); - } - - public override Task CopyToAsync(Func<ReadOnlyMemory<byte>, object?, CancellationToken, ValueTask> callback, object? state, int bufferSize, CancellationToken cancellationToken) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to 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 ReadAsync) when we are not sure. - if (GetType() != typeof(MemoryStream)) - return base.CopyToAsync(callback, state, bufferSize, cancellationToken); - - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - // If canceled - return fast: - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - // Avoid copying data from this buffer into a temp buffer - ReadOnlyMemory<byte> memory = new ReadOnlyMemory<byte>(_buffer, _position, _length - _position); - _position = _length; - - return callback(memory, state, cancellationToken).AsTask(); - } - - public override long Seek(long offset, SeekOrigin loc) - { - EnsureNotClosed(); - - if (offset > MemStreamMaxLength) - throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_StreamLength); - - switch (loc) - { - case SeekOrigin.Begin: - { - int tempPosition = unchecked(_origin + (int)offset); - if (offset < 0 || tempPosition < _origin) - throw new IOException(SR.IO_SeekBeforeBegin); - _position = tempPosition; - break; - } - case SeekOrigin.Current: - { - int tempPosition = unchecked(_position + (int)offset); - if (unchecked(_position + offset) < _origin || tempPosition < _origin) - throw new IOException(SR.IO_SeekBeforeBegin); - _position = tempPosition; - break; - } - case SeekOrigin.End: - { - int tempPosition = unchecked(_length + (int)offset); - if (unchecked(_length + offset) < _origin || tempPosition < _origin) - throw new IOException(SR.IO_SeekBeforeBegin); - _position = tempPosition; - break; - } - default: - throw new ArgumentException(SR.Argument_InvalidSeekOrigin); - } - - Debug.Assert(_position >= 0, "_position >= 0"); - return _position; - } - - // Sets the length of the stream to a given value. The new - // value must be nonnegative and less than the space remaining in - // the array, int.MaxValue - origin - // Origin is 0 in all cases other than a MemoryStream created on - // top of an existing array and a specific starting offset was passed - // into the MemoryStream constructor. The upper bounds prevents any - // situations where a stream may be created on top of an array then - // the stream is made longer than the maximum possible length of the - // array (int.MaxValue). - // - public override void SetLength(long value) - { - if (value < 0 || value > int.MaxValue) - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength); - - EnsureWriteable(); - - // Origin wasn't publicly exposed above. - Debug.Assert(MemStreamMaxLength == int.MaxValue); // Check parameter validation logic in this method if this fails. - if (value > (int.MaxValue - _origin)) - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_StreamLength); - - int newLength = _origin + (int)value; - bool allocatedNewArray = EnsureCapacity(newLength); - if (!allocatedNewArray && newLength > _length) - Array.Clear(_buffer, _length, newLength - _length); - _length = newLength; - if (_position > newLength) - _position = newLength; - } - - public virtual byte[] ToArray() - { - int count = _length - _origin; - if (count == 0) - return Array.Empty<byte>(); - byte[] copy = new byte[count]; - Buffer.BlockCopy(_buffer, _origin, copy, 0, count); - return copy; - } - - public override void Write(byte[] buffer, int offset, int count) - { - 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); - - EnsureNotClosed(); - EnsureWriteable(); - - int i = _position + count; - // Check for overflow - if (i < 0) - throw new IOException(SR.IO_StreamTooLong); - - if (i > _length) - { - bool mustZero = _position > _length; - if (i > _capacity) - { - bool allocatedNewArray = EnsureCapacity(i); - if (allocatedNewArray) - { - mustZero = false; - } - } - if (mustZero) - { - Array.Clear(_buffer, _length, i - _length); - } - _length = i; - } - if ((count <= 8) && (buffer != _buffer)) - { - int byteCount = count; - while (--byteCount >= 0) - { - _buffer[_position + byteCount] = buffer[offset + byteCount]; - } - } - else - { - Buffer.BlockCopy(buffer, offset, _buffer, _position, count); - } - _position = i; - } - - public override void Write(ReadOnlySpan<byte> buffer) - { - if (GetType() != typeof(MemoryStream)) - { - // MemoryStream is not sealed, and a derived type may have overridden Write(byte[], int, int) prior - // to this Write(Span<byte>) overload being introduced. In that case, this Write(Span<byte>) overload - // should use the behavior of Write(byte[],int,int) overload. - base.Write(buffer); - return; - } - - EnsureNotClosed(); - EnsureWriteable(); - - // Check for overflow - int i = _position + buffer.Length; - if (i < 0) - throw new IOException(SR.IO_StreamTooLong); - - if (i > _length) - { - bool mustZero = _position > _length; - if (i > _capacity) - { - bool allocatedNewArray = EnsureCapacity(i); - if (allocatedNewArray) - { - mustZero = false; - } - } - if (mustZero) - { - Array.Clear(_buffer, _length, i - _length); - } - _length = i; - } - - buffer.CopyTo(new Span<byte>(_buffer, _position, buffer.Length)); - _position = i; - } - - 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); - - // If cancellation is already requested, bail early - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - try - { - Write(buffer, offset, count); - return Task.CompletedTask; - } - catch (OperationCanceledException oce) - { - return Task.FromCanceled(oce); - } - catch (Exception exception) - { - return Task.FromException(exception); - } - } - - public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask(Task.FromCanceled(cancellationToken)); - } - - try - { - // See corresponding comment in ReadAsync for why we don't just always use Write(ReadOnlySpan<byte>). - // Unlike ReadAsync, we could delegate to WriteAsync(byte[], ...) here, but we don't for consistency. - if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> sourceArray)) - { - Write(sourceArray.Array!, sourceArray.Offset, sourceArray.Count); - } - else - { - Write(buffer.Span); - } - return default; - } - catch (OperationCanceledException oce) - { - return new ValueTask(Task.FromCanceled(oce)); - } - catch (Exception exception) - { - return new ValueTask(Task.FromException(exception)); - } - } - - public override void WriteByte(byte value) - { - EnsureNotClosed(); - EnsureWriteable(); - - if (_position >= _length) - { - int newLength = _position + 1; - bool mustZero = _position > _length; - if (newLength >= _capacity) - { - bool allocatedNewArray = EnsureCapacity(newLength); - if (allocatedNewArray) - { - mustZero = false; - } - } - if (mustZero) - { - Array.Clear(_buffer, _length, _position - _length); - } - _length = newLength; - } - _buffer[_position++] = value; - } - - // Writes this MemoryStream to another stream. - public virtual void WriteTo(Stream stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream), SR.ArgumentNull_Stream); - - EnsureNotClosed(); - - stream.Write(_buffer, _origin, _length - _origin); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/Path.Unix.cs b/netcore/System.Private.CoreLib/shared/System/IO/Path.Unix.cs deleted file mode 100644 index 487c880c8e0..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/Path.Unix.cs +++ /dev/null @@ -1,147 +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' }; - - // 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_PathEmpty, nameof(path)); - - if (path.Contains('\0')) - throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(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 = PathInternal.RemoveRelativeSegments(path, PathInternal.GetRootLength(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."); - - string result = collapsedString.Length == 0 ? PathInternal.DirectorySeparatorCharAsString : collapsedString; - - return result; - } - - public static string GetFullPath(string path, string basePath) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - - if (basePath == null) - throw new ArgumentNullException(nameof(basePath)); - - if (!IsPathFullyQualified(basePath)) - throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath)); - - if (basePath.Contains('\0') || path.Contains('\0')) - throw new ArgumentException(SR.Argument_InvalidPathChars); - - if (IsPathFullyQualified(path)) - return GetFullPath(path); - - return GetFullPath(CombineInternal(basePath, 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; - - return IsPathRooted(path.AsSpan()); - } - - public static bool IsPathRooted(ReadOnlySpan<char> path) - { - return path.Length > 0 && path[0] == PathInternal.DirectorySeparatorChar; - } - - /// <summary> - /// Returns the path root or null if path is empty or null. - /// </summary> - public static string? GetPathRoot(string? path) - { - if (PathInternal.IsEffectivelyEmpty(path)) return null; - - return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : string.Empty; - } - - public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path) - { - return PathInternal.IsEffectivelyEmpty(path) && IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan<char>.Empty; - } - - /// <summary>Gets whether the system is case-sensitive.</summary> - internal static bool IsCaseSensitive - { - get - { - #if PLATFORM_OSX - return false; - #else - return true; - #endif - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/Path.Windows.cs b/netcore/System.Private.CoreLib/shared/System/IO/Path.Windows.cs deleted file mode 100644 index 1e2bf76b3b0..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/Path.Windows.cs +++ /dev/null @@ -1,286 +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. - -#nullable enable -using System.Diagnostics; -using System.Text; - -#if MS_IO_REDIST -using System; -using System.IO; - -namespace Microsoft.IO -#else -namespace System.IO -#endif -{ - 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 - }; - - // Expands the given path to a fully qualified path. - public static string GetFullPath(string path) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - - // If the path would normalize to string empty, we'll consider it empty - if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) - throw new ArgumentException(SR.Arg_PathEmpty, nameof(path)); - - // Embedded null characters are the only invalid character case we trully care about. - // This is because the nulls will signal the end of the string to Win32 and therefore have - // unpredictable results. - if (path.Contains('\0')) - throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); - - if (PathInternal.IsExtended(path.AsSpan())) - { - // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\ - // paths and neither should we. Even if we wanted to GetFullPathName does not work - // properly with device paths. If one wants to pass a \\?\ path through normalization - // one can chop off the prefix, pass it to GetFullPath and add it again. - return path; - } - - return PathHelper.Normalize(path); - } - - public static string GetFullPath(string path, string basePath) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - - if (basePath == null) - throw new ArgumentNullException(nameof(basePath)); - - if (!IsPathFullyQualified(basePath)) - throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath)); - - if (basePath.Contains('\0') || path.Contains('\0')) - throw new ArgumentException(SR.Argument_InvalidPathChars); - - if (IsPathFullyQualified(path)) - return GetFullPath(path); - - if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) - return basePath; - - int length = path.Length; - string? combinedPath = null; - - if (length >= 1 && PathInternal.IsDirectorySeparator(path[0])) - { - // Path is current drive rooted i.e. starts with \: - // "\Foo" and "C:\Bar" => "C:\Foo" - // "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo" - combinedPath = Join(GetPathRoot(basePath.AsSpan()), path.AsSpan(1)); // Cut the separator to ensure we don't end up with two separators when joining with the root. - } - else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar) - { - // Drive relative paths - Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2])); - - if (GetVolumeName(path.AsSpan()).EqualsOrdinal(GetVolumeName(basePath.AsSpan()))) - { - // Matching root - // "C:Foo" and "C:\Bar" => "C:\Bar\Foo" - // "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = Join(basePath.AsSpan(), path.AsSpan(2)); - } - else - { - // No matching root, root to specified drive - // "D:Foo" and "C:\Bar" => "D:Foo" - // "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo" - combinedPath = !PathInternal.IsDevice(basePath.AsSpan()) - ? path.Insert(2, @"\") - : length == 2 - ? JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(), @"\".AsSpan()) - : JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(0, 2), @"\".AsSpan(), path.AsSpan(2)); - } - } - else - { - // "Simple" relative path - // "Foo" and "C:\Bar" => "C:\Bar\Foo" - // "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo" - combinedPath = JoinInternal(basePath.AsSpan(), path.AsSpan()); - } - - // Device paths are normalized by definition, so passing something of this format (i.e. \\?\C:\.\tmp, \\.\C:\foo) - // to Windows APIs won't do anything by design. Additionally, GetFullPathName() in Windows doesn't root - // them properly. As such we need to manually remove segments and not use GetFullPath(). - - return PathInternal.IsDevice(combinedPath.AsSpan()) - ? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath.AsSpan())) - : GetFullPath(combinedPath); - } - - public static string GetTempPath() - { - var builder = new ValueStringBuilder(stackalloc char[PathInternal.MaxShortPath]); - - GetTempPath(ref builder); - - string path = PathHelper.Normalize(ref builder); - builder.Dispose(); - return path; - } - - private static void GetTempPath(ref ValueStringBuilder builder) - { - uint result; - while ((result = Interop.Kernel32.GetTempPathW(builder.Capacity, ref builder.GetPinnableReference())) > builder.Capacity) - { - // Reported size is greater than the buffer size. Increase the capacity. - builder.EnsureCapacity(checked((int)result)); - } - - if (result == 0) - throw Win32Marshal.GetExceptionForLastWin32Error(); - - builder.Length = (int)result; - } - - // Returns a unique temporary file name, and creates a 0-byte file by that - // name on disk. - public static string GetTempFileName() - { - var tempPathBuilder = new ValueStringBuilder(stackalloc char[PathInternal.MaxShortPath]); - - GetTempPath(ref tempPathBuilder); - - var builder = new ValueStringBuilder(stackalloc char[PathInternal.MaxShortPath]); - - uint result = Interop.Kernel32.GetTempFileNameW( - ref tempPathBuilder.GetPinnableReference(), "tmp", 0, ref builder.GetPinnableReference()); - - tempPathBuilder.Dispose(); - - if (result == 0) - throw Win32Marshal.GetExceptionForLastWin32Error(); - - builder.Length = builder.RawChars.IndexOf('\0'); - - string path = PathHelper.Normalize(ref builder); - builder.Dispose(); - return path; - } - - // Tests if the given path contains a root. A path is considered rooted - // if it starts with a backslash ("\") or a valid drive letter and a colon (":"). - public static bool IsPathRooted(string? path) - { - return path != null && IsPathRooted(path.AsSpan()); - } - - public static bool IsPathRooted(ReadOnlySpan<char> path) - { - int length = path.Length; - return (length >= 1 && PathInternal.IsDirectorySeparator(path[0])) - || (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar); - } - - // 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. If the path is empty or - // only contains whitespace characters an ArgumentException gets thrown. - public static string? GetPathRoot(string? path) - { - if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) - return null; - - ReadOnlySpan<char> result = GetPathRoot(path.AsSpan()); - if (path!.Length == result.Length) - return PathInternal.NormalizeDirectorySeparators(path); - - return PathInternal.NormalizeDirectorySeparators(result.ToString()); - } - - /// <remarks> - /// Unlike the string overload, this method will not normalize directory separators. - /// </remarks> - public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path) - { - if (PathInternal.IsEffectivelyEmpty(path)) - return ReadOnlySpan<char>.Empty; - - int pathRoot = PathInternal.GetRootLength(path); - return pathRoot <= 0 ? ReadOnlySpan<char>.Empty : path.Slice(0, pathRoot); - } - - /// <summary>Gets whether the system is case-sensitive.</summary> - internal static bool IsCaseSensitive => false; - - /// <summary> - /// Returns the volume name for dos, UNC and device paths. - /// </summary> - internal static ReadOnlySpan<char> GetVolumeName(ReadOnlySpan<char> path) - { - // 3 cases: UNC ("\\server\share"), Device ("\\?\C:\"), or Dos ("C:\") - ReadOnlySpan<char> root = GetPathRoot(path); - if (root.Length == 0) - return root; - - // Cut from "\\?\UNC\Server\Share" to "Server\Share" - // Cut from "\\Server\Share" to "Server\Share" - int startOffset = GetUncRootLength(path); - if (startOffset == -1) - { - if (PathInternal.IsDevice(path)) - { - startOffset = 4; // Cut from "\\?\C:\" to "C:" - } - else - { - startOffset = 0; // e.g. "C:" - } - } - - ReadOnlySpan<char> pathToTrim = root.Slice(startOffset); - return Path.EndsInDirectorySeparator(pathToTrim) ? pathToTrim.Slice(0, pathToTrim.Length - 1) : pathToTrim; - } - - /// <summary> - /// Returns offset as -1 if the path is not in Unc format, otherwise returns the root length. - /// </summary> - /// <param name="path"></param> - /// <returns></returns> - internal static int GetUncRootLength(ReadOnlySpan<char> path) - { - bool isDevice = PathInternal.IsDevice(path); - - if (!isDevice && path.Slice(0, 2).EqualsOrdinal(@"\\".AsSpan())) - return 2; - else if (isDevice && path.Length >= 8 - && (path.Slice(0, 8).EqualsOrdinal(PathInternal.UncExtendedPathPrefix.AsSpan()) - || path.Slice(5, 4).EqualsOrdinal(@"UNC\".AsSpan()))) - return 8; - - return -1; - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/Path.cs b/netcore/System.Private.CoreLib/shared/System/IO/Path.cs deleted file mode 100644 index b0cdad93bc7..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/Path.cs +++ /dev/null @@ -1,930 +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. - -#nullable enable -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.InteropServices; -using System.Text; - -#if MS_IO_REDIST -using System; -using System.IO; - -namespace Microsoft.IO -#else -namespace System.IO -#endif -{ - // Provides methods for processing file system strings in a cross-platform manner. - // Most of the methods don't do a complete parsing (such as examining a UNC hostname), - // but they will handle most string operations. - public static partial class Path - { - // Public static readonly variant of the separators. The Path implementation itself is using - // internal const variant of the separators for better performance. - public static readonly char DirectorySeparatorChar = PathInternal.DirectorySeparatorChar; - public static readonly char AltDirectorySeparatorChar = PathInternal.AltDirectorySeparatorChar; - public static readonly char VolumeSeparatorChar = PathInternal.VolumeSeparatorChar; - public static readonly char PathSeparator = PathInternal.PathSeparator; - - // For generating random file names - // 8 random bytes provides 12 chars in our encoding for the 8.3 name. - private const int KeyLength = 8; - - [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")] - public static readonly char[] InvalidPathChars = GetInvalidPathChars(); - - // Changes the extension of a file path. The path parameter - // specifies a file path, and the extension parameter - // specifies a file extension (with a leading period, such as - // ".exe" or ".cs"). - // - // The function returns a file path with the same root, directory, and base - // name parts as path, but with the file extension changed to - // the specified extension. If path is null, the function - // returns null. If path does not contain a file extension, - // the new file extension is appended to the path. If extension - // is null, any existing extension is removed from path. - [return: NotNullIfNotNull("path")] - public static string? ChangeExtension(string? path, string? extension) - { - if (path == null) - return null; - - int subLength = path.Length; - if (subLength == 0) - return string.Empty; - - for (int i = path.Length - 1; i >= 0; i--) - { - char ch = path[i]; - - if (ch == '.') - { - subLength = i; - break; - } - - if (PathInternal.IsDirectorySeparator(ch)) - { - break; - } - } - - if (extension == null) - { - return path.Substring(0, subLength); - } - - ReadOnlySpan<char> subpath = path.AsSpan(0, subLength); -#if MS_IO_REDIST - return extension.Length != 0 && extension[0] == '.' ? - StringExtensions.Concat(subpath, extension.AsSpan()) : - StringExtensions.Concat(subpath, ".".AsSpan(), extension.AsSpan()); -#else - return extension.StartsWith('.') ? - string.Concat(subpath, extension) : - string.Concat(subpath, ".", extension); -#endif - } - - /// <summary> - /// Returns the directory portion of a file path. This method effectively - /// removes the last segment 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 - /// specified path is null, empty, or a root (such as "\", "C:", or - /// "\\server\share"). - /// </summary> - /// <remarks> - /// Directory separators are normalized in the returned string. - /// </remarks> - public static string? GetDirectoryName(string? path) - { - if (path == null || PathInternal.IsEffectivelyEmpty(path.AsSpan())) - return null; - - int end = GetDirectoryNameOffset(path.AsSpan()); - return end >= 0 ? PathInternal.NormalizeDirectorySeparators(path.Substring(0, end)) : null; - } - - /// <summary> - /// Returns the directory portion of a file path. The returned value is empty - /// if the specified path is null, empty, or a root (such as "\", "C:", or - /// "\\server\share"). - /// </summary> - /// <remarks> - /// Unlike the string overload, this method will not normalize directory separators. - /// </remarks> - public static ReadOnlySpan<char> GetDirectoryName(ReadOnlySpan<char> path) - { - if (PathInternal.IsEffectivelyEmpty(path)) - return ReadOnlySpan<char>.Empty; - - int end = GetDirectoryNameOffset(path); - return end >= 0 ? path.Slice(0, end) : ReadOnlySpan<char>.Empty; - } - - private static int GetDirectoryNameOffset(ReadOnlySpan<char> path) - { - int rootLength = PathInternal.GetRootLength(path); - int end = path.Length; - if (end <= rootLength) - return -1; - - while (end > rootLength && !PathInternal.IsDirectorySeparator(path[--end])) ; - - // Trim off any remaining separators (to deal with C:\foo\\bar) - while (end > rootLength && PathInternal.IsDirectorySeparator(path[end - 1])) - end--; - - return end; - } - - /// <summary> - /// 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 empty if the given path does not include an - /// extension. - /// </summary> - [return: NotNullIfNotNull("path")] - public static string? GetExtension(string? path) - { - if (path == null) - return null; - - return GetExtension(path.AsSpan()).ToString(); - } - - /// <summary> - /// Returns the extension of the given path. - /// </summary> - /// <remarks> - /// The returned value is an empty ReadOnlySpan if the given path does not include an extension. - /// </remarks> - public static ReadOnlySpan<char> GetExtension(ReadOnlySpan<char> 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.Slice(i, length - i); - else - return ReadOnlySpan<char>.Empty; - } - if (PathInternal.IsDirectorySeparator(ch)) - break; - } - return ReadOnlySpan<char>.Empty; - } - - /// <summary> - /// 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. - /// </summary> - [return: NotNullIfNotNull("path")] - public static string? GetFileName(string? path) - { - if (path == null) - return null; - - ReadOnlySpan<char> result = GetFileName(path.AsSpan()); - if (path.Length == result.Length) - return path; - - return result.ToString(); - } - - /// <summary> - /// The returned ReadOnlySpan contains the characters of the path that follows the last separator in path. - /// </summary> - public static ReadOnlySpan<char> GetFileName(ReadOnlySpan<char> path) - { - int root = GetPathRoot(path).Length; - - // We don't want to cut off "C:\file.txt:stream" (i.e. should be "file.txt:stream") - // but we *do* want "C:Foo" => "Foo". This necessitates checking for the root. - - for (int i = path.Length; --i >= 0;) - { - if (i < root || PathInternal.IsDirectorySeparator(path[i])) - return path.Slice(i + 1, path.Length - i - 1); - } - - return path; - } - - [return: NotNullIfNotNull("path")] - public static string? GetFileNameWithoutExtension(string? path) - { - if (path == null) - return null; - - ReadOnlySpan<char> result = GetFileNameWithoutExtension(path.AsSpan()); - if (path.Length == result.Length) - return path; - - return result.ToString(); - } - - /// <summary> - /// Returns the characters between the last separator and last (.) in the path. - /// </summary> - public static ReadOnlySpan<char> GetFileNameWithoutExtension(ReadOnlySpan<char> path) - { - ReadOnlySpan<char> fileName = GetFileName(path); - int lastPeriod = fileName.LastIndexOf('.'); - return lastPeriod == -1 ? - fileName : // No extension was found - fileName.Slice(0, lastPeriod); - } - - /// <summary> - /// Returns a cryptographically strong random 8.3 string that can be - /// used as either a folder name or a file name. - /// </summary> - public static unsafe string GetRandomFileName() - { - byte* pKey = stackalloc byte[KeyLength]; - Interop.GetRandomBytes(pKey, KeyLength); - -#if MS_IO_REDIST - return StringExtensions.Create( -#else - return string.Create( -#endif - 12, (IntPtr)pKey, (span, key) => // 12 == 8 + 1 (for period) + 3 - Populate83FileNameFromRandomBytes((byte*)key, KeyLength, span)); - } - - /// <summary> - /// Returns true 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). - /// Returns false if the path specified is relative to the current drive or working directory. - /// </summary> - /// <remarks> - /// Handles paths that use the alternate directory separator. It is a frequent mistake to - /// assume that rooted paths <see cref="Path.IsPathRooted(string)"/> 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> - /// <exception cref="ArgumentNullException"> - /// Thrown if <paramref name="path"/> is null. - /// </exception> - public static bool IsPathFullyQualified(string path) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - - return IsPathFullyQualified(path.AsSpan()); - } - - public static bool IsPathFullyQualified(ReadOnlySpan<char> path) - { - return !PathInternal.IsPartiallyQualified(path); - } - - /// <summary> - /// Tests if a path's file name includes a file extension. A trailing period - /// is not considered an extension. - /// </summary> - public static bool HasExtension(string? path) - { - if (path != null) - { - return HasExtension(path.AsSpan()); - } - return false; - } - - public static bool HasExtension(ReadOnlySpan<char> path) - { - for (int i = path.Length - 1; i >= 0; i--) - { - char ch = path[i]; - if (ch == '.') - { - return i != path.Length - 1; - } - if (PathInternal.IsDirectorySeparator(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)); - - return CombineInternal(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)); - - return CombineInternal(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)); - - return CombineInternal(path1, path2, path3, path4); - } - - public static string Combine(params string[] paths) - { - if (paths == null) - { - throw new ArgumentNullException(nameof(paths)); - } - - int maxSize = 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; - } - - if (IsPathRooted(paths[i])) - { - firstComponent = i; - maxSize = paths[i].Length; - } - else - { - maxSize += paths[i].Length; - } - - char ch = paths[i][paths[i].Length - 1]; - if (!PathInternal.IsDirectorySeparator(ch)) - maxSize++; - } - - var builder = new ValueStringBuilder(stackalloc char[260]); // MaxShortPath on Windows - builder.EnsureCapacity(maxSize); - - for (int i = firstComponent; i < paths.Length; i++) - { - if (paths[i].Length == 0) - { - continue; - } - - if (builder.Length == 0) - { - builder.Append(paths[i]); - } - else - { - char ch = builder[builder.Length - 1]; - if (!PathInternal.IsDirectorySeparator(ch)) - { - builder.Append(PathInternal.DirectorySeparatorChar); - } - - builder.Append(paths[i]); - } - } - - return builder.ToString(); - } - - // Unlike Combine(), Join() methods do not consider rooting. They simply combine paths, ensuring that there - // is a directory separator between them. - - public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2) - { - if (path1.Length == 0) - return path2.ToString(); - if (path2.Length == 0) - return path1.ToString(); - - return JoinInternal(path1, path2); - } - - public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3) - { - if (path1.Length == 0) - return Join(path2, path3); - - if (path2.Length == 0) - return Join(path1, path3); - - if (path3.Length == 0) - return Join(path1, path2); - - return JoinInternal(path1, path2, path3); - } - - public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, ReadOnlySpan<char> path4) - { - if (path1.Length == 0) - return Join(path2, path3, path4); - - if (path2.Length == 0) - return Join(path1, path3, path4); - - if (path3.Length == 0) - return Join(path1, path2, path4); - - if (path4.Length == 0) - return Join(path1, path2, path3); - - return JoinInternal(path1, path2, path3, path4); - } - - public static string Join(string? path1, string? path2) - { - return Join(path1.AsSpan(), path2.AsSpan()); - } - - public static string Join(string? path1, string? path2, string? path3) - { - return Join(path1.AsSpan(), path2.AsSpan(), path3.AsSpan()); - } - - public static string Join(string? path1, string? path2, string? path3, string? path4) - { - return Join(path1.AsSpan(), path2.AsSpan(), path3.AsSpan(), path4.AsSpan()); - } - - public static string Join(params string?[] paths) - { - if (paths == null) - { - throw new ArgumentNullException(nameof(paths)); - } - - if (paths.Length == 0) - { - return string.Empty; - } - - int maxSize = 0; - foreach (string? path in paths) - { - maxSize += path?.Length ?? 0; - } - maxSize += paths.Length - 1; - - var builder = new ValueStringBuilder(stackalloc char[260]); // MaxShortPath on Windows - builder.EnsureCapacity(maxSize); - - for (int i = 0; i < paths.Length; i++) - { - string? path = paths[i]; - if (string.IsNullOrEmpty(path)) - { - continue; - } - - if (builder.Length == 0) - { - builder.Append(path); - } - else - { - if (!PathInternal.IsDirectorySeparator(builder[builder.Length - 1]) && !PathInternal.IsDirectorySeparator(path[0])) - { - builder.Append(PathInternal.DirectorySeparatorChar); - } - - builder.Append(path); - } - } - - return builder.ToString(); - } - - public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, Span<char> destination, out int charsWritten) - { - charsWritten = 0; - if (path1.Length == 0 && path2.Length == 0) - return true; - - if (path1.Length == 0 || path2.Length == 0) - { - ref ReadOnlySpan<char> pathToUse = ref path1.Length == 0 ? ref path2 : ref path1; - if (destination.Length < pathToUse.Length) - { - return false; - } - - pathToUse.CopyTo(destination); - charsWritten = pathToUse.Length; - return true; - } - - bool needsSeparator = !(EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2)); - int charsNeeded = path1.Length + path2.Length + (needsSeparator ? 1 : 0); - if (destination.Length < charsNeeded) - return false; - - path1.CopyTo(destination); - if (needsSeparator) - destination[path1.Length] = DirectorySeparatorChar; - - path2.CopyTo(destination.Slice(path1.Length + (needsSeparator ? 1 : 0))); - - charsWritten = charsNeeded; - return true; - } - - public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten) - { - charsWritten = 0; - if (path1.Length == 0 && path2.Length == 0 && path3.Length == 0) - return true; - - if (path1.Length == 0) - return TryJoin(path2, path3, destination, out charsWritten); - if (path2.Length == 0) - return TryJoin(path1, path3, destination, out charsWritten); - if (path3.Length == 0) - return TryJoin(path1, path2, destination, out charsWritten); - - int neededSeparators = EndsInDirectorySeparator(path1) || PathInternal.StartsWithDirectorySeparator(path2) ? 0 : 1; - bool needsSecondSeparator = !(EndsInDirectorySeparator(path2) || PathInternal.StartsWithDirectorySeparator(path3)); - if (needsSecondSeparator) - neededSeparators++; - - int charsNeeded = path1.Length + path2.Length + path3.Length + neededSeparators; - if (destination.Length < charsNeeded) - return false; - - bool result = TryJoin(path1, path2, destination, out charsWritten); - Debug.Assert(result, "should never fail joining first two paths"); - - if (needsSecondSeparator) - destination[charsWritten++] = DirectorySeparatorChar; - - path3.CopyTo(destination.Slice(charsWritten)); - charsWritten += path3.Length; - - return true; - } - - private static string CombineInternal(string first, string second) - { - if (string.IsNullOrEmpty(first)) - return second; - - if (string.IsNullOrEmpty(second)) - return first; - - if (IsPathRooted(second.AsSpan())) - return second; - - return JoinInternal(first.AsSpan(), second.AsSpan()); - } - - private static string CombineInternal(string first, string second, string third) - { - if (string.IsNullOrEmpty(first)) - return CombineInternal(second, third); - if (string.IsNullOrEmpty(second)) - return CombineInternal(first, third); - if (string.IsNullOrEmpty(third)) - return CombineInternal(first, second); - - if (IsPathRooted(third.AsSpan())) - return third; - if (IsPathRooted(second.AsSpan())) - return CombineInternal(second, third); - - return JoinInternal(first.AsSpan(), second.AsSpan(), third.AsSpan()); - } - - private static string CombineInternal(string first, string second, string third, string fourth) - { - if (string.IsNullOrEmpty(first)) - return CombineInternal(second, third, fourth); - if (string.IsNullOrEmpty(second)) - return CombineInternal(first, third, fourth); - if (string.IsNullOrEmpty(third)) - return CombineInternal(first, second, fourth); - if (string.IsNullOrEmpty(fourth)) - return CombineInternal(first, second, third); - - if (IsPathRooted(fourth.AsSpan())) - return fourth; - if (IsPathRooted(third.AsSpan())) - return CombineInternal(third, fourth); - if (IsPathRooted(second.AsSpan())) - return CombineInternal(second, third, fourth); - - return JoinInternal(first.AsSpan(), second.AsSpan(), third.AsSpan(), fourth.AsSpan()); - } - - private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second) - { - Debug.Assert(first.Length > 0 && second.Length > 0, "should have dealt with empty paths"); - - bool hasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1]) - || PathInternal.IsDirectorySeparator(second[0]); - - fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second)) - { -#if MS_IO_REDIST - return StringExtensions.Create( -#else - return string.Create( -#endif - first.Length + second.Length + (hasSeparator ? 0 : 1), - (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length, HasSeparator: hasSeparator), - (destination, state) => - { - new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination); - if (!state.HasSeparator) - destination[state.FirstLength] = PathInternal.DirectorySeparatorChar; - new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.HasSeparator ? 0 : 1))); - }); - } - } - - private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third) - { - Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0, "should have dealt with empty paths"); - - bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1]) - || PathInternal.IsDirectorySeparator(second[0]); - bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1]) - || PathInternal.IsDirectorySeparator(third[0]); - - fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third)) - { -#if MS_IO_REDIST - return StringExtensions.Create( -#else - return string.Create( -#endif - first.Length + second.Length + third.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1), - (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length, - Third: (IntPtr)t, ThirdLength: third.Length, FirstHasSeparator: firstHasSeparator, ThirdHasSeparator: thirdHasSeparator), - (destination, state) => - { - new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination); - if (!state.FirstHasSeparator) - destination[state.FirstLength] = PathInternal.DirectorySeparatorChar; - new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1))); - if (!state.ThirdHasSeparator) - destination[destination.Length - state.ThirdLength - 1] = PathInternal.DirectorySeparatorChar; - new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(destination.Length - state.ThirdLength)); - }); - } - } - - private static unsafe string JoinInternal(ReadOnlySpan<char> first, ReadOnlySpan<char> second, ReadOnlySpan<char> third, ReadOnlySpan<char> fourth) - { - Debug.Assert(first.Length > 0 && second.Length > 0 && third.Length > 0 && fourth.Length > 0, "should have dealt with empty paths"); - - bool firstHasSeparator = PathInternal.IsDirectorySeparator(first[first.Length - 1]) - || PathInternal.IsDirectorySeparator(second[0]); - bool thirdHasSeparator = PathInternal.IsDirectorySeparator(second[second.Length - 1]) - || PathInternal.IsDirectorySeparator(third[0]); - bool fourthHasSeparator = PathInternal.IsDirectorySeparator(third[third.Length - 1]) - || PathInternal.IsDirectorySeparator(fourth[0]); - - fixed (char* f = &MemoryMarshal.GetReference(first), s = &MemoryMarshal.GetReference(second), t = &MemoryMarshal.GetReference(third), u = &MemoryMarshal.GetReference(fourth)) - { -#if MS_IO_REDIST - return StringExtensions.Create( -#else - return string.Create( -#endif - first.Length + second.Length + third.Length + fourth.Length + (firstHasSeparator ? 0 : 1) + (thirdHasSeparator ? 0 : 1) + (fourthHasSeparator ? 0 : 1), - (First: (IntPtr)f, FirstLength: first.Length, Second: (IntPtr)s, SecondLength: second.Length, - Third: (IntPtr)t, ThirdLength: third.Length, Fourth: (IntPtr)u, FourthLength: fourth.Length, - FirstHasSeparator: firstHasSeparator, ThirdHasSeparator: thirdHasSeparator, FourthHasSeparator: fourthHasSeparator), - (destination, state) => - { - new Span<char>((char*)state.First, state.FirstLength).CopyTo(destination); - if (!state.FirstHasSeparator) - destination[state.FirstLength] = PathInternal.DirectorySeparatorChar; - new Span<char>((char*)state.Second, state.SecondLength).CopyTo(destination.Slice(state.FirstLength + (state.FirstHasSeparator ? 0 : 1))); - if (!state.ThirdHasSeparator) - destination[state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1)] = PathInternal.DirectorySeparatorChar; - new Span<char>((char*)state.Third, state.ThirdLength).CopyTo(destination.Slice(state.FirstLength + state.SecondLength + (state.FirstHasSeparator ? 0 : 1) + (state.ThirdHasSeparator ? 0 : 1))); - if (!state.FourthHasSeparator) - destination[destination.Length - state.FourthLength - 1] = PathInternal.DirectorySeparatorChar; - new Span<char>((char*)state.Fourth, state.FourthLength).CopyTo(destination.Slice(destination.Length - state.FourthLength)); - }); - } - } - - private static ReadOnlySpan<byte> Base32Char => new byte[32] { // uses C# compiler's optimization for static byte[] data - (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', (byte)'h', - (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', (byte)'o', (byte)'p', - (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', (byte)'v', (byte)'w', (byte)'x', - (byte)'y', (byte)'z', (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5' }; - - private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int byteCount, Span<char> chars) - { - // This method requires bytes of length 8 and chars of length 12. - Debug.Assert(bytes != null); - Debug.Assert(byteCount == 8, $"Unexpected {nameof(byteCount)}"); - Debug.Assert(chars.Length == 12, $"Unexpected {nameof(chars)}.Length"); - - byte b0 = bytes[0]; - byte b1 = bytes[1]; - byte b2 = bytes[2]; - byte b3 = bytes[3]; - byte b4 = bytes[4]; - - // write to chars[11] first in order to eliminate redundant bounds checks - chars[11] = (char)Base32Char[bytes[7] & 0x1F]; - - // Consume the 5 Least significant bits of the first 5 bytes - chars[0] = (char)Base32Char[b0 & 0x1F]; - chars[1] = (char)Base32Char[b1 & 0x1F]; - chars[2] = (char)Base32Char[b2 & 0x1F]; - chars[3] = (char)Base32Char[b3 & 0x1F]; - chars[4] = (char)Base32Char[b4 & 0x1F]; - - // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4 - chars[5] = (char)Base32Char[ - ((b0 & 0xE0) >> 5) | - ((b3 & 0x60) >> 2)]; - - chars[6] = (char)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] = (char)Base32Char[b2]; - - // Set the file extension separator - chars[8] = '.'; - - // Consume the 5 Least significant bits of the remaining 3 bytes - chars[9] = (char)Base32Char[bytes[5] & 0x1F]; - chars[10] = (char)Base32Char[bytes[6] & 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 (relativeTo == null) - throw new ArgumentNullException(nameof(relativeTo)); - - if (PathInternal.IsEffectivelyEmpty(relativeTo.AsSpan())) - throw new ArgumentException(SR.Arg_PathEmpty, nameof(relativeTo)); - - if (path == null) - throw new ArgumentNullException(nameof(path)); - - if (PathInternal.IsEffectivelyEmpty(path.AsSpan())) - throw new ArgumentException(SR.Arg_PathEmpty, 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 (EndsInDirectorySeparator(relativeTo.AsSpan())) - relativeToLength--; - - bool pathEndsInSeparator = EndsInDirectorySeparator(path.AsSpan()); - 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 - - var sb = new ValueStringBuilder(stackalloc char[260]); - sb.EnsureCapacity(Math.Max(relativeTo.Length, path.Length)); - - // Add parent segments for segments past the common on the "from" path - if (commonLength < relativeToLength) - { - sb.Append(".."); - - for (int i = commonLength + 1; i < relativeToLength; i++) - { - if (PathInternal.IsDirectorySeparator(relativeTo[i])) - { - sb.Append(DirectorySeparatorChar); - sb.Append(".."); - } - } - } - 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 differenceLength = pathLength - commonLength; - if (pathEndsInSeparator) - differenceLength++; - - if (differenceLength > 0) - { - if (sb.Length > 0) - { - sb.Append(DirectorySeparatorChar); - } - - sb.Append(path.AsSpan(commonLength, differenceLength)); - } - - return sb.ToString(); - } - - /// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary> - internal static StringComparison StringComparison => - IsCaseSensitive ? - StringComparison.Ordinal : - StringComparison.OrdinalIgnoreCase; - - /// <summary> - /// Trims one trailing directory separator beyond the root of the path. - /// </summary> - public static string TrimEndingDirectorySeparator(string path) => PathInternal.TrimEndingDirectorySeparator(path); - - /// <summary> - /// Trims one trailing directory separator beyond the root of the path. - /// </summary> - public static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) => PathInternal.TrimEndingDirectorySeparator(path); - - /// <summary> - /// Returns true if the path ends in a directory separator. - /// </summary> - public static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => PathInternal.EndsInDirectorySeparator(path); - - /// <summary> - /// Returns true if the path ends in a directory separator. - /// </summary> - public static bool EndsInDirectorySeparator(string path) => PathInternal.EndsInDirectorySeparator(path); - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs b/netcore/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs deleted file mode 100644 index abf5c346148..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PathHelper.Windows.cs +++ /dev/null @@ -1,251 +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. - -#nullable enable -using System.Diagnostics; -using System.Runtime.InteropServices; -using System.Text; - -namespace System.IO -{ - /// <summary> - /// Wrapper to help with path normalization. - /// </summary> - internal static class PathHelper - { - /// <summary> - /// Normalize the given path. - /// </summary> - /// <remarks> - /// Normalizes via Win32 GetFullPathName(). - /// </remarks> - /// <param name="path">Path to normalize</param> - /// <exception cref="PathTooLongException">Thrown if we have a string that is too large to fit into a UNICODE_STRING.</exception> - /// <exception cref="IOException">Thrown if the path is empty.</exception> - /// <returns>Normalized path</returns> - internal static string Normalize(string path) - { - var builder = new ValueStringBuilder(stackalloc char[PathInternal.MaxShortPath]); - - // Get the full path - GetFullPathName(path.AsSpan(), ref builder); - - // If we have the exact same string we were passed in, don't allocate another string. - // TryExpandShortName does this input identity check. - string result = builder.AsSpan().IndexOf('~') >= 0 - ? TryExpandShortFileName(ref builder, originalPath: path) - : builder.AsSpan().Equals(path.AsSpan(), StringComparison.Ordinal) ? path : builder.ToString(); - - // Clear the buffer - builder.Dispose(); - return result; - } - - /// <summary> - /// Normalize the given path. - /// </summary> - /// <remarks> - /// Exceptions are the same as the string overload. - /// </remarks> - internal static string Normalize(ref ValueStringBuilder path) - { - var builder = new ValueStringBuilder(stackalloc char[PathInternal.MaxShortPath]); - - // Get the full path - GetFullPathName(path.AsSpan(terminate: true), ref builder); - - string result = builder.AsSpan().IndexOf('~') >= 0 - ? TryExpandShortFileName(ref builder, originalPath: null) - : builder.ToString(); - - // Clear the buffer - builder.Dispose(); - return result; - } - - /// <summary> - /// Calls GetFullPathName on the given path. - /// </summary> - /// <param name="path">The path name. MUST be null terminated after the span.</param> - /// <param name="builder">Builder that will store the result.</param> - private static void GetFullPathName(ReadOnlySpan<char> path, ref ValueStringBuilder builder) - { - // 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)); - - uint result; - while ((result = Interop.Kernel32.GetFullPathNameW(ref MemoryMarshal.GetReference(path), (uint)builder.Capacity, ref builder.GetPinnableReference(), IntPtr.Zero)) > builder.Capacity) - { - // Reported size is greater than the buffer size. Increase the capacity. - builder.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.ToString()); - } - - builder.Length = (int)result; - } - - internal static int PrependDevicePathChars(ref ValueStringBuilder content, bool isDosUnc, ref ValueStringBuilder buffer) - { - int length = content.Length; - - length += isDosUnc - ? PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength - : PathInternal.DevicePrefixLength; - - buffer.EnsureCapacity(length + 1); - buffer.Length = 0; - - if (isDosUnc) - { - // Is a \\Server\Share, put \\?\UNC\ in the front - buffer.Append(PathInternal.UncExtendedPathPrefix); - - // Copy Server\Share\... over to the buffer - buffer.Append(content.AsSpan(PathInternal.UncPrefixLength)); - - // Return the prefix difference - return PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength; - } - else - { - // Not an UNC, put the \\?\ prefix in front, then the original string - buffer.Append(PathInternal.ExtendedPathPrefix); - buffer.Append(content.AsSpan()); - return PathInternal.DevicePrefixLength; - } - } - - internal static string TryExpandShortFileName(ref ValueStringBuilder outputBuilder, 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 a lot we'll create only one input array and modify the contents with embedded nulls. - - Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuilder.AsSpan()), "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 (for long path support and speed), 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(outputBuilder.AsSpan()); - bool isDevice = PathInternal.IsDevice(outputBuilder.AsSpan()); - - // As this is a corner case we're not going to add a stackalloc here to keep the stack pressure down. - ValueStringBuilder inputBuilder = default; - - 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 \\.\) - inputBuilder.Append(outputBuilder.AsSpan()); - - if (outputBuilder[2] == '.') - { - wasDotDevice = true; - inputBuilder[2] = '?'; - } - } - else - { - isDosUnc = !PathInternal.IsDevice(outputBuilder.AsSpan()) && outputBuilder.Length > 1 && outputBuilder[0] == '\\' && outputBuilder[1] == '\\'; - rootDifference = PrependDevicePathChars(ref outputBuilder, isDosUnc, ref inputBuilder); - } - - rootLength += rootDifference; - int inputLength = inputBuilder.Length; - - bool success = false; - int foundIndex = inputBuilder.Length - 1; - - while (!success) - { - uint result = Interop.Kernel32.GetLongPathNameW( - ref inputBuilder.GetPinnableReference(terminate: true), ref outputBuilder.GetPinnableReference(), (uint)outputBuilder.Capacity); - - // Replace any temporary null we added - if (inputBuilder[foundIndex] == '\0') inputBuilder[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 && inputBuilder[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 - inputBuilder[foundIndex] = '\0'; - } - } - else if (result > outputBuilder.Capacity) - { - // Not enough space. The result count for this API does not include the null terminator. - outputBuilder.EnsureCapacity(checked((int)result)); - } - else - { - // Found the path - success = true; - outputBuilder.Length = checked((int)result); - if (foundIndex < inputLength - 1) - { - // It was a partial find, put the non-existent part of the path back - outputBuilder.Append(inputBuilder.AsSpan(foundIndex, inputBuilder.Length - foundIndex)); - } - } - } - - // If we were able to expand the path, use it, otherwise use the original full path result - ref ValueStringBuilder builderToUse = ref (success ? ref outputBuilder : ref inputBuilder); - - // Switch back from \\?\ to \\.\ if necessary - if (wasDotDevice) - builderToUse[2] = '.'; - - // Change from \\?\UNC\ to \\?\UN\\ if needed - if (isDosUnc) - builderToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\'; - - // Strip out any added characters at the front of the string - ReadOnlySpan<char> output = builderToUse.AsSpan(rootDifference); - - string returnValue = ((originalPath != null) && output.Equals(originalPath.AsSpan(), StringComparison.Ordinal)) - ? originalPath : output.ToString(); - - inputBuilder.Dispose(); - return returnValue; - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs b/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs deleted file mode 100644 index f9a018ae5e3..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.Unix.cs +++ /dev/null @@ -1,98 +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. - -#nullable enable -using System.Diagnostics; -using System.Text; -using System.Runtime.InteropServices; - -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 = "/"; - internal const string ParentDirectoryPrefix = @"../"; - - internal static int GetRootLength(ReadOnlySpan<char> 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(); - } - - internal static bool IsPartiallyQualified(ReadOnlySpan<char> 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); - } - - /// <summary> - /// Returns true if the path is effectively empty for the current OS. - /// For unix, this is empty or null. For Windows, this is empty, null, or - /// just spaces ((char)32). - /// </summary> - internal static bool IsEffectivelyEmpty(string? path) - { - return string.IsNullOrEmpty(path); - } - - internal static bool IsEffectivelyEmpty(ReadOnlySpan<char> path) - { - return path.IsEmpty; - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs b/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs deleted file mode 100644 index 300d1265579..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.Windows.cs +++ /dev/null @@ -1,414 +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. - -#nullable enable -using System.Diagnostics.CodeAnalysis; -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 DevicePrefixLength = 4; - // \\ - internal const int UncPrefixLength = 2; - // \\?\UNC\, \\.\UNC\ - internal const int UncExtendedPrefixLength = 8; - - /// <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'); - } - - internal static bool EndsWithPeriodOrSpace(string? path) - { - if (string.IsNullOrEmpty(path)) - return false; - - char c = path[path.Length - 1]; - return c == ' ' || c == '.'; - } - - /// <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). This will also insert the extended - /// prefix if the path ends with a period or a space. Trailing periods and spaces are normally eaten - /// away from paths during normalization, but if we see such a path at this point it should be - /// normalized and has retained the final characters. (Typically from one of the *Info classes) - /// </summary> - [return: NotNullIfNotNull("path")] - internal static string? EnsureExtendedPrefixIfNeeded(string? path) - { - if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path))) - { - 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.AsSpan()) || IsDevice(path.AsSpan())) - 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(ReadOnlySpan<char> 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 is a device UNC (\\?\UNC\, \\.\UNC\) - /// </summary> - internal static bool IsDeviceUNC(ReadOnlySpan<char> path) - { - return path.Length >= UncExtendedPrefixLength - && IsDevice(path) - && IsDirectorySeparator(path[7]) - && path[4] == 'U' - && path[5] == 'N' - && path[6] == 'C'; - } - - /// <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(ReadOnlySpan<char> 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> - /// Check for known wildcard characters. '*' and '?' are the most common ones. - /// </summary> - internal static bool HasWildCardCharacters(ReadOnlySpan<char> 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 static int GetRootLength(ReadOnlySpan<char> path) - { - int pathLength = path.Length; - int i = 0; - - bool deviceSyntax = IsDevice(path); - bool deviceUnc = deviceSyntax && IsDeviceUNC(path); - - if ((!deviceSyntax || deviceUnc) && pathLength > 0 && IsDirectorySeparator(path[0])) - { - // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") - if (deviceUnc || (pathLength > 1 && IsDirectorySeparator(path[1]))) - { - // UNC (\\?\UNC\ or \\), scan past server\share - - // Start past the prefix ("\\" or "\\?\UNC\") - i = deviceUnc ? UncExtendedPrefixLength : UncPrefixLength; - - // Skip two separators at most - int n = 2; - while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) - i++; - } - else - { - // Current drive rooted (e.g. "\foo") - i = 1; - } - } - else if (deviceSyntax) - { - // Device path (e.g. "\\?\.", "\\.\") - // Skip any characters following the prefix that aren't a separator - i = DevicePrefixLength; - while (i < pathLength && !IsDirectorySeparator(path[i])) - i++; - - // If there is another separator take it, as long as we have had at least one - // non-separator after the prefix (e.g. don't take "\\?\\", but take "\\?\a\") - if (i < pathLength && i > DevicePrefixLength && IsDirectorySeparator(path[i])) - i++; - } - else if (pathLength >= 2 - && path[1] == VolumeSeparatorChar - && IsValidDriveChar(path[0])) - { - // Valid drive specified path ("C:", "D:", etc.) - i = 2; - - // If the colon is followed by a directory separator, move past it (e.g "C:\") - if (pathLength > 2 && IsDirectorySeparator(path[2])) - i++; - } - - return i; - } - - /// <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(ReadOnlySpan<char> 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> - /// 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; - - // 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; - - var builder = new ValueStringBuilder(stackalloc char[MaxShortPath]); - - int start = 0; - 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 path is effectively empty for the current OS. - /// For unix, this is empty or null. For Windows, this is empty, null, or - /// just spaces ((char)32). - /// </summary> - internal static bool IsEffectivelyEmpty(ReadOnlySpan<char> path) - { - if (path.IsEmpty) - return true; - - foreach (char c in path) - { - if (c != ' ') - return false; - } - return true; - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.cs b/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.cs deleted file mode 100644 index dfea93658d3..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PathInternal.cs +++ /dev/null @@ -1,248 +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 - { - /// <summary> - /// Returns true if the path starts in a directory separator. - /// </summary> - internal static bool StartsWithDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[0]); - -#if MS_IO_REDIST - internal static string EnsureTrailingSeparator(string path) - => EndsInDirectorySeparator(path) ? path : path + DirectorySeparatorCharAsString; -#else - internal static string EnsureTrailingSeparator(string path) - => EndsInDirectorySeparator(path.AsSpan()) ? path : path + DirectorySeparatorCharAsString; -#endif - - internal static bool IsRoot(ReadOnlySpan<char> path) - => path.Length == GetRootLength(path); - - /// <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> - internal static unsafe 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.AsSpan()); - int secondRootLength = GetRootLength(second.AsSpan()); - - return firstRootLength == secondRootLength - && string.Compare( - strA: first, - indexA: 0, - strB: second, - indexB: 0, - length: firstRootLength, - comparisonType: comparisonType) == 0; - } - - /// <summary> - /// Try to remove relative segments from the given path (without combining with a root). - /// </summary> - /// <param name="path">Input path</param> - /// <param name="rootLength">The length of the root of the given path</param> - internal static string RemoveRelativeSegments(string path, int rootLength) - { - var sb = new ValueStringBuilder(stackalloc char[260 /* PathInternal.MaxShortPath */]); - - if (RemoveRelativeSegments(path.AsSpan(), rootLength, ref sb)) - { - path = sb.ToString(); - } - - sb.Dispose(); - return path; - } - - /// <summary> - /// Try to remove relative segments from the given path (without combining with a root). - /// </summary> - /// <param name="path">Input path</param> - /// <param name="rootLength">The length of the root of the given path</param> - /// <param name="sb">String builder that will store the result</param> - /// <returns>"true" if the path was modified</returns> - internal static bool RemoveRelativeSegments(ReadOnlySpan<char> path, int rootLength, ref ValueStringBuilder sb) - { - Debug.Assert(rootLength > 0); - bool flippedSeparator = false; - - int skip = rootLength; - // We treat "\.." , "\." and "\\" as a relative segment. We want to collapse the first separator past the root presuming - // the root actually ends in a separator. Otherwise the first segment for RemoveRelativeSegments - // in cases like "\\?\C:\.\" and "\\?\C:\..\", the first segment after the root will be ".\" and "..\" which is not considered as a relative segment and hence not be removed. - if (PathInternal.IsDirectorySeparator(path[skip - 1])) - skip--; - - // 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. - if (skip > 0) - { - sb.Append(path.Slice(0, skip)); - } - - for (int i = skip; i < path.Length; i++) - { - char c = path[i]; - - if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length) - { - // 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 >= skip; s--) - { - if (PathInternal.IsDirectorySeparator(sb[s])) - { - sb.Length = (i + 3 >= path.Length && s == skip) ? s + 1 : s; // to avoid removing the complete "\tmp\" segment in cases like \\?\C:\tmp\..\, C:\tmp\.. - break; - } - } - if (s < skip) - { - sb.Length = skip; - } - - i += 2; - continue; - } - } - - // Normalize the directory separator if needed - if (c != PathInternal.DirectorySeparatorChar && c == PathInternal.AltDirectorySeparatorChar) - { - c = PathInternal.DirectorySeparatorChar; - flippedSeparator = true; - } - - sb.Append(c); - } - - // If we haven't changed the source path, return the original - if (!flippedSeparator && sb.Length == path.Length) - { - return false; - } - - // We may have eaten the trailing separator from the root when we started and not replaced it - if (skip != rootLength && sb.Length < rootLength) - { - sb.Append(path[rootLength - 1]); - } - - return true; - } - - /// <summary> - /// Trims one trailing directory separator beyond the root of the path. - /// </summary> - internal static string TrimEndingDirectorySeparator(string path) => - EndsInDirectorySeparator(path) && !IsRoot(path.AsSpan()) ? - path.Substring(0, path.Length - 1) : - path; - - /// <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> - /// Trims one trailing directory separator beyond the root of the path. - /// </summary> - internal static ReadOnlySpan<char> TrimEndingDirectorySeparator(ReadOnlySpan<char> path) => - EndsInDirectorySeparator(path) && !IsRoot(path) ? - path.Slice(0, path.Length - 1) : - path; - - /// <summary> - /// Returns true if the path ends in a directory separator. - /// </summary> - internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => - path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); - } -}
\ No newline at end of file diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PathTooLongException.cs b/netcore/System.Private.CoreLib/shared/System/IO/PathTooLongException.cs deleted file mode 100644 index 3a69930a911..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PathTooLongException.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.Runtime.Serialization; - -namespace System.IO -{ - [Serializable] - [System.Runtime.CompilerServices.TypeForwardedFrom("mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")] - public class PathTooLongException : IOException - { - public PathTooLongException() - : base(SR.IO_PathTooLong) - { - HResult = HResults.COR_E_PATHTOOLONG; - } - - public PathTooLongException(string? message) - : base(message) - { - HResult = HResults.COR_E_PATHTOOLONG; - } - - public PathTooLongException(string? message, Exception? innerException) - : base(message, innerException) - { - HResult = HResults.COR_E_PATHTOOLONG; - } - - protected PathTooLongException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PersistedFiles.Names.Unix.cs b/netcore/System.Private.CoreLib/shared/System/IO/PersistedFiles.Names.Unix.cs deleted file mode 100644 index 8984f1aee32..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PersistedFiles.Names.Unix.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - internal static partial class PersistedFiles - { - // Temporary data, /tmp/.dotnet/corefx - // User-persisted data, ~/.dotnet/corefx/ - // System-persisted data, /etc/dotnet/corefx/ - - internal const string TopLevelDirectory = "dotnet"; - internal const string TopLevelHiddenDirectory = "." + TopLevelDirectory; - internal const string SecondLevelDirectory = "corefx"; - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PersistedFiles.Unix.cs b/netcore/System.Private.CoreLib/shared/System/IO/PersistedFiles.Unix.cs deleted file mode 100644 index a610614f276..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PersistedFiles.Unix.cs +++ /dev/null @@ -1,164 +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. - -#nullable enable -using System.Diagnostics; -using System.Runtime.InteropServices; - -namespace System.IO -{ - internal static partial class PersistedFiles - { - private static string? s_userProductDirectory; - - /// <summary> - /// Get the location of where to persist information for a particular aspect of the framework, - /// such as "cryptography". - /// </summary> - /// <param name="featureName">The directory name for the feature</param> - /// <returns>A path within the user's home directory for persisting data for the feature</returns> - internal static string GetUserFeatureDirectory(string featureName) - { - if (s_userProductDirectory == null) - { - EnsureUserDirectories(); - } - - return Path.Combine(s_userProductDirectory!, featureName); - } - - /// <summary> - /// Get the location of where to persist information for a particular aspect of a feature of - /// the framework, such as "x509stores" within "cryptography". - /// </summary> - /// <param name="featureName">The directory name for the feature</param> - /// <param name="subFeatureName">The directory name for the sub-feature</param> - /// <returns>A path within the user's home directory for persisting data for the sub-feature</returns> - internal static string GetUserFeatureDirectory(string featureName, string subFeatureName) - { - if (s_userProductDirectory == null) - { - EnsureUserDirectories(); - } - - return Path.Combine(s_userProductDirectory!, featureName, subFeatureName); - } - - /// <summary> - /// Get the location of where to persist information for a particular aspect of the framework, - /// with a lot of hierarchy, such as ["cryptography", "x509stores", "my"] - /// </summary> - /// <param name="featurePathParts">A non-empty set of directories to use for the storage hierarchy</param> - /// <returns>A path within the user's home directory for persisting data for the feature</returns> - internal static string GetUserFeatureDirectory(params string[] featurePathParts) - { - Debug.Assert(featurePathParts != null); - Debug.Assert(featurePathParts.Length > 0); - - if (s_userProductDirectory == null) - { - EnsureUserDirectories(); - } - - return Path.Combine(s_userProductDirectory!, Path.Combine(featurePathParts)); - } - - private static void EnsureUserDirectories() - { - string? userHomeDirectory = GetHomeDirectory(); - - if (string.IsNullOrEmpty(userHomeDirectory)) - { - throw new InvalidOperationException(SR.PersistedFiles_NoHomeDirectory); - } - - s_userProductDirectory = Path.Combine( - userHomeDirectory, - TopLevelHiddenDirectory, - SecondLevelDirectory); - } - - /// <summary>Gets the current user's home directory.</summary> - /// <returns>The path to the home directory, or null if it could not be determined.</returns> - internal static string? GetHomeDirectory() - { - // First try to get the user's home directory from the HOME environment variable. - // This should work in most cases. - string? userHomeDirectory = Environment.GetEnvironmentVariable("HOME"); - if (!string.IsNullOrEmpty(userHomeDirectory)) - return userHomeDirectory; - - // In initialization conditions, however, the "HOME" environment variable may - // not yet be set. For such cases, consult with the password entry. - unsafe - { - // First try with a buffer that should suffice for 99% of cases. - // Note that, theoretically, userHomeDirectory may be null in the success case - // if we simply couldn't find a home directory for the current user. - // In that case, we pass back the null value and let the caller decide - // what to do. - const int BufLen = Interop.Sys.Passwd.InitialBufferSize; - byte* stackBuf = stackalloc byte[BufLen]; - if (TryGetHomeDirectoryFromPasswd(stackBuf, BufLen, out userHomeDirectory)) - return userHomeDirectory; - - // Fallback to heap allocations if necessary, growing the buffer until - // we succeed. TryGetHomeDirectory will throw if there's an unexpected error. - int lastBufLen = BufLen; - while (true) - { - lastBufLen *= 2; - byte[] heapBuf = new byte[lastBufLen]; - fixed (byte* buf = &heapBuf[0]) - { - if (TryGetHomeDirectoryFromPasswd(buf, heapBuf.Length, out userHomeDirectory)) - return userHomeDirectory; - } - } - } - } - - /// <summary>Wrapper for getpwuid_r.</summary> - /// <param name="buf">The scratch buffer to pass into getpwuid_r.</param> - /// <param name="bufLen">The length of <paramref name="buf"/>.</param> - /// <param name="path">The resulting path; null if the user didn't have an entry.</param> - /// <returns>true if the call was successful (path may still be null); false is a larger buffer is needed.</returns> - private static unsafe bool TryGetHomeDirectoryFromPasswd(byte* buf, int bufLen, out string? path) - { - // Call getpwuid_r to get the passwd struct - Interop.Sys.Passwd passwd; - int error = Interop.Sys.GetPwUidR(Interop.Sys.GetEUid(), out passwd, buf, bufLen); - - // If the call succeeds, give back the home directory path retrieved - if (error == 0) - { - Debug.Assert(passwd.HomeDirectory != null); - path = Marshal.PtrToStringAnsi((IntPtr)passwd.HomeDirectory); - return true; - } - - // If the current user's entry could not be found, give back null - // path, but still return true as false indicates the buffer was - // too small. - if (error == -1) - { - path = null; - return true; - } - - var errorInfo = new Interop.ErrorInfo(error); - - // If the call failed because the buffer was too small, return false to - // indicate the caller should try again with a larger buffer. - if (errorInfo.Error == Interop.Error.ERANGE) - { - path = null; - return false; - } - - // Otherwise, fail. - throw new IOException(errorInfo.GetErrorMessage(), errorInfo.RawErrno); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/PinnedBufferMemoryStream.cs b/netcore/System.Private.CoreLib/shared/System/IO/PinnedBufferMemoryStream.cs deleted file mode 100644 index 9c002974786..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/PinnedBufferMemoryStream.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** -** -** Purpose: Pins a byte[], exposing it as an unmanaged memory -** stream. Used in ResourceReader for corner cases. -** -** -===========================================================*/ - -using System.Runtime.InteropServices; -using System.Diagnostics; - -namespace System.IO -{ - internal sealed unsafe class PinnedBufferMemoryStream : UnmanagedMemoryStream - { - private readonly byte[] _array; - private GCHandle _pinningHandle; - - internal PinnedBufferMemoryStream(byte[] array) - { - Debug.Assert(array != null, "Array can't be null"); - - _array = array; - _pinningHandle = GCHandle.Alloc(array, GCHandleType.Pinned); - // Now the byte[] is pinned for the lifetime of this instance. - // But I also need to get a pointer to that block of memory... - int len = array.Length; - fixed (byte* ptr = &MemoryMarshal.GetReference((Span<byte>)array)) - Initialize(ptr, len, len, FileAccess.Read); - } - -#if !NETSTANDARD2_0 - public override int Read(Span<byte> buffer) => ReadCore(buffer); - - public override void Write(ReadOnlySpan<byte> buffer) => WriteCore(buffer); -#endif - - ~PinnedBufferMemoryStream() - { - Dispose(false); - } - - protected override void Dispose(bool disposing) - { - if (_pinningHandle.IsAllocated) - { - _pinningHandle.Free(); - } - - base.Dispose(disposing); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/SeekOrigin.cs b/netcore/System.Private.CoreLib/shared/System/IO/SeekOrigin.cs deleted file mode 100644 index 3798a0ce702..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/SeekOrigin.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace System.IO -{ - // Provides seek reference points. To seek to the end of a stream, - // call stream.Seek(0, SeekOrigin.End). - public enum SeekOrigin - { - // These constants match Win32's FILE_BEGIN, FILE_CURRENT, and FILE_END - Begin = 0, - Current = 1, - End = 2, - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/Stream.cs b/netcore/System.Private.CoreLib/shared/System/IO/Stream.cs deleted file mode 100644 index cf05e669ba3..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/Stream.cs +++ /dev/null @@ -1,1400 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** -** -** Purpose: Abstract base class for all Streams. Provides -** default implementations of asynchronous reads & writes, in -** terms of the synchronous reads & writes (and vice versa). -** -** -===========================================================*/ - -using System.Buffers; -using System.Diagnostics; -using System.Runtime.ExceptionServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO -{ - public abstract partial class Stream : MarshalByRefObject, IDisposable, IAsyncDisposable - { - public static readonly Stream Null = new NullStream(); - - // We pick a value that is the largest multiple of 4096 that is still smaller than the large object heap threshold (85K). - // The CopyTo/CopyToAsync buffer is short-lived and is likely to be collected at Gen0, and it offers a significant - // improvement in Copy performance. - private const int DefaultCopyBufferSize = 81920; - - // To implement Async IO operations on streams that don't support async IO - - private ReadWriteTask? _activeReadWriteTask; - private SemaphoreSlim? _asyncActiveSemaphore; - - internal SemaphoreSlim EnsureAsyncActiveSemaphoreInitialized() - { - // Lazily-initialize _asyncActiveSemaphore. As we're never accessing the SemaphoreSlim's - // WaitHandle, we don't need to worry about Disposing it. - return LazyInitializer.EnsureInitialized(ref _asyncActiveSemaphore, () => new SemaphoreSlim(1, 1)); - } - - public abstract bool CanRead - { - get; - } - - // If CanSeek is false, Position, Seek, Length, and SetLength should throw. - public abstract bool CanSeek - { - get; - } - - public virtual bool CanTimeout => false; - - public abstract bool CanWrite - { - get; - } - - public abstract long Length - { - get; - } - - public abstract long Position - { - get; - set; - } - - public virtual int ReadTimeout - { - get => throw new InvalidOperationException(SR.InvalidOperation_TimeoutsNotSupported); - set => throw new InvalidOperationException(SR.InvalidOperation_TimeoutsNotSupported); - } - - public virtual int WriteTimeout - { - get => throw new InvalidOperationException(SR.InvalidOperation_TimeoutsNotSupported); - set => throw new InvalidOperationException(SR.InvalidOperation_TimeoutsNotSupported); - } - - public Task CopyToAsync(Stream destination) - { - int bufferSize = GetCopyBufferSize(); - - return CopyToAsync(destination, bufferSize); - } - - public Task CopyToAsync(Stream destination, int bufferSize) - { - return CopyToAsync(destination, bufferSize, CancellationToken.None); - } - - public Task CopyToAsync(Stream destination, CancellationToken cancellationToken) - { - int bufferSize = GetCopyBufferSize(); - - return CopyToAsync(destination, bufferSize, cancellationToken); - } - - public virtual Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); - - return CopyToAsyncInternal(destination, bufferSize, cancellationToken); - } - - private async Task CopyToAsyncInternal(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); - try - { - while (true) - { - int bytesRead = await ReadAsync(new Memory<byte>(buffer), cancellationToken).ConfigureAwait(false); - if (bytesRead == 0) break; - await destination.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false); - } - } - finally - { - ArrayPool<byte>.Shared.Return(buffer); - } - } - - // Reads the bytes from the current stream and writes the bytes to - // the destination stream until all bytes are read, starting at - // the current position. - public void CopyTo(Stream destination) - { - int bufferSize = GetCopyBufferSize(); - - CopyTo(destination, bufferSize); - } - - public virtual void CopyTo(Stream destination, int bufferSize) - { - StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); - - byte[] buffer = ArrayPool<byte>.Shared.Rent(bufferSize); - try - { - int read; - while ((read = Read(buffer, 0, buffer.Length)) != 0) - { - destination.Write(buffer, 0, read); - } - } - finally - { - ArrayPool<byte>.Shared.Return(buffer); - } - } - - private int GetCopyBufferSize() - { - int bufferSize = DefaultCopyBufferSize; - - if (CanSeek) - { - long length = Length; - long position = Position; - if (length <= position) // Handles negative overflows - { - // There are no bytes left in the stream to copy. - // However, because CopyTo{Async} is virtual, we need to - // ensure that any override is still invoked to provide its - // own validation, so we use the smallest legal buffer size here. - bufferSize = 1; - } - else - { - long remaining = length - position; - if (remaining > 0) - { - // In the case of a positive overflow, stick to the default size - bufferSize = (int)Math.Min(bufferSize, remaining); - } - } - } - - return bufferSize; - } - - public virtual void CopyTo(ReadOnlySpanAction<byte, object?> callback, object? state, int bufferSize) - { - if (callback == null) throw new ArgumentNullException(nameof(callback)); - - CopyTo(new WriteCallbackStream(callback, state), bufferSize); - } - - public virtual Task CopyToAsync(Func<ReadOnlyMemory<byte>, object?, CancellationToken, ValueTask> callback, object? state, int bufferSize, CancellationToken cancellationToken) - { - if (callback == null) throw new ArgumentNullException(nameof(callback)); - - return CopyToAsync(new WriteCallbackStream(callback, state), bufferSize, cancellationToken); - } - - private sealed class WriteCallbackStream : Stream - { - private readonly ReadOnlySpanAction<byte, object?>? _action; - private readonly Func<ReadOnlyMemory<byte>, object?, CancellationToken, ValueTask>? _func; - private readonly object? _state; - - public WriteCallbackStream(ReadOnlySpanAction<byte, object?> action, object? state) - { - _action = action; - _state = state; - } - - public WriteCallbackStream(Func<ReadOnlyMemory<byte>, object?, CancellationToken, ValueTask> func, object? state) - { - _func = func; - _state = state; - } - - public override void Write(byte[] buffer, int offset, int count) - { - Write(new ReadOnlySpan<byte>(buffer, offset, count)); - } - - public override void Write(ReadOnlySpan<byte> span) - { - if (_action != null) - { - _action(span, _state); - return; - } - - // In case a poorly implemented CopyToAsync(Stream, ...) method decides to call - // the destination stream's Write rather than WriteAsync, we make it work, but this - // does not need to be efficient. - Debug.Assert(_func != null); - _func(span.ToArray(), _state, CancellationToken.None).AsTask().GetAwaiter().GetResult(); - - } - - public override Task WriteAsync(byte[] buffer, int offset, int length, CancellationToken cancellationToken) - { - return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, length), cancellationToken).AsTask(); - } - - public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken) - { - if (_func != null) - { - return _func(buffer, _state, cancellationToken); - } - - // In case a poorly implemented CopyTo(Stream, ...) method decides to call - // the destination stream's WriteAsync rather than Write, we make it work, - // but this does not need to be efficient. - Debug.Assert(_action != null); - try - { - cancellationToken.ThrowIfCancellationRequested(); - _action(buffer.Span, _state); - return default; - } - catch (Exception e) - { - return new ValueTask(Task.FromException(e)); - } - } - - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - public override void Flush() { } - public override Task FlushAsync(CancellationToken token) => Task.CompletedTask; - public override long Length => throw new NotSupportedException(); - public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); } - public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); - public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); - public override void SetLength(long value) => throw new NotSupportedException(); - } - - // Stream used to require that all cleanup logic went into Close(), - // which was thought up before we invented IDisposable. However, we - // need to follow the IDisposable pattern so that users can write - // sensible subclasses without needing to inspect all their base - // classes, and without worrying about version brittleness, from a - // base class switching to the Dispose pattern. We're moving - // Stream to the Dispose(bool) pattern - that's where all subclasses - // should put their cleanup now. - public virtual void Close() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void Dispose() - { - Close(); - } - - protected virtual void Dispose(bool disposing) - { - // Note: Never change this to call other virtual methods on Stream - // like Write, since the state on subclasses has already been - // torn down. This is the last code to run on cleanup for a stream. - } - - public virtual ValueTask DisposeAsync() - { - try - { - Dispose(); - return default; - } - catch (Exception exc) - { - return new ValueTask(Task.FromException(exc)); - } - } - - public abstract void Flush(); - - public Task FlushAsync() - { - return FlushAsync(CancellationToken.None); - } - - public virtual Task FlushAsync(CancellationToken cancellationToken) - { - return Task.Factory.StartNew(state => ((Stream)state!).Flush(), this, - cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - - [Obsolete("CreateWaitHandle will be removed eventually. Please use \"new ManualResetEvent(false)\" instead.")] - protected virtual WaitHandle CreateWaitHandle() - { - return new ManualResetEvent(false); - } - - public virtual IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { - return BeginReadInternal(buffer, offset, count, callback, state, serializeAsynchronously: false, apm: true); - } - - internal IAsyncResult BeginReadInternal( - byte[] buffer, int offset, int count, AsyncCallback? callback, object? state, - bool serializeAsynchronously, bool apm) - { - if (!CanRead) throw Error.GetReadNotSupported(); - - // To avoid a race with a stream's position pointer & generating race conditions - // with internal buffer indexes in our own streams that - // don't natively support async IO operations when there are multiple - // async requests outstanding, we will block the application's main - // thread if it does a second IO request until the first one completes. - SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized(); - Task? semaphoreTask = null; - if (serializeAsynchronously) - { - semaphoreTask = semaphore.WaitAsync(); - } - else - { - semaphore.Wait(); - } - - // Create the task to asynchronously do a Read. This task serves both - // as the asynchronous work item and as the IAsyncResult returned to the user. - var asyncResult = new ReadWriteTask(true /*isRead*/, apm, delegate - { - // The ReadWriteTask stores all of the parameters to pass to Read. - // As we're currently inside of it, we can get the current task - // and grab the parameters from it. - var thisTask = Task.InternalCurrent as ReadWriteTask; - Debug.Assert(thisTask != null && thisTask._stream != null && thisTask._buffer != null, - "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask, and stream and buffer should be set"); - - try - { - // Do the Read and return the number of bytes read - return thisTask._stream.Read(thisTask._buffer, thisTask._offset, thisTask._count); - } - finally - { - // If this implementation is part of Begin/EndXx, then the EndXx method will handle - // finishing the async operation. However, if this is part of XxAsync, then there won't - // be an end method, and this task is responsible for cleaning up. - if (!thisTask._apm) - { - thisTask._stream.FinishTrackingAsyncOperation(); - } - - thisTask.ClearBeginState(); // just to help alleviate some memory pressure - } - }, state, this, buffer, offset, count, callback); - - // Schedule it - if (semaphoreTask != null) - RunReadWriteTaskWhenReady(semaphoreTask, asyncResult); - else - RunReadWriteTask(asyncResult); - - - return asyncResult; // return it - } - - public virtual int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - ReadWriteTask? readTask = _activeReadWriteTask; - - if (readTask == null) - { - throw new ArgumentException(SR.InvalidOperation_WrongAsyncResultOrEndReadCalledMultiple); - } - else if (readTask != asyncResult) - { - throw new InvalidOperationException(SR.InvalidOperation_WrongAsyncResultOrEndReadCalledMultiple); - } - else if (!readTask._isRead) - { - throw new ArgumentException(SR.InvalidOperation_WrongAsyncResultOrEndReadCalledMultiple); - } - - try - { - return readTask.GetAwaiter().GetResult(); // block until completion, then get result / propagate any exception - } - finally - { - FinishTrackingAsyncOperation(); - } - } - - public Task<int> ReadAsync(byte[] buffer, int offset, int count) - { - return ReadAsync(buffer, offset, count, CancellationToken.None); - } - - public virtual Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - // If cancellation was requested, bail early with an already completed task. - // Otherwise, return a task that represents the Begin/End methods. - return cancellationToken.IsCancellationRequested - ? Task.FromCanceled<int>(cancellationToken) - : BeginEndReadAsync(buffer, offset, count); - } - - public virtual ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) - { - if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> array)) - { - return new ValueTask<int>(ReadAsync(array.Array!, array.Offset, array.Count, cancellationToken)); - } - else - { - byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length); - return FinishReadAsync(ReadAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer, buffer); - - static async ValueTask<int> FinishReadAsync(Task<int> readTask, byte[] localBuffer, Memory<byte> localDestination) - { - try - { - int result = await readTask.ConfigureAwait(false); - new Span<byte>(localBuffer, 0, result).CopyTo(localDestination.Span); - return result; - } - finally - { - ArrayPool<byte>.Shared.Return(localBuffer); - } - } - } - } - - private Task<int> BeginEndReadAsync(byte[] buffer, int offset, int count) - { - if (!HasOverriddenBeginEndRead()) - { - // If the Stream does not override Begin/EndRead, then we can take an optimized path - // that skips an extra layer of tasks / IAsyncResults. - return (Task<int>)BeginReadInternal(buffer, offset, count, null, null, serializeAsynchronously: true, apm: false); - } - - // Otherwise, we need to wrap calls to Begin/EndWrite to ensure we use the derived type's functionality. - return TaskFactory<int>.FromAsyncTrim( - this, new ReadWriteParameters { Buffer = buffer, Offset = offset, Count = count }, - (stream, args, callback, state) => stream.BeginRead(args.Buffer, args.Offset, args.Count, callback, state), // cached by compiler - (stream, asyncResult) => stream.EndRead(asyncResult)); // cached by compiler - } - - private struct ReadWriteParameters // struct for arguments to Read and Write calls - { - internal byte[] Buffer; - internal int Offset; - internal int Count; - } - - - - public virtual IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { - return BeginWriteInternal(buffer, offset, count, callback, state, serializeAsynchronously: false, apm: true); - } - - internal IAsyncResult BeginWriteInternal( - byte[] buffer, int offset, int count, AsyncCallback? callback, object? state, - bool serializeAsynchronously, bool apm) - { - if (!CanWrite) throw Error.GetWriteNotSupported(); - - // To avoid a race condition with a stream's position pointer & generating conditions - // with internal buffer indexes in our own streams that - // don't natively support async IO operations when there are multiple - // async requests outstanding, we will block the application's main - // thread if it does a second IO request until the first one completes. - SemaphoreSlim semaphore = EnsureAsyncActiveSemaphoreInitialized(); - Task? semaphoreTask = null; - if (serializeAsynchronously) - { - semaphoreTask = semaphore.WaitAsync(); // kick off the asynchronous wait, but don't block - } - else - { - semaphore.Wait(); // synchronously wait here - } - - // Create the task to asynchronously do a Write. This task serves both - // as the asynchronous work item and as the IAsyncResult returned to the user. - var asyncResult = new ReadWriteTask(false /*isRead*/, apm, delegate - { - // The ReadWriteTask stores all of the parameters to pass to Write. - // As we're currently inside of it, we can get the current task - // and grab the parameters from it. - var thisTask = Task.InternalCurrent as ReadWriteTask; - Debug.Assert(thisTask != null && thisTask._stream != null && thisTask._buffer != null, - "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask, and stream and buffer should be set"); - - try - { - // Do the Write - thisTask._stream.Write(thisTask._buffer, thisTask._offset, thisTask._count); - return 0; // not used, but signature requires a value be returned - } - finally - { - // If this implementation is part of Begin/EndXx, then the EndXx method will handle - // finishing the async operation. However, if this is part of XxAsync, then there won't - // be an end method, and this task is responsible for cleaning up. - if (!thisTask._apm) - { - thisTask._stream.FinishTrackingAsyncOperation(); - } - - thisTask.ClearBeginState(); // just to help alleviate some memory pressure - } - }, state, this, buffer, offset, count, callback); - - // Schedule it - if (semaphoreTask != null) - RunReadWriteTaskWhenReady(semaphoreTask, asyncResult); - else - RunReadWriteTask(asyncResult); - - return asyncResult; // return it - } - - private void RunReadWriteTaskWhenReady(Task asyncWaiter, ReadWriteTask readWriteTask) - { - Debug.Assert(readWriteTask != null); - Debug.Assert(asyncWaiter != null); - - // If the wait has already completed, run the task. - if (asyncWaiter.IsCompleted) - { - Debug.Assert(asyncWaiter.IsCompletedSuccessfully, "The semaphore wait should always complete successfully."); - RunReadWriteTask(readWriteTask); - } - else // Otherwise, wait for our turn, and then run the task. - { - asyncWaiter.ContinueWith((t, state) => - { - Debug.Assert(t.IsCompletedSuccessfully, "The semaphore wait should always complete successfully."); - var rwt = (ReadWriteTask)state!; - Debug.Assert(rwt._stream != null); - rwt._stream.RunReadWriteTask(rwt); // RunReadWriteTask(readWriteTask); - }, readWriteTask, default, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); - } - } - - private void RunReadWriteTask(ReadWriteTask readWriteTask) - { - Debug.Assert(readWriteTask != null); - Debug.Assert(_activeReadWriteTask == null, "Expected no other readers or writers"); - - // Schedule the task. ScheduleAndStart must happen after the write to _activeReadWriteTask to avoid a race. - // Internally, we're able to directly call ScheduleAndStart rather than Start, avoiding - // two interlocked operations. However, if ReadWriteTask is ever changed to use - // a cancellation token, this should be changed to use Start. - _activeReadWriteTask = readWriteTask; // store the task so that EndXx can validate it's given the right one - readWriteTask.m_taskScheduler = TaskScheduler.Default; - readWriteTask.ScheduleAndStart(needsProtection: false); - } - - private void FinishTrackingAsyncOperation() - { - _activeReadWriteTask = null; - Debug.Assert(_asyncActiveSemaphore != null, "Must have been initialized in order to get here."); - _asyncActiveSemaphore.Release(); - } - - public virtual void EndWrite(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - ReadWriteTask? writeTask = _activeReadWriteTask; - if (writeTask == null) - { - throw new ArgumentException(SR.InvalidOperation_WrongAsyncResultOrEndWriteCalledMultiple); - } - else if (writeTask != asyncResult) - { - throw new InvalidOperationException(SR.InvalidOperation_WrongAsyncResultOrEndWriteCalledMultiple); - } - else if (writeTask._isRead) - { - throw new ArgumentException(SR.InvalidOperation_WrongAsyncResultOrEndWriteCalledMultiple); - } - - try - { - writeTask.GetAwaiter().GetResult(); // block until completion, then propagate any exceptions - Debug.Assert(writeTask.Status == TaskStatus.RanToCompletion); - } - finally - { - FinishTrackingAsyncOperation(); - } - } - - // Task used by BeginRead / BeginWrite to do Read / Write asynchronously. - // A single instance of this task serves four purposes: - // 1. The work item scheduled to run the Read / Write operation - // 2. The state holding the arguments to be passed to Read / Write - // 3. The IAsyncResult returned from BeginRead / BeginWrite - // 4. The completion action that runs to invoke the user-provided callback. - // This last item is a bit tricky. Before the AsyncCallback is invoked, the - // IAsyncResult must have completed, so we can't just invoke the handler - // from within the task, since it is the IAsyncResult, and thus it's not - // yet completed. Instead, we use AddCompletionAction to install this - // task as its own completion handler. That saves the need to allocate - // a separate completion handler, it guarantees that the task will - // have completed by the time the handler is invoked, and it allows - // the handler to be invoked synchronously upon the completion of the - // task. This all enables BeginRead / BeginWrite to be implemented - // with a single allocation. - private sealed class ReadWriteTask : Task<int>, ITaskCompletionAction - { - internal readonly bool _isRead; - internal readonly bool _apm; // true if this is from Begin/EndXx; false if it's from XxAsync - internal Stream? _stream; - internal byte[]? _buffer; - internal readonly int _offset; - internal readonly int _count; - private AsyncCallback? _callback; - private ExecutionContext? _context; - - internal void ClearBeginState() // Used to allow the args to Read/Write to be made available for GC - { - _stream = null; - _buffer = null; - } - - public ReadWriteTask( - bool isRead, - bool apm, - Func<object?, int> function, object? state, - Stream stream, byte[] buffer, int offset, int count, AsyncCallback? callback) : - base(function, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach) - { - Debug.Assert(function != null); - Debug.Assert(stream != null); - Debug.Assert(buffer != null); - - // Store the arguments - _isRead = isRead; - _apm = apm; - _stream = stream; - _buffer = buffer; - _offset = offset; - _count = count; - - // If a callback was provided, we need to: - // - Store the user-provided handler - // - Capture an ExecutionContext under which to invoke the handler - // - Add this task as its own completion handler so that the Invoke method - // will run the callback when this task completes. - if (callback != null) - { - _callback = callback; - _context = ExecutionContext.Capture(); - base.AddCompletionAction(this); - } - } - - private static void InvokeAsyncCallback(object? completedTask) - { - Debug.Assert(completedTask is ReadWriteTask); - var rwc = (ReadWriteTask)completedTask; - AsyncCallback? callback = rwc._callback; - Debug.Assert(callback != null); - rwc._callback = null; - callback(rwc); - } - - private static ContextCallback? s_invokeAsyncCallback; - - void ITaskCompletionAction.Invoke(Task completingTask) - { - // Get the ExecutionContext. If there is none, just run the callback - // directly, passing in the completed task as the IAsyncResult. - // If there is one, process it with ExecutionContext.Run. - ExecutionContext? context = _context; - if (context == null) - { - AsyncCallback? callback = _callback; - Debug.Assert(callback != null); - _callback = null; - callback(completingTask); - } - else - { - _context = null; - - ContextCallback? invokeAsyncCallback = s_invokeAsyncCallback ??= InvokeAsyncCallback; - - ExecutionContext.RunInternal(context, invokeAsyncCallback, this); - } - } - - bool ITaskCompletionAction.InvokeMayRunArbitraryCode => true; - } - - public Task WriteAsync(byte[] buffer, int offset, int count) - { - return WriteAsync(buffer, offset, count, CancellationToken.None); - } - - public virtual Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - // If cancellation was requested, bail early with an already completed task. - // Otherwise, return a task that represents the Begin/End methods. - return cancellationToken.IsCancellationRequested - ? Task.FromCanceled(cancellationToken) - : BeginEndWriteAsync(buffer, offset, count); - } - - public virtual ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) - { - if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> array)) - { - return new ValueTask(WriteAsync(array.Array!, array.Offset, array.Count, cancellationToken)); - } - else - { - byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length); - buffer.Span.CopyTo(sharedBuffer); - return new ValueTask(FinishWriteAsync(WriteAsync(sharedBuffer, 0, buffer.Length, cancellationToken), sharedBuffer)); - } - } - - private async Task FinishWriteAsync(Task writeTask, byte[] localBuffer) - { - try - { - await writeTask.ConfigureAwait(false); - } - finally - { - ArrayPool<byte>.Shared.Return(localBuffer); - } - } - - private Task BeginEndWriteAsync(byte[] buffer, int offset, int count) - { - if (!HasOverriddenBeginEndWrite()) - { - // If the Stream does not override Begin/EndWrite, then we can take an optimized path - // that skips an extra layer of tasks / IAsyncResults. - return (Task)BeginWriteInternal(buffer, offset, count, null, null, serializeAsynchronously: true, apm: false); - } - - // Otherwise, we need to wrap calls to Begin/EndWrite to ensure we use the derived type's functionality. - return TaskFactory<VoidTaskResult>.FromAsyncTrim( - this, new ReadWriteParameters { Buffer = buffer, Offset = offset, Count = count }, - (stream, args, callback, state) => stream.BeginWrite(args.Buffer, args.Offset, args.Count, callback, state), // cached by compiler - (stream, asyncResult) => // cached by compiler - { - stream.EndWrite(asyncResult); - return default; - }); - } - - public abstract long Seek(long offset, SeekOrigin origin); - - public abstract void SetLength(long value); - - public abstract int Read(byte[] buffer, int offset, int count); - - public virtual int Read(Span<byte> buffer) - { - byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length); - try - { - int numRead = Read(sharedBuffer, 0, buffer.Length); - if ((uint)numRead > (uint)buffer.Length) - { - throw new IOException(SR.IO_StreamTooLong); - } - new Span<byte>(sharedBuffer, 0, numRead).CopyTo(buffer); - return numRead; - } - finally { ArrayPool<byte>.Shared.Return(sharedBuffer); } - } - - // Reads one byte from the stream by calling Read(byte[], int, int). - // Will return an unsigned byte cast to an int or -1 on end of stream. - // This implementation does not perform well because it allocates a new - // byte[] each time you call it, and should be overridden by any - // subclass that maintains an internal buffer. Then, it can help perf - // significantly for people who are reading one byte at a time. - public virtual int ReadByte() - { - byte[] oneByteArray = new byte[1]; - int r = Read(oneByteArray, 0, 1); - if (r == 0) - return -1; - return oneByteArray[0]; - } - - public abstract void Write(byte[] buffer, int offset, int count); - - public virtual void Write(ReadOnlySpan<byte> buffer) - { - byte[] sharedBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length); - try - { - buffer.CopyTo(sharedBuffer); - Write(sharedBuffer, 0, buffer.Length); - } - finally { ArrayPool<byte>.Shared.Return(sharedBuffer); } - } - - // Writes one byte from the stream by calling Write(byte[], int, int). - // This implementation does not perform well because it allocates a new - // byte[] each time you call it, and should be overridden by any - // subclass that maintains an internal buffer. Then, it can help perf - // significantly for people who are writing one byte at a time. - public virtual void WriteByte(byte value) - { - byte[] oneByteArray = new byte[1]; - oneByteArray[0] = value; - Write(oneByteArray, 0, 1); - } - - public static Stream Synchronized(Stream stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - if (stream is SyncStream) - return stream; - - return new SyncStream(stream); - } - - [Obsolete("Do not call or override this method.")] - protected virtual void ObjectInvariant() - { - } - - internal IAsyncResult BlockingBeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { - // To avoid a race with a stream's position pointer & generating conditions - // with internal buffer indexes in our own streams that - // don't natively support async IO operations when there are multiple - // async requests outstanding, we will block the application's main - // thread and do the IO synchronously. - // This can't perform well - use a different approach. - SynchronousAsyncResult asyncResult; - try - { - int numRead = Read(buffer, offset, count); - asyncResult = new SynchronousAsyncResult(numRead, state); - } - catch (IOException ex) - { - asyncResult = new SynchronousAsyncResult(ex, state, isWrite: false); - } - - callback?.Invoke(asyncResult); - - return asyncResult; - } - - internal static int BlockingEndRead(IAsyncResult asyncResult) - { - return SynchronousAsyncResult.EndRead(asyncResult); - } - - internal IAsyncResult BlockingBeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { - // To avoid a race condition with a stream's position pointer & generating conditions - // with internal buffer indexes in our own streams that - // don't natively support async IO operations when there are multiple - // async requests outstanding, we will block the application's main - // thread and do the IO synchronously. - // This can't perform well - use a different approach. - SynchronousAsyncResult asyncResult; - try - { - Write(buffer, offset, count); - asyncResult = new SynchronousAsyncResult(state); - } - catch (IOException ex) - { - asyncResult = new SynchronousAsyncResult(ex, state, isWrite: true); - } - - callback?.Invoke(asyncResult); - - return asyncResult; - } - - internal static void BlockingEndWrite(IAsyncResult asyncResult) - { - SynchronousAsyncResult.EndWrite(asyncResult); - } - - private sealed class NullStream : Stream - { - private static readonly Task<int> s_zeroTask = Task.FromResult(0); - - internal NullStream() { } - - public override bool CanRead => true; - - public override bool CanWrite => true; - - public override bool CanSeek => true; - - public override long Length => 0; - - public override long Position - { - get => 0; - set { } - } - - public override void CopyTo(Stream destination, int bufferSize) - { - StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); - - // After we validate arguments this is a nop. - } - - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - // Validate arguments here for compat, since previously this method - // was inherited from Stream (which did check its arguments). - StreamHelpers.ValidateCopyToArgs(this, destination, bufferSize); - - return cancellationToken.IsCancellationRequested ? - Task.FromCanceled(cancellationToken) : - Task.CompletedTask; - } - - public override void CopyTo(ReadOnlySpanAction<byte, object?> callback, object? state, int bufferSize) - { - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - // After we validate arguments this is a nop. - } - - public override Task CopyToAsync(Func<ReadOnlyMemory<byte>, object?, CancellationToken, ValueTask> callback, object? state, int bufferSize, CancellationToken cancellationToken) - { - StreamHelpers.ValidateCopyToArgs(this, callback, bufferSize); - - return cancellationToken.IsCancellationRequested ? - Task.FromCanceled(cancellationToken) : - Task.CompletedTask; - } - - protected override void Dispose(bool disposing) - { - // Do nothing - we don't want NullStream singleton (static) to be closable - } - - public override void Flush() - { - } - - public override Task FlushAsync(CancellationToken cancellationToken) - { - return cancellationToken.IsCancellationRequested ? - Task.FromCanceled(cancellationToken) : - Task.CompletedTask; - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { - if (!CanRead) throw Error.GetReadNotSupported(); - - return BlockingBeginRead(buffer, offset, count, callback, state); - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - return BlockingEndRead(asyncResult); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { - if (!CanWrite) throw Error.GetWriteNotSupported(); - - return BlockingBeginWrite(buffer, offset, count, callback, state); - } - - public override void EndWrite(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - BlockingEndWrite(asyncResult); - } - - public override int Read(byte[] buffer, int offset, int count) - { - return 0; - } - - public override int Read(Span<byte> buffer) - { - return 0; - } - - public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return s_zeroTask; - } - - public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) - { - return new ValueTask<int>(0); - } - - public override int ReadByte() - { - return -1; - } - - public override void Write(byte[] buffer, int offset, int count) - { - } - - public override void Write(ReadOnlySpan<byte> buffer) - { - } - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return cancellationToken.IsCancellationRequested ? - Task.FromCanceled(cancellationToken) : - Task.CompletedTask; - } - - public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) - { - return cancellationToken.IsCancellationRequested ? - new ValueTask(Task.FromCanceled(cancellationToken)) : - default; - } - - public override void WriteByte(byte value) - { - } - - public override long Seek(long offset, SeekOrigin origin) - { - return 0; - } - - public override void SetLength(long length) - { - } - } - - - /// <summary>Used as the IAsyncResult object when using asynchronous IO methods on the base Stream class.</summary> - private sealed class SynchronousAsyncResult : IAsyncResult - { - private readonly object? _stateObject; - private readonly bool _isWrite; - private ManualResetEvent? _waitHandle; - private readonly ExceptionDispatchInfo? _exceptionInfo; - - private bool _endXxxCalled; - private readonly int _bytesRead; - - internal SynchronousAsyncResult(int bytesRead, object? asyncStateObject) - { - _bytesRead = bytesRead; - _stateObject = asyncStateObject; - } - - internal SynchronousAsyncResult(object? asyncStateObject) - { - _stateObject = asyncStateObject; - _isWrite = true; - } - - internal SynchronousAsyncResult(Exception ex, object? asyncStateObject, bool isWrite) - { - _exceptionInfo = ExceptionDispatchInfo.Capture(ex); - _stateObject = asyncStateObject; - _isWrite = isWrite; - } - - public bool IsCompleted => true; - - public WaitHandle AsyncWaitHandle => - LazyInitializer.EnsureInitialized(ref _waitHandle, () => new ManualResetEvent(true)); - - public object? AsyncState => _stateObject; - - public bool CompletedSynchronously => true; - - internal void ThrowIfError() - { - if (_exceptionInfo != null) - _exceptionInfo.Throw(); - } - - internal static int EndRead(IAsyncResult asyncResult) - { - if (!(asyncResult is SynchronousAsyncResult ar) || ar._isWrite) - throw new ArgumentException(SR.Arg_WrongAsyncResult); - - if (ar._endXxxCalled) - throw new ArgumentException(SR.InvalidOperation_EndReadCalledMultiple); - - ar._endXxxCalled = true; - - ar.ThrowIfError(); - return ar._bytesRead; - } - - internal static void EndWrite(IAsyncResult asyncResult) - { - if (!(asyncResult is SynchronousAsyncResult ar) || !ar._isWrite) - throw new ArgumentException(SR.Arg_WrongAsyncResult); - - if (ar._endXxxCalled) - throw new ArgumentException(SR.InvalidOperation_EndWriteCalledMultiple); - - ar._endXxxCalled = true; - - ar.ThrowIfError(); - } - } // class SynchronousAsyncResult - - - // SyncStream is a wrapper around a stream that takes - // a lock for every operation making it thread safe. - private sealed class SyncStream : Stream, IDisposable - { - private readonly Stream _stream; - - internal SyncStream(Stream stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); - _stream = stream; - } - - public override bool CanRead => _stream.CanRead; - - public override bool CanWrite => _stream.CanWrite; - - public override bool CanSeek => _stream.CanSeek; - - public override bool CanTimeout => _stream.CanTimeout; - - public override long Length - { - get - { - lock (_stream) - { - return _stream.Length; - } - } - } - - public override long Position - { - get - { - lock (_stream) - { - return _stream.Position; - } - } - set - { - lock (_stream) - { - _stream.Position = value; - } - } - } - - public override int ReadTimeout - { - get => _stream.ReadTimeout; - set => _stream.ReadTimeout = value; - } - - public override int WriteTimeout - { - get => _stream.WriteTimeout; - set => _stream.WriteTimeout = value; - } - - // In the off chance that some wrapped stream has different - // semantics for Close vs. Dispose, let's preserve that. - public override void Close() - { - lock (_stream) - { - try - { - _stream.Close(); - } - finally - { - base.Dispose(true); - } - } - } - - protected override void Dispose(bool disposing) - { - lock (_stream) - { - try - { - // Explicitly pick up a potentially methodimpl'ed Dispose - if (disposing) - ((IDisposable)_stream).Dispose(); - } - finally - { - base.Dispose(disposing); - } - } - } - - public override ValueTask DisposeAsync() - { - lock (_stream) - return _stream.DisposeAsync(); - } - - public override void Flush() - { - lock (_stream) - _stream.Flush(); - } - - public override int Read(byte[] bytes, int offset, int count) - { - lock (_stream) - return _stream.Read(bytes, offset, count); - } - - public override int Read(Span<byte> buffer) - { - lock (_stream) - return _stream.Read(buffer); - } - - public override int ReadByte() - { - lock (_stream) - return _stream.ReadByte(); - } - - public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { -#if CORERT - throw new NotImplementedException(); // TODO: https://github.com/dotnet/corert/issues/3251 -#else - bool overridesBeginRead = _stream.HasOverriddenBeginEndRead(); - - lock (_stream) - { - // If the Stream does have its own BeginRead implementation, then we must use that override. - // If it doesn't, then we'll use the base implementation, but we'll make sure that the logic - // which ensures only one asynchronous operation does so with an asynchronous wait rather - // than a synchronous wait. A synchronous wait will result in a deadlock condition, because - // the EndXx method for the outstanding async operation won't be able to acquire the lock on - // _stream due to this call blocked while holding the lock. - return overridesBeginRead ? - _stream.BeginRead(buffer, offset, count, callback, state) : - _stream.BeginReadInternal(buffer, offset, count, callback, state, serializeAsynchronously: true, apm: true); - } -#endif - } - - public override int EndRead(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - lock (_stream) - return _stream.EndRead(asyncResult); - } - - public override long Seek(long offset, SeekOrigin origin) - { - lock (_stream) - return _stream.Seek(offset, origin); - } - - public override void SetLength(long length) - { - lock (_stream) - _stream.SetLength(length); - } - - public override void Write(byte[] bytes, int offset, int count) - { - lock (_stream) - _stream.Write(bytes, offset, count); - } - - public override void Write(ReadOnlySpan<byte> buffer) - { - lock (_stream) - _stream.Write(buffer); - } - - public override void WriteByte(byte b) - { - lock (_stream) - _stream.WriteByte(b); - } - - public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object? state) - { -#if CORERT - throw new NotImplementedException(); // TODO: https://github.com/dotnet/corert/issues/3251 -#else - bool overridesBeginWrite = _stream.HasOverriddenBeginEndWrite(); - - lock (_stream) - { - // If the Stream does have its own BeginWrite implementation, then we must use that override. - // If it doesn't, then we'll use the base implementation, but we'll make sure that the logic - // which ensures only one asynchronous operation does so with an asynchronous wait rather - // than a synchronous wait. A synchronous wait will result in a deadlock condition, because - // the EndXx method for the outstanding async operation won't be able to acquire the lock on - // _stream due to this call blocked while holding the lock. - return overridesBeginWrite ? - _stream.BeginWrite(buffer, offset, count, callback, state) : - _stream.BeginWriteInternal(buffer, offset, count, callback, state, serializeAsynchronously: true, apm: true); - } -#endif - } - - public override void EndWrite(IAsyncResult asyncResult) - { - if (asyncResult == null) - throw new ArgumentNullException(nameof(asyncResult)); - - lock (_stream) - _stream.EndWrite(asyncResult); - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/StreamHelpers.CopyValidation.cs b/netcore/System.Private.CoreLib/shared/System/IO/StreamHelpers.CopyValidation.cs deleted file mode 100644 index 1d120e5a077..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/StreamHelpers.CopyValidation.cs +++ /dev/null @@ -1,70 +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.Threading; -using System.Threading.Tasks; - -namespace System.IO -{ - /// <summary>Provides methods to help in the implementation of Stream-derived types.</summary> - internal static partial class StreamHelpers - { - /// <summary>Validate the arguments to CopyTo, as would Stream.CopyTo.</summary> - public static void ValidateCopyToArgs(Stream source, Stream destination, int bufferSize) - { - if (destination == null) - { - throw new ArgumentNullException(nameof(destination)); - } - - if (bufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, SR.ArgumentOutOfRange_NeedPosNum); - } - - bool sourceCanRead = source.CanRead; - if (!sourceCanRead && !source.CanWrite) - { - throw new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed); - } - - bool destinationCanWrite = destination.CanWrite; - if (!destinationCanWrite && !destination.CanRead) - { - throw new ObjectDisposedException(nameof(destination), SR.ObjectDisposed_StreamClosed); - } - - if (!sourceCanRead) - { - throw new NotSupportedException(SR.NotSupported_UnreadableStream); - } - - if (!destinationCanWrite) - { - throw new NotSupportedException(SR.NotSupported_UnwritableStream); - } - } - - public static void ValidateCopyToArgs(Stream source, Delegate callback, int bufferSize) - { - if (callback == null) - { - throw new ArgumentNullException(nameof(callback)); - } - - if (bufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, SR.ArgumentOutOfRange_NeedPosNum); - } - - if (!source.CanRead) - { - throw source.CanWrite ? (Exception) - new NotSupportedException(SR.NotSupported_UnreadableStream) : - new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed); - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/StreamReader.cs b/netcore/System.Private.CoreLib/shared/System/IO/StreamReader.cs deleted file mode 100644 index 5ce2d117a99..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/StreamReader.cs +++ /dev/null @@ -1,1347 +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.CodeAnalysis; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO -{ - // This class implements a TextReader for reading characters to a Stream. - // This is designed for character input in a particular Encoding, - // whereas the Stream class is designed for byte input and output. - public class StreamReader : TextReader - { - // StreamReader.Null is threadsafe. - public static new readonly StreamReader Null = new NullStreamReader(); - - // Using a 1K byte buffer and a 4K FileStream buffer works out pretty well - // perf-wise. On even a 40 MB text file, any perf loss by using a 4K - // buffer is negated by the win of allocating a smaller byte[], which - // saves construction time. This does break adaptive buffering, - // but this is slightly faster. - private const int DefaultBufferSize = 1024; // Byte buffer size - private const int DefaultFileStreamBufferSize = 4096; - private const int MinBufferSize = 128; - - private readonly Stream _stream; - private Encoding _encoding = null!; // only null in NullStreamReader where this is never used - private Decoder _decoder = null!; // only null in NullStreamReader where this is never used - private readonly byte[] _byteBuffer = null!; // only null in NullStreamReader where this is never used - private char[] _charBuffer = null!; // only null in NullStreamReader where this is never used - private int _charPos; - private int _charLen; - // Record the number of valid bytes in the byteBuffer, for a few checks. - private int _byteLen; - // This is used only for preamble detection - private int _bytePos; - - // This is the maximum number of chars we can get from one call to - // ReadBuffer. Used so ReadBuffer can tell when to copy data into - // a user's char[] directly, instead of our internal char[]. - private int _maxCharsPerBuffer; - - /// <summary>True if the writer has been disposed; otherwise, false.</summary> - private bool _disposed; - - // We will support looking for byte order marks in the stream and trying - // to decide what the encoding might be from the byte order marks, IF they - // exist. But that's all we'll do. - private bool _detectEncoding; - - // Whether we must still check for the encoding's given preamble at the - // beginning of this file. - private bool _checkPreamble; - - // Whether the stream is most likely not going to give us back as much - // data as we want the next time we call it. We must do the computation - // before we do any byte order mark handling and save the result. Note - // that we need this to allow users to handle streams used for an - // interactive protocol, where they block waiting for the remote end - // to send a response, like logging in on a Unix machine. - private bool _isBlocked; - - // The intent of this field is to leave open the underlying stream when - // disposing of this StreamReader. A name like _leaveOpen is better, - // but this type is serializable, and this field's name was _closable. - private readonly bool _closable; // Whether to close the underlying stream. - - // We don't guarantee thread safety on StreamReader, but we should at - // least prevent users from trying to read anything while an Async - // read from the same thread is in progress. - private Task _asyncReadTask = Task.CompletedTask; - - private void CheckAsyncTaskInProgress() - { - // We are not locking the access to _asyncReadTask because this is not meant to guarantee thread safety. - // We are simply trying to deter calling any Read APIs while an async Read from the same thread is in progress. - if (!_asyncReadTask.IsCompleted) - { - ThrowAsyncIOInProgress(); - } - } - - [DoesNotReturn] - private static void ThrowAsyncIOInProgress() => - throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress); - - // StreamReader by default will ignore illegal UTF8 characters. We don't want to - // throw here because we want to be able to read ill-formed data without choking. - // The high level goal is to be tolerant of encoding errors when we read and very strict - // when we write. Hence, default StreamWriter encoding will throw on error. - - private StreamReader() - { - Debug.Assert(this is NullStreamReader); - _stream = Stream.Null; - _closable = true; - } - - public StreamReader(Stream stream) - : this(stream, true) - { - } - - public StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks) - : this(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize, false) - { - } - - public StreamReader(Stream stream, Encoding encoding) - : this(stream, encoding, true, DefaultBufferSize, false) - { - } - - public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks) - : this(stream, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize, false) - { - } - - // Creates a new StreamReader for the given stream. The - // character encoding is set by encoding and the buffer size, - // in number of 16-bit characters, is set by bufferSize. - // - // Note that detectEncodingFromByteOrderMarks is a very - // loose attempt at detecting the encoding by looking at the first - // 3 bytes of the stream. It will recognize UTF-8, little endian - // unicode, and big endian unicode text, but that's it. If neither - // of those three match, it will use the Encoding you provided. - // - public StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) - : this(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false) - { - } - - public StreamReader(Stream stream, Encoding? encoding = null, bool detectEncodingFromByteOrderMarks = true, int bufferSize = -1, bool leaveOpen = false) - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - if (encoding == null) - { - encoding = Encoding.UTF8; - } - if (!stream.CanRead) - { - throw new ArgumentException(SR.Argument_StreamNotReadable); - } - if (bufferSize == -1) - { - bufferSize = DefaultBufferSize; - } - else if (bufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - } - - _stream = stream; - _encoding = encoding; - _decoder = encoding.GetDecoder(); - if (bufferSize < MinBufferSize) - { - bufferSize = MinBufferSize; - } - - _byteBuffer = new byte[bufferSize]; - _maxCharsPerBuffer = encoding.GetMaxCharCount(bufferSize); - _charBuffer = new char[_maxCharsPerBuffer]; - _byteLen = 0; - _bytePos = 0; - _detectEncoding = detectEncodingFromByteOrderMarks; - _checkPreamble = encoding.Preamble.Length > 0; - _isBlocked = false; - _closable = !leaveOpen; - } - - public StreamReader(string path) - : this(path, true) - { - } - - public StreamReader(string path, bool detectEncodingFromByteOrderMarks) - : this(path, Encoding.UTF8, detectEncodingFromByteOrderMarks, DefaultBufferSize) - { - } - - public StreamReader(string path, Encoding encoding) - : this(path, encoding, true, DefaultBufferSize) - { - } - - public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks) - : this(path, encoding, detectEncodingFromByteOrderMarks, DefaultBufferSize) - { - } - - public StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) : - this(ValidateArgsAndOpenPath(path, encoding, bufferSize), encoding, detectEncodingFromByteOrderMarks, bufferSize, leaveOpen: false) - { - } - - private static Stream ValidateArgsAndOpenPath(string path, Encoding encoding, int bufferSize) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); - if (path.Length == 0) - throw new ArgumentException(SR.Argument_EmptyPath); - if (bufferSize <= 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - - return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); - } - - public override void Close() - { - Dispose(true); - } - - protected override void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - _disposed = true; - - // Dispose of our resources if this StreamReader is closable. - if (_closable) - { - try - { - // Note that Stream.Close() can potentially throw here. So we need to - // ensure cleaning up internal resources, inside the finally block. - if (disposing) - { - _stream.Close(); - } - } - finally - { - _charPos = 0; - _charLen = 0; - base.Dispose(disposing); - } - } - } - - public virtual Encoding CurrentEncoding => _encoding; - - public virtual Stream BaseStream => _stream; - - // DiscardBufferedData tells StreamReader to throw away its internal - // buffer contents. This is useful if the user needs to seek on the - // underlying stream to a known location then wants the StreamReader - // to start reading from this new point. This method should be called - // very sparingly, if ever, since it can lead to very poor performance. - // However, it may be the only way of handling some scenarios where - // users need to re-read the contents of a StreamReader a second time. - public void DiscardBufferedData() - { - CheckAsyncTaskInProgress(); - - _byteLen = 0; - _charLen = 0; - _charPos = 0; - // in general we'd like to have an invariant that encoding isn't null. However, - // for startup improvements for NullStreamReader, we want to delay load encoding. - if (_encoding != null) - { - _decoder = _encoding.GetDecoder(); - } - _isBlocked = false; - } - - public bool EndOfStream - { - get - { - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (_charPos < _charLen) - { - return false; - } - - // This may block on pipes! - int numRead = ReadBuffer(); - return numRead == 0; - } - } - - public override int Peek() - { - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (_charPos == _charLen) - { - if (_isBlocked || ReadBuffer() == 0) - { - return -1; - } - } - return _charBuffer[_charPos]; - } - - public override int Read() - { - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (_charPos == _charLen) - { - if (ReadBuffer() == 0) - { - return -1; - } - } - int result = _charBuffer[_charPos]; - _charPos++; - return result; - } - - public override int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0 || count < 0) - { - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - return ReadSpan(new Span<char>(buffer, index, count)); - } - - public override int Read(Span<char> buffer) => - GetType() == typeof(StreamReader) ? ReadSpan(buffer) : - base.Read(buffer); // Defer to Read(char[], ...) if a derived type may have previously overridden it - - private int ReadSpan(Span<char> buffer) - { - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - int charsRead = 0; - // As a perf optimization, if we had exactly one buffer's worth of - // data read in, let's try writing directly to the user's buffer. - bool readToUserBuffer = false; - int count = buffer.Length; - while (count > 0) - { - int n = _charLen - _charPos; - if (n == 0) - { - n = ReadBuffer(buffer.Slice(charsRead), out readToUserBuffer); - } - if (n == 0) - { - break; // We're at EOF - } - if (n > count) - { - n = count; - } - if (!readToUserBuffer) - { - new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Slice(charsRead)); - _charPos += n; - } - - charsRead += n; - count -= n; - // This function shouldn't block for an indefinite amount of time, - // or reading from a network stream won't work right. If we got - // fewer bytes than we requested, then we want to break right here. - if (_isBlocked) - { - break; - } - } - - return charsRead; - } - - public override string ReadToEnd() - { - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - // Call ReadBuffer, then pull data out of charBuffer. - StringBuilder sb = new StringBuilder(_charLen - _charPos); - do - { - sb.Append(_charBuffer, _charPos, _charLen - _charPos); - _charPos = _charLen; // Note we consumed these characters - ReadBuffer(); - } while (_charLen > 0); - return sb.ToString(); - } - - public override int ReadBlock(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0 || count < 0) - { - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - return base.ReadBlock(buffer, index, count); - } - - public override int ReadBlock(Span<char> buffer) - { - if (GetType() != typeof(StreamReader)) - { - // Defer to Read(char[], ...) if a derived type may have previously overridden it. - return base.ReadBlock(buffer); - } - - int i, n = 0; - do - { - i = ReadSpan(buffer.Slice(n)); - n += i; - } while (i > 0 && n < buffer.Length); - return n; - } - - // Trims n bytes from the front of the buffer. - private void CompressBuffer(int n) - { - Debug.Assert(_byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length. Are two threads using this StreamReader at the same time?"); - Buffer.BlockCopy(_byteBuffer, n, _byteBuffer, 0, _byteLen - n); - _byteLen -= n; - } - - private void DetectEncoding() - { - if (_byteLen < 2) - { - return; - } - _detectEncoding = false; - bool changedEncoding = false; - if (_byteBuffer[0] == 0xFE && _byteBuffer[1] == 0xFF) - { - // Big Endian Unicode - - _encoding = Encoding.BigEndianUnicode; - CompressBuffer(2); - changedEncoding = true; - } - else if (_byteBuffer[0] == 0xFF && _byteBuffer[1] == 0xFE) - { - // Little Endian Unicode, or possibly little endian UTF32 - if (_byteLen < 4 || _byteBuffer[2] != 0 || _byteBuffer[3] != 0) - { - _encoding = Encoding.Unicode; - CompressBuffer(2); - changedEncoding = true; - } - else - { - _encoding = Encoding.UTF32; - CompressBuffer(4); - changedEncoding = true; - } - } - else if (_byteLen >= 3 && _byteBuffer[0] == 0xEF && _byteBuffer[1] == 0xBB && _byteBuffer[2] == 0xBF) - { - // UTF-8 - _encoding = Encoding.UTF8; - CompressBuffer(3); - changedEncoding = true; - } - else if (_byteLen >= 4 && _byteBuffer[0] == 0 && _byteBuffer[1] == 0 && - _byteBuffer[2] == 0xFE && _byteBuffer[3] == 0xFF) - { - // Big Endian UTF32 - _encoding = new UTF32Encoding(bigEndian: true, byteOrderMark: true); - CompressBuffer(4); - changedEncoding = true; - } - else if (_byteLen == 2) - { - _detectEncoding = true; - } - // Note: in the future, if we change this algorithm significantly, - // we can support checking for the preamble of the given encoding. - - if (changedEncoding) - { - _decoder = _encoding.GetDecoder(); - int newMaxCharsPerBuffer = _encoding.GetMaxCharCount(_byteBuffer.Length); - if (newMaxCharsPerBuffer > _maxCharsPerBuffer) - { - _charBuffer = new char[newMaxCharsPerBuffer]; - } - _maxCharsPerBuffer = newMaxCharsPerBuffer; - } - } - - // Trims the preamble bytes from the byteBuffer. This routine can be called multiple times - // and we will buffer the bytes read until the preamble is matched or we determine that - // there is no match. If there is no match, every byte read previously will be available - // for further consumption. If there is a match, we will compress the buffer for the - // leading preamble bytes - private bool IsPreamble() - { - if (!_checkPreamble) - { - return _checkPreamble; - } - - ReadOnlySpan<byte> preamble = _encoding.Preamble; - - Debug.Assert(_bytePos <= preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length. Are two threads using this StreamReader at the same time?"); - int len = (_byteLen >= (preamble.Length)) ? (preamble.Length - _bytePos) : (_byteLen - _bytePos); - - for (int i = 0; i < len; i++, _bytePos++) - { - if (_byteBuffer[_bytePos] != preamble[_bytePos]) - { - _bytePos = 0; - _checkPreamble = false; - break; - } - } - - Debug.Assert(_bytePos <= preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); - - if (_checkPreamble) - { - if (_bytePos == preamble.Length) - { - // We have a match - CompressBuffer(preamble.Length); - _bytePos = 0; - _checkPreamble = false; - _detectEncoding = false; - } - } - - return _checkPreamble; - } - - internal virtual int ReadBuffer() - { - _charLen = 0; - _charPos = 0; - - if (!_checkPreamble) - { - _byteLen = 0; - } - - do - { - if (_checkPreamble) - { - Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); - int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos); - Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); - - if (len == 0) - { - // EOF but we might have buffered bytes from previous - // attempt to detect preamble that needs to be decoded now - if (_byteLen > 0) - { - _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen); - // Need to zero out the byteLen after we consume these bytes so that we don't keep infinitely hitting this code path - _bytePos = _byteLen = 0; - } - - return _charLen; - } - - _byteLen += len; - } - else - { - Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length); - Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); - - if (_byteLen == 0) // We're at EOF - { - return _charLen; - } - } - - // _isBlocked == whether we read fewer bytes than we asked for. - // Note we must check it here because CompressBuffer or - // DetectEncoding will change byteLen. - _isBlocked = (_byteLen < _byteBuffer.Length); - - // Check for preamble before detect encoding. This is not to override the - // user supplied Encoding for the one we implicitly detect. The user could - // customize the encoding which we will loose, such as ThrowOnError on UTF8 - if (IsPreamble()) - { - continue; - } - - // If we're supposed to detect the encoding and haven't done so yet, - // do it. Note this may need to be called more than once. - if (_detectEncoding && _byteLen >= 2) - { - DetectEncoding(); - } - - _charLen += _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, _charLen); - } while (_charLen == 0); - return _charLen; - } - - - // This version has a perf optimization to decode data DIRECTLY into the - // user's buffer, bypassing StreamReader's own buffer. - // This gives a > 20% perf improvement for our encodings across the board, - // but only when asking for at least the number of characters that one - // buffer's worth of bytes could produce. - // This optimization, if run, will break SwitchEncoding, so we must not do - // this on the first call to ReadBuffer. - private int ReadBuffer(Span<char> userBuffer, out bool readToUserBuffer) - { - _charLen = 0; - _charPos = 0; - - if (!_checkPreamble) - { - _byteLen = 0; - } - - int charsRead = 0; - - // As a perf optimization, we can decode characters DIRECTLY into a - // user's char[]. We absolutely must not write more characters - // into the user's buffer than they asked for. Calculating - // encoding.GetMaxCharCount(byteLen) each time is potentially very - // expensive - instead, cache the number of chars a full buffer's - // worth of data may produce. Yes, this makes the perf optimization - // less aggressive, in that all reads that asked for fewer than AND - // returned fewer than _maxCharsPerBuffer chars won't get the user - // buffer optimization. This affects reads where the end of the - // Stream comes in the middle somewhere, and when you ask for - // fewer chars than your buffer could produce. - readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer; - - do - { - Debug.Assert(charsRead == 0); - - if (_checkPreamble) - { - Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); - int len = _stream.Read(_byteBuffer, _bytePos, _byteBuffer.Length - _bytePos); - Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); - - if (len == 0) - { - // EOF but we might have buffered bytes from previous - // attempt to detect preamble that needs to be decoded now - if (_byteLen > 0) - { - if (readToUserBuffer) - { - charsRead = _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush: false); - _charLen = 0; // StreamReader's buffer is empty. - } - else - { - charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead); - _charLen += charsRead; // Number of chars in StreamReader's buffer. - } - } - - return charsRead; - } - - _byteLen += len; - } - else - { - Debug.Assert(_bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - - _byteLen = _stream.Read(_byteBuffer, 0, _byteBuffer.Length); - - Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); - - if (_byteLen == 0) // EOF - { - break; - } - } - - // _isBlocked == whether we read fewer bytes than we asked for. - // Note we must check it here because CompressBuffer or - // DetectEncoding will change byteLen. - _isBlocked = (_byteLen < _byteBuffer.Length); - - // Check for preamble before detect encoding. This is not to override the - // user supplied Encoding for the one we implicitly detect. The user could - // customize the encoding which we will loose, such as ThrowOnError on UTF8 - // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble - // doesn't change the encoding or affect _maxCharsPerBuffer - if (IsPreamble()) - { - continue; - } - - // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it. - if (_detectEncoding && _byteLen >= 2) - { - DetectEncoding(); - // DetectEncoding changes some buffer state. Recompute this. - readToUserBuffer = userBuffer.Length >= _maxCharsPerBuffer; - } - - _charPos = 0; - if (readToUserBuffer) - { - charsRead += _decoder.GetChars(new ReadOnlySpan<byte>(_byteBuffer, 0, _byteLen), userBuffer.Slice(charsRead), flush: false); - _charLen = 0; // StreamReader's buffer is empty. - } - else - { - charsRead = _decoder.GetChars(_byteBuffer, 0, _byteLen, _charBuffer, charsRead); - _charLen += charsRead; // Number of chars in StreamReader's buffer. - } - } while (charsRead == 0); - - _isBlocked &= charsRead < userBuffer.Length; - - return charsRead; - } - - - // Reads a line. A line is defined as a sequence of characters followed by - // a carriage return ('\r'), a line feed ('\n'), or a carriage return - // immediately followed by a line feed. The resulting string does not - // contain the terminating carriage return and/or line feed. The returned - // value is null if the end of the input stream has been reached. - // - public override string? ReadLine() - { - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (_charPos == _charLen) - { - if (ReadBuffer() == 0) - { - return null; - } - } - - StringBuilder? sb = null; - do - { - int i = _charPos; - do - { - char ch = _charBuffer[i]; - // Note the following common line feed chars: - // \n - UNIX \r\n - DOS \r - Mac - if (ch == '\r' || ch == '\n') - { - string s; - if (sb != null) - { - sb.Append(_charBuffer, _charPos, i - _charPos); - s = sb.ToString(); - } - else - { - s = new string(_charBuffer, _charPos, i - _charPos); - } - _charPos = i + 1; - if (ch == '\r' && (_charPos < _charLen || ReadBuffer() > 0)) - { - if (_charBuffer[_charPos] == '\n') - { - _charPos++; - } - } - return s; - } - i++; - } while (i < _charLen); - - i = _charLen - _charPos; - sb ??= new StringBuilder(i + 80); - sb.Append(_charBuffer, _charPos, i); - } while (ReadBuffer() > 0); - return sb.ToString(); - } - - public override Task<string?> ReadLineAsync() - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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) when we are not sure. - if (GetType() != typeof(StreamReader)) - { - return base.ReadLineAsync(); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task<string?> task = ReadLineAsyncInternal(); - _asyncReadTask = task; - - return task; - } - - private async Task<string?> ReadLineAsyncInternal() - { - if (_charPos == _charLen && (await ReadBufferAsync(CancellationToken.None).ConfigureAwait(false)) == 0) - { - return null; - } - - StringBuilder? sb = null; - - do - { - char[] tmpCharBuffer = _charBuffer; - int tmpCharLen = _charLen; - int tmpCharPos = _charPos; - int i = tmpCharPos; - - do - { - char ch = tmpCharBuffer[i]; - - // Note the following common line feed chars: - // \n - UNIX \r\n - DOS \r - Mac - if (ch == '\r' || ch == '\n') - { - string s; - - if (sb != null) - { - sb.Append(tmpCharBuffer, tmpCharPos, i - tmpCharPos); - s = sb.ToString(); - } - else - { - s = new string(tmpCharBuffer, tmpCharPos, i - tmpCharPos); - } - - _charPos = tmpCharPos = i + 1; - - if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync(CancellationToken.None).ConfigureAwait(false)) > 0)) - { - tmpCharPos = _charPos; - if (_charBuffer[tmpCharPos] == '\n') - { - _charPos = ++tmpCharPos; - } - } - - return s; - } - - i++; - } while (i < tmpCharLen); - - i = tmpCharLen - tmpCharPos; - sb ??= new StringBuilder(i + 80); - sb.Append(tmpCharBuffer, tmpCharPos, i); - } while (await ReadBufferAsync(CancellationToken.None).ConfigureAwait(false) > 0); - - return sb.ToString(); - } - - public override Task<string> ReadToEndAsync() - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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) when we are not sure. - if (GetType() != typeof(StreamReader)) - { - return base.ReadToEndAsync(); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task<string> task = ReadToEndAsyncInternal(); - _asyncReadTask = task; - - return task; - } - - private async Task<string> ReadToEndAsyncInternal() - { - // Call ReadBuffer, then pull data out of charBuffer. - StringBuilder sb = new StringBuilder(_charLen - _charPos); - do - { - int tmpCharPos = _charPos; - sb.Append(_charBuffer, tmpCharPos, _charLen - tmpCharPos); - _charPos = _charLen; // We consumed these characters - await ReadBufferAsync(CancellationToken.None).ConfigureAwait(false); - } while (_charLen > 0); - - return sb.ToString(); - } - - public override Task<int> ReadAsync(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0 || count < 0) - { - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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) when we are not sure. - if (GetType() != typeof(StreamReader)) - { - return base.ReadAsync(buffer, index, count); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task<int> task = ReadAsyncInternal(new Memory<char>(buffer, index, count), CancellationToken.None).AsTask(); - _asyncReadTask = task; - - return task; - } - - public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default) - { - if (GetType() != typeof(StreamReader)) - { - // Ensure we use existing overrides if a class already overrode existing overloads. - return base.ReadAsync(buffer, cancellationToken); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)); - } - - return ReadAsyncInternal(buffer, cancellationToken); - } - - internal override async ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken) - { - if (_charPos == _charLen && (await ReadBufferAsync(cancellationToken).ConfigureAwait(false)) == 0) - { - return 0; - } - - int charsRead = 0; - - // As a perf optimization, if we had exactly one buffer's worth of - // data read in, let's try writing directly to the user's buffer. - bool readToUserBuffer = false; - - byte[] tmpByteBuffer = _byteBuffer; - Stream tmpStream = _stream; - - int count = buffer.Length; - while (count > 0) - { - // n is the characters available in _charBuffer - int n = _charLen - _charPos; - - // charBuffer is empty, let's read from the stream - if (n == 0) - { - _charLen = 0; - _charPos = 0; - - if (!_checkPreamble) - { - _byteLen = 0; - } - - readToUserBuffer = count >= _maxCharsPerBuffer; - - // We loop here so that we read in enough bytes to yield at least 1 char. - // We break out of the loop if the stream is blocked (EOF is reached). - do - { - Debug.Assert(n == 0); - - if (_checkPreamble) - { - Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); - int tmpBytePos = _bytePos; - int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false); - Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); - - if (len == 0) - { - // EOF but we might have buffered bytes from previous - // attempts to detect preamble that needs to be decoded now - if (_byteLen > 0) - { - if (readToUserBuffer) - { - n = _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false); - _charLen = 0; // StreamReader's buffer is empty. - } - else - { - n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0); - _charLen += n; // Number of chars in StreamReader's buffer. - } - } - - // How can part of the preamble yield any chars? - Debug.Assert(n == 0); - - _isBlocked = true; - break; - } - else - { - _byteLen += len; - } - } - else - { - Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - - _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false); - - Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); - - if (_byteLen == 0) // EOF - { - _isBlocked = true; - break; - } - } - - // _isBlocked == whether we read fewer bytes than we asked for. - // Note we must check it here because CompressBuffer or - // DetectEncoding will change _byteLen. - _isBlocked = (_byteLen < tmpByteBuffer.Length); - - // Check for preamble before detect encoding. This is not to override the - // user supplied Encoding for the one we implicitly detect. The user could - // customize the encoding which we will loose, such as ThrowOnError on UTF8 - // Note: we don't need to recompute readToUserBuffer optimization as IsPreamble - // doesn't change the encoding or affect _maxCharsPerBuffer - if (IsPreamble()) - { - continue; - } - - // On the first call to ReadBuffer, if we're supposed to detect the encoding, do it. - if (_detectEncoding && _byteLen >= 2) - { - DetectEncoding(); - // DetectEncoding changes some buffer state. Recompute this. - readToUserBuffer = count >= _maxCharsPerBuffer; - } - - Debug.Assert(n == 0); - - _charPos = 0; - if (readToUserBuffer) - { - n += _decoder.GetChars(new ReadOnlySpan<byte>(tmpByteBuffer, 0, _byteLen), buffer.Span.Slice(charsRead), flush: false); - - // Why did the bytes yield no chars? - Debug.Assert(n > 0); - - _charLen = 0; // StreamReader's buffer is empty. - } - else - { - n = _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, 0); - - // Why did the bytes yield no chars? - Debug.Assert(n > 0); - - _charLen += n; // Number of chars in StreamReader's buffer. - } - } while (n == 0); - - if (n == 0) - { - break; // We're at EOF - } - } // if (n == 0) - - // Got more chars in charBuffer than the user requested - if (n > count) - { - n = count; - } - - if (!readToUserBuffer) - { - new Span<char>(_charBuffer, _charPos, n).CopyTo(buffer.Span.Slice(charsRead)); - _charPos += n; - } - - charsRead += n; - count -= n; - - // This function shouldn't block for an indefinite amount of time, - // or reading from a network stream won't work right. If we got - // fewer bytes than we requested, then we want to break right here. - if (_isBlocked) - { - break; - } - } // while (count > 0) - - return charsRead; - } - - public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0 || count < 0) - { - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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) when we are not sure. - if (GetType() != typeof(StreamReader)) - { - return base.ReadBlockAsync(buffer, index, count); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task<int> task = base.ReadBlockAsync(buffer, index, count); - _asyncReadTask = task; - - return task; - } - - public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default) - { - if (GetType() != typeof(StreamReader)) - { - // If a derived type may have overridden ReadBlockAsync(char[], ...) before this overload - // was introduced, defer to it. - return base.ReadBlockAsync(buffer, cancellationToken); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)); - } - - ValueTask<int> vt = ReadBlockAsyncInternal(buffer, cancellationToken); - if (vt.IsCompletedSuccessfully) - { - return vt; - } - - Task<int> t = vt.AsTask(); - _asyncReadTask = t; - return new ValueTask<int>(t); - } - - private async ValueTask<int> ReadBufferAsync(CancellationToken cancellationToken) - { - _charLen = 0; - _charPos = 0; - byte[] tmpByteBuffer = _byteBuffer; - Stream tmpStream = _stream; - - if (!_checkPreamble) - { - _byteLen = 0; - } - do - { - if (_checkPreamble) - { - Debug.Assert(_bytePos <= _encoding.Preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); - int tmpBytePos = _bytePos; - int len = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos), cancellationToken).ConfigureAwait(false); - Debug.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); - - if (len == 0) - { - // EOF but we might have buffered bytes from previous - // attempt to detect preamble that needs to be decoded now - if (_byteLen > 0) - { - _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen); - // Need to zero out the _byteLen after we consume these bytes so that we don't keep infinitely hitting this code path - _bytePos = 0; _byteLen = 0; - } - - return _charLen; - } - - _byteLen += len; - } - else - { - Debug.Assert(_bytePos == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); - _byteLen = await tmpStream.ReadAsync(new Memory<byte>(tmpByteBuffer), cancellationToken).ConfigureAwait(false); - Debug.Assert(_byteLen >= 0, "Stream.Read returned a negative number! Bug in stream class."); - - if (_byteLen == 0) // We're at EOF - { - return _charLen; - } - } - - // _isBlocked == whether we read fewer bytes than we asked for. - // Note we must check it here because CompressBuffer or - // DetectEncoding will change _byteLen. - _isBlocked = (_byteLen < tmpByteBuffer.Length); - - // Check for preamble before detect encoding. This is not to override the - // user supplied Encoding for the one we implicitly detect. The user could - // customize the encoding which we will loose, such as ThrowOnError on UTF8 - if (IsPreamble()) - { - continue; - } - - // If we're supposed to detect the encoding and haven't done so yet, - // do it. Note this may need to be called more than once. - if (_detectEncoding && _byteLen >= 2) - { - DetectEncoding(); - } - - _charLen += _decoder.GetChars(tmpByteBuffer, 0, _byteLen, _charBuffer, _charLen); - } while (_charLen == 0); - - return _charLen; - } - - private void ThrowIfDisposed() - { - if (_disposed) - { - ThrowObjectDisposedException(); - } - - void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_ReaderClosed); - } - - // No data, class doesn't need to be serializable. - // Note this class is threadsafe. - private sealed class NullStreamReader : StreamReader - { - public override Encoding CurrentEncoding => Encoding.Unicode; - - protected override void Dispose(bool disposing) - { - // Do nothing - this is essentially unclosable. - } - - public override int Peek() - { - return -1; - } - - public override int Read() - { - return -1; - } - - public override int Read(char[] buffer, int index, int count) - { - return 0; - } - - public override string? ReadLine() - { - return null; - } - - public override string ReadToEnd() - { - return string.Empty; - } - - internal override int ReadBuffer() - { - return 0; - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/StreamWriter.cs b/netcore/System.Private.CoreLib/shared/System/IO/StreamWriter.cs deleted file mode 100644 index 3987457bbfe..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/StreamWriter.cs +++ /dev/null @@ -1,1078 +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.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO -{ - // This class implements a TextWriter for writing characters to a Stream. - // This is designed for character output in a particular Encoding, - // whereas the Stream class is designed for byte input and output. - public class StreamWriter : TextWriter - { - // For UTF-8, the values of 1K for the default buffer size and 4K for the - // file stream buffer size are reasonable & give very reasonable - // performance for in terms of construction time for the StreamWriter and - // write perf. Note that for UTF-8, we end up allocating a 4K byte buffer, - // which means we take advantage of adaptive buffering code. - // The performance using UnicodeEncoding is acceptable. - private const int DefaultBufferSize = 1024; // char[] - private const int DefaultFileStreamBufferSize = 4096; - private const int MinBufferSize = 128; - - // Bit bucket - Null has no backing store. Non closable. - public static new readonly StreamWriter Null = new StreamWriter(Stream.Null, UTF8NoBOM, MinBufferSize, leaveOpen: true); - - private readonly Stream _stream; - private readonly Encoding _encoding; - private readonly Encoder _encoder; - private readonly byte[] _byteBuffer; - private readonly char[] _charBuffer; - private int _charPos; - private int _charLen; - private bool _autoFlush; - private bool _haveWrittenPreamble; - private readonly bool _closable; - private bool _disposed; - - // We don't guarantee thread safety on StreamWriter, but we should at - // least prevent users from trying to write anything while an Async - // write from the same thread is in progress. - private Task _asyncWriteTask = Task.CompletedTask; - - private void CheckAsyncTaskInProgress() - { - // We are not locking the access to _asyncWriteTask because this is not meant to guarantee thread safety. - // We are simply trying to deter calling any Write APIs while an async Write from the same thread is in progress. - if (!_asyncWriteTask.IsCompleted) - { - ThrowAsyncIOInProgress(); - } - } - - [DoesNotReturn] - private static void ThrowAsyncIOInProgress() => - throw new InvalidOperationException(SR.InvalidOperation_AsyncIOInProgress); - - // The high level goal is to be tolerant of encoding errors when we read and very strict - // when we write. Hence, default StreamWriter encoding will throw on encoding error. - // Note: when StreamWriter throws on invalid encoding chars (for ex, high surrogate character - // D800-DBFF without a following low surrogate character DC00-DFFF), it will cause the - // internal StreamWriter's state to be irrecoverable as it would have buffered the - // illegal chars and any subsequent call to Flush() would hit the encoding error again. - // Even Close() will hit the exception as it would try to flush the unwritten data. - // Maybe we can add a DiscardBufferedData() method to get out of such situation (like - // StreamReader though for different reason). Either way, the buffered data will be lost! - private static Encoding UTF8NoBOM => EncodingCache.UTF8NoBOM; - - public StreamWriter(Stream stream) - : this(stream, UTF8NoBOM, DefaultBufferSize, false) - { - } - - public StreamWriter(Stream stream, Encoding encoding) - : this(stream, encoding, DefaultBufferSize, false) - { - } - - // Creates a new StreamWriter for the given stream. The - // character encoding is set by encoding and the buffer size, - // in number of 16-bit characters, is set by bufferSize. - // - public StreamWriter(Stream stream, Encoding encoding, int bufferSize) - : this(stream, encoding, bufferSize, false) - { - } - - public StreamWriter(Stream stream, Encoding? encoding = null, int bufferSize = -1, bool leaveOpen = false) - : base(null) // Ask for CurrentCulture all the time - { - if (stream == null) - { - throw new ArgumentNullException(nameof(stream)); - } - if (encoding == null) - { - encoding = UTF8NoBOM; - } - if (!stream.CanWrite) - { - throw new ArgumentException(SR.Argument_StreamNotWritable); - } - if (bufferSize == -1) - { - bufferSize = DefaultBufferSize; - } - else if (bufferSize <= 0) - { - throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - } - - _stream = stream; - _encoding = encoding; - _encoder = _encoding.GetEncoder(); - if (bufferSize < MinBufferSize) - { - bufferSize = MinBufferSize; - } - - _charBuffer = new char[bufferSize]; - _byteBuffer = new byte[_encoding.GetMaxByteCount(bufferSize)]; - _charLen = bufferSize; - // If we're appending to a Stream that already has data, don't write - // the preamble. - if (_stream.CanSeek && _stream.Position > 0) - { - _haveWrittenPreamble = true; - } - - _closable = !leaveOpen; - } - - public StreamWriter(string path) - : this(path, false, UTF8NoBOM, DefaultBufferSize) - { - } - - public StreamWriter(string path, bool append) - : this(path, append, UTF8NoBOM, DefaultBufferSize) - { - } - - public StreamWriter(string path, bool append, Encoding encoding) - : this(path, append, encoding, DefaultBufferSize) - { - } - - public StreamWriter(string path, bool append, Encoding encoding, int bufferSize) : - this(ValidateArgsAndOpenPath(path, append, encoding, bufferSize), encoding, bufferSize, leaveOpen: false) - { - } - - private static Stream ValidateArgsAndOpenPath(string path, bool append, Encoding encoding, int bufferSize) - { - if (path == null) - throw new ArgumentNullException(nameof(path)); - if (encoding == null) - throw new ArgumentNullException(nameof(encoding)); - if (path.Length == 0) - throw new ArgumentException(SR.Argument_EmptyPath); - if (bufferSize <= 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - - return new FileStream(path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan); - } - - public override void Close() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected override void Dispose(bool disposing) - { - try - { - // We need to flush any buffered data if we are being closed/disposed. - // Also, we never close the handles for stdout & friends. So we can safely - // write any buffered data to those streams even during finalization, which - // is generally the right thing to do. - if (!_disposed && disposing) - { - // Note: flush on the underlying stream can throw (ex., low disk space) - CheckAsyncTaskInProgress(); - Flush(flushStream: true, flushEncoder: true); - } - } - finally - { - CloseStreamFromDispose(disposing); - } - } - - private void CloseStreamFromDispose(bool disposing) - { - // Dispose of our resources if this StreamWriter is closable. - if (_closable && !_disposed) - { - try - { - // Attempt to close the stream even if there was an IO error from Flushing. - // Note that Stream.Close() can potentially throw here (may or may not be - // due to the same Flush error). In this case, we still need to ensure - // cleaning up internal resources, hence the finally block. - if (disposing) - { - _stream.Close(); - } - } - finally - { - _disposed = true; - _charLen = 0; - base.Dispose(disposing); - } - } - } - - public override ValueTask DisposeAsync() => - GetType() != typeof(StreamWriter) ? - base.DisposeAsync() : - DisposeAsyncCore(); - - private async ValueTask DisposeAsyncCore() - { - // Same logic as in Dispose(), but with async flushing. - Debug.Assert(GetType() == typeof(StreamWriter)); - try - { - if (!_disposed) - { - await FlushAsync().ConfigureAwait(false); - } - } - finally - { - CloseStreamFromDispose(disposing: true); - } - GC.SuppressFinalize(this); - } - - public override void Flush() - { - CheckAsyncTaskInProgress(); - - Flush(true, true); - } - - private void Flush(bool flushStream, bool flushEncoder) - { - // flushEncoder should be true at the end of the file and if - // the user explicitly calls Flush (though not if AutoFlush is true). - // This is required to flush any dangling characters from our UTF-7 - // and UTF-8 encoders. - ThrowIfDisposed(); - - // Perf boost for Flush on non-dirty writers. - if (_charPos == 0 && !flushStream && !flushEncoder) - { - return; - } - - if (!_haveWrittenPreamble) - { - _haveWrittenPreamble = true; - ReadOnlySpan<byte> preamble = _encoding.Preamble; - if (preamble.Length > 0) - { - _stream.Write(preamble); - } - } - - int count = _encoder.GetBytes(_charBuffer, 0, _charPos, _byteBuffer, 0, flushEncoder); - _charPos = 0; - if (count > 0) - { - _stream.Write(_byteBuffer, 0, count); - } - // By definition, calling Flush should flush the stream, but this is - // only necessary if we passed in true for flushStream. The Web - // Services guys have some perf tests where flushing needlessly hurts. - if (flushStream) - { - _stream.Flush(); - } - } - - public virtual bool AutoFlush - { - get => _autoFlush; - - set - { - CheckAsyncTaskInProgress(); - - _autoFlush = value; - if (value) - { - Flush(true, false); - } - } - } - - public virtual Stream BaseStream => _stream; - - public override Encoding Encoding => _encoding; - - public override void Write(char value) - { - CheckAsyncTaskInProgress(); - - if (_charPos == _charLen) - { - Flush(false, false); - } - - _charBuffer[_charPos] = value; - _charPos++; - if (_autoFlush) - { - Flush(true, false); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites - public override void Write(char[]? buffer) - { - WriteSpan(buffer, appendNewLine: false); - } - - [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites - public override void Write(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - WriteSpan(buffer.AsSpan(index, count), appendNewLine: false); - } - - [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites - public override void Write(ReadOnlySpan<char> buffer) - { - if (GetType() == typeof(StreamWriter)) - { - WriteSpan(buffer, appendNewLine: false); - } - else - { - // If a derived class may have overridden existing Write behavior, - // we need to make sure we use it. - base.Write(buffer); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private unsafe void WriteSpan(ReadOnlySpan<char> buffer, bool appendNewLine) - { - CheckAsyncTaskInProgress(); - - if (buffer.Length <= 4 && // Threshold of 4 chosen based on perf experimentation - buffer.Length <= _charLen - _charPos) - { - // For very short buffers and when we don't need to worry about running out of space - // in the char buffer, just copy the chars individually. - for (int i = 0; i < buffer.Length; i++) - { - _charBuffer[_charPos++] = buffer[i]; - } - } - else - { - // For larger buffers or when we may run out of room in the internal char buffer, copy in chunks. - // Use unsafe code until https://github.com/dotnet/coreclr/issues/13827 is addressed, as spans are - // resulting in significant overhead (even when the if branch above is taken rather than this - // else) due to temporaries that need to be cleared. Given the use of unsafe code, we also - // make local copies of instance state to protect against potential concurrent misuse. - - ThrowIfDisposed(); - char[] charBuffer = _charBuffer; - - fixed (char* bufferPtr = &MemoryMarshal.GetReference(buffer)) - fixed (char* dstPtr = &charBuffer[0]) - { - char* srcPtr = bufferPtr; - int count = buffer.Length; - int dstPos = _charPos; // use a local copy of _charPos for safety - while (count > 0) - { - if (dstPos == charBuffer.Length) - { - Flush(false, false); - dstPos = 0; - } - - int n = Math.Min(charBuffer.Length - dstPos, count); - int bytesToCopy = n * sizeof(char); - - Buffer.MemoryCopy(srcPtr, dstPtr + dstPos, bytesToCopy, bytesToCopy); - - _charPos += n; - dstPos += n; - srcPtr += n; - count -= n; - } - } - } - - if (appendNewLine) - { - char[] coreNewLine = CoreNewLine; - for (int i = 0; i < coreNewLine.Length; i++) // Generally 1 (\n) or 2 (\r\n) iterations - { - if (_charPos == _charLen) - { - Flush(false, false); - } - - _charBuffer[_charPos] = coreNewLine[i]; - _charPos++; - } - } - - if (_autoFlush) - { - Flush(true, false); - } - } - - [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites - public override void Write(string? value) - { - WriteSpan(value, appendNewLine: false); - } - - [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites - public override void WriteLine(string? value) - { - CheckAsyncTaskInProgress(); - WriteSpan(value, appendNewLine: true); - } - - [MethodImpl(MethodImplOptions.NoInlining)] // prevent WriteSpan from bloating call sites - public override void WriteLine(ReadOnlySpan<char> value) - { - if (GetType() == typeof(StreamWriter)) - { - CheckAsyncTaskInProgress(); - WriteSpan(value, appendNewLine: true); - } - else - { - // If a derived class may have overridden existing WriteLine behavior, - // we need to make sure we use it. - base.WriteLine(value); - } - } - - private void WriteFormatHelper(string format, ParamsArray args, bool appendNewLine) - { - StringBuilder sb = - StringBuilderCache.Acquire((format?.Length ?? 0) + args.Length * 8) - .AppendFormatHelper(null, format!, args); // AppendFormatHelper will appropriately throw ArgumentNullException for a null format - - StringBuilder.ChunkEnumerator chunks = sb.GetChunks(); - - bool more = chunks.MoveNext(); - while (more) - { - ReadOnlySpan<char> current = chunks.Current.Span; - more = chunks.MoveNext(); - - // If final chunk, include the newline if needed - WriteSpan(current, appendNewLine: more ? false : appendNewLine); - } - - StringBuilderCache.Release(sb); - } - - public override void Write(string format, object? arg0) - { - if (GetType() == typeof(StreamWriter)) - { - WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: false); - } - else - { - base.Write(format, arg0); - } - } - - public override void Write(string format, object? arg0, object? arg1) - { - if (GetType() == typeof(StreamWriter)) - { - WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: false); - } - else - { - base.Write(format, arg0, arg1); - } - } - - public override void Write(string format, object? arg0, object? arg1, object? arg2) - { - if (GetType() == typeof(StreamWriter)) - { - WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: false); - } - else - { - base.Write(format, arg0, arg1, arg2); - } - } - - public override void Write(string format, params object?[] arg) - { - if (GetType() == typeof(StreamWriter)) - { - if (arg == null) - { - throw new ArgumentNullException((format == null) ? nameof(format) : nameof(arg)); // same as base logic - } - WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: false); - } - else - { - base.Write(format, arg); - } - } - - public override void WriteLine(string format, object? arg0) - { - if (GetType() == typeof(StreamWriter)) - { - WriteFormatHelper(format, new ParamsArray(arg0), appendNewLine: true); - } - else - { - base.WriteLine(format, arg0); - } - } - - public override void WriteLine(string format, object? arg0, object? arg1) - { - if (GetType() == typeof(StreamWriter)) - { - WriteFormatHelper(format, new ParamsArray(arg0, arg1), appendNewLine: true); - } - else - { - base.WriteLine(format, arg0, arg1); - } - } - - public override void WriteLine(string format, object? arg0, object? arg1, object? arg2) - { - if (GetType() == typeof(StreamWriter)) - { - WriteFormatHelper(format, new ParamsArray(arg0, arg1, arg2), appendNewLine: true); - } - else - { - base.WriteLine(format, arg0, arg1, arg2); - } - } - - public override void WriteLine(string format, params object?[] arg) - { - if (GetType() == typeof(StreamWriter)) - { - if (arg == null) - { - throw new ArgumentNullException(nameof(arg)); - } - WriteFormatHelper(format, new ParamsArray(arg), appendNewLine: true); - } - else - { - base.WriteLine(format, arg); - } - } - - public override Task WriteAsync(char value) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() 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) when we are not sure. - if (GetType() != typeof(StreamWriter)) - { - return base.WriteAsync(value); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false); - _asyncWriteTask = task; - - return task; - } - - // We pass in private instance fields of this MarshalByRefObject-derived type as local params - // to ensure performant access inside the state machine that corresponds this async method. - // Fields that are written to must be assigned at the end of the method *and* before instance invocations. - private static async Task WriteAsyncInternal(StreamWriter _this, char value, - char[] charBuffer, int charPos, int charLen, char[] coreNewLine, - bool autoFlush, bool appendNewLine) - { - if (charPos == charLen) - { - await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - charBuffer[charPos] = value; - charPos++; - - if (appendNewLine) - { - for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy - { - if (charPos == charLen) - { - await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - charBuffer[charPos] = coreNewLine[i]; - charPos++; - } - } - - if (autoFlush) - { - await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - _this._charPos = charPos; - } - - public override Task WriteAsync(string? value) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() 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) when we are not sure. - if (GetType() != typeof(StreamWriter)) - { - return base.WriteAsync(value); - } - - if (value != null) - { - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false); - _asyncWriteTask = task; - - return task; - } - else - { - return Task.CompletedTask; - } - } - - // We pass in private instance fields of this MarshalByRefObject-derived type as local params - // to ensure performant access inside the state machine that corresponds this async method. - // Fields that are written to must be assigned at the end of the method *and* before instance invocations. - private static async Task WriteAsyncInternal(StreamWriter _this, string value, - char[] charBuffer, int charPos, int charLen, char[] coreNewLine, - bool autoFlush, bool appendNewLine) - { - Debug.Assert(value != null); - - int count = value.Length; - int index = 0; - - while (count > 0) - { - if (charPos == charLen) - { - await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - int n = charLen - charPos; - if (n > count) - { - n = count; - } - - Debug.Assert(n > 0, "StreamWriter::Write(String) isn't making progress! This is most likely a race condition in user code."); - - value.CopyTo(index, charBuffer, charPos, n); - - charPos += n; - index += n; - count -= n; - } - - if (appendNewLine) - { - for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy - { - if (charPos == charLen) - { - await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - charBuffer[charPos] = coreNewLine[i]; - charPos++; - } - } - - if (autoFlush) - { - await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - _this._charPos = charPos; - } - - public override Task WriteAsync(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() 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) when we are not sure. - if (GetType() != typeof(StreamWriter)) - { - return base.WriteAsync(buffer, index, count); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: default); - _asyncWriteTask = task; - - return task; - } - - public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) - { - if (GetType() != typeof(StreamWriter)) - { - // If a derived type may have overridden existing WriteASync(char[], ...) behavior, make sure we use it. - return base.WriteAsync(buffer, cancellationToken); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: false, cancellationToken: cancellationToken); - _asyncWriteTask = task; - return task; - } - - // We pass in private instance fields of this MarshalByRefObject-derived type as local params - // to ensure performant access inside the state machine that corresponds this async method. - // Fields that are written to must be assigned at the end of the method *and* before instance invocations. - private static async Task WriteAsyncInternal(StreamWriter _this, ReadOnlyMemory<char> source, - char[] charBuffer, int charPos, int charLen, char[] coreNewLine, - bool autoFlush, bool appendNewLine, CancellationToken cancellationToken) - { - int copied = 0; - while (copied < source.Length) - { - if (charPos == charLen) - { - await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - int n = Math.Min(charLen - charPos, source.Length - copied); - Debug.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code."); - - source.Span.Slice(copied, n).CopyTo(new Span<char>(charBuffer, charPos, n)); - charPos += n; - copied += n; - } - - if (appendNewLine) - { - for (int i = 0; i < coreNewLine.Length; i++) // Expect 2 iterations, no point calling BlockCopy - { - if (charPos == charLen) - { - await _this.FlushAsyncInternal(false, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - charBuffer[charPos] = coreNewLine[i]; - charPos++; - } - } - - if (autoFlush) - { - await _this.FlushAsyncInternal(true, false, charBuffer, charPos, cancellationToken).ConfigureAwait(false); - Debug.Assert(_this._charPos == 0); - charPos = 0; - } - - _this._charPos = charPos; - } - - public override Task WriteLineAsync() - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() 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) when we are not sure. - if (GetType() != typeof(StreamWriter)) - { - return base.WriteLineAsync(); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = WriteAsyncInternal(this, ReadOnlyMemory<char>.Empty, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default); - _asyncWriteTask = task; - - return task; - } - - - public override Task WriteLineAsync(char value) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() 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) when we are not sure. - if (GetType() != typeof(StreamWriter)) - { - return base.WriteLineAsync(value); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true); - _asyncWriteTask = task; - - return task; - } - - - public override Task WriteLineAsync(string? value) - { - if (value == null) - { - return WriteLineAsync(); - } - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() 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) when we are not sure. - if (GetType() != typeof(StreamWriter)) - { - return base.WriteLineAsync(value); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = WriteAsyncInternal(this, value, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true); - _asyncWriteTask = task; - - return task; - } - - - public override Task WriteLineAsync(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Write() 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) when we are not sure. - if (GetType() != typeof(StreamWriter)) - { - return base.WriteLineAsync(buffer, index, count); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = WriteAsyncInternal(this, new ReadOnlyMemory<char>(buffer, index, count), _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: default); - _asyncWriteTask = task; - - return task; - } - - public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) - { - if (GetType() != typeof(StreamWriter)) - { - return base.WriteLineAsync(buffer, cancellationToken); - } - - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - Task task = WriteAsyncInternal(this, buffer, _charBuffer, _charPos, _charLen, CoreNewLine, _autoFlush, appendNewLine: true, cancellationToken: cancellationToken); - _asyncWriteTask = task; - - return task; - } - - - public override Task FlushAsync() - { - // 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(StreamWriter)) - { - return base.FlushAsync(); - } - - // flushEncoder should be true at the end of the file and if - // the user explicitly calls Flush (though not if AutoFlush is true). - // This is required to flush any dangling characters from our UTF-7 - // and UTF-8 encoders. - ThrowIfDisposed(); - CheckAsyncTaskInProgress(); - - Task task = FlushAsyncInternal(true, true, _charBuffer, _charPos); - _asyncWriteTask = task; - - return task; - } - - private Task FlushAsyncInternal(bool flushStream, bool flushEncoder, - char[] sCharBuffer, int sCharPos, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - // Perf boost for Flush on non-dirty writers. - if (sCharPos == 0 && !flushStream && !flushEncoder) - { - return Task.CompletedTask; - } - - Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, _haveWrittenPreamble, - _encoding, _encoder, _byteBuffer, _stream, cancellationToken); - - _charPos = 0; - return flushTask; - } - - - // We pass in private instance fields of this MarshalByRefObject-derived type as local params - // to ensure performant access inside the state machine that corresponds this async method. - private static async Task FlushAsyncInternal(StreamWriter _this, bool flushStream, bool flushEncoder, - char[] charBuffer, int charPos, bool haveWrittenPreamble, - Encoding encoding, Encoder encoder, byte[] byteBuffer, Stream stream, CancellationToken cancellationToken) - { - if (!haveWrittenPreamble) - { - _this._haveWrittenPreamble = true; - byte[] preamble = encoding.GetPreamble(); - if (preamble.Length > 0) - { - await stream.WriteAsync(new ReadOnlyMemory<byte>(preamble), cancellationToken).ConfigureAwait(false); - } - } - - int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder); - if (count > 0) - { - await stream.WriteAsync(new ReadOnlyMemory<byte>(byteBuffer, 0, count), cancellationToken).ConfigureAwait(false); - } - - // By definition, calling Flush should flush the stream, but this is - // only necessary if we passed in true for flushStream. The Web - // Services guys have some perf tests where flushing needlessly hurts. - if (flushStream) - { - await stream.FlushAsync(cancellationToken).ConfigureAwait(false); - } - } - - private void ThrowIfDisposed() - { - if (_disposed) - { - ThrowObjectDisposedException(); - } - - void ThrowObjectDisposedException() => throw new ObjectDisposedException(GetType().Name, SR.ObjectDisposed_WriterClosed); - } - } // class StreamWriter -} // namespace diff --git a/netcore/System.Private.CoreLib/shared/System/IO/TextReader.cs b/netcore/System.Private.CoreLib/shared/System/IO/TextReader.cs deleted file mode 100644 index 50c4d77e03f..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/TextReader.cs +++ /dev/null @@ -1,406 +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.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Buffers; - -namespace System.IO -{ - // This abstract base class represents a reader that can read a sequential - // stream of characters. This is not intended for reading bytes - - // there are methods on the Stream class to read bytes. - // A subclass must minimally implement the Peek() and Read() methods. - // - // This class is intended for character input, not bytes. - // There are methods on the Stream class for reading bytes. - public abstract partial class TextReader : MarshalByRefObject, IDisposable - { - public static readonly TextReader Null = new NullTextReader(); - - protected TextReader() { } - - public virtual void Close() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - } - - // Returns the next available character without actually reading it from - // the input stream. The current position of the TextReader is not changed by - // this operation. The returned value is -1 if no further characters are - // available. - // - // This default method simply returns -1. - // - public virtual int Peek() - { - return -1; - } - - // Reads the next character from the input stream. The returned value is - // -1 if no further characters are available. - // - // This default method simply returns -1. - // - public virtual int Read() - { - return -1; - } - - // Reads a block of characters. This method will read up to - // count characters from this TextReader into the - // buffer character array starting at position - // index. Returns the actual number of characters read. - // - public virtual int Read(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - int n; - for (n = 0; n < count; n++) - { - int ch = Read(); - if (ch == -1) break; - buffer[index + n] = (char)ch; - } - - return n; - } - - // Reads a span of characters. This method will read up to - // count characters from this TextReader into the - // span of characters Returns the actual number of characters read. - // - public virtual int Read(Span<char> buffer) - { - char[] array = ArrayPool<char>.Shared.Rent(buffer.Length); - - try - { - int numRead = Read(array, 0, buffer.Length); - if ((uint)numRead > (uint)buffer.Length) - { - throw new IOException(SR.IO_InvalidReadLength); - } - new Span<char>(array, 0, numRead).CopyTo(buffer); - return numRead; - } - finally - { - ArrayPool<char>.Shared.Return(array); - } - } - - // Reads all characters from the current position to the end of the - // TextReader, and returns them as one string. - public virtual string ReadToEnd() - { - char[] chars = new char[4096]; - int len; - StringBuilder sb = new StringBuilder(4096); - while ((len = Read(chars, 0, chars.Length)) != 0) - { - sb.Append(chars, 0, len); - } - return sb.ToString(); - } - - // Blocking version of read. Returns only when count - // characters have been read or the end of the file was reached. - // - public virtual int ReadBlock(char[] buffer, int index, int count) - { - int i, n = 0; - do - { - n += (i = Read(buffer, index + n, count - n)); - } while (i > 0 && n < count); - return n; - } - - // Blocking version of read for span of characters. Returns only when count - // characters have been read or the end of the file was reached. - // - public virtual int ReadBlock(Span<char> buffer) - { - char[] array = ArrayPool<char>.Shared.Rent(buffer.Length); - - try - { - int numRead = ReadBlock(array, 0, buffer.Length); - if ((uint)numRead > (uint)buffer.Length) - { - throw new IOException(SR.IO_InvalidReadLength); - } - new Span<char>(array, 0, numRead).CopyTo(buffer); - return numRead; - } - finally - { - ArrayPool<char>.Shared.Return(array); - } - } - - // Reads a line. A line is defined as a sequence of characters followed by - // a carriage return ('\r'), a line feed ('\n'), or a carriage return - // immediately followed by a line feed. The resulting string does not - // contain the terminating carriage return and/or line feed. The returned - // value is null if the end of the input stream has been reached. - // - public virtual string? ReadLine() - { - StringBuilder sb = new StringBuilder(); - while (true) - { - int ch = Read(); - if (ch == -1) break; - if (ch == '\r' || ch == '\n') - { - if (ch == '\r' && Peek() == '\n') - { - Read(); - } - - return sb.ToString(); - } - sb.Append((char)ch); - } - if (sb.Length > 0) - { - return sb.ToString(); - } - - return null; - } - - #region Task based Async APIs - public virtual Task<string?> ReadLineAsync() => - Task<string?>.Factory.StartNew(state => ((TextReader)state!).ReadLine(), this, - CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - - public virtual async Task<string> ReadToEndAsync() - { - var sb = new StringBuilder(4096); - char[] chars = ArrayPool<char>.Shared.Rent(4096); - try - { - int len; - while ((len = await ReadAsyncInternal(chars, default).ConfigureAwait(false)) != 0) - { - sb.Append(chars, 0, len); - } - } - finally - { - ArrayPool<char>.Shared.Return(chars); - } - return sb.ToString(); - } - - public virtual Task<int> ReadAsync(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0 || count < 0) - { - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - return ReadAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask(); - } - - public virtual ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default) => - new ValueTask<int>(MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ? - ReadAsync(array.Array!, array.Offset, array.Count) : - Task<int>.Factory.StartNew(state => - { - var t = (Tuple<TextReader, Memory<char>>)state!; - return t.Item1.Read(t.Item2.Span); - }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default)); - - internal virtual ValueTask<int> ReadAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken) - { - var tuple = new Tuple<TextReader, Memory<char>>(this, buffer); - return new ValueTask<int>(Task<int>.Factory.StartNew(state => - { - var t = (Tuple<TextReader, Memory<char>>)state!; - return t.Item1.Read(t.Item2.Span); - }, - tuple, cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default)); - } - - public virtual Task<int> ReadBlockAsync(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0 || count < 0) - { - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - return ReadBlockAsyncInternal(new Memory<char>(buffer, index, count), default).AsTask(); - } - - public virtual ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default) => - new ValueTask<int>(MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ? - ReadBlockAsync(array.Array!, array.Offset, array.Count) : - Task<int>.Factory.StartNew(state => - { - var t = (Tuple<TextReader, Memory<char>>)state!; - return t.Item1.ReadBlock(t.Item2.Span); - }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default)); - - internal async ValueTask<int> ReadBlockAsyncInternal(Memory<char> buffer, CancellationToken cancellationToken) - { - int n = 0, i; - do - { - i = await ReadAsyncInternal(buffer.Slice(n), cancellationToken).ConfigureAwait(false); - n += i; - } while (i > 0 && n < buffer.Length); - - return n; - } - #endregion - - private sealed class NullTextReader : TextReader - { - public NullTextReader() { } - - public override int Read(char[] buffer, int index, int count) - { - return 0; - } - - public override string? ReadLine() - { - return null; - } - } - - public static TextReader Synchronized(TextReader reader) - { - if (reader == null) - throw new ArgumentNullException(nameof(reader)); - - return reader is SyncTextReader ? reader : new SyncTextReader(reader); - } - - internal sealed class SyncTextReader : TextReader - { - internal readonly TextReader _in; - - internal SyncTextReader(TextReader t) - { - _in = t; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Close() => _in.Close(); - - [MethodImpl(MethodImplOptions.Synchronized)] - protected override void Dispose(bool disposing) - { - // Explicitly pick up a potentially methodimpl'ed Dispose - if (disposing) - ((IDisposable)_in).Dispose(); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override int Peek() => _in.Peek(); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override int Read() => _in.Read(); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override int Read(char[] buffer, int index, int count) => _in.Read(buffer, index, count); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override int ReadBlock(char[] buffer, int index, int count) => _in.ReadBlock(buffer, index, count); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override string? ReadLine() => _in.ReadLine(); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override string ReadToEnd() => _in.ReadToEnd(); - - // - // On SyncTextReader all APIs should run synchronously, even the async ones. - // - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task<string?> ReadLineAsync() => Task.FromResult(ReadLine()); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task<string> ReadToEndAsync() => Task.FromResult(ReadToEnd()); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - if (index < 0 || count < 0) - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - if (buffer.Length - index < count) - throw new ArgumentException(SR.Argument_InvalidOffLen); - - return Task.FromResult(ReadBlock(buffer, index, count)); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task<int> ReadAsync(char[] buffer, int index, int count) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - if (index < 0 || count < 0) - throw new ArgumentOutOfRangeException(index < 0 ? nameof(index) : nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - if (buffer.Length - index < count) - throw new ArgumentException(SR.Argument_InvalidOffLen); - - return Task.FromResult(Read(buffer, index, count)); - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/TextWriter.cs b/netcore/System.Private.CoreLib/shared/System/IO/TextWriter.cs deleted file mode 100644 index 8263072b14e..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/TextWriter.cs +++ /dev/null @@ -1,1016 +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.Text; -using System.Threading; -using System.Globalization; -using System.Threading.Tasks; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Buffers; -using System.Diagnostics.CodeAnalysis; - -namespace System.IO -{ - // This abstract base class represents a writer that can write a sequential - // stream of characters. A subclass must minimally implement the - // Write(char) method. - // - // This class is intended for character output, not bytes. - // There are methods on the Stream class for writing bytes. - public abstract partial class TextWriter : MarshalByRefObject, IDisposable, IAsyncDisposable - { - public static readonly TextWriter Null = new NullTextWriter(); - - // We don't want to allocate on every TextWriter creation, so cache the char array. - private static readonly char[] s_coreNewLine = Environment.NewLineConst.ToCharArray(); - - /// <summary> - /// This is the 'NewLine' property expressed as a char[]. - /// It is exposed to subclasses as a protected field for read-only - /// purposes. You should only modify it by using the 'NewLine' property. - /// In particular you should never modify the elements of the array - /// as they are shared among many instances of TextWriter. - /// </summary> - protected char[] CoreNewLine = s_coreNewLine; - private string CoreNewLineStr = Environment.NewLineConst; - - // Can be null - if so, ask for the Thread's CurrentCulture every time. - private readonly IFormatProvider? _internalFormatProvider; - - protected TextWriter() - { - _internalFormatProvider = null; // Ask for CurrentCulture all the time. - } - - protected TextWriter(IFormatProvider? formatProvider) - { - _internalFormatProvider = formatProvider; - } - - public virtual IFormatProvider FormatProvider - { - get - { - if (_internalFormatProvider == null) - { - return CultureInfo.CurrentCulture; - } - else - { - return _internalFormatProvider; - } - } - } - - public virtual void Close() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected virtual void Dispose(bool disposing) - { - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public virtual ValueTask DisposeAsync() - { - try - { - Dispose(); - return default; - } - catch (Exception exc) - { - return new ValueTask(Task.FromException(exc)); - } - } - - // Clears all buffers for this TextWriter and causes any buffered data to be - // written to the underlying device. This default method is empty, but - // descendant classes can override the method to provide the appropriate - // functionality. - public virtual void Flush() - { - } - - public abstract Encoding Encoding - { - get; - } - - /// <summary> - /// Returns the line terminator string used by this TextWriter. The default line - /// terminator string is Environment.NewLine, which is platform specific. - /// On Windows this is a carriage return followed by a line feed ("\r\n"). - /// On OSX and Linux this is a line feed ("\n"). - /// </summary> - /// <remarks> - /// The line terminator string is written to the text stream whenever one of the - /// WriteLine methods are called. In order for text written by - /// the TextWriter to be readable by a TextReader, only one of the following line - /// terminator strings should be used: "\r", "\n", or "\r\n". - /// </remarks> - [AllowNull] - public virtual string NewLine - { - get => CoreNewLineStr; - set - { - if (value == null) - { - value = Environment.NewLineConst; - } - - CoreNewLineStr = value; - CoreNewLine = value.ToCharArray(); - } - } - - // Writes a character to the text stream. This default method is empty, - // but descendant classes can override the method to provide the - // appropriate functionality. - // - public virtual void Write(char value) - { - } - - // Writes a character array to the text stream. This default method calls - // Write(char) for each of the characters in the character array. - // If the character array is null, nothing is written. - // - public virtual void Write(char[]? buffer) - { - if (buffer != null) - { - Write(buffer, 0, buffer.Length); - } - } - - // Writes a range of a character array to the text stream. This method will - // write count characters of data into this TextWriter from the - // buffer character array starting at position index. - // - public virtual void Write(char[] buffer, int index, int count) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer); - } - if (index < 0) - { - throw new ArgumentOutOfRangeException(nameof(index), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (count < 0) - { - throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.Length - index < count) - { - throw new ArgumentException(SR.Argument_InvalidOffLen); - } - - for (int i = 0; i < count; i++) Write(buffer[index + i]); - } - - // Writes a span of characters to the text stream. - // - public virtual void Write(ReadOnlySpan<char> buffer) - { - char[] array = ArrayPool<char>.Shared.Rent(buffer.Length); - - try - { - buffer.CopyTo(new Span<char>(array)); - Write(array, 0, buffer.Length); - } - finally - { - ArrayPool<char>.Shared.Return(array); - } - } - - // Writes the text representation of a boolean to the text stream. This - // method outputs either bool.TrueString or bool.FalseString. - // - public virtual void Write(bool value) - { - Write(value ? "True" : "False"); - } - - // Writes the text representation of an integer to the text stream. The - // text representation of the given value is produced by calling the - // int.ToString() method. - // - public virtual void Write(int value) - { - Write(value.ToString(FormatProvider)); - } - - // Writes the text representation of an integer to the text stream. The - // text representation of the given value is produced by calling the - // uint.ToString() method. - // - [CLSCompliant(false)] - public virtual void Write(uint value) - { - Write(value.ToString(FormatProvider)); - } - - // Writes the text representation of a long to the text stream. The - // text representation of the given value is produced by calling the - // long.ToString() method. - // - public virtual void Write(long value) - { - Write(value.ToString(FormatProvider)); - } - - // Writes the text representation of an unsigned long to the text - // stream. The text representation of the given value is produced - // by calling the ulong.ToString() method. - // - [CLSCompliant(false)] - public virtual void Write(ulong value) - { - Write(value.ToString(FormatProvider)); - } - - // Writes the text representation of a float to the text stream. The - // text representation of the given value is produced by calling the - // float.ToString(float) method. - // - public virtual void Write(float value) - { - Write(value.ToString(FormatProvider)); - } - - // Writes the text representation of a double to the text stream. The - // text representation of the given value is produced by calling the - // double.ToString(double) method. - // - public virtual void Write(double value) - { - Write(value.ToString(FormatProvider)); - } - - public virtual void Write(decimal value) - { - Write(value.ToString(FormatProvider)); - } - - // Writes a string to the text stream. If the given string is null, nothing - // is written to the text stream. - // - public virtual void Write(string? value) - { - if (value != null) - { - Write(value.ToCharArray()); - } - } - - // Writes the text representation of an object to the text stream. If the - // given object is null, nothing is written to the text stream. - // Otherwise, the object's ToString method is called to produce the - // string representation, and the resulting string is then written to the - // output stream. - // - public virtual void Write(object? value) - { - if (value != null) - { - if (value is IFormattable f) - { - Write(f.ToString(null, FormatProvider)); - } - else - Write(value.ToString()); - } - } - - /// <summary> - /// Equivalent to Write(stringBuilder.ToString()) however it uses the - /// StringBuilder.GetChunks() method to avoid creating the intermediate string - /// </summary> - /// <param name="value">The string (as a StringBuilder) to write to the stream</param> - public virtual void Write(StringBuilder? value) - { - if (value != null) - { - foreach (ReadOnlyMemory<char> chunk in value.GetChunks()) - Write(chunk.Span); - } - } - - // Writes out a formatted string. Uses the same semantics as - // string.Format. - // - public virtual void Write(string format, object? arg0) - { - Write(string.Format(FormatProvider, format, arg0)); - } - - // Writes out a formatted string. Uses the same semantics as - // string.Format. - // - public virtual void Write(string format, object? arg0, object? arg1) - { - Write(string.Format(FormatProvider, format, arg0, arg1)); - } - - // Writes out a formatted string. Uses the same semantics as - // string.Format. - // - public virtual void Write(string format, object? arg0, object? arg1, object? arg2) - { - Write(string.Format(FormatProvider, format, arg0, arg1, arg2)); - } - - // Writes out a formatted string. Uses the same semantics as - // string.Format. - // - public virtual void Write(string format, params object?[] arg) - { - Write(string.Format(FormatProvider, format, arg)); - } - - // Writes a line terminator to the text stream. The default line terminator - // is Environment.NewLine, but this value can be changed by setting the NewLine property. - // - public virtual void WriteLine() - { - Write(CoreNewLine); - } - - // Writes a character followed by a line terminator to the text stream. - // - public virtual void WriteLine(char value) - { - Write(value); - WriteLine(); - } - - // Writes an array of characters followed by a line terminator to the text - // stream. - // - public virtual void WriteLine(char[]? buffer) - { - Write(buffer); - WriteLine(); - } - - // Writes a range of a character array followed by a line terminator to the - // text stream. - // - public virtual void WriteLine(char[] buffer, int index, int count) - { - Write(buffer, index, count); - WriteLine(); - } - - public virtual void WriteLine(ReadOnlySpan<char> buffer) - { - char[] array = ArrayPool<char>.Shared.Rent(buffer.Length); - - try - { - buffer.CopyTo(new Span<char>(array)); - WriteLine(array, 0, buffer.Length); - } - finally - { - ArrayPool<char>.Shared.Return(array); - } - } - - // Writes the text representation of a boolean followed by a line - // terminator to the text stream. - // - public virtual void WriteLine(bool value) - { - Write(value); - WriteLine(); - } - - // Writes the text representation of an integer followed by a line - // terminator to the text stream. - // - public virtual void WriteLine(int value) - { - Write(value); - WriteLine(); - } - - // Writes the text representation of an unsigned integer followed by - // a line terminator to the text stream. - // - [CLSCompliant(false)] - public virtual void WriteLine(uint value) - { - Write(value); - WriteLine(); - } - - // Writes the text representation of a long followed by a line terminator - // to the text stream. - // - public virtual void WriteLine(long value) - { - Write(value); - WriteLine(); - } - - // Writes the text representation of an unsigned long followed by - // a line terminator to the text stream. - // - [CLSCompliant(false)] - public virtual void WriteLine(ulong value) - { - Write(value); - WriteLine(); - } - - // Writes the text representation of a float followed by a line terminator - // to the text stream. - // - public virtual void WriteLine(float value) - { - Write(value); - WriteLine(); - } - - // Writes the text representation of a double followed by a line terminator - // to the text stream. - // - public virtual void WriteLine(double value) - { - Write(value); - WriteLine(); - } - - public virtual void WriteLine(decimal value) - { - Write(value); - WriteLine(); - } - - // Writes a string followed by a line terminator to the text stream. - // - public virtual void WriteLine(string? value) - { - if (value != null) - { - Write(value); - } - Write(CoreNewLineStr); - } - - /// <summary> - /// Equivalent to WriteLine(stringBuilder.ToString()) however it uses the - /// StringBuilder.GetChunks() method to avoid creating the intermediate string - /// </summary> - public virtual void WriteLine(StringBuilder? value) - { - Write(value); - WriteLine(); - } - - // Writes the text representation of an object followed by a line - // terminator to the text stream. - // - public virtual void WriteLine(object? value) - { - if (value == null) - { - WriteLine(); - } - else - { - // Call WriteLine(value.ToString), not Write(Object), WriteLine(). - // This makes calls to WriteLine(Object) atomic. - if (value is IFormattable f) - { - WriteLine(f.ToString(null, FormatProvider)); - } - else - { - WriteLine(value.ToString()); - } - } - } - - // Writes out a formatted string and a new line. Uses the same - // semantics as string.Format. - // - public virtual void WriteLine(string format, object? arg0) - { - WriteLine(string.Format(FormatProvider, format, arg0)); - } - - // Writes out a formatted string and a new line. Uses the same - // semantics as string.Format. - // - public virtual void WriteLine(string format, object? arg0, object? arg1) - { - WriteLine(string.Format(FormatProvider, format, arg0, arg1)); - } - - // Writes out a formatted string and a new line. Uses the same - // semantics as string.Format. - // - public virtual void WriteLine(string format, object? arg0, object? arg1, object? arg2) - { - WriteLine(string.Format(FormatProvider, format, arg0, arg1, arg2)); - } - - // Writes out a formatted string and a new line. Uses the same - // semantics as string.Format. - // - public virtual void WriteLine(string format, params object?[] arg) - { - WriteLine(string.Format(FormatProvider, format, arg)); - } - - #region Task based Async APIs - public virtual Task WriteAsync(char value) - { - var tuple = new Tuple<TextWriter, char>(this, value); - return Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, char>)state!; - t.Item1.Write(t.Item2); - }, - tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - - public virtual Task WriteAsync(string? value) - { - var tuple = new Tuple<TextWriter, string?>(this, value); - return Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, string?>)state!; - t.Item1.Write(t.Item2); - }, - tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - - /// <summary> - /// Equivalent to WriteAsync(stringBuilder.ToString()) however it uses the - /// StringBuilder.GetChunks() method to avoid creating the intermediate string - /// </summary> - /// <param name="value">The string (as a StringBuilder) to write to the stream</param> - public virtual Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) - { - return - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : - value == null ? Task.CompletedTask : - WriteAsyncCore(value, cancellationToken); - - async Task WriteAsyncCore(StringBuilder sb, CancellationToken ct) - { - foreach (ReadOnlyMemory<char> chunk in sb.GetChunks()) - { - await WriteAsync(chunk, ct).ConfigureAwait(false); - } - } - } - - public Task WriteAsync(char[]? buffer) - { - if (buffer == null) - { - return Task.CompletedTask; - } - - return WriteAsync(buffer, 0, buffer.Length); - } - - public virtual Task WriteAsync(char[] buffer, int index, int count) - { - var tuple = new Tuple<TextWriter, char[], int, int>(this, buffer, index, count); - return Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, char[], int, int>)state!; - t.Item1.Write(t.Item2, t.Item3, t.Item4); - }, - tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - - public virtual Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : - MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ? - WriteAsync(array.Array!, array.Offset, array.Count) : - Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state!; - t.Item1.Write(t.Item2.Span); - }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - - public virtual Task WriteLineAsync(char value) - { - var tuple = new Tuple<TextWriter, char>(this, value); - return Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, char>)state!; - t.Item1.WriteLine(t.Item2); - }, - tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - - public virtual Task WriteLineAsync(string? value) - { - var tuple = new Tuple<TextWriter, string?>(this, value); - return Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, string?>)state!; - t.Item1.WriteLine(t.Item2); - }, - tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - - /// <summary> - /// Equivalent to WriteLineAsync(stringBuilder.ToString()) however it uses the - /// StringBuilder.GetChunks() method to avoid creating the intermediate string - /// </summary> - /// <param name="value">The string (as a StringBuilder) to write to the stream</param> - public virtual Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) - { - return - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : - value == null ? WriteAsync(CoreNewLine, cancellationToken) : - WriteLineAsyncCore(value, cancellationToken); - - async Task WriteLineAsyncCore(StringBuilder sb, CancellationToken ct) - { - foreach (ReadOnlyMemory<char> chunk in sb.GetChunks()) - { - await WriteAsync(chunk, ct).ConfigureAwait(false); - } - await WriteAsync(CoreNewLine, ct).ConfigureAwait(false); - } - } - - public Task WriteLineAsync(char[]? buffer) - { - if (buffer == null) - { - return WriteLineAsync(); - } - - return WriteLineAsync(buffer, 0, buffer.Length); - } - - public virtual Task WriteLineAsync(char[] buffer, int index, int count) - { - var tuple = new Tuple<TextWriter, char[], int, int>(this, buffer, index, count); - return Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, char[], int, int>)state!; - t.Item1.WriteLine(t.Item2, t.Item3, t.Item4); - }, - tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - - public virtual Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) => - cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : - MemoryMarshal.TryGetArray(buffer, out ArraySegment<char> array) ? - WriteLineAsync(array.Array!, array.Offset, array.Count) : - Task.Factory.StartNew(state => - { - var t = (Tuple<TextWriter, ReadOnlyMemory<char>>)state!; - t.Item1.WriteLine(t.Item2.Span); - }, Tuple.Create(this, buffer), cancellationToken, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - - public virtual Task WriteLineAsync() - { - return WriteAsync(CoreNewLine); - } - - public virtual Task FlushAsync() - { - return Task.Factory.StartNew(state => ((TextWriter)state!).Flush(), this, - CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); - } - #endregion - - private sealed class NullTextWriter : TextWriter - { - internal NullTextWriter() : base(CultureInfo.InvariantCulture) - { - } - - public override Encoding Encoding => Encoding.Unicode; - - public override void Write(char[] buffer, int index, int count) - { - } - - public override void Write(string? value) - { - } - - // Not strictly necessary, but for perf reasons - public override void WriteLine() - { - } - - // Not strictly necessary, but for perf reasons - public override void WriteLine(string? value) - { - } - - public override void WriteLine(object? value) - { - } - - public override void Write(char value) - { - } - } - - public static TextWriter Synchronized(TextWriter writer) - { - if (writer == null) - throw new ArgumentNullException(nameof(writer)); - - return writer is SyncTextWriter ? writer : new SyncTextWriter(writer); - } - - internal sealed class SyncTextWriter : TextWriter, IDisposable - { - private readonly TextWriter _out; - - internal SyncTextWriter(TextWriter t) : base(t.FormatProvider) - { - _out = t; - } - - public override Encoding Encoding => _out.Encoding; - - public override IFormatProvider FormatProvider => _out.FormatProvider; - - [AllowNull] - public override string NewLine - { - [MethodImpl(MethodImplOptions.Synchronized)] - get => _out.NewLine; - [MethodImpl(MethodImplOptions.Synchronized)] - set => _out.NewLine = value; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Close() => _out.Close(); - - [MethodImpl(MethodImplOptions.Synchronized)] - protected override void Dispose(bool disposing) - { - // Explicitly pick up a potentially methodimpl'ed Dispose - if (disposing) - ((IDisposable)_out).Dispose(); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Flush() => _out.Flush(); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(char value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(char[]? buffer) => _out.Write(buffer); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(char[] buffer, int index, int count) => _out.Write(buffer, index, count); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(ReadOnlySpan<char> buffer) => _out.Write(buffer); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(bool value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(int value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(uint value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(long value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(ulong value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(float value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(double value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(decimal value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(string? value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(StringBuilder? value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(object? value) => _out.Write(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(string format, object? arg0) => _out.Write(format, arg0); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(string format, object? arg0, object? arg1) => _out.Write(format, arg0, arg1); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(string format, object? arg0, object? arg1, object? arg2) => _out.Write(format, arg0, arg1, arg2); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void Write(string format, object?[] arg) => _out.Write(format, arg); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine() => _out.WriteLine(); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(char value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(decimal value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(char[]? buffer) => _out.WriteLine(buffer); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(char[] buffer, int index, int count) => _out.WriteLine(buffer, index, count); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(ReadOnlySpan<char> buffer) => _out.WriteLine(buffer); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(bool value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(int value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(uint value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(long value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(ulong value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(float value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(double value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(string? value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(StringBuilder? value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(object? value) => _out.WriteLine(value); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(string format, object? arg0) => _out.WriteLine(format, arg0); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(string format, object? arg0, object? arg1) => _out.WriteLine(format, arg0, arg1); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(string format, object? arg0, object? arg1, object? arg2) => _out.WriteLine(format, arg0, arg1, arg2); - - [MethodImpl(MethodImplOptions.Synchronized)] - public override void WriteLine(string format, object?[] arg) => _out.WriteLine(format, arg); - - // - // On SyncTextWriter all APIs should run synchronously, even the async ones. - // - - [MethodImpl(MethodImplOptions.Synchronized)] - public override ValueTask DisposeAsync() - { - Dispose(); - return default; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteAsync(char value) - { - Write(value); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteAsync(string? value) - { - Write(value); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteAsync(StringBuilder? value, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - Write(value); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteAsync(char[] buffer, int index, int count) - { - Write(buffer, index, count); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - Write(buffer.Span); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - WriteLine(buffer.Span); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteLineAsync(char value) - { - WriteLine(value); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteLineAsync() - { - WriteLine(); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteLineAsync(string? value) - { - WriteLine(value); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteLineAsync(StringBuilder? value, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return Task.FromCanceled(cancellationToken); - } - - WriteLine(value); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task WriteLineAsync(char[] buffer, int index, int count) - { - WriteLine(buffer, index, count); - return Task.CompletedTask; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - public override Task FlushAsync() - { - Flush(); - return Task.CompletedTask; - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryAccessor.cs b/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryAccessor.cs deleted file mode 100644 index a2c6bd1764f..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryAccessor.cs +++ /dev/null @@ -1,668 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** -** Purpose: Provides a fast, AV free, cross-language way of -** accessing unmanaged memory in a random fashion. -** -** -===========================================================*/ - -using System.Runtime.InteropServices; -using Internal.Runtime.CompilerServices; - -namespace System.IO -{ - /// Perf notes: ReadXXX, WriteXXX (for basic types) acquire and release the - /// SafeBuffer pointer rather than relying on generic Read(T) from SafeBuffer because - /// this gives better throughput; benchmarks showed about 12-15% better. - public class UnmanagedMemoryAccessor : IDisposable - { - private SafeBuffer _buffer = null!; // initialized in helper called by ctor - private long _offset; - private long _capacity; - private FileAccess _access; - private bool _isOpen; - private bool _canRead; - private bool _canWrite; - - protected UnmanagedMemoryAccessor() - { - _isOpen = false; - } - - public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity) - { - Initialize(buffer, offset, capacity, FileAccess.Read); - } - - public UnmanagedMemoryAccessor(SafeBuffer buffer, long offset, long capacity, FileAccess access) - { - Initialize(buffer, offset, capacity, access); - } - - protected void Initialize(SafeBuffer buffer, long offset, long capacity, FileAccess access) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (capacity < 0) - { - throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.ByteLength < (ulong)(offset + capacity)) - { - throw new ArgumentException(SR.Argument_OffsetAndCapacityOutOfBounds); - } - if (access < FileAccess.Read || access > FileAccess.ReadWrite) - { - throw new ArgumentOutOfRangeException(nameof(access)); - } - - if (_isOpen) - { - throw new InvalidOperationException(SR.InvalidOperation_CalledTwice); - } - - unsafe - { - byte* pointer = null; - - try - { - buffer.AcquirePointer(ref pointer); - if (((byte*)((long)pointer + offset + capacity)) < pointer) - { - throw new ArgumentException(SR.Argument_UnmanagedMemAccessorWrapAround); - } - } - finally - { - if (pointer != null) - { - buffer.ReleasePointer(); - } - } - } - - _offset = offset; - _buffer = buffer; - _capacity = capacity; - _access = access; - _isOpen = true; - _canRead = (_access & FileAccess.Read) != 0; - _canWrite = (_access & FileAccess.Write) != 0; - } - - public long Capacity => _capacity; - - public bool CanRead => _isOpen && _canRead; - - public bool CanWrite => _isOpen && _canWrite; - - protected virtual void Dispose(bool disposing) - { - _isOpen = false; - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - protected bool IsOpen => _isOpen; - - // ************** Read Methods ****************/ - - public bool ReadBoolean(long position) => ReadByte(position) != 0; - - public byte ReadByte(long position) - { - EnsureSafeToRead(position, sizeof(byte)); - - byte result; - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - result = *((byte*)(pointer + _offset + position)); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - return result; - } - - public char ReadChar(long position) => unchecked((char)ReadInt16(position)); - - public short ReadInt16(long position) - { - EnsureSafeToRead(position, sizeof(short)); - - short result; - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - result = Unsafe.ReadUnaligned<short>(pointer + _offset + position); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - return result; - } - - public int ReadInt32(long position) - { - EnsureSafeToRead(position, sizeof(int)); - - int result; - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - result = Unsafe.ReadUnaligned<int>(pointer + _offset + position); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - return result; - } - - public long ReadInt64(long position) - { - EnsureSafeToRead(position, sizeof(long)); - - long result; - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - result = Unsafe.ReadUnaligned<long>(pointer + _offset + position); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - return result; - } - - public decimal ReadDecimal(long position) - { - const int ScaleMask = 0x00FF0000; - const int SignMask = unchecked((int)0x80000000); - - EnsureSafeToRead(position, sizeof(decimal)); - - int lo, mid, hi, flags; - - unsafe - { - byte* pointer = null; - try - { - _buffer.AcquirePointer(ref pointer); - pointer += (_offset + position); - - lo = Unsafe.ReadUnaligned<int>(pointer); - mid = Unsafe.ReadUnaligned<int>(pointer + 4); - hi = Unsafe.ReadUnaligned<int>(pointer + 8); - flags = Unsafe.ReadUnaligned<int>(pointer + 12); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - - // Check for invalid Decimal values - if (!((flags & ~(SignMask | ScaleMask)) == 0 && (flags & ScaleMask) <= (28 << 16))) - { - throw new ArgumentException(SR.Arg_BadDecimal); // Throw same Exception type as Decimal(int[]) ctor for compat - } - - bool isNegative = (flags & SignMask) != 0; - byte scale = (byte)(flags >> 16); - - return new decimal(lo, mid, hi, isNegative, scale); - } - - public float ReadSingle(long position) => BitConverter.Int32BitsToSingle(ReadInt32(position)); - - public double ReadDouble(long position) => BitConverter.Int64BitsToDouble(ReadInt64(position)); - - [CLSCompliant(false)] - public sbyte ReadSByte(long position) => unchecked((sbyte)ReadByte(position)); - - [CLSCompliant(false)] - public ushort ReadUInt16(long position) => unchecked((ushort)ReadInt16(position)); - - [CLSCompliant(false)] - public uint ReadUInt32(long position) => unchecked((uint)ReadInt32(position)); - - [CLSCompliant(false)] - public ulong ReadUInt64(long position) => unchecked((ulong)ReadInt64(position)); - - // Reads a struct of type T from unmanaged memory, into the reference pointed to by ref value. - // Note: this method is not safe, since it overwrites the contents of a structure, it can be - // used to modify the private members of a struct. - // This method is most performant when used with medium to large sized structs - // (larger than 8 bytes -- though this is number is JIT and architecture dependent). As - // such, it is best to use the ReadXXX methods for small standard types such as ints, longs, - // bools, etc. - public void Read<T>(long position, out T structure) where T : struct - { - if (position < 0) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum); - } - - if (!_isOpen) - { - throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed); - } - if (!_canRead) - { - throw new NotSupportedException(SR.NotSupported_Reading); - } - - uint sizeOfT = SafeBuffer.SizeOf<T>(); - if (position > _capacity - sizeOfT) - { - if (position >= _capacity) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired); - } - else - { - throw new ArgumentException(SR.Format(SR.Argument_NotEnoughBytesToRead, typeof(T)), nameof(position)); - } - } - - structure = _buffer.Read<T>((ulong)(_offset + position)); - } - - // Reads 'count' structs of type T from unmanaged memory, into 'array' starting at 'offset'. - // Note: this method is not safe, since it overwrites the contents of structures, it can - // be used to modify the private members of a struct. - public int ReadArray<T>(long position, T[] array, int offset, int count) where T : struct - { - 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); - } - if (!_isOpen) - { - throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed); - } - if (!_canRead) - { - throw new NotSupportedException(SR.NotSupported_Reading); - } - if (position < 0) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum); - } - - uint sizeOfT = SafeBuffer.AlignedSizeOf<T>(); - - // only check position and ask for fewer Ts if count is too big - if (position >= _capacity) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired); - } - - int n = count; - long spaceLeft = _capacity - position; - if (spaceLeft < 0) - { - n = 0; - } - else - { - ulong spaceNeeded = (ulong)(sizeOfT * count); - if ((ulong)spaceLeft < spaceNeeded) - { - n = (int)(spaceLeft / sizeOfT); - } - } - - _buffer.ReadArray<T>((ulong)(_offset + position), array, offset, n); - - return n; - } - - // ************** Write Methods ****************/ - - public void Write(long position, bool value) => Write(position, (byte)(value ? 1 : 0)); - - public void Write(long position, byte value) - { - EnsureSafeToWrite(position, sizeof(byte)); - - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - *((byte*)(pointer + _offset + position)) = value; - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - } - - public void Write(long position, char value) => Write(position, unchecked((short)value)); - - public void Write(long position, short value) - { - EnsureSafeToWrite(position, sizeof(short)); - - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - Unsafe.WriteUnaligned<short>(pointer + _offset + position, value); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - } - - public void Write(long position, int value) - { - EnsureSafeToWrite(position, sizeof(int)); - - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - Unsafe.WriteUnaligned<int>(pointer + _offset + position, value); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - } - - public void Write(long position, long value) - { - EnsureSafeToWrite(position, sizeof(long)); - - unsafe - { - byte* pointer = null; - - try - { - _buffer.AcquirePointer(ref pointer); - Unsafe.WriteUnaligned<long>(pointer + _offset + position, value); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - } - - public void Write(long position, decimal value) - { - EnsureSafeToWrite(position, sizeof(decimal)); - - unsafe - { - int* valuePtr = (int*)(&value); - int flags = *valuePtr; - int hi = *(valuePtr + 1); - int lo = *(valuePtr + 2); - int mid = *(valuePtr + 3); - - byte* pointer = null; - try - { - _buffer.AcquirePointer(ref pointer); - pointer += (_offset + position); - - Unsafe.WriteUnaligned<int>(pointer, lo); - Unsafe.WriteUnaligned<int>(pointer + 4, mid); - Unsafe.WriteUnaligned<int>(pointer + 8, hi); - Unsafe.WriteUnaligned<int>(pointer + 12, flags); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - } - - public void Write(long position, float value) => Write(position, BitConverter.SingleToInt32Bits(value)); - - public void Write(long position, double value) => Write(position, BitConverter.DoubleToInt64Bits(value)); - - [CLSCompliant(false)] - public void Write(long position, sbyte value) => Write(position, unchecked((byte)value)); - - [CLSCompliant(false)] - public void Write(long position, ushort value) => Write(position, unchecked((short)value)); - - [CLSCompliant(false)] - public void Write(long position, uint value) => Write(position, unchecked((int)value)); - - [CLSCompliant(false)] - public void Write(long position, ulong value) => Write(position, unchecked((long)value)); - - // Writes the struct pointed to by ref value into unmanaged memory. Note that this method - // is most performant when used with medium to large sized structs (larger than 8 bytes - // though this is number is JIT and architecture dependent). As such, it is best to use - // the WriteX methods for small standard types such as ints, longs, bools, etc. - public void Write<T>(long position, ref T structure) where T : struct - { - if (position < 0) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (!_isOpen) - { - throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed); - } - if (!_canWrite) - { - throw new NotSupportedException(SR.NotSupported_Writing); - } - - uint sizeOfT = SafeBuffer.SizeOf<T>(); - if (position > _capacity - sizeOfT) - { - if (position >= _capacity) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired); - } - else - { - throw new ArgumentException(SR.Format(SR.Argument_NotEnoughBytesToWrite, typeof(T)), nameof(position)); - } - } - - _buffer.Write<T>((ulong)(_offset + position), structure); - } - - // Writes 'count' structs of type T from 'array' (starting at 'offset') into unmanaged memory. - public void WriteArray<T>(long position, T[] array, int offset, int count) where T : struct - { - 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); - } - if (position < 0) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (position >= Capacity) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired); - } - - if (!_isOpen) - { - throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed); - } - if (!_canWrite) - { - throw new NotSupportedException(SR.NotSupported_Writing); - } - - _buffer.WriteArray<T>((ulong)(_offset + position), array, offset, count); - } - - private void EnsureSafeToRead(long position, int sizeOfType) - { - if (!_isOpen) - { - throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed); - } - if (!_canRead) - { - throw new NotSupportedException(SR.NotSupported_Reading); - } - if (position < 0) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (position > _capacity - sizeOfType) - { - if (position >= _capacity) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired); - } - else - { - throw new ArgumentException(SR.Argument_NotEnoughBytesToRead, nameof(position)); - } - } - } - - private void EnsureSafeToWrite(long position, int sizeOfType) - { - if (!_isOpen) - { - throw new ObjectDisposedException(nameof(UnmanagedMemoryAccessor), SR.ObjectDisposed_ViewAccessorClosed); - } - if (!_canWrite) - { - throw new NotSupportedException(SR.NotSupported_Writing); - } - if (position < 0) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (position > _capacity - sizeOfType) - { - if (position >= _capacity) - { - throw new ArgumentOutOfRangeException(nameof(position), SR.ArgumentOutOfRange_PositionLessThanCapacityRequired); - } - else - { - throw new ArgumentException(SR.Argument_NotEnoughBytesToWrite, nameof(position)); - } - } - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs b/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs deleted file mode 100644 index 4dbd34a95a7..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStream.cs +++ /dev/null @@ -1,951 +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.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; -using System.Threading.Tasks; - -#pragma warning disable SA1121 // explicitly using type aliases instead of built-in types -#if BIT64 -using nuint = System.UInt64; -#else -using nuint = System.UInt32; -#endif - -namespace System.IO -{ - /* - * This class is used to access a contiguous block of memory, likely outside - * the GC heap (or pinned in place in the GC heap, but a MemoryStream may - * make more sense in those cases). It's great if you have a pointer and - * a length for a section of memory mapped in by someone else and you don't - * want to copy this into the GC heap. UnmanagedMemoryStream assumes these - * two things: - * - * 1) All the memory in the specified block is readable or writable, - * depending on the values you pass to the constructor. - * 2) The lifetime of the block of memory is at least as long as the lifetime - * of the UnmanagedMemoryStream. - * 3) You clean up the memory when appropriate. The UnmanagedMemoryStream - * currently will do NOTHING to free this memory. - * 4) All calls to Write and WriteByte may not be threadsafe currently. - * - * It may become necessary to add in some sort of - * DeallocationMode enum, specifying whether we unmap a section of memory, - * call free, run a user-provided delegate to free the memory, etc. - * We'll suggest user write a subclass of UnmanagedMemoryStream that uses - * a SafeHandle subclass to hold onto the memory. - * - */ - - /// <summary> - /// Stream over a memory pointer or over a SafeBuffer - /// </summary> - public class UnmanagedMemoryStream : Stream - { - private SafeBuffer? _buffer; - private unsafe byte* _mem; - private long _length; - private long _capacity; - private long _position; - private long _offset; - private FileAccess _access; - private bool _isOpen; - private Task<int>? _lastReadTask; // The last successful task returned from ReadAsync - - /// <summary> - /// Creates a closed stream. - /// </summary> - // Needed for subclasses that need to map a file, etc. - protected UnmanagedMemoryStream() - { - unsafe - { - _mem = null; - } - _isOpen = false; - } - - /// <summary> - /// Creates a stream over a SafeBuffer. - /// </summary> - /// <param name="buffer"></param> - /// <param name="offset"></param> - /// <param name="length"></param> - public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length) - { - Initialize(buffer, offset, length, FileAccess.Read); - } - - /// <summary> - /// Creates a stream over a SafeBuffer. - /// </summary> - public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access) - { - Initialize(buffer, offset, length, access); - } - - /// <summary> - /// Subclasses must call this method (or the other overload) to properly initialize all instance fields. - /// </summary> - /// <param name="buffer"></param> - /// <param name="offset"></param> - /// <param name="length"></param> - /// <param name="access"></param> - protected void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access) - { - if (buffer == null) - { - throw new ArgumentNullException(nameof(buffer)); - } - if (offset < 0) - { - throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (length < 0) - { - throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum); - } - if (buffer.ByteLength < (ulong)(offset + length)) - { - throw new ArgumentException(SR.Argument_InvalidSafeBufferOffLen); - } - if (access < FileAccess.Read || access > FileAccess.ReadWrite) - { - throw new ArgumentOutOfRangeException(nameof(access)); - } - - if (_isOpen) - { - throw new InvalidOperationException(SR.InvalidOperation_CalledTwice); - } - - // check for wraparound - unsafe - { - byte* pointer = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - buffer.AcquirePointer(ref pointer); - if ((pointer + offset + length) < pointer) - { - throw new ArgumentException(SR.ArgumentOutOfRange_UnmanagedMemStreamWrapAround); - } - } - finally - { - if (pointer != null) - { - buffer.ReleasePointer(); - } - } - } - - _offset = offset; - _buffer = buffer; - _length = length; - _capacity = length; - _access = access; - _isOpen = true; - } - - /// <summary> - /// Creates a stream over a byte*. - /// </summary> - [CLSCompliant(false)] - public unsafe UnmanagedMemoryStream(byte* pointer, long length) - { - Initialize(pointer, length, length, FileAccess.Read); - } - - /// <summary> - /// Creates a stream over a byte*. - /// </summary> - [CLSCompliant(false)] - public unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access) - { - Initialize(pointer, length, capacity, access); - } - - /// <summary> - /// Subclasses must call this method (or the other overload) to properly initialize all instance fields. - /// </summary> - [CLSCompliant(false)] - protected unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access) - { - if (pointer == null) - throw new ArgumentNullException(nameof(pointer)); - if (length < 0 || capacity < 0) - throw new ArgumentOutOfRangeException((length < 0) ? nameof(length) : nameof(capacity), SR.ArgumentOutOfRange_NeedNonNegNum); - if (length > capacity) - throw new ArgumentOutOfRangeException(nameof(length), SR.ArgumentOutOfRange_LengthGreaterThanCapacity); - // Check for wraparound. - if (((byte*)((long)pointer + capacity)) < pointer) - throw new ArgumentOutOfRangeException(nameof(capacity), SR.ArgumentOutOfRange_UnmanagedMemStreamWrapAround); - if (access < FileAccess.Read || access > FileAccess.ReadWrite) - throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum); - if (_isOpen) - throw new InvalidOperationException(SR.InvalidOperation_CalledTwice); - - _mem = pointer; - _offset = 0; - _length = length; - _capacity = capacity; - _access = access; - _isOpen = true; - } - - /// <summary> - /// Returns true if the stream can be read; otherwise returns false. - /// </summary> - public override bool CanRead => _isOpen && (_access & FileAccess.Read) != 0; - - /// <summary> - /// Returns true if the stream can seek; otherwise returns false. - /// </summary> - public override bool CanSeek => _isOpen; - - /// <summary> - /// Returns true if the stream can be written to; otherwise returns false. - /// </summary> - public override bool CanWrite => _isOpen && (_access & FileAccess.Write) != 0; - - /// <summary> - /// Calls the given callback with a span of the memory stream data - /// </summary> - /// <param name="callback">the callback to be called</param> - /// <param name="state">A user-defined state, passed to the callback</param> - /// <param name="bufferSize">the maximum size of the memory span</param> - public override void CopyTo(ReadOnlySpanAction<byte, object?> callback, object? state, int bufferSize) - { - // If we have been inherited into a subclass, the following implementation could be incorrect - // since it does not call through to Read() 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) when we are not sure. - if (GetType() != typeof(UnmanagedMemoryStream)) - { - base.CopyTo(callback, state, bufferSize); - return; - } - - if (callback == null) throw new ArgumentNullException(nameof(callback)); - - EnsureNotClosed(); - EnsureReadable(); - - // Use a local variable to avoid a race where another thread - // changes our position after we decide we can read some bytes. - long pos = Interlocked.Read(ref _position); - long len = Interlocked.Read(ref _length); - long n = len - pos; - if (n <= 0) - { - return; - } - - int nInt = (int)n; // Safe because n <= count, which is an Int32 - if (nInt < 0) - { - return; // _position could be beyond EOF - } - - unsafe - { - if (_buffer != null) - { - byte* pointer = null; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - _buffer.AcquirePointer(ref pointer); - ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(pointer + pos + _offset, nInt); - Interlocked.Exchange(ref _position, pos + n); - callback(span, state); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - else - { - ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(_mem + pos, nInt); - Interlocked.Exchange(ref _position, pos + n); - callback(span, state); - } - } - } - - /// <summary> - /// Closes the stream. The stream's memory needs to be dealt with separately. - /// </summary> - /// <param name="disposing"></param> - protected override void Dispose(bool disposing) - { - _isOpen = false; - unsafe { _mem = null; } - - // Stream allocates WaitHandles for async calls. So for correctness - // call base.Dispose(disposing) for better perf, avoiding waiting - // for the finalizers to run on those types. - base.Dispose(disposing); - } - - private void EnsureNotClosed() - { - if (!_isOpen) - throw Error.GetStreamIsClosed(); - } - - private void EnsureReadable() - { - if (!CanRead) - throw Error.GetReadNotSupported(); - } - - private void EnsureWriteable() - { - if (!CanWrite) - throw Error.GetWriteNotSupported(); - } - - /// <summary> - /// Since it's a memory stream, this method does nothing. - /// </summary> - public override void Flush() - { - EnsureNotClosed(); - } - - /// <summary> - /// Since it's a memory stream, this method does nothing specific. - /// </summary> - /// <param name="cancellationToken"></param> - /// <returns></returns> - public override Task FlushAsync(CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - try - { - Flush(); - return Task.CompletedTask; - } - catch (Exception ex) - { - return Task.FromException(ex); - } - } - - /// <summary> - /// Number of bytes in the stream. - /// </summary> - public override long Length - { - get - { - EnsureNotClosed(); - return Interlocked.Read(ref _length); - } - } - - /// <summary> - /// Number of bytes that can be written to the stream. - /// </summary> - public long Capacity - { - get - { - EnsureNotClosed(); - return _capacity; - } - } - - /// <summary> - /// ReadByte will read byte at the Position in the stream - /// </summary> - public override long Position - { - get - { - if (!CanSeek) throw Error.GetStreamIsClosed(); - return Interlocked.Read(ref _position); - } - set - { - if (value < 0) throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum); - if (!CanSeek) throw Error.GetStreamIsClosed(); - - Interlocked.Exchange(ref _position, value); - } - } - - /// <summary> - /// Pointer to memory at the current Position in the stream. - /// </summary> - [CLSCompliant(false)] - public unsafe byte* PositionPointer - { - get - { - if (_buffer != null) - throw new NotSupportedException(SR.NotSupported_UmsSafeBuffer); - - EnsureNotClosed(); - - // Use a temp to avoid a race - long pos = Interlocked.Read(ref _position); - if (pos > _capacity) - throw new IndexOutOfRangeException(SR.IndexOutOfRange_UMSPosition); - byte* ptr = _mem + pos; - return ptr; - } - set - { - if (_buffer != null) - throw new NotSupportedException(SR.NotSupported_UmsSafeBuffer); - - EnsureNotClosed(); - - if (value < _mem) - throw new IOException(SR.IO_SeekBeforeBegin); - long newPosition = (long)value - (long)_mem; - if (newPosition < 0) - throw new ArgumentOutOfRangeException("offset", SR.ArgumentOutOfRange_UnmanagedMemStreamLength); - - Interlocked.Exchange(ref _position, newPosition); - } - } - - /// <summary> - /// Reads bytes from stream and puts them into the buffer - /// </summary> - /// <param name="buffer">Buffer to read the bytes to.</param> - /// <param name="offset">Starting index in the buffer.</param> - /// <param name="count">Maximum number of bytes to read.</param> - /// <returns>Number of bytes actually read.</returns> - public override int Read(byte[] buffer, int offset, int count) - { - 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); - - return ReadCore(new Span<byte>(buffer, offset, count)); - } - - public override int Read(Span<byte> buffer) - { - if (GetType() == typeof(UnmanagedMemoryStream)) - { - return ReadCore(buffer); - } - else - { - // UnmanagedMemoryStream is not sealed, and a derived type may have overridden Read(byte[], int, int) prior - // to this Read(Span<byte>) overload being introduced. In that case, this Read(Span<byte>) overload - // should use the behavior of Read(byte[],int,int) overload. - return base.Read(buffer); - } - } - - internal int ReadCore(Span<byte> buffer) - { - EnsureNotClosed(); - EnsureReadable(); - - // Use a local variable to avoid a race where another thread - // changes our position after we decide we can read some bytes. - long pos = Interlocked.Read(ref _position); - long len = Interlocked.Read(ref _length); - long n = Math.Min(len - pos, buffer.Length); - if (n <= 0) - { - return 0; - } - - int nInt = (int)n; // Safe because n <= count, which is an Int32 - if (nInt < 0) - { - return 0; // _position could be beyond EOF - } - Debug.Assert(pos + nInt >= 0, "_position + n >= 0"); // len is less than 2^63 -1. - - unsafe - { - fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer)) - { - if (_buffer != null) - { - byte* pointer = null; - - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - _buffer.AcquirePointer(ref pointer); - Buffer.Memcpy(pBuffer, pointer + pos + _offset, nInt); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - else - { - Buffer.Memcpy(pBuffer, _mem + pos, nInt); - } - } - } - Interlocked.Exchange(ref _position, pos + n); - return nInt; - } - - /// <summary> - /// Reads bytes from stream and puts them into the buffer - /// </summary> - /// <param name="buffer">Buffer to read the bytes to.</param> - /// <param name="offset">Starting index in the buffer.</param> - /// <param name="count">Maximum number of bytes to read.</param> - /// <param name="cancellationToken">Token that can be used to cancel this operation.</param> - /// <returns>Task that can be used to access the number of bytes actually read.</returns> - 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); - - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled<int>(cancellationToken); - - try - { - int n = Read(buffer, offset, count); - Task<int>? t = _lastReadTask; - return (t != null && t.Result == n) ? t : (_lastReadTask = Task.FromResult<int>(n)); - } - catch (Exception ex) - { - Debug.Assert(!(ex is OperationCanceledException)); - return Task.FromException<int>(ex); - } - } - - /// <summary> - /// Reads bytes from stream and puts them into the buffer - /// </summary> - /// <param name="buffer">Buffer to read the bytes to.</param> - /// <param name="cancellationToken">Token that can be used to cancel this operation.</param> - public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)); - } - - try - { - // ReadAsync(Memory<byte>,...) needs to delegate to an existing virtual to do the work, in case an existing derived type - // has changed or augmented the logic associated with reads. If the Memory wraps an array, we could delegate to - // ReadAsync(byte[], ...), but that would defeat part of the purpose, as ReadAsync(byte[], ...) often needs to allocate - // a Task<int> for the return value, so we want to delegate to one of the synchronous methods. We could always - // delegate to the Read(Span<byte>) method, and that's the most efficient solution when dealing with a concrete - // UnmanagedMemoryStream, but if we're dealing with a type derived from UnmanagedMemoryStream, Read(Span<byte>) will end up delegating - // to Read(byte[], ...), which requires it to get a byte[] from ArrayPool and copy the data. So, we special-case the - // very common case of the Memory<byte> wrapping an array: if it does, we delegate to Read(byte[], ...) with it, - // as that will be efficient in both cases, and we fall back to Read(Span<byte>) if the Memory<byte> wrapped something - // else; if this is a concrete UnmanagedMemoryStream, that'll be efficient, and only in the case where the Memory<byte> wrapped - // something other than an array and this is an UnmanagedMemoryStream-derived type that doesn't override Read(Span<byte>) will - // it then fall back to doing the ArrayPool/copy behavior. - return new ValueTask<int>( - MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> destinationArray) ? - Read(destinationArray.Array!, destinationArray.Offset, destinationArray.Count) : - Read(buffer.Span)); - } - catch (Exception ex) - { - return new ValueTask<int>(Task.FromException<int>(ex)); - } - } - - /// <summary> - /// Returns the byte at the stream current Position and advances the Position. - /// </summary> - /// <returns></returns> - public override int ReadByte() - { - EnsureNotClosed(); - EnsureReadable(); - - long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition - long len = Interlocked.Read(ref _length); - if (pos >= len) - return -1; - Interlocked.Exchange(ref _position, pos + 1); - int result; - if (_buffer != null) - { - unsafe - { - byte* pointer = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - _buffer.AcquirePointer(ref pointer); - result = *(pointer + pos + _offset); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - } - else - { - unsafe - { - result = _mem[pos]; - } - } - return result; - } - - /// <summary> - /// Advanced the Position to specific location in the stream. - /// </summary> - /// <param name="offset">Offset from the loc parameter.</param> - /// <param name="loc">Origin for the offset parameter.</param> - /// <returns></returns> - public override long Seek(long offset, SeekOrigin loc) - { - EnsureNotClosed(); - - switch (loc) - { - case SeekOrigin.Begin: - if (offset < 0) - throw new IOException(SR.IO_SeekBeforeBegin); - Interlocked.Exchange(ref _position, offset); - break; - - case SeekOrigin.Current: - long pos = Interlocked.Read(ref _position); - if (offset + pos < 0) - throw new IOException(SR.IO_SeekBeforeBegin); - Interlocked.Exchange(ref _position, offset + pos); - break; - - case SeekOrigin.End: - long len = Interlocked.Read(ref _length); - if (len + offset < 0) - throw new IOException(SR.IO_SeekBeforeBegin); - Interlocked.Exchange(ref _position, len + offset); - break; - - default: - throw new ArgumentException(SR.Argument_InvalidSeekOrigin); - } - - long finalPos = Interlocked.Read(ref _position); - Debug.Assert(finalPos >= 0, "_position >= 0"); - return finalPos; - } - - /// <summary> - /// Sets the Length of the stream. - /// </summary> - /// <param name="value"></param> - public override void SetLength(long value) - { - if (value < 0) - throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum); - if (_buffer != null) - throw new NotSupportedException(SR.NotSupported_UmsSafeBuffer); - - EnsureNotClosed(); - EnsureWriteable(); - - if (value > _capacity) - throw new IOException(SR.IO_FixedCapacity); - - long pos = Interlocked.Read(ref _position); - long len = Interlocked.Read(ref _length); - if (value > len) - { - unsafe - { - Buffer.ZeroMemory(_mem + len, (nuint)(value - len)); - } - } - Interlocked.Exchange(ref _length, value); - if (pos > value) - { - Interlocked.Exchange(ref _position, value); - } - } - - /// <summary> - /// Writes buffer into the stream - /// </summary> - /// <param name="buffer">Buffer that will be written.</param> - /// <param name="offset">Starting index in the buffer.</param> - /// <param name="count">Number of bytes to write.</param> - public override void Write(byte[] buffer, int offset, int count) - { - 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); - - WriteCore(new ReadOnlySpan<byte>(buffer, offset, count)); - } - - public override void Write(ReadOnlySpan<byte> buffer) - { - if (GetType() == typeof(UnmanagedMemoryStream)) - { - WriteCore(buffer); - } - else - { - // UnmanagedMemoryStream is not sealed, and a derived type may have overridden Write(byte[], int, int) prior - // to this Write(Span<byte>) overload being introduced. In that case, this Write(Span<byte>) overload - // should use the behavior of Write(byte[],int,int) overload. - base.Write(buffer); - } - } - - internal unsafe void WriteCore(ReadOnlySpan<byte> buffer) - { - EnsureNotClosed(); - EnsureWriteable(); - - long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition - long len = Interlocked.Read(ref _length); - long n = pos + buffer.Length; - // Check for overflow - if (n < 0) - { - throw new IOException(SR.IO_StreamTooLong); - } - - if (n > _capacity) - { - throw new NotSupportedException(SR.IO_FixedCapacity); - } - - if (_buffer == null) - { - // Check to see whether we are now expanding the stream and must - // zero any memory in the middle. - if (pos > len) - { - Buffer.ZeroMemory(_mem + len, (nuint)(pos - len)); - } - - // set length after zeroing memory to avoid race condition of accessing unzeroed memory - if (n > len) - { - Interlocked.Exchange(ref _length, n); - } - } - - fixed (byte* pBuffer = &MemoryMarshal.GetReference(buffer)) - { - if (_buffer != null) - { - long bytesLeft = _capacity - pos; - if (bytesLeft < buffer.Length) - { - throw new ArgumentException(SR.Arg_BufferTooSmall); - } - - byte* pointer = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - _buffer.AcquirePointer(ref pointer); - Buffer.Memcpy(pointer + pos + _offset, pBuffer, buffer.Length); - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - else - { - Buffer.Memcpy(_mem + pos, pBuffer, buffer.Length); - } - } - - Interlocked.Exchange(ref _position, n); - return; - } - - /// <summary> - /// Writes buffer into the stream. The operation completes synchronously. - /// </summary> - /// <param name="buffer">Buffer that will be written.</param> - /// <param name="offset">Starting index in the buffer.</param> - /// <param name="count">Number of bytes to write.</param> - /// <param name="cancellationToken">Token that can be used to cancel the operation.</param> - /// <returns>Task that can be awaited </returns> - 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); - - if (cancellationToken.IsCancellationRequested) - return Task.FromCanceled(cancellationToken); - - try - { - Write(buffer, offset, count); - return Task.CompletedTask; - } - catch (Exception ex) - { - Debug.Assert(!(ex is OperationCanceledException)); - return Task.FromException(ex); - } - } - - /// <summary> - /// Writes buffer into the stream. The operation completes synchronously. - /// </summary> - /// <param name="buffer">Buffer that will be written.</param> - /// <param name="cancellationToken">Token that can be used to cancel the operation.</param> - public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) - { - if (cancellationToken.IsCancellationRequested) - { - return new ValueTask(Task.FromCanceled(cancellationToken)); - } - - try - { - // See corresponding comment in ReadAsync for why we don't just always use Write(ReadOnlySpan<byte>). - // Unlike ReadAsync, we could delegate to WriteAsync(byte[], ...) here, but we don't for consistency. - if (MemoryMarshal.TryGetArray(buffer, out ArraySegment<byte> sourceArray)) - { - Write(sourceArray.Array!, sourceArray.Offset, sourceArray.Count); - } - else - { - Write(buffer.Span); - } - return default; - } - catch (Exception ex) - { - return new ValueTask(Task.FromException(ex)); - } - } - - /// <summary> - /// Writes a byte to the stream and advances the current Position. - /// </summary> - /// <param name="value"></param> - public override void WriteByte(byte value) - { - EnsureNotClosed(); - EnsureWriteable(); - - long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition - long len = Interlocked.Read(ref _length); - long n = pos + 1; - if (pos >= len) - { - // Check for overflow - if (n < 0) - throw new IOException(SR.IO_StreamTooLong); - - if (n > _capacity) - throw new NotSupportedException(SR.IO_FixedCapacity); - - // Check to see whether we are now expanding the stream and must - // zero any memory in the middle. - // don't do if created from SafeBuffer - if (_buffer == null) - { - if (pos > len) - { - unsafe - { - Buffer.ZeroMemory(_mem + len, (nuint)(pos - len)); - } - } - - // set length after zeroing memory to avoid race condition of accessing unzeroed memory - Interlocked.Exchange(ref _length, n); - } - } - - if (_buffer != null) - { - unsafe - { - byte* pointer = null; - RuntimeHelpers.PrepareConstrainedRegions(); - try - { - _buffer.AcquirePointer(ref pointer); - *(pointer + pos + _offset) = value; - } - finally - { - if (pointer != null) - { - _buffer.ReleasePointer(); - } - } - } - } - else - { - unsafe - { - _mem[pos] = value; - } - } - Interlocked.Exchange(ref _position, n); - } - } -} diff --git a/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs b/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs deleted file mode 100644 index e90c094df74..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/UnmanagedMemoryStreamWrapper.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -/*============================================================ -** -** -** -** -** Purpose: Create a Memorystream over an UnmanagedMemoryStream -** -===========================================================*/ - -using System.Threading; -using System.Threading.Tasks; - -namespace System.IO -{ - // Needed for backwards compatibility with V1.x usages of the - // ResourceManager, where a MemoryStream is now returned as an - // UnmanagedMemoryStream from ResourceReader. - internal sealed class UnmanagedMemoryStreamWrapper : MemoryStream - { - private readonly UnmanagedMemoryStream _unmanagedStream; - - internal UnmanagedMemoryStreamWrapper(UnmanagedMemoryStream stream) - { - _unmanagedStream = stream; - } - - public override bool CanRead => _unmanagedStream.CanRead; - - public override bool CanSeek => _unmanagedStream.CanSeek; - - public override bool CanWrite => _unmanagedStream.CanWrite; - - protected override void Dispose(bool disposing) - { - try - { - if (disposing) - _unmanagedStream.Dispose(); - } - finally - { - base.Dispose(disposing); - } - } - - public override void Flush() - { - _unmanagedStream.Flush(); - } - - public override byte[] GetBuffer() - { - throw new UnauthorizedAccessException(SR.UnauthorizedAccess_MemStreamBuffer); - } - - public override bool TryGetBuffer(out ArraySegment<byte> buffer) - { - buffer = default; - return false; - } - - public override int Capacity - { - get => (int)_unmanagedStream.Capacity; - set => throw new IOException(SR.IO_FixedCapacity); - } - - public override long Length => _unmanagedStream.Length; - - public override long Position - { - get => _unmanagedStream.Position; - set => _unmanagedStream.Position = value; - } - - public override int Read(byte[] buffer, int offset, int count) - { - return _unmanagedStream.Read(buffer, offset, count); - } - - public override int Read(Span<byte> buffer) - { - return _unmanagedStream.Read(buffer); - } - - public override int ReadByte() - { - return _unmanagedStream.ReadByte(); - } - - public override long Seek(long offset, SeekOrigin loc) - { - return _unmanagedStream.Seek(offset, loc); - } - - public override unsafe byte[] ToArray() - { - byte[] buffer = new byte[_unmanagedStream.Length]; - _unmanagedStream.Read(buffer, 0, (int)_unmanagedStream.Length); - return buffer; - } - - public override void Write(byte[] buffer, int offset, int count) - { - _unmanagedStream.Write(buffer, offset, count); - } - - public override void Write(ReadOnlySpan<byte> buffer) - { - _unmanagedStream.Write(buffer); - } - - public override void WriteByte(byte value) - { - _unmanagedStream.WriteByte(value); - } - - // Writes this MemoryStream to another stream. - public override unsafe void WriteTo(Stream stream) - { - if (stream == null) - throw new ArgumentNullException(nameof(stream), SR.ArgumentNull_Stream); - - byte[] buffer = ToArray(); - - stream.Write(buffer, 0, buffer.Length); - } - - public override void SetLength(long value) - { - // This was probably meant to call _unmanagedStream.SetLength(value), but it was forgotten in V.4.0. - // Now this results in a call to the base which touches the underlying array which is never actually used. - // We cannot fix it due to compat now, but we should fix this at the next SxS release oportunity. - base.SetLength(value); - } - - - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - // The parameter checks must be in sync with the base version: - if (destination == null) - throw new ArgumentNullException(nameof(destination)); - - if (bufferSize <= 0) - throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - - if (!CanRead && !CanWrite) - throw new ObjectDisposedException(null, SR.ObjectDisposed_StreamClosed); - - if (!destination.CanRead && !destination.CanWrite) - throw new ObjectDisposedException(nameof(destination), SR.ObjectDisposed_StreamClosed); - - if (!CanRead) - throw new NotSupportedException(SR.NotSupported_UnreadableStream); - - if (!destination.CanWrite) - throw new NotSupportedException(SR.NotSupported_UnwritableStream); - - - return _unmanagedStream.CopyToAsync(destination, bufferSize, cancellationToken); - } - - - public override Task FlushAsync(CancellationToken cancellationToken) - { - return _unmanagedStream.FlushAsync(cancellationToken); - } - - - public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _unmanagedStream.ReadAsync(buffer, offset, count, cancellationToken); - } - - public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) - { - return _unmanagedStream.ReadAsync(buffer, cancellationToken); - } - - - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return _unmanagedStream.WriteAsync(buffer, offset, count, cancellationToken); - } - - public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) - { - return _unmanagedStream.WriteAsync(buffer, cancellationToken); - } - } // class UnmanagedMemoryStreamWrapper -} // namespace diff --git a/netcore/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs b/netcore/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs deleted file mode 100644 index 35f7d2eefe9..00000000000 --- a/netcore/System.Private.CoreLib/shared/System/IO/Win32Marshal.cs +++ /dev/null @@ -1,101 +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. - -#nullable enable -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, optionally - /// including the specified path in the error message. - /// </summary> - internal static Exception GetExceptionForLastWin32Error(string? path = "") - => GetExceptionForWin32Error(Marshal.GetLastWin32Error(), path); - - /// <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 = "") - { - // ERROR_SUCCESS gets thrown when another unexpected interop call was made before checking GetLastWin32Error(). - // Errors have to get retrieved as soon as possible after P/Invoking to avoid this. - Debug.Assert(errorCode != Interop.Errors.ERROR_SUCCESS); - - switch (errorCode) - { - case Interop.Errors.ERROR_FILE_NOT_FOUND: - return new FileNotFoundException( - string.IsNullOrEmpty(path) ? SR.IO_FileNotFound : SR.Format(SR.IO_FileNotFound_FileName, path), path); - case Interop.Errors.ERROR_PATH_NOT_FOUND: - return new DirectoryNotFoundException( - string.IsNullOrEmpty(path) ? SR.IO_PathNotFound_NoPathName : SR.Format(SR.IO_PathNotFound_Path, path)); - case Interop.Errors.ERROR_ACCESS_DENIED: - return new UnauthorizedAccessException( - string.IsNullOrEmpty(path) ? SR.UnauthorizedAccess_IODenied_NoPathName : SR.Format(SR.UnauthorizedAccess_IODenied_Path, path)); - case Interop.Errors.ERROR_ALREADY_EXISTS: - if (string.IsNullOrEmpty(path)) - goto default; - return new IOException(SR.Format(SR.IO_AlreadyExists_Name, path), MakeHRFromErrorCode(errorCode)); - case Interop.Errors.ERROR_FILENAME_EXCED_RANGE: - return new PathTooLongException( - string.IsNullOrEmpty(path) ? SR.IO_PathTooLong : SR.Format(SR.IO_PathTooLong_Path, path)); - case Interop.Errors.ERROR_SHARING_VIOLATION: - return new IOException( - string.IsNullOrEmpty(path) ? SR.IO_SharingViolation_NoFileName : SR.Format(SR.IO_SharingViolation_File, path), - MakeHRFromErrorCode(errorCode)); - case Interop.Errors.ERROR_FILE_EXISTS: - if (string.IsNullOrEmpty(path)) - goto default; - return new IOException(SR.Format(SR.IO_FileExists_Name, path), MakeHRFromErrorCode(errorCode)); - case Interop.Errors.ERROR_OPERATION_ABORTED: - return new OperationCanceledException(); - case Interop.Errors.ERROR_INVALID_PARAMETER: - default: - return new IOException( - string.IsNullOrEmpty(path) ? GetMessage(errorCode) : $"{GetMessage(errorCode)} : '{path}'", - MakeHRFromErrorCode(errorCode)); - } - } - - /// <summary> - /// If not already an HRESULT, returns an HRESULT for the specified Win32 error code. - /// </summary> - internal static int MakeHRFromErrorCode(int errorCode) - { - // Don't convert it if it is already an HRESULT - if ((0xFFFF0000 & errorCode) != 0) - return errorCode; - - return unchecked(((int)0x80070000) | errorCode); - } - - /// <summary> - /// Returns a Win32 error code for the specified HRESULT if it came from FACILITY_WIN32 - /// If not, returns the HRESULT unchanged - /// </summary> - internal static int TryMakeWin32ErrorCodeFromHR(int hr) - { - if ((0xFFFF0000 & hr) == 0x80070000) - { - // Win32 error, Win32Marshal.GetExceptionForWin32Error expects the Win32 format - hr &= 0x0000FFFF; - } - - return hr; - } - - /// <summary> - /// Returns a string message for the specified Win32 error code. - /// </summary> - internal static string GetMessage(int errorCode) => Interop.Kernel32.GetMessage(errorCode); - } -} |